Skip to main content

ApplicationContext

本文相关代码(来自官方源码spring-test模块)请参见spring-framework org.springframework.mylearntest包下。

统一资源加载策略

Spring提出了一套基于org.springframework.core.io.Resource和org.springframework.core.io.ResourceLoader接口的资源抽象和加载策略。

Resource: 接口可以根据资源的不同类型,或者资源所处的不同场合,给出相应的具体实现。可以帮助我们查询资源状态、访问资源内容,甚至根据当前资源创建新的相对资源。我们可以继承org.springframework.core.io.AbstractResource抽象类。

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

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

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

四种加载方式

使用以ResourceLoader身份登场的ApplicationContext

ResourceLoader resourceLoader = new ClassPathXmlApplicationContext("配置文件路径");

ResourceLoader类型的注入

  1. 依赖于ResourceLoader
resourceloader.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="resourceLoader" class="org.springframework.core.io.DefaultResourceLoader">
</bean>

<bean id="fooBar" class="org.springframework.mylearntest.ioc.resourceloader.FooBar">
<property name="resourceLoader" ref="resourceLoader"/>
</bean>
</beans>
  1. 实现了ResourceLoaderAware或者ApplicationContextAware接口的实例类
resourceloader4ContextBoo.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="fooBar" class="org.springframework.mylearntest.ioc.resourceloader.FooBarImplApplicationContextAware">
</bean>
</beans>
  1. Resource类型的注入
xmailer.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="xMailer" class="org.springframework.mylearntest.ioc.resourceloader.XMailer">
<property name="template" value="resourceloader/resources.default_template.vm"/>
</bean>
</beans>
  1. ApplicationContext的Resource加载行为 当ClassPathXmlApplicationContext在实例化的时候,即使没有指明classpath:或者classpath*:等前缀,它会默认从classpath中加载bean定义配置文件,而FileSystemXmlApplicationContext则有些同 ,如果我们像如下代码那样指定conf/appContext.xml,它会尝试从文件系统中加载bean定义文件

国际化信息支持(i18n MessageSource)

Java SE 提供的国际化支持

  • Locale

不同的Locale代表不同的国家和地区,每个国家和地区在Locale这里都有相应的简写代码表示,包括语言代码以及国家代码,这些代码是ISO标准代码。如,Locale.CHINA代表中国。

  • ResourceBundle

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

messages.properties
messages_zh.properties
messages_zh_CN.properties
messages_en.properties
messages_en_US.properties

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

如果某个业务对象需要国际化的信息支持,那么最简单的办法就是让它实现MessageSourceAware接口,然后注册到ApplicationContext容器。不过这样一来,该业务对象对ApplicationContext容器的依赖性就太强了,显得容器具有较强的侵入性。而实际上, 如果真的某个业务对象需要依赖于MessageSource的话,直接通过构造方法注入或者setter方法注入的方式声明依赖就可以了。

容器内部事件发布

  1. 自定义事件发布 给出自定义事件类型( define your own event object)。 为了针对具体场景可以区分具体的事件类型, 我们需要给出自己的事件类型的定义,通常做法是扩展java.util.EventObject类来实现自定义的事件类型。
定义事件类型
package org.springframework.mylearntest.ioc.eventpublication.applicationevent;

import org.springframework.context.ApplicationEvent;
import org.springframework.mylearntest.ioc.eventpublication.event.MethodExecutionStatus;

public class MethodExecutionEvent extends ApplicationEvent {
private static final long serialVersionUID = -71960369269303337L;
private String methodName;
private MethodExecutionStatus methodExecutionStatus;

public MethodExecutionEvent(Object source) {
super(source);
}

public MethodExecutionEvent(Object source, String methodName, MethodExecutionStatus methodExecutionStatus) {
super(source);
this.methodName = methodName;
this.methodExecutionStatus = methodExecutionStatus;
}

public String getMethodName() {
return methodName;
}

public void setMethodName(String methodName) {
this.methodName = methodName;
}

public MethodExecutionStatus getMethodExecutionStatus() {
return methodExecutionStatus;
}

public void setMethodExecutionStatus(MethodExecutionStatus methodExecutionStatus) {
this.methodExecutionStatus = methodExecutionStatus;
}
}
定义事件监听器接口
package org.springframework.mylearntest.ioc.eventpublication.event;


import java.util.EventListener;

/**
* 自定义事件监听器
*/
public interface MethodExecutionEventListener extends EventListener {
/**
* 处理方法开始执行的时候发布的MethodExecutionEvent事件
*/
void onMethodBegin(MethodExecutionEvent evt);
/**
* 处理方法执行将结束时候发布的MethodExecutionEvent事件
*/
void onMethodEnd(MethodExecutionEvent evt);
}

自定义事件监听器实现
package org.springframework.mylearntest.ioc.eventpublication.event;

/**
* 自定义事件监听器实现
*/
public class SimpleMethodExecutionEventListener implements MethodExecutionEventListener {

public void onMethodBegin(MethodExecutionEvent evt) {
String methodName = evt.getMethodName();
System.out.println("start to execute the method[" + methodName + "].");
}

public void onMethodEnd(MethodExecutionEvent evt) {
String methodName = evt.getMethodName();
System.out.println("finished to execute the method[" + methodName + "].");
}
}
定义事状态枚举类以及事件发布者
package org.springframework.mylearntest.ioc.eventpublication.event;

public enum MethodExecutionStatus {
BEGIN,END
}
事件发布类
package org.springframework.mylearntest.ioc.eventpublication.event;

import java.util.ArrayList;
import java.util.List;

public class MethodExecutionEventPublisher {
private List<MethodExecutionEventListener> listeners = new ArrayList<>();

public void methodToMonitor() {
MethodExecutionEvent event2Publish = new MethodExecutionEvent(this, "methodToMonitor");
publishEvent(MethodExecutionStatus.BEGIN, event2Publish);
// 执行实际的方法逻辑
// ...
publishEvent(MethodExecutionStatus.END, event2Publish);
}

// 为了避免事件处理期间事件监听器的注册或移除操作影响处理过程,我们对事件发布时点的监听器列表进行了一个安全复制( safe-copy)
protected void publishEvent(MethodExecutionStatus status, MethodExecutionEvent methodExecutionEvent) {
List<MethodExecutionEventListener> copyListeners = new ArrayList<>(listeners);
for (MethodExecutionEventListener listener : copyListeners) {
if (MethodExecutionStatus.BEGIN.equals(status)) {
listener.onMethodBegin(methodExecutionEvent);
} else {
listener.onMethodEnd(methodExecutionEvent);
}
}
}

public void addMethodExecutionEventListener(MethodExecutionEventListener listener) {
this.listeners.add(listener);
}

public void removeListener(MethodExecutionEventListener listener) {
this.listeners.remove(listener);
}

public void removeAllListeners() {
this.listeners.clear();
}

}
测试类
package org.springframework.mylearntest.ioc.eventpublication.event;

public class Test4Event {
public static void main(String[] args) {
MethodExecutionEventPublisher eventPublisher = new MethodExecutionEventPublisher();
eventPublisher.addMethodExecutionEventListener(new SimpleMethodExecutionEventListener());
eventPublisher.methodToMonitor();
eventPublisher.removeAllListeners();
}
}

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

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

Spring的ApplicationContext容器内部允许以org.springframework.context.ApplicationEvent的形式发布事件,容器内注册的org.springframework.context.ApplicationListener类型的bean定义会被ApplicationContext容器自动识别,它们负责监听容器内发布的所有ApplicationEvent类型的事件。

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

  • ContextClosedEvent: ApplicationContext容器在即将关闭的时候发布的事件类型。
  • ContextRefreshedEvent: ApplicationContext容器在初始化或者刷新的时候发布的事件类 型。
  • RequestHandledEvent: Web请求处理后发布的事件,其有一子类ServletRequestHandledEvent提供特定于Java EE的Servlet相关事件。

ApplicationListener: ApplicationContext容器内使用的自定义事件监听器接口定义,继承自java.util.EventListener。

ApplicationContext: 容器在启动时,会自动识别并加载EventListener类型bean定义,一旦容器内有事件发布,将通知这些注册到容器的EventListener。

MethodExecutionEventListener
package org.springframework.mylearntest.eventpublication.applicationevent;


import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;

@SuppressWarnings("rawtypes")
public class MethodExecutionEventListener implements ApplicationListener {
public void onApplicationEvent(ApplicationEvent evt) {
if (evt instanceof MethodExecutionEvent) {
// 执行处理逻辑
}
}
}

ApplicationContext: 还记得ApplicationContext的定义吧?除了之前的ResourceLoader和MessageSource,ApplicationContext接口定义还继承了ApplicationEventPublisher接口,该接口提供了void publishEvent(ApplicationEvent event)方法定义。不难看出, ApplicationContext容器现在担当的就是事件发布者的角色。ApplicationContext容器的具体实现类在实现事件的发布和事件监听器的注册方面,并没事必躬亲,而是把这些活儿转包给了一个称作org.springframework.context.event.ApplicationEventMulticaster的接口。

MethodExeuctionEventPublisher
package org.springframework.mylearntest.eventpublication.applicationevent;

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.mylearntest.eventpublication.event.MethodExecutionStatus;

public class MethodExeuctionEventPublisher implements ApplicationEventPublisherAware {
private ApplicationEventPublisher eventPublisher;

public void methodToMonitor() {
MethodExecutionEvent beginEvt = new
MethodExecutionEvent(this, "methodToMonitor", MethodExecutionStatus.BEGIN);
this.eventPublisher.publishEvent(beginEvt);
// 执行实际方法逻辑
// ...
MethodExecutionEvent endEvt = new
MethodExecutionEvent(this, "methodToMonitor", MethodExecutionStatus.END);
this.eventPublisher.publishEvent(endEvt);
}

public void setApplicationEventPublisher(ApplicationEventPublisher appCtx) {
this.eventPublisher = appCtx;
}
}
applicationevent.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="methodExecListener" class="org.springframework.mylearntest.eventpublication.applicationevent.MethodExecutionEventListener">
</bean>
<bean id="evtPublisher" class="org.springframework.mylearntest.eventpublication.applicationevent.MethodExeuctionEventPublisher">
</bean>

</beans>
Test4AppEvent
package org.springframework.mylearntest.eventpublication.applicationevent;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test4AppEvent {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("eventpublication/applicationevent.xml");
MethodExeuctionEventPublisher evtPublisher = (MethodExeuctionEventPublisher) context.getBean("evtPublisher");
evtPublisher.methodToMonitor();
}
}

ApplicationEventMulticaster有一抽象实现类——org.springframework.context.event.AbstractApplicationEventMulticaster,它实现了事件监听器的管理功能。事件的发布功能则委托给了其子类。 org.springframework.context.event.SimpleApplicationEventMulticaster。其默认使用了SyncTaskExecutor进行事件的发布。为了避免这种方式可能存在的性能问题,我们可以为其提供其他类型的TaskExecutor实现类。

容器启动开始,就会检查容器内是否存在名称为applicationEventMulticaster的ApplicationEventMulticaster对象实例。有的话就使用提供的实现,没有则默认初始化一个SimpleApplicationEventMulticaster作为将会使用的ApplicationEventMulticaster。

IoC相关注解

看着依赖注入相关的信息,一半分散在Java源代码中( @Autowired标注的信息),一半依然留在XML配置文件里,有很多bean标签依然存在。 当使用@Autoware注解能够同时找到两个或者多个同一类型的对象实例,可以使用@Qualifier对依赖注入的条件做进一步限定,指定具体是哪个id。

xml方式
<beans>
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
<bean id="newsProvider" class="..FXNewsProvider"/>
<bean id="djNewsListener" class="..DowJonesNewsListener"/>
<bean id="reutersNewsListner" class="..ReutersNewsListener"/>
<bean id="djNewsPersister" class="..DowJonesNewsPersister"/>
</beans>
@Qualifier位于属性上
public class FXNewsProvider {
@Autowired
@Qualifier("reutersNewsListner")// 此时注入id=reutersNewsListner
private IFXNewsListener newsListener;
@Autowired
private IFXNewsPersister newPersistener;
//...
}
@Qualifier注解位于参数上
// @Qualifier注解位于参数上
public class FXNewsProvider{
// ...
@Autowired
public void setUp(@Qualifier("reutersNewsListner") IFXNewsListener newsListener,IFXNewsPersister newPersistener) {
this.newsListener = newsListener;
this.newPersistener = newPersistener;
}
// ...
}

@Resource与@Autowired不同,它遵循的是byName自动绑定形式的行为准则,也就是说, IoC容器将根据@Resource所指定的名称,到容器中查找beanName 与之对应的实例,然后将查找到的对象实例注入给@Resource所标注的对象。

@PostConstruct和@PreDestroy不是服务于依赖注入的,它们主要用于标注对象生命周期管理相关方法,这与Spring的InitializingBean和DisposableBean接口,以及配置项中的init -method和destroy-method起到类似的作用。

就像@Autowired需要AutowiredAnnotationBeanPostProcessor为它与IoC容器牵线搭桥一样,JSR250的这些注解也同样需要一个BeanPostProcessor帮助它们实现自身的价值。这个BeanPostProcessor就是org.springframework.context.annotation.CommonAnnotationBeanPostProcessor,只有将CommonAnnotationBeanPostProcessor添加到容器, JSR250的相关注解才能发挥作用。

XML配置
<beans>
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/>
<bean id="newsProvider" class="..FXNewsProvider"/>
<bean id="djNewsListener" class="..DowJonesNewsListener"/>
<bean id="djNewsPersister" class="..DowJonesNewsPersister"/>
</beans>

<context:annotation-config> 不 但 帮 我 们 把 AutowiredAnnotationBeanPostProcessor 和 CommonAnnotationBeanPostProcessor 注册到容器,同时还会把 PersistenceAnnotationBeanPostProcessor 和 RequiredAnnotationBeanPostProcessor 一并进行注册,可谓一举四得啊!

使用相应的注解对组成应用程序的相关类进行标注之后, classpath-scanning功能可以从某一顶层包(base package)开始扫描。当扫描到某个类标注了相应的注解之后,就会提取该类的相关信息,构建对应的BeanDefinition,然后把构建完的BeanDefinition注册到容器。

XML配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan base-package="org.spring21"/>
</beans>

参考资料

  1. 书籍名称:Spring揭秘 作者:王福强