前文链接:
Spring复杂的BeanFactory继承体系该如何理解? ----上
Spring复杂的BeanFactory继承体系该如何理解? ----中
作为Spring提供的较之BeanFactory更为先进的IoC容器实现,ApplicationContext除了拥有BeanFactory支持的所有功能之外,还进一步扩展了基本容器的功能,包括BeanFactoryPostProcessor、BeanPostProcessor以及其他特殊类型bean的自动识别、容器启动后bean实例的自动初始化、国际化的信息支持、容器内事件发布等。真是“青出于蓝而胜于蓝”啊!
Spring为基本的BeanFactory类型容器提供了XmlBeanFactory实现。相应地,它也为ApplicationContext类型容器提供了以下几个常用的实现
更多实现可以参照org.springframework.context.ApplicationContext接口定义的Javadoc,这里不再赘述。
前面两节说明了ApplicationContext所支持的大部分功能。下面主要围绕ApplicationContext较之BeanFactory特有的一些特性展开讨论,即国际化(I18n)信息支持、统一资源加载策略以及容器内事件发布等。
要搞清楚Spring为什么提供这么一个功能,还是从Java SE提供的标准类java.net.URL说起比较好。URL全名是Uniform Resource Locator(统一资源定位器),但多少有些名不副实的味道。
首先,说是统一资源定位,但基本实现却只限于网络形式发布的资源的查找和定位工作,基本上只提供了基于HTTP、FTP、File等协议(sun.net.www.protocol包下所支持的协议)的资源定位功能。
虽然也提供了扩展的接口,但从一开始,其自身的“定位”就已经趋于狭隘了。实际上,资源这个词的范围比较广义,资源可以任何形式存在,如以二进制对象形式存在、以字节流形式存在、以文件形式存在等;而且,资源也可以存在于任何场所,如存在于文件系统、存在于Java应用的Classpath中,甚至存在于URL可以定位的地方。
其次,从某些程度上来说,该类的功能职责划分不清,资源的查找和资源的表示没有一个清晰的界限。当前情况是,资源查找后返回的形式多种多样,没有一个统一的抽象。理想情况下,资源查找完成后,返回给客户端的应该是一个统一的资源抽象接口,客户端要对资源进行什么样的处理,应该由资源抽象接口来界定,而不应该成为资源的定位者和查找者同时要关心的事情。
所以,在这个前提下 ,Spring提出了一套基于org.springframework.core.io.Resource和org.springframework.core.io.ResourceLoader接口的资源抽象和加载策略。
Spring框架内部使用org.springframework.core.io.Resource接口作为所有资源的抽象和访问接口,我们之前在构造BeanFactory的时候已经接触过它,如下代码:
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("..."));
...
其中ClassPathResource就是Resource的一个特定类型的实现,代表的是位于Classpath中的资源。
Resource接口可以根据资源的不同类型,或者资源所处的不同场合,给出相应的具体实现。Spring框架在这个理念的基础上,提供了一些实现类(可以在org.springframework.core.io包下找到这些实现类)。
如果以上这些资源实现还不能满足要求,那么我们还可以根据相应场景给出自己的实现,只需实现org.springframework.core.io.Resource接口就是了
Resource接口定义
public interface Resource extends InputStreamSource {
boolean exists();
default boolean isReadable() {
return this.exists();
}
default boolean isOpen() {
return false;
}
default boolean isFile() {
return false;
}
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
default ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(this.getInputStream());
}
long contentLength() throws IOException;
long lastModified() throws IOException;
Resource createRelative(String var1) throws IOException;
@Nullable
String getFilename();
String getDescription();
}
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
该接口定义了很多方法,可以帮助我们查询资源状态、访问资源内容,甚至根据当前资源创建新的相对资源。
不过,要真想实现自定义的Resource,倒是真没必要直接实现该接口,我们可以继承org.springframework.core.io.AbstractResource
抽象类,然后根据当前具体资源特征,覆盖相应的方法就可以了。
什么?让我给个实现的例子?算了吧,目前我还没碰到这样的需求。呵呵!要真的碰上了,你只要知道有这么“一出儿”就行了。
资源是有了,但如何去查找和定位这些资源,则应该是ResourceLoader的职责所在了。
org.springframework.core.io.ResourceLoader接口是资源查找定位策略的统一抽象,具体的资源查找定位策略则由相应的ResourceLoader实现类给出。我想,把ResourceLoader称作统一资源定位器或许才更恰当一些吧!ResourceLoader定义如下:
public interface ResourceLoader {
String CLASSPATH_URL_PREFIX = "classpath:";
Resource getResource(String var1);
@Nullable
ClassLoader getClassLoader();
}
其中最主要的就是Resource getResource(String location);方法,通过它,我们就可以根据指定的资源位置,定位到具体的资源实例。
DefaultResourceLoader
ResourceLoader有一个默认的实现类,即org.springframework.core.io.DefaultResourceLoader,该类默认的资源查找处理逻辑如下。
(1) 首先检查资源路径是否以classpath:前缀打头,如果是,则尝试构造ClassPathResource类型资源并返回。
(2) 否则:
(a) 尝试通过URL,根据资源路径来定位资源,如果没有抛出MalformedURLException,有则会构造UrlResource类型的资源并返回;
(b)如果还是无法根据资源路径定位指定的资源,则委派getResourceByPath(String) 方法来定位, DefaultResourceLoader 的
getResourceByPath(String)方法默认实现逻辑是,构造ClassPathResource类型的资源并返回。
DefaultResourceLoader源码:
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
Iterator var2 = this.getProtocolResolvers().iterator();
Resource resource;
do {
if (!var2.hasNext()) {
if (location.startsWith("/")) {
return this.getResourceByPath(location);
}
if (location.startsWith("classpath:")) {
return new ClassPathResource(location.substring("classpath:".length()), this.getClassLoader());
}
try {
URL url = new URL(location);
return (Resource)(ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
} catch (MalformedURLException var5) {
return this.getResourceByPath(location);
}
}
ProtocolResolver protocolResolver = (ProtocolResolver)var2.next();
resource = protocolResolver.resolve(location, this);
} while(resource == null);
return resource;
}
protected Resource getResourceByPath(String path) {
return new DefaultResourceLoader.ClassPathContextResource(path, this.getClassLoader());
}
protected static class ClassPathContextResource extends ClassPathResource implements ContextResource {
public ClassPathContextResource(String path, @Nullable ClassLoader classLoader) {
super(path, classLoader);
}
public String getPathWithinContext() {
return this.getPath();
}
public Resource createRelative(String relativePath) {
String pathToUse = StringUtils.applyRelativePath(this.getPath(), relativePath);
return new DefaultResourceLoader.ClassPathContextResource(pathToUse, this.getClassLoader());
}
}
在这个基础上,让我们来看一下DefaultResourceLoader的行为是如何反应到程序中的吧!
DefaultResourceLoader使用演示
ResourceLoader resourceLoader = new DefaultResourceLoader();
Resource fakeFileResource = resourceLoader.getResource("C:\\Users\\zdh\\Desktop\\hh.txt");
System.out.println(fakeFileResource instanceof ClassPathResource);
System.out.println(fakeFileResource.exists());
System.out.println("---------------------------------------------------------------------");
Resource fakeFileResource1 = resourceLoader.getResource("file:C:\\Users\\zdh\\Desktop\\hh.txt");
System.out.println(fakeFileResource1 instanceof FileUrlResource);
System.out.println(fakeFileResource1.exists());
//获取文件,输出文件内容
File file = fakeFileResource1.getFile();
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
String line = reader.readLine();
System.out.println("文件内容: "+line);
System.out.println("---------------------------------------------------------------------");
Resource urlResource2 = resourceLoader.getResource("http://www.baidu.cn");
System.out.println(fakeFileResource1 instanceof UrlResource);
System.out.println(fakeFileResource1.exists());
URL url = urlResource2.getURL();
InputStream inputStream = url.openStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String readLine = bufferedReader.readLine();
System.out.println("URI读取到的数据第一行为: "+readLine);
System.out.println("---------------------------------------------------------------------");
尤其注意fakeFileResource资源的类型,并不是我们所预期的FileSystemResource类型,而是ClassPathResource类型,这是由DefaultResourceLoader的资源查找逻辑所决定的。
如果最终没有找到符合条件的相应资源,getResourceByPath(String)方法就会构造一个实际上并不存在的资源并返回。而指定有协议前缀的资源路径,则通过URL能够定位,所以,返回的都是UrlResource类型
FileSystemResourceLoader
为了避免DefaultResourceLoader在最后getResourceByPath(String)方法上的不恰当处理,我们可以使用org.springframework.core.io.FileSystemResourceLoader,它继承自DefaultResourceLoader,但覆写了getResourceByPath(String)方法,使之从文件系统加载资源并以FileSystemResource类型返回。这样,我们就可以取得预想的资源类型。
public class FileSystemResourceLoader extends DefaultResourceLoader {
public FileSystemResourceLoader() {
}
protected Resource getResourceByPath(String path) {
if (path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResourceLoader.FileSystemContextResource(path);
}
private static class FileSystemContextResource extends FileSystemResource implements ContextResource {
public FileSystemContextResource(String path) {
super(path);
}
public String getPathWithinContext() {
return this.getPath();
}
}
}
使用FileSystemResourceLoader
ResourceLoader resourceLoader = new FileSystemResourceLoader();
Resource fakeFileResource = resourceLoader.getResource("C:\\Users\\zdh\\Desktop\\hh.txt");
System.out.println(fakeFileResource instanceof ClassPathResource);
System.out.println(fakeFileResource.exists());
//获取文件,输出文件内容
File file1 = fakeFileResource.getFile();
BufferedReader reader1 = new BufferedReader(new InputStreamReader(new FileInputStream(file1)));
String line1 = reader1.readLine();
System.out.println("文件内容: "+line1);
System.out.println("---------------------------------------------------------------------");
Resource fakeFileResource1 = resourceLoader.getResource("file:C:\\Users\\zdh\\Desktop\\hh.txt");
System.out.println(fakeFileResource1 instanceof FileUrlResource);
System.out.println(fakeFileResource1.exists());
//获取文件,输出文件内容
File file = fakeFileResource1.getFile();
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
String line = reader.readLine();
System.out.println("文件内容: "+line);
System.out.println("---------------------------------------------------------------------");
FileSystemResourceLoader在ResourceLoader家族中的兄弟FileSystemXmlApplicationContext,也是覆写了getResourceByPath(String)方法的逻辑,以改变DefaultResourceLoader的默认资源加载行为,最终从文件系统中加载并返回FileSystemResource类型的资源。
ResourcePatternResolver是ResourceLoader的扩展,ResourceLoader每次只能根据资源路径返回确定的单个Resource实例,而ResourcePatternResolver则可以根据指定的资源路径匹配模式,每次返回多个Resource实例。
接口org.springframework.core.io.support.ResourcePatternResolver定义如下:
public interface ResourcePatternResolver extends ResourceLoader {
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
Resource[] getResources(String var1) throws IOException;
}
ResourcePatternResolver在继承ResourceLoader原有定义的基础上,又引入了Resource[] getResources(String)方法定义,以支持根据路径匹配模式返回多个Resources的功能。
它同时还引入了一种新的协议前缀classpath*:,针对这一点的支持,将由相应的子类实现给出。
ResourcePatternResolver最常用的一个实现是org.springframework.core.io.support. PathMatchingResourcePatternResolver,该实现类支持ResourceLoader级别的资源加载,支持基于Ant风格的路径匹配模式(类似于**/.suffix之类的路径形式),支ResourcePatternResolver新增加的classpath:前缀等,基本上集所有技能于一身。
在构造PathMatchingResourcePatternResolver实例的时候,可以指定一个ResourceLoader,如果不指定的话,则PathMatchingResourcePatternResolver内部会默认构造一个DefaultResourceLoader实例。PathMatchingResourcePatternResolver内部会将匹配后确定的资源路径,委派给它的ResourceLoader来查找和定位资源。
这样,如果不指定任何ResourceLoader的话,PathMatchingResourcePatternResolver在加载资源的行为上会与DefaultResourceLoader基本相同,只存在返回的Resource数量上的差异。如下代码表明了二者在资源加载行为上的一致性:
ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
Resource fileResource = resourceResolver.getResource("D:/spring21site/README");
assertTrue(fileResource instanceof ClassPathResource);
assertFalse(fileResource.exists());
不过,可以通过传入其他类型的ResourceLoader来替换PathMatchingResourcePatternResolver内部默认使用的DefaultResourceLoader,从而改变其默认行为。
比如使用FileSystemResourceLoader替换默认的DefaultResourceLoader,从而使得PathMatchingResourcePatternResolver的行为跟使用FileSystemResourceLoader一样。
替换DefaultResourceLoader后的PathMatchingResourcePatternResolver
public void testResourceTypesWithPathMatchingResourcePatternResolver()
{
ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
Resource fileResource = resourceResolver.getResource("D:/spring21site/README");
assertTrue(fileResource instanceof ClassPathResource);
assertFalse(fileResource.exists());
resourceResolver = new PathMatchingResourcePatternResolver(new ➥
FileSystemResourceLoader());
fileResource = resourceResolver.getResource("D:/spring21site/README");
assertTrue(fileResource instanceof FileSystemResource);
assertTrue(fileResource.exists());
}
现在我们应该对Spring的统一资源加载策略有了一个整体上的认识
虽然现在看来比较“单薄”,不过,稍后,我们就会发现情况并非如此了。
说是讲 ApplicationContext的统一资源加载策略,到目前为止却一直没有涉及任何ApplicationContext相关的内容,不知道你是否开始奇怪了呢?
实际上,我是有意为之,就是不想让各位因为过多关注ApplicationContext,却忽略了事情的本质。
如果回头看一下此图,
就会发现,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来做。
有了这些做前提,让我们看看作为ResourceLoader或者ResourcePatternResolver的ApplicationContext,到底因此拥有了何等神通吧!
既然ApplicationContext可以作为ResourceLoader或者ResourcePatternResolver来使用,那么,很显然,我们可以通过ApplicationContext来加载任何Spring支持的Resource类型。
与直接使用ResourceLoader来做这些事情相比,很明显,ApplicationContext的表现过于“谦虚”了。
下面演示的正是“大材小用”后的ApplicationContext
以ResourceLoader身份登场的ApplicationContext
ResourceLoader resourceLoader = new ClassPathXmlApplicationContext("配置文件路径");
// 或者
// ResourceLoader resourceLoader = new FileSystemXmlApplicationContext("配置文件路径");
Resource fileResource = resourceLoader.getResource("D:/spring21site/README");
assertTrue(fileResource instanceof ClassPathResource);
assertFalse(fileResource.exists());
Resource urlResource2 = resourceLoader.getResource("http://www.spring21.cn");
assertTrue(urlResource2 instanceof UrlResource);
我想这样的使用场景,你一定比我先猜到,不是吗?
在大部分情况下,如果某个bean需要依赖于ResourceLoader来查找定位资源,我们可以为其注入容器中声明的某个具体的ResourceLoader实现,该bean也无需实现任何接口,直接通过构造方法注入或者setter方法注入规则声明依赖即可,这样处理是比较合理的。不过,如果你不介意你的bean定义依赖于Spring的API,那不妨考虑用一下Spring提供的便利。
前面两节中曾经提到几个对ApplicationContext特定的Aware接口,这其中就包括ResourceLoaderAware和ApplicationContextAware接口。
依赖于ResourceLoader的实例类
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;
}
}
该类出于什么目的要依赖于ResourceLoader,我们暂且不论,要为其注入什么样的ResourceLoader实例才是我们当下该操心的事情。姑且先给它注入DefaultResourceLoader。这样也就有了如下配置:
<bean id="resourceLoader" class="org.springframework.core.io.DefaultResourceLoader">
</bean>
<bean id="fooBar" class="...FooBar">
<property name="resourceLoader">
<ref bean="resourceLoader"/>
</property>
</bean>
不过,ApplicationContext容器本身就是一个ResourceLoader,我们为了该类还需要单独提供一个resourceLoader实例就有些多于了,直接将当前的ApplicationContext容器作为ResourceLoader注入不就行了?
而ResourceLoaderAware和ApplicationContextAware接口正好可以帮助我们做到这一点,只不过现在的FooBar需要依赖于Spring的API了。
不过,在我看来,这没有什么大不了,因为我们从来也没有真正逃脱过依赖(这种依赖也好,那种依赖也罢)。
现在,修改我们的 FooBar 定义,让其实现 ResourceLoaderAware 或 者 ApplicationContextAware接口,修改后的定义如下:
实现了ResourceLoaderAware或者ApplicationContextAware接口的实例类
public class FooBar implements ResourceLoaderAware{
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;
}
}
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;
}
}
剩下的就是直接将一个FooBar配置到bean定义文件即可,如下所示:
<bean id="fooBar" class="...FooBar">
</bean>
哇,简洁多了不是嘛?现在,容器启动的时候,就会自动将当前ApplicationContext容器本身注入到FooBar中,因为ApplicationContext类型容器可以自动识别Aware接口。
当然,如果应用场景仅使用ResourceLoader类型即可满足需求,那么,还是使用ResourceLoaderAware比较合ApplicationContextAware相对来说过于宽泛了些(当然,使用也未尝不可)。
我们之前讲过,容器可以将bean定义文件中的字符串形式表达的信息,正确地转换成具体对象定义的依赖类型。对于那些Spring容器提供的默认的PropertyEditors无法识别的对象类型,我们可以提供自定义的PropertyEditor实现并注册到容器中,以供容器做类型转换的时候使用。
默认情况下,BeanFactory容器不会为org.springframework.core.io.Resource类型提供相应的PropertyEditor,所以,如果我们想注入Resource类型的bean定义,就需要注册自定义的PropertyEditor到BeanFactory容器。
不过,对于ApplicationContext来说,我们无需这么做,因为ApplicationContext容器可以正确识别Resource类型并转换后注入相关对象。
假设有一个XMailer类,它依赖于一个模板来提供邮件发送的内容,我们声明模板为Resource类型,那么,最终的XMailer定义也就如下所示:
依赖于Resource的XMailer类定义
public class XMailer {
private Resource template;
public void sendMail(Map mailCtx)
{
// String mailContext = merge(getTemplate().getInputStream(),mailCtx);
//...
}
public Resource getTemplate() {
return template;
}
public void setTemplate(Resource template) {
this.template = template;
}
}
该类定义与平常的bean定义没有什么差别,我们直接在配置文件中以String形式指定template所在位置,ApplicatonContext就可以正确地转换类型并注入依赖,配置内容如下:
<bean id="mailer" class="...XMailer">
<property name="template" value="..resources.default_template.vm"/>
...
</bean>
.vm是一个文件,这里的value是资源路径
至于这里面的奥秘,估计你也猜个八九不离十了。
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告知容器即可。
特定的ApplicationContext容器实现,在作为ResourceLoader加载资源时,会有其特定的行为。
我们下面主要讨论两种类型的ApplicationContext容器,即ClassPathXmlApplicationContext和FileSystemXmlApplicationContext。其他类型的ApplicationContext容器,会在稍后章节中提到。
我们知道,对于URL所接受的资源路径来说,通常开始都会有一个协议前缀,比如file:、http:、ftp:等。
既然Spring使用UrlResource对URL定位查找的资源进行了抽象,那么,同样也支持这样类型的资源路径,而且,在这个基础上,Spring还扩展了协议前缀的集合。
ResourceLoader中增加了一种新的资源路径协议——classpath:,ResourcePatternResolver又增加了一种——classpath*:。这样,我们就可以通过这些资源路径协议前缀,明确地告知Spring容器要从classpath中加载资源,如下所示:
// 代码中使用协议前缀
ResourceLoader resourceLoader = new ➥
FileSystemXmlApplicationContext("classpath:conf/container-conf.xml");
// 配置中使用协议前缀
<bean id="..." class="...">
<property name="...">
<value>classpath:resource/template.vm</value>
</property>
</bean>
classpath*:与classpath:的唯一区别就在于,如果能够在classpath中找到多个指定的资源,则返回多个。我们可以通过这两个前缀改变某些ApplicationContext实现类的默认资源加载行为。
ClassPathXmlApplicationContext和FileSystemXmlApplicationContext在处理资源加载的默认行为上有所不同。
当ClassPathXmlApplicationContext在实例化的时候,即使没有指明classpath:或者classpath*:等前缀,它会默认从classpath中加载bean定义配置文件,以下代码中演示的两种实例化方式效果是相同的:
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
以及
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:conf/appContext.xml");
而FileSystemXmlApplicationContext则有些不同,如果我们像如下代码那样指定conf/ appContext.xml,它会尝试从文件系统中加载bean定义文件:
ApplicationContext ctx = new FileSystemXmlApplicationContext("conf/appContext.xml");
不过,我们可以像如下代码所示,通过在资源路径之前增加classpath:前缀,明确指定FileSystemXmlApplicationContext从classpath中加载bean定义的配置文件:
ApplicationContext ctx = new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
这时,FileSystemXmlApplicationContext就是从Classpath中加载配置,而不是从文件系统中加载。
也就是说,它现在对应的是ClassPathResource类型的资源,而不是默认的FileSystemResource类型资源。
FileSystemXmlApplicationContext之所以如此,是因为它与org.springframework.core.io.FileSystemResourceLoader一样,也覆写了DefaultResourceLoader的getResourceByPath(String)方法,逻辑跟 FileSystemResourceLoader一模一样。
当实例化相应的ApplicationContext时,各种实现会根据自身的特性,从不同的位置加载bean定义配置文件。当容器实例化并启动完毕,我们要用相应容器作为ResourceLoader来加载其他资源时,各种ApplicationContext容器的实现类依然会有不同的表现。
对于ClassPathXmlApplicationContext来说,如果我们不指定路径之前的前缀,它也不会像资源路径所表现的那样,从文件系统加载资源,而是像实例化时候的行为一样,从Classpath中加载这种没有路径前缀的资源。
如类似如下指定的资源路径,ClassPathXmlApplicationContext依然尝试从Classpath加载:
<bean id="..." class="...">
<property name="..." value="conf/appContext.xml"/>
</bean>
如果当前容器类型为FileSystemXmlApplicationContext,事情则会像预想的那样进行,FileSystemXmlApplicationContext将从文件系统中给我们加载该文件。
但是,就跟实例化时可以通过classpath:前缀覆盖掉FileSystemXmlApplicationContext的默认加载行为一样,我们也可以在这个时候用classpath:前缀强制指定FileSystemXmlApplicationContext从Classpath中加载该文件,如以下代码所示:
<bean id="..." class="...">
<property name="..." value="classpath:conf/appContext.xml"/>
</bean>
去掉配置中的classpath:前缀,FileSystemXmlApplicationContext默认从文件系统加载资源。
小心
即使在FileSystemXmlApplicationContext实例化启动时,通过classpath:前缀强制让它从Classpath中加载bean定义文件,但这也仅限于容器的实例化并加载bean定义文件这个特定阶段。
容器实例化并启动后,作为ResourceLoader来加载资源,如果不是每个地方都使用classpath:前缀,强制FileSystemXmlApplicationContext从 Classpath 中加载资源,FileSystemXmlApplicationContext还会默认从文件系统中加载资源。
如果细化下去,这部分内容还有许多,如通配符加载的行为、FileSystemResource的特定行为等。
这里不做赘述,更多相关特性,请参照Spring参考文档。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://cjdhy.blog.csdn.net/article/details/123597084
内容来源于网络,如有侵权,请联系作者删除!