本文共 6725 字,大约阅读时间需要 22 分钟。
前面的三篇文章,主要用BeanFactory介绍了Spring中IoC容器的两个阶段:容器启动阶段和实例化阶段。接下来的这篇文章主要说的是Spring的统一资源定位策略。
写下这篇文章之前的绝大部分时间,我都在思考,为什么要整这个功能。任何一个功能、实现肯定有其道理。那道理是什么呢?有人是这么解释的:
要搞清楚Spring为什么提供这么一个功能,还是从Java SE提供的标准类java.net.URL说起比较好。URL全名是Uniform Resource Locator(统一资源定位器),但多少有些名不副实的味道。
首先,说是统一资源定位,但基本实现却只限于网络形式发布的资源的查找和定位工作,基本上只提供了基于HTTP、FTP、File等协议(sun.net.www.protocol 包下所支持的协议)的资源定位功能。虽然也提供了扩展的接口,但从一开始,其自身的“定位”就已经趋于狭隘了。实际上,资源这个词的范围比较广义,资源可以任何形式存在,如以二进制对象形式存在、以字节流形式存在、以文件形式存在等;而且,资源也可以存在于任何场所,如存在于文件系统、存在于Java应用的Classpath中,甚至存在于URL可以定位的地方。
其次,从某些程度上来说,该类的功能职责划分不清,资源的查找和资源的表示没有一个清晰的界限。当前情况是,资源查找后返回的形式多种多样,没有一个统一的抽象。理想情况下,资源查找完成后,返回给客户端的应该是一个统一的资源抽象接口,客户端要对资源进行什么样的处理,应该由资源抽象接口来界定,而不应该成为资源的定位者和查找者同时要关心的事情。
以上的这段文字来自《Spring揭秘》,写的非常正确。但是我第一遍看的时候,我大致还是没看懂。
可能是我底子确实太差了。也可能是因为写书,总归要写的正式一点,而我对于正式语言的理解能力着实不足。这三大段文字的主要意思就是:
一开始肯定是要先定义一个借口Resource
Resource
package org.springframework.core.io; import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URL; public interface InputStreamSource{ //每次调用都将返回一个新的资源对应的java.io.InputStream字节流,调用者在使用完毕后必须关闭该资源 InputStream getInputStream() throws IOException; } public interface Resource extends InputStreamSource{ //返回当前Resouce代表的底层资源是否存在,true表示存在 boolean exists(); //返回当前Resouce代表的底层资源是否可读,true表示可读 boolean isReadable(); //返回当前Resouce代表的底层资源是否已经打开,如果返回true,则只能被读取一次然后关闭以避免内存泄露;常见的Resource实现一般返回false; boolean isOpen(); //如果当前Resouce代表的底层资源能由java.util.URL代表,则返回该URL,否则抛出IOException URL getURL() throws IOException; //如果当前Resouce代表的底层资源能由java.util.URI代表,则返回该URI,否则抛出IOException URI getURI() throws IOException; //如果当前Resouce代表的底层资源能由java.io.File代表,则返回该File,否则抛出IOException File getFile() throws IOException; //返回当前Resouce代表的底层文件资源的长度,一般的值代表的文件资源的长度 long contentLength() throws IOException; //返回当前Resouce代表的底层文件资源的最后修改时间 long lastModified() throws IOException; //用于创建相对于当前Resource代表的底层资源的资源,比如当前Resource代表文件资源“D:/test/”则createRelative("test.txt")将返回代表文件资源“D:/test/test.txt”Resource资源。 Resource createRelative(String relativePath) throws IOException; //返回当前Resource代表的底层文件资源的文件路径,比如File资源:file://d:/test.txt 将返回d:/test.txt,而URL资源http://www.javass.cn将返回“”,因为只返回文件路径 String getFilename(); //返回当前Resource代表的底层资源的描述符,通常就是资源的全路径(实际文件名或实际URL地址) String getDescription(); }
接口提供这些方法,基本上是足够我们日常使用的,除了这个接口,当然也提供了这些接口的sh实现类。
可以看到,有16个实现类,当然,我们常用的并没有那么多,主要有以下几个
其实啦,上面说了半天,都是在说Resource,有了资源,怎么去查找,怎么去定位呢,用ResourceLoader。
这必然,我猜它就是个接口,这么简洁的名字。
ResourceLoader
package org.springframework.core.io; import org.springframework.util.ResourceUtils; public interface ResourceLoader { // CLASSPATH_URL_PREFIX = classpath String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX; // 拿到Resource Resource getResource(String location); // 拿到ClassLoader ClassLoader getClassLoader(); }
事实证明,我想的还是没错的,但是这个接口也真是够简洁的。
看看实现类吧~
18个,但还是先看画圈的几个吧。
DefaultResourceLoader
public Resource getResource(String location) { Assert.notNull(location, "Location must not be null"); for (ProtocolResolver protocolResolver : this.protocolResolvers) { Resource resource = protocolResolver.resolve(location, this); if (resource != null) { return resource; } } if (location.startsWith("/")) { return getResourceByPath(location); } else if (location.startsWith(CLASSPATH_URL_PREFIX)) { return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); } else { try { // Try to parse the location as a URL... URL url = new URL(location); return new UrlResource(url); } catch (MalformedURLException ex) { // No URL -> resolve as resource path. return getResourceByPath(location); } } }
这是DefaultResourceLoader返回Resource的方法,可以看到:
4.如果还是无法根据资源路径定位指定的资源,则委派getResourceByPath(String) 方法来定位, DefaultResourceLoader 的getResourceByPath(String)方法默认实现逻辑是,构造ClassPathResource类型的资源并返回。
FileSystemResourceLoader虽然是DefaultResourceLoader的继承类,但是在getResourceByPaht方法上就不是返回ClassPathResource,而是返回FileSystemResource
@Override protected Resource getResourceByPath(String path) { if (path != null && path.startsWith("/")) { path = path.substring(1); } return new FileSystemContextResource(path); }
前面一直就只是在讲资源,资源定位器,但是,在我们Spring的体现又在哪里?
先看这个图,很明显,ApplicationContext也是ResourceLoader。如果再看两个重要的实现类,你就会懂了。原来如此
ClassPathXmlApplicationContext
FileSystemXmlApplicationContext
剩下的,你说一个bean里面,如果要注入ResourceLoader,或者要注入Resource怎么办呢?
可以这么做
public class FooBar { private ResourceLoader resourceLoader; public void foo(String location) { System.out.println(getResourceLoader().getResource(location).getClass()); } public ResourceLoader getResourceLoader() { return resourceLoader; } public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; }}
然后配置一下bens.xml
有点复杂诶,你说,ApplicationContext不就是资源定位器吗?为什么还要再弄一个新的出来呢?
前一小节的两个接口有用了,ResourceLoaderAware和ApplicationContextAware,他们都能拿到IoC容器本身呀。
public class FooBar implements ApplicationContextAware { private ResourceLoader resourceLoader; public void foo(String location) { System.out.println(getResourceLoader().getResource(location).getClass()); } public ResourceLoader getResourceLoader() { return resourceLoader; } public void setApplicationContext(ApplicationContext ctx) throws BeansException { this.resourceLoader = ctx; }}
看这配置文件不就只有一行了,方便。
写完这篇,基本算是把资源一部分搞懂了一点,但是最近一直在反思,这种总结的办法,可以我是确实不会用Markdown吧,每次花在编辑上的时间就非常多了。还是要想想办法解决这个问题。
转载地址:http://pxovx.baihongyu.com/