统一资源加载策略
ApplicationContext与ResourceLoader
ApplicationContext
继承了ResourcePatternResolver
,当然就间接实现了ResourceLoader
接口。所以,任何的ApplicationContext
实现都可以看作是一个ResourceLoader
甚至ResourcePatternResolver
。而这就是ApplicationContext
支持Spring内统一资源加载策略的真相。
通常,所有的ApplicationContext
实现类会直接或者间接地继承org.springframework.context.support.AbstractApplicationContext
,从这个类上,我们就可以看到ApplicationContext
与ResourceLoader
之间的所有关系。
AbstractApplicationContext
继承了DefaultResourceLoader
,那么,它的getResource(String)
当然就直接用DefaultResourceLoader
的了。剩下需要它“效劳”的,就是ResourcePatternResolver
的Resource[] getResources(String)
,当然,AbstractApplicationContext
也不负众望,当即拿下。
AbstractApplicationContext
类的内部声明有一个resourcePatternResolver
,类型是ResourcePatternResolver
,对应的实例类型为PathMatchingResourcePatternResolver
。之前我们说过PathMatchingResourcePatternResolver
构造的时候会接受一个ResourceLoader
,而AbstractApplicationContext
本身又继承自DefaultResourceLoader
,当然就直接把自身给“贡献”了。
这样,整个ApplicationContext
的实现类就完全可以支持ResourceLoader
或者ResourcePatternResolver
接口,你能说ApplicationContext
不支持Spring的统一资源加载吗?说白了,ApplicationContext
的实现类在作为ResourceLoader
或者ResourcePatternResolver
时候的行为,完全就是委派给了PathMatchingResourcePatternResolver
和DefaultResourceLoader
来做。
图5-2给出了AbstractApplicationContext
与ResourceLoader
和ResourcePatternResolver
之间的类层次关系。
有了这些做前提,让我们看看作为ResourceLoader
或者ResourcePatternResolver
的ApplicationContext
,到底因此拥有了何等神通吧!
1. 扮演 ResourceLoader 的角色
既然ApplicationContext
可以作为ResourceLoader
或者ResourcePatternResolver
来使用,那么,很显然,我们可以通过ApplicationContext
来加载任何Spring支持的Resource类型。与直接使用ResourceLoader
来做这些事情相比,很明显,ApplicationContext
的表现过于“谦虚”了。下方代码演示的正是“大材小用”后的ApplicationContext
。
2. ResourceLoader 类型的注入
在大部分情况下,如果某个bean需要依赖于ResourceLoader
来 查找定位资源
,我们可以为其注入容器中声明的某个具体的ResourceLoader
实现,该bean也无需实现任何接口,直接通过构造方法注入或者setter方法注入规则声明依赖即可,这样处理是比较合理的。
不过,如果你不介意你的bean定义依赖于Spring的API,那不妨考虑用一下Spring提供的便利。
上一章曾经提到几个对ApplicationContext
特定的Aware接口,这其中就包括ResourceLoaderAware
和ApplicationContextAware
接口。
假设我们有类定义如下方代码所示。
该类出于什么目的要依赖于ResourceLoader
,我们暂且不论,要为其注入什么样的ResourceLoader
实例才是我们当下该操心的事情。姑且先给它注入DefaultResourceLoader
。这样也就有了如下配置:
不过,ApplicationContext
容器本身就是一个ResourceLoader
,我们为了该类还需要单独提供一个resourceLoader
实例就有些多于了,直接将当前的ApplicationContext
容器作为ResourceLoader
注入不就行了?
而ResourceLoaderAware
和ApplicationContextAware
接口正好可以帮助我们做到这一点,只不过现在的FooBar
需要依赖于Spring的API了。不过,在我看来,这没有什么大不了,因为我们从来也没有真正逃脱过依赖(这种依赖也好,那种依赖也罢)。
现在,修改我们的FooBar
定义,让其实现ResourceLoaderAware
或者ApplicationContextAware
接口,修改后的定义如下方代码清单所示。
或者
剩下的就是直接将一个FooBar
配置到bean定义文件即可,如下所示:
现在,容器启动的时候,就会自动将当前ApplicationContext
容器本身注入到FooBar中,因为ApplicationContext
类型容器可以自动识别Aware接口。
当然,如果应用场景仅使用ResourceLoader
类型即可满足需求,那么,还是使用ResourceLoaderAware
比较合适,ApplicationContextAware
相对来说过于宽泛了些(当然,使用也未尝不可)。
3. Resource 类型的注入
我们之前讲过, 容器可以将bean定义文件中的字符串形式表达的信息,正确地转换成具体对象定义的依赖类型。对于那些Spring容器提供的默认的PropertyEditors
无法识别的对象类型,我们可以提供自定义的PropertyEditor
实现并注册到容器中,以供容器做类型转换 的时候使用。
默认情况下,BeanFactory
容器不会为org.springframework.core.io.Resource
类型提供相应的PropertyEditor
,所以,如果我们想注入Resource类型的bean定义,就需要注册自定义的PropertyEditor
到BeanFactory
容器。不过,对于ApplicationContext
来说,我们无需这么做,因为ApplicationContext
容器可以正确识别Resource类型并转换后注入相关对象。
假设有一个XMailer类,它依赖于一个模板来提供邮件发送的内容,我们声明模板为Resource类型,那么,最终的XMailer定义也就如下方代码所示。
该类定义与平常的bean定义没有什么差别,我们直接在配置文件中以String形式指定template所在位置,ApplicatonContext
就可以正确地转换类型并注入依赖,配置内容如下:
ApplicationContext启动时,会通过一个org.springframework.beans.support.ResourceEditorRegistrar
来注册Spring提供的针对Resource类型的PropertyEditor
实现到容器中,这个PropertyEditor
叫做org.springframework.core.io.ResourceEditor
。
这样,ApplicationContext
就可以正确地识别Resource类型的依赖了。至于ResourceEditor
怎么实现我就不用说了吧?你想啊,把配置文件中的路径让ApplicationContext
作为ResourceLoader
给你定位一下不就得了。
如果应用对象需要依赖一组Resource,与ApplicationContext注册了ResourceEditor类似,Spring提供了org.springframework.core.io.support.ResourceArrayPropertyEditor实现,我们只需要通过CustomEditorConfigurar告知容器即可。
4. 在特定情况下,ApplicationContext的Resource加载行为
特定的ApplicationContext
容器实现,在作为ResourceLoader
加载资源时,会有其特定的行为。我们下面主要讨论两种类型的ApplicationContext容器,即ClassPathXmlApplicationContext
和FileSystemXmlApplicationContext
。其他类型的ApplicationContext容器,会在稍后章节中提到。
我们知道,对于URL所接受的资源路径来说,通常开始都会有一个协议前缀,比如file:
、http:
、ftp:
等。既然Spring使用UrlResource对URL定位查找的资源进行了抽象,那么,同样也支持这样类型的资源路径,而且,在这个基础上,Spring还扩展了协议前缀的集合。
ResourceLoader
中增加了一种新的资源路径协议——classpath:
,ResourcePatternResolver
又增加了一种——classpath*:
。这样,我们就可以通过这些资源路径协议前缀,明确地告知Spring容器要从classpath中加载资源,如下所示:
classpath*:
与classpath:
的唯一区别就在于,如果能够在classpath中找到多个指定的资源,则返回多个。我们可以通过这两个前缀改变某些ApplicationContext
实现类的默认资源加载行为。
ClassPathXmlApplicationContext
和FileSystemXmlApplicationContext
在处理资源加载的默认行为上有所不同。当ClassPathXmlApplicationContext
在实例化的时候,即使没有指明classpath:
或者classpath*:
等前缀,它会默认从classpath
中加载bean定义配置文件,以下代码中演示的两种实例化方式效果是相同的:
以及
而FileSystemXmlApplicationContext
则有些不同,如果我们像如下代码那样指定conf/appContext.xml
,它会尝试从文件系统中加载bean定义文件:
不过,我们可以像如下代码所示,通过在资源路径之前增加classpath:
前缀,明确指定FileSystemXmlApplicationContext
从classpath中加载bean定义的配置文件:
这时,FileSystemXmlApplicationContext
就是从Classpath中加载配置,而不是从文件系统中加载。也就是说,它现在对应的是ClassPathResource
类型的资源,而不是默认的FileSystemResource
类型资源。FileSystemXmlApplicationContext
之所以如此,是因为它与org.springframework.core.io.FileSystemResourceLoader
一样,也覆写了DefaultResourceLoader
的getResourceByPath(String)
方法,逻辑跟FileSystemResourceLoader
一模一样。
当实例化相应的ApplicationContext
时,各种实现会根据自身的特性,从不同的位置加载bean定义配置文件。当容器实例化并启动完毕,我们要用相应容器作为ResourceLoader
来加载其他资源时,各种ApplicationContext
容器的实现类依然会有不同的表现。
对于ClassPathXmlApplicationContext
来说,如果我们不指定路径之前的前缀,它会从Classpath中加载这种没有路径前缀的资源。如类似如下指定的资源路径,ClassPathXmlApplicationContext
依然尝试从Classpath加载:
如果当前容器类型为FileSystemXmlApplicationContext
,它将从文件系统中给我们加载该文件。但是,就跟实例化时可以通过classpath:
前缀覆盖掉FileSystemXmlApplicationContext
的默认加载行为一样,我们也可以在这个时候用classpath:
前缀强制指定它从Classpath中加载该文件,如以下代码所示: