Spring IoC容器 ApplicationContext

作为Spring提供的较之BeanFactory更为先进的IoC容器实现,ApplicationContext除了拥有 BeanFactory支持的所有功能之外,还进一步扩展了基本容器的功能,包括BeanFactoryPostProces- sor、BeanPostProcessor以及其他特殊类型bean的自动识别、容器启动后bean实例的自动初始化、 国际化的信息支持、容器内事件发布等。真是“青出于蓝而胜于蓝”啊!

Spring为基本的BeanFactory类型容器提供了XmlBeanFactory实现。相应地,它也为Applica- tionContext类型容器提供了以下几个常用的实现。

  • org.springframework.context.support.FileSystemXmlApplicationContext。在默认 情况下,从文件系统加载bean定义以及相关资源的ApplicationContext实现。

  • org.springframework.context.support.ClassPathXmlApplicationContext。在默认情 况下,从Classpath加载bean定义以及相关资源的ApplicationContext实现。

  • org.springframework.web.context.support.XmlWebApplicationContext。Spring提供的用于Web应用程序的ApplicationContext实现,我们将在第六部分更多地接触到它。

统一资源加载策略

要搞清楚Spring为什么提供这么一个功能,还是从Java SE提供的标准类java.net.URL说起比较 好。URL全名是Uniform Resource Locator(统一资源定位器),但多少有些名不副实的味道。

首先,说是统一资源定位,但基本实现却只限于网络形式发布的资源的查找和定位工作,基本上 只提供了基于HTTP、FTP、File等协议的资源定位功能。

虽然也提供了扩展的接口,但从一开始,其自身的“定位”就已经趋于狭隘了。实际上,资源这个词 的范围比较广义,资源可以任何形式存在,如以二进制对象形式存在、以字节流形式存在、以文件形 式存在等;而且,资源也可以存在于任何场所,如存在于文件系统、存在于Java应用的Classpath中, 甚至存在于URL可以定位的地方。

其次,从某些程度上来说,该类的功能职责划分不清,资源的查找和资源的表示没有一个清晰的界限。当前情况是,资源查找后返回的形式多种多样,没有一个统一的抽象。理想情况下,资源查找完成后,返回给客户端的应该是一个统一的资源抽象接口,客户端要对资源进行什么样的处理,应该由资源抽象接口来界定,而不应该成为资源的定位者和查找者同时要关心的事情。

所以,在这个前提下1 ,Spring提出了一套基于org.springframework.core.io.Resource和 org.springframework.core.io.ResourceLoader接口的资源抽象和加载策略。

Spring中的Resource

Spring框架内部使用org.springframework.core.io.Resource接口作为所有资源的抽象和访 问接口,我们之前在构造BeanFactory的时候已经接触过它,如下代码:

BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("..."));

其中ClassPathResource就是Resource的一个特定类型的实现,代表的是位于Classpath中的资源。

Resource接口可以根据资源的不同类型,或者资源所处的不同场合,给出相应的具体实现。Spring 框架在这个理念的基础上,提供了一些实现类(可以在org.springframework.core.io包下找到这些实现类)。

  • ByteArrayResource。将字节(byte)数组提供的数据作为一种资源进行封装,如果通过InputStream形式访问该类型的资源,该实现会根据字节数组的数据,构造相应的ByteArray-InputStream并返回。

  • ClassPathResource。该实现从Java应用程序的ClassPath中加载具体资源并进行封装,可以使用指定的类加载器(ClassLoader)或者给定的类进行资源加载。

  • FileSystemResource。对java.io.File类型的封装,所以,我们可以以文件或者URL的形式对该类型资源进行访问,只要能跟File打的交道,基本上跟FileSystemResource也可以

  • UrlResource。通过java.net.URL进行的具体资源查找定位的实现类,内部委派URL进行具体的资源操作。

如果以上这些资源实现还不能满足要求,那么我们还可以根据相应场景给出自己的实现,只需实现org.springframework.core.io.Resource接口就是了。代码清单5-1给出了该接口的定义。

image.png

该接口定义了7个方法,可以帮助我们查询资源状态、访问资源内容,甚至根据当前资源创建新 的相对资源。不过,要真想实现自定义的Resource,倒是真没必要直接实现该接口,我们可以继承 org.springframework.core.io.AbstractResource抽象类,然后根据当前具体资源特征,覆盖相 应的方法就可以了。什么?让我给个实现的例子?算了吧,目前我还没碰到这样的需求。呵呵!要真 的碰上了,你只要知道有这么“一出儿”就行了。

ResourceLoader,“更广义的URL”

资源是有了,但如何去查找和定位这些资源,则应该是ResourceLoader的职责所在了。org.spr- ingframework.core.io.ResourceLoader接口是资源查找定位策略的统一抽象,具体的资源查找定 位策略则由相应的ResourceLoader实现类给出。我想,把ResourceLoader称作统一资源定位器或许 才更恰当一些吧!ResourceLoader定义如下:

public interface ResourceLoader {
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX; Resource getResource(String location);
ClassLoader getClassLoader();
}

其中最主要的就是Resource getResource(String location);方法,通过它,我们就可以根据指定的资源位置,定位到具体的资源实例。

  1. 可用的ResourceLoader
  • DefaultResourceLoader

ResourceLoader有一个默认的实现类,即org.springframework.core.io.DefaultResource- Loader,该类默认的资源查找处理逻辑如下。

(1) 首先检查资源路径是否以classpath:前缀打头,如果是,则尝试构造ClassPathResource类 型资源并返回。

(2) 否则,(a) 尝试通过URL,根据资源路径来定位资源,如果没有抛出MalformedURLException, 有则会构造UrlResource类型的资源并返回;(b)如果还是无法根据资源路径定位指定的资源,则委派 getResourceByPath(String) 方 法 来 定 位 , DefaultResourceLoader 的 getResourceByPath(String)方法默认实现逻辑是,构造ClassPathResource类型的资源并返回。

在这个基础上,让我们来看一下DefaultResourceLoader的行为是如何反应到程序中的吧!代码 清单5-2给出的代码片段演示了DefaultResourceLoader的具体行为。

image.png

尤其注意fakeFileResource资源的类型,并不是我们所预期的FileSystemResource类型,而是 ClassPathResource类型,这是由DefaultResourceLoader的资源查找逻辑所决定的。如果最终没 有找到符合条件的相应资源,getResourceByPath(String)方法就会构造一个实际上并不存在的资源 并返回。而指定有协议前缀的资源路径,则通过URL能够定位,所以,返回的都是UrlResource类型。

  • FileSystemResourceLoader
    为了避免DefaultResourceLoader在最后getResourceByPath(String)方法上的不恰当处理,
    我们可以使用org.springframework.core.io.FileSystemResourceLoader,它继承自Default- ResourceLoader,但覆写了getResourceByPath(String)方法,使之从文件系统加载资源并以 FileSystemResource类型返回。这样,我们就可以取得预想的资源类型。代码清单5-3中的代码将帮助我们验证这一点。
image.png

FileSystemResourceLoader 在 ResourceLoader 家 族 中 的 兄 弟 FileSystemXmlApplication- Context,也是覆写了getResourceByPath(String)方法的逻辑,以改变DefaultResourceLoader的 默认资源加载行为,最终从文件系统中加载并返回FileSystemResource类型的资源。

  1. ResourcePatternResolver

ResourcePatternResolver是ResourceLoader的扩展,ResourceLoader每次只能根据资源路径 返回确定的单个Resource实例,而ResourcePatternResolver则可以根据指定的资源路径匹配模式, 每次返回多个Resource实例。接口org.springframework.core.io.support.ResourcePattern- Resolver定义如下:


image.png

ResourcePatternResolver在继承ResourceLoader原有定义的基础上,又引入了Resource[]getResources(String)方法定义,以支持根据路径匹配模式返回多个Resources的功能。它同时还 引入了一种新的协议前缀classpath*:,针对这一点的支持,将由相应的子类实现给出。

ResourcePatternResolver 最 常 用 的 一 个 实 现 是 org.springframework.core.io.support. PathMatchingResourcePatternResolver,该实现类支持ResourceLoader级别的资源加载,支持基 于Ant风格的路径匹配模式(类似于/.suffix之类的路径形式),支持ResourcePatternResolver新 增加的classpath:前缀等,基本上集所有技能于一身。

现在我们应该对Spring的统一资源加载策略有了一个整体上的认识,就如图5-1所示。

image.png

ApplicationContext与ResourceLoader

说是讲ApplicationContext的统一资源加载策略,到目前为止却一直没有涉及任何 ApplicationContext相关的内容,不知道你是否开始奇怪了呢?实际上,我是有意为之,就是不想 让各位因为过多关注ApplicationContext,却忽略了事情的本质。

如果回头看一下图4-2,就会发现,ApplicationContext继承了ResourcePatternResolver,当 然就间接实现了ResourceLoader接口。所以,任何的ApplicationContext实现都可以看作是一个 ResourceLoader甚至ResourcePatternResolver。而这就是ApplicationContext支持Spring内统一 资源加载策略的真相。

通常,所有的ApplicationContext实现类会直接或者间接地继承org.springframework.context.support.AbstractApplicationContext,从这个类上,我们就可以看到Application- Context与ResourceLoader之间的所有关系。AbstractApplicationContext继承了DefaultRe- sourceLoader,那么,它的getResource(String)当然就直接用DefaultResourceLoader的了。剩 下需要它“效劳”的,就是ResourcePatternResolver的Resource[]getResources (String),当 然,AbstractApplicationContext也不负众望,当即拿下。AbstractApplicationContext类的内 部声明有一个resourcePatternResolver,类型是ResourcePatternResolver,对应的实例类型为 PathMatchingResourcePatternResolver 。 之 前 我 们 说 过 PathMatchingResourcePattern- Resolver构造的时候会接受一个ResourceLoader,而AbstractApplicationContext本身又继承自 DefaultResourceLoader,当然就直接把自身给“贡献”了。这样,整个ApplicationContext的实 现类就完全可以支持ResourceLoader或者ResourcePatternResolver接口,你能说Application- Context不支持Spring的统一资源加载吗?说白了,ApplicationContext的实现类在作为Resource- 15 Loader或者ResourcePatternResolver时候的行为,完全就是委派给了PathMatchingResource- PatternResolver和DefaultResourceLoader来做。图5-2给出了AbstractApplicationContext与 ResourceLoader和ResourcePatternResolver之间的类层次关系。


image.png

有了这些做前提,让我们看看作为ResourceLoader或者ResourcePatternResolver的Applica- tionContext,到底因此拥有了何等神通吧!

  1. 扮演ResourceLoader的角色

既然ApplicationContext可以作为ResourceLoader或者ResourcePatternResolver来使用, 那么,很显然,我们可以通过ApplicationContext来加载任何Spring支持的Resource类型。与直接 使用ResourceLoader来做这些事情相比,很明显,ApplicationContext的表现过于“谦虚”了。代 码清单5-5演示的正是“大材小用”后的ApplicationContext。

image.png
  1. 在特定情况下,ApplicationContext的Resource加载行为

特定的ApplicationContext容器实现,在作为ResourceLoader加载资源时,会有其特定的行为。 我们下面主要讨论两种类型的ApplicationContext容器,即ClassPathXmlApplicationContext和 FileSystemXmlApplicationContext。

我们知道,对于URL所接受的资源路径来说,通常开始都会有一个协议前缀,比如file:、http:、ftp: 等。既然Spring使用UrlResource对URL定位查找的资源进行了抽象,那么,同样也支持这样类型的 资源路径,而且,在这个基础上,Spring还扩展了协议前缀的集合。ResourceLoader中增加了一种新 的资源路径协议——classpath:,ResourcePatternResolver又增加了一种——classpath*:。这 样,我们就可以通过这些资源路径协议前缀,明确地告知Spring容器要从classpath中加载资源,如下所示:

image.png

classpath*:与classpath:的唯一区别就在于,如果能够在classpath中找到多个指定的资源,则 返回多个。我们可以通过这两个前缀改变某些ApplicationContext实现类的默认资源加载行为。

ClassPathXmlApplicationContext和FileSystemXmlApplicationContext在处理资源加载的默认行为上有所不同。当ClassPathXmlApplicationContext在实例化的时候,即使没有指明 classpath:或者classpath*:等前缀,它会默认从classpath中加载bean定义配置文件,以下代码中演 示的两种实例化方式效果是相同的:


image.png

而FileSystemXmlApplicationContext则有些不同,如果我们像如下代码那样指定conf/appContext.xml,它会尝试从文件系统中加载bean定义文件:

image.png

不过,我们可以像如下代码所示,通过在资源路径之前增加classpath:前缀,明确指定 FileSystemXmlApplicationContext从classpath中加载bean定义的配置文件:

ApplicationContext ctx = new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");

这时,FileSystemXmlApplicationContext就是从Classpath中加载配置,而不是从文件系统中 加载。也就是说,它现在对应的是ClassPathResource类型的资源,而不是默认的FileSystem- Resource类型资源。FileSystemXmlApplicationContext之所以如此,是因为它与org.springfra- mework.core.io.FileSystemResourceLoader一样,也覆写了DefaultResourceLoader的getRes- ourceByPath(String)方法,逻辑跟 FileSystemResourceLoader一模一样

当实例化相应的ApplicationContext时,各种实现会根据自身的特性,从不同的位置加载bean 定义配置文件。当容器实例化并启动完毕,我们要用相应容器作为ResourceLoader来加载其他资源 时,各种ApplicationContext容器的实现类依然会有不同的表现。

ApplicationContext是Spring在BeanFactory基础容器之上,提供的另一个IoC容器实现。它拥 有许多BeanFactory所没有的特性,包括统一的资源加载策略、国际化信息支持、容器内事件发布以 及简化的多配置文件加载功能

你可能感兴趣的:(Spring IoC容器 ApplicationContext)