IoC容器 ApplicationContext

目录

统一资源加载策略

Spring中的Resource

ResourceLoader, "更广义的URL"

DefaultResourceLoader

FileSysternResourceLoader

ResourcePatternResolver --  批量查找的ResourceLoader

ResourceLoader和Resource层次图

 ApplicationContext与ResourceLoader

扮演ResourceLoader的角色

ResourceLoader类型的注入

Resource类型的注入

在特定情况下,ApplicationContext的Resource加载行为

国际化信息支持

Java SE提供的国际化支持

Locale

 ResourceBundle

MessageSource与ApplicationContext

可用的MessageSource实现

MessageSourceAware和MessageSource的注入

容器内部事件发布

自定义事件发布

Spring的容器内事件发布类结构分析

ApplicationEvent

ApplicationListener

ApplicationContext

Spring容器内事件发布的应用

MethodExecutionEvent的改装

MethodExecutionEventListener

MethodExeuctionEventPublisher改造

注册到ApplicationContext容器

多配置模块加载的简化


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

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

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

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

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

更多实现可以参照org.springframework.context.ApplicationContext接口定义的Javadoc, 这里不再赘述。

统一资源加载策略

要搞清楚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中的Resource

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

其中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进行具 体的资源操作。

InputStreamResource。将给定的InputStream视为一种资源的Resource实现类,较为少用。 可能的情况下,以ByteArrayResource以及其他形式资源实现代之。

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

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

ResourceLoader, "更广义的URL"

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

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

DefaultResourceLoader

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

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

(2)否则,

(a)尝试通过URL, 根据资源路径来定位资源,如果没有抛出MalformedURLException, 有则会构造UrlResource类型的资源并返回;

(b)如果还是无法根据资源路径定位指定的资源,则委派 getResourceByPath(String)方法来定位,DefaultResourceLoader的 getResourceByPath(String)方法默认实现逻辑是,构造ClassPathResource类型的资源并返回。

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

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

FileSysternResourceLoader

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

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

ResourcePatternResolver --  批量查找的ResourceLoader

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

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

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

在构造PathMatchingResourcePatternResolver实例的时候,可以指定一个ResourceLoader, 如果不指定的话,则PathMatchingResourcePatternResolver内部会默认构造一个Default- ResourceLoader实例。PathMatchingResourcePatternResolver内部会将匹配后确定的资源路径, 委派给它的ResourceLoader来查找和定位资源。这样,如果不指定任何ResourceLoader的话,PathMatchingResourcePatternResolver在加载资源的行为上会与DefaultResourceLoader基本相同, 只存在返回的Resource数晕上的差异。如下代码表明了二者在资源加载行为上的一致性:

不过,可以通过传入其他类型的ResourceLoader来替换PathMatchingResourcePatternResolver 内部默认使用的DefaultResourceLoader, 从而改变其默认行为。比如,可以如代码清单所示, 使用FileSystemResourceLoader替换默认的DefaultResourceLoader, 从而使得PathMatching­Resource PatternResolver的行为跟使用FileSystemResourceLoader一样。

ResourceLoader和Resource层次图

 ApplicationContext与ResourceLoader

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

如果回头看一下图, 就会发现,ApplicationContext继承了ResourcePatternResolver, 当 然就间接实现了ResourceLoader接口。所以,任何的ApplicationContext实现都可以看作是一个 ResourceLoader甚至Resource PatternResolver。而这就是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接口,你能说ApplicationContext不支持Spring的统一资源加载吗?说自了,ApplicationContext的实现类在作为Resource­Loader或者Resource PatternResolver时候的行为,完全就是委派给了PathMatchingResource­PatternResolver和DefaultResourceLoader来做。图给出了AbstractApplicationContext与 ResourceLoader和ResourcePatternResolver之间的类层次关系。

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

扮演ResourceLoader的角色

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

ResourceLoader类型的注入

在大部分情况下,如果某个bean需要依赖于ResourceLoader来查找定位资源,我们可以为其注 入容器中声明的某个具体的ResourceLoader实现,该bean也无需实现任何接口,直接通过构造方法 注入或者setter方法注入规则声明依赖即可,这样处理是比较合理的。不过,如果你不介意你的bean定 义依赖于Spring的API那不妨考虑用一下Spring提供的便利。

几个对ApplicationContext特定的Aware接口,这其中就包括Resource­Loader Aware和ApplicationContextAware接口

假设我们有类定义如代码清单

该类出于什么目的要依赖于ResourceLoader, 我们暂且不论,要为其注入什么样的Resource­Loader实例才是我们当下该操心的事情。姑且先给它注入DefaultResourceLoader。这样也就有了 如下配置:

不过,ApplicationContext容器本身就是一个ResourceLoader, 我们为了该类还需要单独提供 一个resourceLoader实例就有些多于了,直接将当前的ApplicationContext容器作为Resource­Loader注入不就行了?而ResourceLoaderAware和ApplicationContextAware接口正好可以帮助我 们做到这一点,只不过现在的FooBar需要依赖于Spring的API了。不过,在我看来,这没有什么大不 了,因为我们从来也没有真正逃脱过依赖(这种依赖也好,那种依赖也罢)。

现在,修改我们的FooBar定义,让其实现ResourceLoaderAware或者ApplicationContextAware接口,修改后的定义如代码清单

剩下的就是直接将一个FooBar配置到bean定义文件即可,如下所示:

哇,简洁多了不是唳?现在,容器启动的时候,就会自动将当前ApplicationContext容器本身 注入到FooBar中,因为ApplicationContext类型容器可以自动识别Aware接口。

当然,如果应用场景仅使用ResourceLoader类型即可满足需求,那么,还是使用ResourceLoader­Aware比较合适,ApplicationContextAware相对来说过于宽泛了些(当然,使用也未尝不可)。

Resource类型的注入

我们之前讲过,容器可以将bean定义文件中的字符串形式表达的信息,正确地转换成具体对象定 义的依赖类型。对于那些Spring容器提供的默认的PropertyEditors无法识别的对象类型,我们可以 提供自定义的PropertyEditor实现并注册到容器中,以供容器做类型转换的时候使用。默认情况下, BeanFactory容器不会为org.springframework.core.io.Resource类型提供相应的Property­Editor, 所以,如果我们想注入Resource类型的bean定义,就需要注册自定义的PropertyEditor到 BeanFactory容器。不过,对于ApplicationContext来说,我们无需这么做,因为ApplicationContext容器可以正确识别Resource类型并转换后注入相关对象。

假设有一个XMailer类,它依赖于一个模板来提供邮件发送的内容,我们声明模板为Resource类 型,那么,最终的XMailer定义也就如代码清单所示。

该类定义与平常的bean定义没有什么差别,我们直接在配置文件中以string形式指定template 所在位置,ApplicatonContext就可以正确地转换类型并注入依赖,配置内容如下:

至于这里面的奥秘,估计你也猜个八九不离十了。

ApplicationContext启动伊始,会通过一个org.springframework.beans.support.Resource­EditorRegistrar来注册Spring提供的针对Resource类型的PropertyEditor实现到容器中,这个 PropertyEditor叫做org.springframework.core.io.ResourceEditor。

这样,ApplicationContext就可以正确地识别Resource类型的依赖了。至于ResourceEditor怎么实现我就不用说了 吧?你想啊,把配置文件中的路径让ApplictionContext作为ResourceLoader给你定位一下不就得 了

注意如果应用对象需要依赖一组Resource, 与ApplicationContext注册了ResourceEditor类 似,Spring提供了org.springframework.core.io.support.ResourceArrayProperty­Editor实现,我们只需要通过CustomEditorConfigurar告知容器即可。

在特定情况下,ApplicationContext的Resource加载行为

特定的ApplicationContext容器实现,在作为ResourceLoader加载资源时,会有其特定的行为。

我们下面主要讨论两种类型的ApplicationContext容器,即ClassPathXmlApplicationContext和 FileSystemXmlApplicationContext。其他类型的ApplicationContext容器,会在稍后章节中提到。

我们知道,对于URL所接受的资源路径来说,通常开始都会有一个协议前缀,比如file、http、ftp 等。既然Spring使用UrlResource对UR.L定位查找的资源进行了抽象,那么,同样也支持这样类型的 资源路径,而且,在这个基础上,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类型的资源,而不是默认的FileSystem­Resource类型资源。FileSystemxmlApplicationContext之所以如此,是因为它与org.springfra­mework.core.io.FileSystemResourceLoader一样,也覆写了DefaultResourceLoader的getResourceByPath(String)方法,逻辑跟FileSystemResourceLoader一模一样。

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

对于ClassPathXmlApplicationContext来说,如果我们不指定路径之前的前缀,它也不会像资 源路径所表现的那样,从文件系统加载资源,而是像实例化时候的行为一样,从Class path中加载这种 没有路径前缀的资源。如类似如下指定的资源路径,ClassPathXmlApplicationContext依然尝试从 Classpath加载:

如果当前容器类型为FileSystemXmlApplicationContext, 事情则会像预想的那样进行, FileSystemXmlApplicationContext将从文件系统中给我们加载该文件。但是,就跟实例化时可以 通过classpath:前缀覆盖掉FileSystemXmlApplicationContext的默认加载行为一样,我们也可以 在这个时候用classpath:前缀强制指定FileSystemXmlApplicationContextPath中加载该 文件,如以下代码所示:

即使在FileSystemXmlApplicationContext实例化启动时,通过classpath: 前缀强制 让它从Class path中加载bean定义文件,但这也仅限于容器的实例化并加载bean定义文件这个特 定阶段。容器实例化并启动后,作为ResourceLoader来加载资森,如果不是每个地方都使用 classpath:前缀,强制FileSystemXmlApplicationContext从Classpath中加载资源, FileSystemXmlApplicationContext还会默认从文件系统中加载资源。

国际化信息支持

Java SE提供的国际化支持

程序的国际化不是三言两语可以讲清楚的,它涉及许多的内容,如货币形式的格式化、时间的表 现形式、各国家和地区的语言文字等。

对于Java中的国际化信息处理,主要涉及两个类,即java.util.Locale和java.util.ResourceBundle。

Locale

不同的Locale代表不同的国家和地区,每个国家和地区在Locale这里都有相应的简写代码表示, 包括语言代码以及国家代码,这些代码是ISO标准代码。如,Locale.CHINA代表中国,它的代码表示 为zh_CN; Locale.US代表美国地区,代码表示为en_UST 而美国和英国等都属于英语地区,则可以 使用Locale.ENGLISH来统一表示,这时代码只有语言代码,即en。

Locale类提供了三个构造方法,它们的定义如下:

常用的Locale都提供有静态常量,不用我们自己重新构造。一些不常用的Locale的则需要根据 相应的国家和地区以及语言来进行构造。有了Locale, 我们的应用程序就可以通过它来判别如何为不 同的国家和地区的用户提供相应的信息。

 ResourceBundle

ResourceBundle用来保存特定于某个Locale的信息(可以是String类型信息,也可以是任何类型 的对象)。通常,ResourceBundle管理一组信息序列,所有的信息序列有统一的一个basename, 然后 特定的Locale的信息,可以根据basename后追加的语言或者地区代码来区分。比如,我们用一组properties文件来分别保存不同国家地区的信息,可以像下面这样来命名相应的properties文件:

其中,文件名中的messages部分称作ResourceBundle将加载的资源的basename, 其他语言或地 区的资源在:b asename的基础上追加Locale特定代码。

每个资源文件中都有相同的键来标志具体资源条目,但每个资源内部对应相同键的资源条目内 容,则根据Locale的不同而不同。如下代码片段演示了两个不同的资源文件内容的对比情况:

 

注意 按照规定,properties文件内容是以IS0-8859-1编码的,所以,实际上message_ zh _ CN. properties中各个键对应的内容是不应该以中文提供的,应该使用native2ascii 或者类似的相 关工具进行转码,这里如此举例,只是力了更好地说明差别。

有了ResourceBundle对应的资源文件之后,我们就可以通过ResourceBundle的getBundle (String baseName, Locale locale)方法取得不同Locale对应的ResourceBundle, 然后根据资源 的键取得相应Locale的资源条目内容。

通过结合ResourceBundle和Locale, 我们就能够实现应用程序的国际化信息支持。

MessageSource与ApplicationContext

Spring在Java SE的国际化支持的基础上,进一步抽象了国际化信息的访问接口,也就是 org.springframework.context.MessageSource, 该接口定义如下:

通过该接口,我们统一了国际化信息的访问方式。传入相应的Locale、资源的键以及相应参数, 就可以取得相应的信息,再也不用先根据Locale取得ResourceBundle, 然后再从ResourceBundle查 询信息了。对MessageSource所提供的三个方法的简单说明如下。

string getMessage(String code, Object[] args, String DefaultMessage, Locale locale)。根据传入的资源条目的键(对应方法声明中的code参数)、信息参数以及Locale来查找信息,如果对应信息没有找到,则返回指定的DefaultMessage。

String getMessage(String code, Object[] args, Locale locale)throws NoSuchMessageException。与第一个方法相同,只不过,因为没有指定默认信息,当对应的信息找 不到的情况下,将抛出NoSuchMessageException异常。

String getMessage(MessageSourceResolvable resolvable, Locale locale) throws­NoSuchMessageException。使用MessageSourceResolvable对象对资源条目的键、信息参 数等进行封装,将封住了这些信息的MessageSourceResolvable对象作为查询参数来调用以 上方法。如果根据MessageSourceResolvable中的信息查找不到相应条目内容,将抛出 NoSuclJMessageException异常。

现在我们知道,ApplicationContext除了实现了ResourceLoader以支持统一的资源加载,它还 实现了MessageSource接口,那么就跟ApplicationContext因为实现了ResourceLoader而可以当作 ResourceLoader来使用一样,ApplicationContext现在也是一个MessageSource了。

在默认情况下,ApplicationContext将委派容器中一个名称为messageSource的Message­Source接口实现来完成MessageSource应该完成的职责。如果找不到这样一个名字的MessageSource 实现,ApplicationContext内部会默认实例化一个不含任何内容的staticMessageSource实例,以 保证相应的方法调用。所以通常情况下,如果要提供容器内的国际化信息支持,我们会添加如代码清 单类似的配置信息到容器的配置文件中。

可用的MessageSource实现

Spring提供了三种MessageSource的实现,即staticMessageSource、ResourceBundleMessage­Source和ReloadableResourceBundleMessageSource。

org.springframework.context.support.StaticMessageSource。MessageSource接口的 简单实现,可以通过编程的方式添加信息条目,多用于测试,不应该用于正式的生产环境。

org.springframework.context.support.ResourceBundleMessageSource。基于标准的 java.util.ResourceBundle而实现的MessageSource, 对其父类AbstractMessageSource 的行为进行了扩展,提供对多个ResourceBundle的缓存以提高查询速度。同时,对于参数化 的信息和非参数化信息的处理进行了优化,并对用于参数化信息格式化的MessageFormat实例也进行了缓存。它是最常用的、用于正式生产环境下的MessageSource实现。

org.springframework.context.support.ReloadableResourceBundleMessageSource。同样基于标准的java.util.ResourceBundle而构建的MessageSource实现类,但通过其 cacheSeconds属性可以指定时间段,以定期刷新并检查底层的properties资源文件是否有变更。 对于properties资源文件的加载方式也与ResourceBundleMessageSource有所不同,可以通过 ResourceLoader来加载信息资源文件。使用ReloadableResourceBundleMessageSource时, 应该避免将信息资源文件放到classpath中,因为这无助于ReloadableResourceBundle­MessageSource定期加载文件变更。更多信息参照该类的Javadoc。

这三种实现都可以独立于容器并在独立运行(Standalone形式)的应用程序中使用,而并非只能 依托ApplicationContext才可使用。代码清单为我们演示了这三种MessageSource的简单使用。

之前提到,ApplicationContext需要其配置文件中有一个名称为messageSource的Message­Source实现,自然就是以上的三选一了。

至此,我们有了图

如果说以上三种MessageSource的实现还是不能满足你的要求,那么直接继承AbstractMessageSource, 然后覆写几个方法就行了,甚至可以直接实现MessageSource接口,如果你的需求 真的那么特别。

MessageSourceAware和MessageSource的注入

ApplicationContext启动的时候,会自动识别容器中类型为MessageSourceAware的bean定义, 并将自身作为MessageSource注入相应对象实例中。如果某个业务对象需要国际化的信息支持,那么 最简单的办法就是让它实现MessageSourceAware接口,然后注册到ApplicationContext容器。不过 这样一来,该业务对象对ApplicationContext容器的依赖性就太强了,显得容器具有较强的侵入性。

而实际上,如果真的某个业务对象需要依赖于MessageSource的话,直接通过构造方法注入或者 setter方法注入的方式声明依赖就可以了。只要配置bean定义时,将ApplicationContext容器内部的 那个messageSource注入该业务对象即可。

假设我们有一个通用的Validator数据验证类,它需要通 过MessageSource来返回相应的错误信息,那么可以为其声明一个MessageSource依赖,然后将 ApplicationContext中的那个已经配置好的messageSource注入给它。代码清单给出了该类的 定义以及相关注入配置。

既然MessageSource可以独立使用,那为什么还让ApplicationContext实现该接口呢?在独立 运行的应用程序(Standalone Application)中,就如我们上面这些应用场景所展示的那样,直接使用 MessageSource的相应实现类就行了。不过在Web应用程序中,通常会公开ApplicationContext给 视图(View)层,这样,通过标签(tag)就可以直接访问国际化信息了。

容器内部事件发布

Spring的ApplicationContext容器提供的容器内事件发布功能,是通过提供一套基于Java SE标 准自定义事件类而实现的。为了更好地了解这组自定义事件类,我们可以先从Java SE的标准自定义事 件类实现的推荐流程说起。

自定义事件发布

Java SE提供了实现自定义事件发布(Custom Event publication)功能的基础类,即java.util.Eve­ntObject类和java.util.EventListener接口。所有的自定义事件类型可以通过扩展EventObject 来实现,而事件的监听器则扩展自EventListener。下面让我们看一下要实现一套自定义事件发布类 的架构,应该如何来做。

给出自定义事件类型( define your own event object)。为了针对具体场景可以区分具体的事件 类型,我们需要给出自己的事件类型的定义,通常做法是扩展java.util.EventObject类来实现自定 义的事件类型。我们此次定义的自定义事件类型见代码清单。

我们想对方法的执行情况进行发布和监听,所以,就声明了一个MethodExecutionEvent类型, 它继承自EventObject,当该类型的事件发布之后,相应的监听器即可对该类型的事件进行处理。如 果需要,自定义事件类可以根据情况提供更多信息,不用担心自定义事件类的“承受力”。

实现针对自定义事件类的事件监听器接口( define cListom event listener)。自定义的事件监听 器需要在合适的时机监听自定义的事件,如刚声明的MethodExecutionEvent,我们可以在方法开始 执行的时候发布该事件,也可以在方法执行即将结束之际发布该事件。相应地,自定义的事件监听器需要提供方法对这两种情况下接收到的事件进行处理。代码清单给出了针对 MethodExecutionEvent的事件监听器接口定义。

事件监听器接口定义首先继承了java. util.EventListener, 然后针对不同的事件发布时机提供 相应的处理方法定义,最主要的就是,这些处理方法所接受的参数就是MethodExecutionEvent类型 的事件。也就是说,我们的自定义事件监听器类只负责监听其对应的自定义事件并进行处理,如果什 么事件它都要处理,那么非忙死不可。有了事件监听器接口定义,还必须根据时机需求提供相应的实 现,只有接口定义可是于不了什么事情的啊!出于简化,我们仅给出一个简单的实现定义,见代码清 单

组合事件类和监听器,发布事件。有了自定义事件和自定义事件监听器,剩下的就是发布事件, 然后让相应的监听器监听并处理事件了。通常情况下,我们会有一个事件发布者(EventPublisher) , 它本身作为事件源,会在合适的时点,将相应事件发布给对应的事件监听器。代码清单给出了针 对MethodExecutionEvent的事件发布者类的定义。

我们的事件发布者关注的主要有两点。

具体时点上自定义事件的发布。方法methodMonitor()是事件发布的源头,MethodExecution­EventPublisher在该方法开始和即将结束的时候,分别针对这两个时点发布MethodExecutionEvent 事件。具体实现上,每个时点发布的事件会通过MethodExecutionEventListener的相应方法传给注 册的监听者并被处理掉。

在实现中,需要注意到,为了避免事件处理期间事件监听器的注册或移除操 作影响处理过程,我们对事件发布时点的监听器列表进行了一个安全复制(safe-copy)。另外,事件 的发布是顺序执行,所以为了能够不影响处理性能,事件监听器的处理逻辑应该尽量简短。

自定义事件监听器的管理。MethodExeuctionEventPublisher类提供了与事件监听器的注册和 移除相关的方法,这样,客户端可以根据情况决定是否需要注册或者移除某个事件监听器。这里容易 出现问题的情况是,如果没有提供remove事件监听器的方法,那么注册的监听器实例会一直被 MethodExeuctionEventPublisher引用,即使已经过期了或者废弃不用了,也依然存在于 MethodExeuctionEventPublisher的监听器列表中。这会导致隐性的内存泄漏,在任何事件监听器 的处理上都可能出现这种问题。

整个Java SE中标准的自定义事件实现就是这个样子,基本上涉及三个角色,即自定义的事件类型、 自定义的事件监听器和自定义的事件发布者,关系如图

Spring的容器内事件发布类结构分析

Spring的ApplicationContext容器内部允许以org.springframework.context.ApplicationEvent的形式发布事件,容器内注册的org.springframework.context.Application­Listener类型的bean定义会被ApplicationContext容器自动识别,它们负责监听容器内发布的所有 ApplicationEvent类型的事件。也就是说,一旦容器内发布ApplicationEvent及其子类型的事件, 注册到容器的ApplicationListener就会对这些事件进行处理。

我想你已经猜到是怎么回事了。

ApplicationEvent

Spring容器内自定义事件类型,继承自java.util.EventObject,它是一个抽象类,需要根据情 况提供相应子类以区分不同情况。默认情况下,Spring提供了三个实现。

ContextClosedEvent: ApplicationContext容器在即将关闭的时候发布的事件类型。

ContextRefreshedEvent: ApplicationContext容器在初始化或者刷新的时候发布的事件类 型。

RequestHandledEvent: Web请求处理后发布的事件,其有一子类ServletRequestHandled­Event提供特定于Java EE的Servlet相关事件。

ApplicationListener

ApplicationContext容器内使用的自定义事件监听器接口定义,继承自java.util.EventListener。ApplicationContext容器在启动时,会自动识别并加载EventListener类型bean定义, 一旦容器内有事件发布,将通知这些注册到容器的EventListener。

ApplicationContext

还记得ApplicationContext的定义吧?除了之前的ResourceLoader和MessageSource, ApplicationContext接口定义还继承了ApplicationEventPublisher接口,该接口提供了void publish­Event(ApplicationEvent event)方法定义。不难看出,ApplicationContext容器现在担当的就是 事件发布者的角色。

虽然ApplicationContext继承了ApplicationEventPublisher接口而担当了事件发布者的角 色,但是在具体实现上,与之前提到的自定义事件实现流程有些许差异,且让我一一道来……

ApplicationContext容器的具体实现类在实现事件的发布和事件监听器的注册方面,并没事必 躬亲,而是把这些活儿转包给了一个称作org.springframework.context.event.Application­EventMulticaster的接口。该接口定义了具体事件监听器的注册管理以及事件发布的方法,但接口 终归是接口,还得有具体实现。

ApplicationEventMulticaster有一抽象实现类 org.spring­framework.context.event.AbstractApplicationEventMulticaster, 它实现了事件监听器的管理 功能。出于灵活性和扩展性考虑,事件的发布功能则委托给了其子类。

org.springframework. context.event.SimpleApplicationEventMulticaster是Spring提供的AbstractApplication­EventMulticaster的一个子类实现,添加了事件发布功能的实现。不过,其默认使用了SyncTask­Executor进行事件的发布。与我们给出的样例事件发布者实现—样,事件是同步顺序发布的。为了避 免这种方式可能存在的性能问题,我们可以为其提供其他类型的TaskExecutor实现类 (TaskExecutor的概念将在后面详细介绍)。

因为ApplicationContext容器的事件发布功能全部委托给了ApplicationEventMulticaster 来做,所以,容器启动伊始,就会检查容器内是否存在名称为ApplicationEventMulticaster的 ApplicationEventMulticaster对象实例。有的话就使用提供的实现,没有则默认初始化一个 SimpleApplicationEventMulticaster作为将会使用的ApplicationEventMulticaster。这样,整 个Spring容器内事件发布功能实现结构图就有了,如图

Spring容器内事件发布的应用

Spring的ApplicationContext容器内的事件发布机制,主要用于单一容器内的简单消息通知和处 理,并不适合分布式、多进程、多容器之间的事件通知。虽然可以通过Spring的Remoting支持, “曲 折一点”来实现较为复杂的需求,但是难免弊大于利,失大于得。其他消息机制处理较复杂场景或许 更合适。所以,我们应该在合适的地点、合适的需求分析的前提下,合理地使用Spring提供的 ApplicationContext容器内的事件发布机制。

要让我们的业务类支持容器内的事件 发布 ,需要它拥有ApplicationEventPublisher的事件发 布支持。所以,需要为其注入ApplicationEventPublisher实例。可以通过如下两种方式为我们的 业务对象注入ApplicationEventPublisher的依赖。

使用ApplicationEventPublisherAware接口。在ApplicationContext类型的容器启动时, 会自动识别该类型的bean定义并将ApplicationContext容器本身作为ApplicationEvent- Publisher注入当前对象,而ApplicationContext容器本身就是一个ApplicationEvent- Publisher。

使用ApplicationContextAware接口。既然ApplicationContext本身就是一个Application­EventPublisher, 那么通过ApplicationContextAware几乎达到第一种方式相同的效果。

下面,我们把之前的MethodExecutionEvent相关类改装一下,也好看看改装成使用容器内的事 件发布到底是个什么样子。

MethodExecutionEvent的改装

因为ApplicationListener只通过void onApplicationEvent(ApplicationEvent event)这 一个事件处理方法来处理事件,所以现在要在事件类中尽量保存必要的信息。改装后的 MethodExecutionEvent类定义如代码清单

MethodExecutionEventListener

我们的MethodExecutionEventListener不再是接口,而是具体的ApplicationListener实现 类。因为ApplicationListener巳经取代了MethodExecutionEventListener原来的角色,所以,改 装后的MethodExecutionEventListener定义如下:

MethodExeuctionEventPublisher改造

MethodExeuctionEventPublisher改造后如代码清单

现在,直接使用注入的eventPublisher来发布事件,而不用自己实现事件发布逻辑了。需要注 意的就是,我们实现了ApplicationEventPublisherAware接口(当然,ApplicationContextAware 也是可以的)。

注册到ApplicationContext容器

最后一步工作就是将MethodExeuctionEventPublisher和MethodExecutionEventListener注 册到ApplicationContext容器中。当MethodExecutionEventPublisher的methodToMonitor方法被 调用时,事件即被发布。配置如下所示:

整个改造就此宣告结束!

提示 你可能觉得我们的实例没有任何实际意义。不过,我想提醒的是,如果你尝试在每次发布事件的时候,将当前系统时间或者其他信息也通过MethodExecutionEvent传给具体的 ApplicationListener处理,情况是否有所改观呢?你完全可以通过这样的方式未监控系统 性能了!要知道,完全可以在这个的基础上往简单的AOP迈进哦!

Spring的容器内事件发布机制初步看来无法脱离容器单独使用。不过,要想做,也不是不可以的, 只不过是直接使用ApplicationEventMulticaster接口进行事件发布而已。就提这一句吧,你如果 有兴趣可以自己尝试一下。

多配置模块加载的简化

实际上,这也不算ApplicationContext比较突出的特色功能,只是相对于BeanFactory来说, 在这一点上做得更好罢了。

我们知道,在使用Spring的IoC轻量级容器进行实际开发的过程中,为了避免出现整个团队因某个 资源独占而无法并行、高效地完成工作等问题,通常会将整个系统的配置信息按照某种关注点进行分 割,使得关注点逻辑良好地划分到不同的配置文件中,如按照功能模块或者按照系统划分的层次等。

这样,在加载整个系统的bean定义时,就需要让容器同时读入划分到不同配置文件的信息。相对于 BeanFactory来说,ApplicationContext大大简化了这种情况下的多配置文件的加载工作。

假设在文件系统中存在多个Spring的配置文件,它们所在路径如下所示:

通过ApplicationContext,我们只要以string[]形式传入这些配置文件所在的路径,即可构造 并启动容器,如代码清单

当然,我只是故意给出了一个比较损的对比。实际上,如果通过在某一个主配置文件中使用 <import>分别加载其余的配置文件,然后容器就可以通过加载这个主配置文件,来加载其他的配置文 件了。但使用<import>的问题,在于需要时刻关注主配置文件与其他配置文件的一致性。

除了可以批量加载配置文件之外,ClassPathXmlApplicationContext还可以通过指定Class path 中的某个类所处位置来加载相应配置文件,配置文件分布结构如下(例子来自Spring参考文档)

ClassPathXmlApplicationContext可以通过MessengerService类在Classpath中的位置定位配 置文件,而不用指定每个配置文件的完整路径名,如以下代码所示:

 

 

 

 

 

 

 

 

 

 

发布了524 篇原创文章 · 获赞 80 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/xushiyu1996818/article/details/103997201
今日推荐