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所没有的特性,包括统一的资源加载策略、国际化信息支持、容器内事件发布以 及简化的多配置文件加载功能

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351