作为Spring提供的较之BeanFactory更为先进的IoC容器实现, ApplicationContext除了拥有BeanFactory支持的所有功能之外,还进一步扩展了基本容器的功能,包括BeanFactoryPostProcessor、 BeanPostProcessor以及其他特殊类型bean的自动识别、容器启动后bean实例的自动初始化、国际化的信息支持、容器内事件发布等。
ApplicationContext相对于BeanFactory特有的性质包括:国际化信息支持、统一资源加载、容器内事件发布等;
ApplicationContext的签名如下:
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
}
Java SE对于资源定位提供了URL这一类,即Uniform Resource Locator,虽然号称统一资源定位,实际上只对通过网络发布的资源提供查找和定位功能;
实际上,资源这个词的范围比较广义,资源可以任何形式存在,如以二进制对象形式存在、以字节流形式存在、以文件形式存在等;而且,资源也可以存在于任何场所,如存在于文件系统、存在于Java应用的Classpath中,甚至存在于URL可以定位的地方。
其次,该类的功能职责划分不清,资源的查找和表示没有清晰的界限;当前情况是,资源查找后返回的形式多种多样,没有一个统一的抽象。理想情况下,资源查找完成后,返回给客户端的应该是一个统一的资源抽象接口,客户端要对资源进行什么样的处理,应该由资源抽象接口来界定,而不应该成为资源的定位者和查找者同时要关心的事情。
于是,Spring提供了基于Resource和ResourceLoader接口的资源抽象和加载策略;
Resource接口作为所有资源的抽象和访问接口;Resource接口可以根据资源的不同类型,或者资源所在的不同场合给出相应的具体实现;常见的Resource有ClassPathResource、ByteArrayResource、FileSystemResource、UrlResource等;
资源是有了,但如何去查找和定位这些资源,则应该是ResourceLoader的职责所在了。ResourceLoader接口是资源查找定位策略的统一抽象,具体的资源查找定位策略则由相应的ResourceLoader实现类给出;
扩展自ResourceLoader,根据传入的资源路径,匹配并返回多个Resource实例;
常见的实现类为PathMatchingResourcePatternResolver。将解析后的路径委派给它的ResourceLoader实例进行处理,默认使用DefaultResourceLoader,当然也可以指定ResourceLoader实现类;
ApplicationContext继承了ResourcePatternResolver,也就是间接继承了ResourceLoader,这就是ApplicationContext支持Spring统一资源加载的真相;
ApplicationContext作为顶级接口,定义了其功能点;而真正将其落实的实现类是AbstractApplicationContext这一类,而所有ApplicationContext的实现类都会间接或者直接继承自AbstractApplicationContext类,所以,我们需要进入该类一探究竟;
以下为AbstractApplicationContext的签名及其与ResourcePatternResolver相关的属性:
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
private ResourcePatternResolver resourcePatternResolver;
public AbstractApplicationContext() {
this.resourcePatternResolver = getResourcePatternResolver();
}
protected ResourcePatternResolver getResourcePatternResolver() {
return new PathMatchingResourcePatternResolver(this);
}
}
如此一来,AbstractApplicationContext实现ResourcePatternResolver的策略就一目了然了:首先继承DefaultResourceLoader,然后持有一个ResourcePatternResolver对象,默认为PathMatchingResourcePatternResolver实例,并把自己传递进去;
换言之,ApplicationContext的实现类,将ResourceLoader或者ResourcePatternResolver的行为完全委派给了PathMatchingResourcePatternResolver和DefaultResourceLoader。
它们之间的继承关系如下:
当我们的Bean需要一个ResourceLoader来加载资源的时候,我们就可以将ApplicationContext传入啦,还记得吗?没错,这里用到的就是ApplicationContextAware接口或者ResourceLoaderAware接口——只需要Bean实现对应接口即可完成ResourceLoader的注入;
当然,我们也可以声明一个DefaultResourceLoader实例,然后通过构造函数或者Setter方法将其传给Bean,只不过既然容器本身就能提供相应功能,就没有必要再画蛇添足了;
对于那些Spring容器提供的默认的PropertyEditors无法识别的对象类型,我们可以提供自定义的PropertyEditor实现并注册到容器中,以供容器做类型转换的时候使用;(不太明白再讲什么?知识)
对于ApplicationContext来说,我们无需这么做,因为ApplicationContext容器可以正确识别Resource类型并转换后注入相关对象;
这是因为:ApplicationContext启动伊始,会通过一个ResourceEditorRegistrar来注册Spring提供的针对Resource类型的PropertyEditor实现到容器中,这个PropertyEditor叫做ResourceEditor
对于Resource类型的数组,我们可以将ResourceArrayPropertyEditor类注册给CustomEditorConfigurar即可完成资源数组的注入;
我们知道,ResourceLoader定义了一种资源路径协议——classpath:;ResourcePatternResolver则定义了classspath*:,于是我们就可以通过资源路径协议前缀,明确告诉容器从classpath中加载资源;
classpath*:与classpath:的唯一区别就在于,如果能够在classpath中找到多个指定的资源,则返回多个;
当ClassPathXmlApplicationContext在实例化的时候,即使没有指明classpath:或者classpath*:等前缀,它会默认从classpath中加载bean定义配置文件;
FileSystemXmlApplicationContext则有些不同,如指定conf/appContext.xml,它会尝试从文件系统中加载bean定义文件。这是因为它与FileSystemResourceLoader一样,也覆写了DefaultResourceLoader的getResourceByPath(String)方法,逻辑跟FileSystemResourceLoader一模一样。
当实例化相应的ApplicationContext时,各种实现会根据自身的特性,从不同的位置加载bean定义配置文件。
当容器实例化并启动完毕,我们要用相应容器作为ResourceLoader来加载其他资源时,各种ApplicationContext容器的实现类依然会有不同的表现;
至此,ApplicationContext作为ResourcePatternResolver要介绍的内容就结束啦;更多关于ResourceLoader的知识,不妨参考Spring文档~
要让我们的应用程序可以供全世界不同国家和地区的人们使用,应用程序就必须支持它所面向的国家和地区的语言文字,为不同的国家和地区的用户提供他们各自的语言文字信息。所以,要向全世界推广,应用程序的国际化信息支持自然是势在必行。
程序的国际化涉及很多内容,比如时间表示格式、货币、语言文字;本质上是同一事物的不同表达形式;
Java对于I18n的支持主要涉及两个类:一个用来标记地区,一个用来表示资源,即Local和ResourceBundle;
Local通常包含两个部分:语言和地区;每个国家和地区都有唯一的简写代码来表示语言和国家,这些代码是ISO的标准代码;如Local.CHINA表示中国,代码为zh_CN;其中zn表示语言,CN表示国家;
常用的Local均有提供静态变量,不需要自己构建;不常用的Local则需要通过语言和的确简写代码来构建;
有了Local,就有了分别信息的标准;
通常,ResourceBundle管理一组信息序列,所有的信息序列有统一的特定的Locale的信息,可以根据basename后追加的语言或者地区代码来区分,如Message.properties、Message_zh_CN.properties、Message_en_US.properties等;
每个资源文件中都有相同的键来标志具体资源条目,但每个资源内部对应相同键的资源条目内容,则根据Locale的不同而不同。
有了ResourceBundle对应的资源文件之后,我们就可以通过ResourceBundle的getBundle(String baseName, Locale locale)方法取得不同Locale对应的ResourceBundle,然后根据资源的键取得相应Locale的资源条目内容。
通过结合ResourceBundle和Locale,我们就能够实现应用程序的国际化信息支持。
Spring在Java SE的国际化支持的基础上,进一步抽象了国际化信息的访问接口,也就是MessageSource,传入相应的Locale、资源的键以及相应参数,就可以取得相应的信息,再也不用先根据Locale取得ResourceBundle,然后再从ResourceBundle查询信息了;
我们知道, ApplicationContext除了实现了ResourceLoader以支持统一的资源加载,它还实现了MessageSource接口,那么就跟ApplicationContext因为实现了ResourceLoader而可以当作ResourceLoader来使用一样,ApplicationContext现在也是一个MessageSource了。
在默认情况下, ApplicationContext将委派容器中一个名称为messageSource的MessageSource接口实现来完成MessageSource应该完成的职责。如果找不到这样一个名字的MessageSource实现, ApplicationContext内部会默认实例化一个不含任何内容的StaticMessageSource实例,以保证相应的方法调用。
MessageSource继承图:
还记得吗?这里我们可以通过让Bean实现MessageSourceAware接口来注入MessageSource;在ApplicationContext中,也就是注入其自身啦;
当然,也可以通过Setter方法注入MessageSource了,和普通的依赖注入没有什么区别,并且ApplicationContext也会使用声明的MessageSource;
至此,ApplicationContext对国际化信息的支持就结束啦;
Spring的ApplicationContext容器提供的容器内事件发布功能,是通过提供一套基于Java SE标准自定义事件类而实现的;
Java SE 提供了实现自定义事件发布( Custom Event publication)功能的基础类,即EventObject类和EventListener接口;
所有的自定义事件类型可以通过扩展EventObject来实现,而事件的监听器则扩展自EventListener。
要实现自定义事件发布,需要三个角色:事件、事件监听器和事件发布者;
事件标志着时机:有些操作要执行的时机;事件监听器:操作的执行者;事件发布者:将事件监听器和事件连接起来;
Spring内部允许事件以ApplicationEvent的形式发布;ApplicationListener类型的bean定义将会被容器自动识别,同时对容器内发布的所有ApplicationEvent类型的事件加以监听;
继承自EventObject,抽象类,默认实现有:
ApplicationListener类型的bean定义会被ApplicationContext容器自动识别,它们负责监听容器内发布的所有ApplicationEvent类型的事件,一旦有事件发布,注册到容器的Listener将收到通知;
就是EventPublisher啦;还记得它继承了ApplicationEventPublisher接口吗?没错,它承担起了事件发布的任务;
需要注意的是,对于事件发布功能,ApplicationContext将其继续“外包”,交给ApplicationEventMulticaster接口的实现类来处理;该接口有一个抽象父类:AbstractApplicationEventMulticaster;它实现了Listener的管理功能,但是出于灵活性和扩展性,事件的发布功能则委派给了具体的子类;
因为ApplicationContext将事件发布功能委托给ApplicationEventMulticaster来做,所以容器启动的时候就会检查是否存在名为applicationEventMulticaster的对象实例;有的话就是用提供的实现,没有的话,就初始化一个SimpleApplicationEvenMulticaster。于是,整个容器内事件发布功能就实现了:
Spring的容器内事件发布功能只适合在单一容器内的简单消息通知和处理,对于分布式、多进程和多容器之间的时间通知并不擅长;
同MessageSource和ResourceLoader,这里也有一个Aware接口用来为业务对象注入ApplicationEventPublisher;当然,通过直接注入ApplicaitonContext也是可以的;
ApplicationListener只通过void onApplicationEvent(ApplicationEvent event)这一个事件处理方法来处理事件,所以现在要在事件类中尽量保存必要的信息;
在使用Spring的IoC轻量级容器进行实际开发的过程中,为了避免出现整个团队因某个
资源独占而无法并行、高效地完成工作等问题,通常会将整个系统的配置信息按照某种关注点进行分
割,使得关注点逻辑良好地划分到不同的配置文件中,如按照功能模块或者按照系统划分的层次等
这样,在加载整个系统的bean定义时,就需要让容器同时读入划分到不同配置文件的信息
通过ApplicationContext,我们只要以String[]形式传入这些配置文件所在的路径,即可构造
并启动容器
实际上,我们也可以通过在主配置文件里使用<import>来引入其他配置文件,然后容器只需要加载主配置文件即可;但是,使用标签的话,就需要时刻注意主配置文件和其他配置文件的一致性;
到这里,ApplicationContext就介绍完毕啦;这里主要介绍了其相对于BeanFactory进行的功能增强:统一资源加载策略、国际化信息支持、容器内事件发布和多配置文件加载等功能;
了解完ApplicationContext之后,我们就基本上完成了对IoC容器的介绍,接下来就是激动人心的AOP功能啦;