Spring 揭秘之Spring IoC 容器之ApplicationContext

Spring IoC 容器之ApplicationContext

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

ApplicationContext相对于BeanFactory特有的性质包括:国际化信息支持、统一资源加载、容器内事件发布等;

G:\MyBlog\Spring 揭秘\图片材料\ApplicationContext继承关系.PNG

ApplicationContext的签名如下:

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
}

统一资源加载

Java SE对于资源定位提供了URL这一类,即Uniform Resource Locator,虽然号称统一资源定位,实际上只对通过网络发布的资源提供查找和定位功能;

实际上,资源这个词的范围比较广义资源可以任何形式存在,如以二进制对象形式存在、以字节流形式存在、以文件形式存在等;而且,资源也可以存在于任何场所,如存在于文件系统、存在于Java应用的Classpath中,甚至存在于URL可以定位的地方。

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

于是,Spring提供了基于Resource和ResourceLoader接口的资源抽象和加载策略;

Spring中的Resource

Resource接口作为所有资源的抽象和访问接口;Resource接口可以根据资源的不同类型,或者资源所在的不同场合给出相应的具体实现;常见的Resource有ClassPathResource、ByteArrayResource、FileSystemResource、UrlResource等;

ResourceLoader,更广义的URL

资源是有了,但如何去查找和定位这些资源,则应该是ResourceLoader的职责所在了。ResourceLoader接口是资源查找定位策略的统一抽象,具体的资源查找定位策略则由相应的ResourceLoader实现类给出;

可用的RsourceLoader
  1. DefaultResourceLoader:默认实现类,其查找逻辑为:首先检查是否可以被自定义的ProtocolResolver处理,如果是可以,则返回对应结果;然后检查是资源路径是否以classpath:开头;如果是则返回ClassPathResource;否则根据路径尝试通过URL定位资源,有则返回UrlResource或其子类FileUrlResource,否则交给getResourceByPath()方法处理;而该方法的默认实现逻辑是构造ClassPathResource并返回;
  2. FileSystemResourceLoader:继承DefaultResourceLoader并重写getResourceByPath方法;
ResourcePatternResolver

扩展自ResourceLoader,根据传入的资源路径,匹配并返回多个Resource实例;

常见的实现类为PathMatchingResourcePatternResolver。将解析后的路径委派给它的ResourceLoader实例进行处理,默认使用DefaultResourceLoader,当然也可以指定ResourceLoader实现类;
ResourceLoader和Resource类层次图.png

ApplicationContext与ResourceLoader

扮演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

当我们的Bean需要一个ResourceLoader来加载资源的时候,我们就可以将ApplicationContext传入啦,还记得各种Aware接口吗?没错,这里用到的就是ApplicationContextAware接口或者ResourceLoaderAware接口——只需要Bean实现对应接口即可完成ResourceLoader的注入;

当然,我们也可以声明一个DefaultResourceLoader实例,然后通过构造函数或者Setter方法将其传给Bean,只不过既然容器本身就能提供相应功能,就没有必要再画蛇添足了;

注入Resource

对于那些Spring容器提供的默认的PropertyEditors无法识别的对象类型,我们可以提供自定义的PropertyEditor实现并注册到容器中,以供容器做类型转换的时候使用;(不太明白再讲什么?知识点我达

对于ApplicationContext来说,我们无需这么做,因为ApplicationContext容器可以正确识别Resource类型并转换后注入相关对象;

这是因为:ApplicationContext启动伊始,会通过一个ResourceEditorRegistrar来注册Spring提供的针对Resource类型的PropertyEditor实现到容器中,这个PropertyEditor叫做ResourceEditor

对于Resource类型的数组,我们可以将ResourceArrayPropertyEditor类注册给CustomEditorConfigurar即可完成资源数组的注入;

特定情况下ApplicationContext的加载行为

我们知道,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 SE提供的国际化支持

程序的国际化涉及很多内容,比如时间表示格式、货币、语言文字;本质上是同一事物的不同表达形式;

Java对于I18n的支持主要涉及两个类:一个用来标记地区,一个用来表示资源,即Local和ResourceBundle;

Local

Local通常包含两个部分:语言和地区;每个国家和地区都有唯一的简写代码来表示语言和国家,这些代码是ISO的标准代码;如Local.CHINA表示中国,代码为zh_CN;其中zn表示语言,CN表示国家;

常用的Local均有提供静态变量,不需要自己构建;不常用的Local则需要通过语言和的确简写代码来构建;

有了Local,就有了分别信息的标准;

ResourceBundle

通常,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,我们就能够实现应用程序的国际化信息支持。

MessageResource和ApplicationContext

Spring在Java SE的国际化支持的基础上,进一步抽象了国际化信息的访问接口,也就是MessageSource,传入相应的Locale、资源的键以及相应参数,就可以取得相应的信息,再也不用先根据Locale取得ResourceBundle,然后再从ResourceBundle查询信息了;

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

在默认情况下, ApplicationContext将委派容器中一个名称为messageSource的MessageSource接口实现来完成MessageSource应该完成的职责。如果找不到这样一个名字的MessageSource实现, ApplicationContext内部会默认实例化一个不含任何内容的StaticMessageSource实例,以保证相应的方法调用。

可用的MessageSource实现
  1. StaticMessageSource:简单实现,可用于通过编程的方式添加信息条目,多用于测试,不应该用于正式的生产环境;
  2. ResourceBundleMessageSource:基于标准ResourceBundle实现的MessageSource;提供对ResourceBundle的缓存以提高查询效率;对参数化的信息和非参数化的信息做了优化,对MessageFormat示例也进行缓存;是最常用的、用于生产环境下的MessageSource实现;
  3. ReloadableResourceBundleMessageSource:同样基于ResourceBundle实现的是MessageSource;但是可以指定cacheSeconds指定缓存时间以定期检查properties文件是否有变更;可以使用ResourceLoader加载资源文件,使用该MessageSource时应注意避免将资源文件放在classpath中;因为这无助于定期加载文件变更;

MessageSource继承图:
在这里插入图片描述

MessageSourceAware和MessageSource的注入

还记得各种Aware接口吗?这里我们可以通过让Bean实现MessageSourceAware接口来注入MessageSource;在ApplicationContext中,也就是注入其自身啦;

当然,也可以通过Setter方法注入MessageSource了,和普通的依赖注入没有什么区别,并且ApplicationContext也会使用声明的MessageSource;

至此,ApplicationContext对国际化信息的支持就结束啦;

容器内部事件发布

Spring的ApplicationContext容器提供的容器内事件发布功能,是通过提供一套基于Java SE标准自定义事件类而实现的;

自定义事件发布

Java SE 提供了实现自定义事件发布( Custom Event publication)功能的基础类,即EventObject类和EventListener接口;

所有的自定义事件类型可以通过扩展EventObject来实现,而事件的监听器则扩展自EventListener。

要实现自定义事件发布,需要三个角色:事件、事件监听器和事件发布者;

事件标志着时机:有些操作要执行的时机;事件监听器:操作的执行者;事件发布者:将事件监听器和事件连接起来;
在这里插入图片描述

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

Spring内部允许事件以ApplicationEvent的形式发布;ApplicationListener类型的bean定义将会被容器自动识别,同时对容器内发布的所有ApplicationEvent类型的事件加以监听;

ApplicationEvent

继承自EventObject,抽象类,默认实现有:

  1. ContextClosedEvent:容器即将关闭时发布该事件;
  2. ContextRefreshedEvent:容器初始化或者刷新时发布该事件;
  3. RequestHandledEvent:Web请求处理后发布的事件;其子类ServletRequestHandledEvent用于提供JavaEE的Servlet相关事件;
ApplicationListeren

ApplicationListener类型的bean定义会被ApplicationContext容器自动识别,它们负责监听容器内发布的所有ApplicationEvent类型的事件,一旦有事件发布,注册到容器的Listener将收到通知;

ApplicationContext

就是EventPublisher啦;还记得它继承了ApplicationEventPublisher接口吗?没错,它承担起了事件发布的任务;

需要注意的是,对于事件发布功能,ApplicationContext将其继续“外包”,交给ApplicationEventMulticaster接口的实现类来处理;该接口有一个抽象父类:AbstractApplicationEventMulticaster;它实现了Listener的管理功能,但是出于灵活性和扩展性,事件的发布功能则委派给了具体的子类;

因为ApplicationContext将事件发布功能委托给ApplicationEventMulticaster来做,所以容器启动的时候就会检查是否存在名为applicationEventMulticaster的对象实例;有的话就是用提供的实现,没有的话,就初始化一个SimpleApplicationEvenMulticaster。于是,整个容器内事件发布功能就实现了:
ApplicationContext的事件发布功能.PNG

Spring 容器内事件发布的应用

Spring的容器内事件发布功能只适合在单一容器内的简单消息通知和处理,对于分布式、多进程和多容器之间的时间通知并不擅长;

同MessageSource和ResourceLoader,这里也有一个Aware接口用来为业务对象注入ApplicationEventPublisher;当然,通过直接注入ApplicaitonContext也是可以的;

ApplicationListener只通过void onApplicationEvent(ApplicationEvent event)这一个事件处理方法来处理事件,所以现在要在事件类中尽量保存必要的信息;

多配置模块加载的简化

在使用Spring的IoC轻量级容器进行实际开发的过程中,为了避免出现整个团队因某个
资源独占而无法并行、高效地完成工作等问题,通常会将整个系统的配置信息按照某种关注点进行分
割,使得关注点逻辑良好地划分到不同的配置文件中,如按照功能模块或者按照系统划分的层次等
这样,在加载整个系统的bean定义时,就需要让容器同时读入划分到不同配置文件的信息
通过ApplicationContext,我们只要以String[]形式传入这些配置文件所在的路径,即可构造
并启动容器

实际上,我们也可以通过在主配置文件里使用<import>来引入其他配置文件,然后容器只需要加载主配置文件即可;但是,使用标签的话,就需要时刻注意主配置文件和其他配置文件的一致性;

小结

到这里,ApplicationContext就介绍完毕啦;这里主要介绍了其相对于BeanFactory进行的功能增强:统一资源加载策略、国际化信息支持、容器内事件发布和多配置文件加载等功能;

了解完ApplicationContext之后,我们就基本上完成了对IoC容器的介绍,接下来就是激动人心的AOP功能啦;

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值