您的位置:首页 > 移动开发

Spring 源码分析:ApplicationContext

2005-11-03 11:44 375 查看
分析ApplicationContext

Spring的bean包支持通过编码方式管理和操作bean的基本功能,ApplicationContext则以Framework的
方式提供BeanFactory的所有功能。使用ApplicationContext,你可以让系统加载你的bean(例如,在Servlet容器初始
化ContextLoaderServlet时,通过ContextLoader类加载Spring Framework),而不是使用编码方式来加载。

ApplicationContext接口是context包的基础,位于org.springframework.context包里,
提供了BeanFactory的所有功能。除此之外, ApplicationContext为了支持Framework的工作方式,提供了以下的功能:

l.MessageSource,提供了语言信息的国际化支持

2.提供资源(如URL和文件系统)的访问支持

3.为实现了ApplicationListener接口的bean提供了事件传播支持

4.为不同的应用环境提供不同的context,例如支持web应用的XmlWebApplicationContext类

下面的源代码分析主要集中在ApplicationContext接口特有的功能上,如国际化支持,资源访问和bean的事件传播。

我的问题

现在我的问题是,ApplicationContext是如何实现上面提到的功能的?下面的分析将作出回答。

准备测试用例

1. 首先在类路径根目录编写测试国际化支持的testmsg.xml,并将它加入Spring IDE的管理范围:

<beans>

<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">

<property name="basenames">

<list>

<value>message</value>

</list>

</property>

</bean>

</beans>

2. 编写测试用例,测试国际化支持和资源访问的功能。

public class MsgTest extends TestCase {

ApplicationContext ctx = null;

public MsgTest(String arg0) {

super(arg0);

}

protected void setUp() throws Exception {

super.setUp();

ctx = new FileSystemXmlApplicationContext("testmsg.xml");

}

public void testMessageResource() {

Object[] args = {"我", "你"};

String msg = ctx.getMessage("hello", args, Locale.CHINA);

//System.out.println("msg=" + msg);

assertEquals("我和你", msg);

Resource rs = ctx.getResource("classpath:log4j.properties");

assertTrue(rs.exists());

}

}

3. 在类路径根目录创建属性文件message.properties,内容为hello={0}和{1}。

此时运行TestCase,果然不出所料,Junit视图的测试状态是红条。将打印msg变量的语句打开,重新测试,发现"和"字是乱码。

在message.properties文件中将"和"字改为ASCII码/u548c,重新运行TestCase,绿条,测试通过。

将testmsg.xml中bean的id改为messageSource1,重新运行测试,出现红条,测试失败,说明bean的名称必须是messageSource,这点值得注意。至于其中的原因稍后说明。

ApplicationContext类图

ApplicationContext接口相关的类图如下,其中getParent和publishEvent方法分别支持分层的context和事件传播功能。



如以上的类继承层次图所示,ApplicationContext接口通过继承BeanFactory,MessageSource和ResourceLoader三个接口,分别支持管理和操作bean的功能,语言信息的国际化支持以及对资源访问的支持。

AbstractApplicationContect是ApplicationContext的抽象实现类,它的继承层次较为紊乱,我觉
得这里应该进行代码重构。AbstractXmlApplicationContext是AbstractApplicationContext的子类,
提供了对XML配置文件的支持,它有三个子类,分别用于不同的应用环境。

对于MessageSource,Spring提供了两个bean实现,ResourceBundleMessageSource和
ReloadableResourceBundleMessageSource。前者提供了访问Properties文件的支持,后者添加了无需重启
JVM,重新加载Properties文件的支持。

ApplicationContext的国际化和资源访问支持

1. 如类层次图所示,在我们的例子中,FileSystemXmlApplicationContext使用DefaultListableBeanFactory装载和解释testmsg.xml配置文件(参见代码分析的BeanFactory部分)。

2. FileSystemXmlApplicationContext根据配置文件的BeanDefinition创建
ResourceBundleMessageSource,加载<list>元素定义的Properties文件,并保存在
AbstractApplicationContext的属性中。当客户程序调用getMessage方法时,
AbstractApplicationContext调用ResourceBundleMessageSource的getMessage方法返回
Message信息。

3. 至于上节提到的MessageSource的id只能是messageSource,是因为AbstractApplicationContext的initMessageSource()方法中,有这样一段代码:

this.messageSource = (MessageSource)

getBean(MESSAGE_SOURCE_BEAN_NAME);

其中MESSAGE_SOURCE_BEAN_NAME的定义为:

static final String MESSAGE_SOURCE_BEAN_NAME = "messageSource";

原因找到了,其实只要稍做代码重构,即可消除这个缺陷。

4. 如类层次图所示,AbstractApplicationContext继承了DefaultResourceLoader,当客户程序调用getResource方法时,使用父类中实现的方法来处理。

ApplicationContext的事件传播

准备测试用例

1. 首先编写测试用例。

public class SenderBeanTest extends TestCase {

ApplicationContext ctx = null;

protected void setUp() throws Exception {

super.setUp();

ctx = new FileSystemXmlApplicationContext("testlistener.xml");

}

public void testSendEmail() {

SenderBean sender = (SenderBean)ctx.getBean("sender");

String msg = sender.sendMessage("test message");

assertEquals("test message", msg);

}

}

2. 接着编写testlistener.xml配置文件。

<beans>

<bean id="sender" class="unittest.SenderBean"/>

<bean id="listener" class="unittest.MessageListener"/>

</beans>

3. 最后编写SenderBean,MessageListener和MessageEvent类。

public class SenderBean implements ApplicationContextAware {

private ApplicationContext applicationContext;

public void setApplicationContext(ApplicationContext applicationContext)

throws BeansException {

this.applicationContext = applicationContext;

}

public String sendMessage(String msg) {

MessageEvent event = new MessageEvent(msg);

this.applicationContext.publishEvent(event);

return msg;

}

}

public class MessageListener implements ApplicationListener {

public void onApplicationEvent(ApplicationEvent event) {

if (event instanceof MessageEvent) {

System.out.println("I got the message:" + event.getSource());

}

}

}

public class MessageEvent extends ApplicationEvent {

public MessageEvent(Object source) {

super(source);

System.out.println(this.getTimestamp() + ":" + source);

}

}

运行测试案例SenderBeanTest,绿条,测试通过。Console窗口出现以下DEBUG信息:

……

796 DEBUG support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'sender'

1085553911796:test message

796 DEBUG support.FileSystemXmlApplicationContext - Publishing event in context
[org.springframework.context.support.FileSystemXmlApplicationContext;hashCode=13549765]:
unittest.MessageEvent[source=test message]

I got the message:test message

ApplicationContext有关事件的类图

ApplicationContext事件传播的类结构图如下。



事件传播的实现

1. FileSystemXmlApplicationContext的构造器调用AbstractApplicationContext的refresh方法。如图所示,refresh方法调用refreshListeners方法。

2. AbstractApplicationContext的refreshListeners方法使用BeanFactory的
getBeanOfType方法得到所有ApplicationListener类(本例中是MessageListener),并使用
addListener方法把它们都放入ApplicationEventMulticasterImpl的Set容器中(eventListeners
变量)。

3. 如图所示,SenderBean实现ApplicationContextAware接口,并通过setApplicationContext方法注入ApplicationContext对象实例。

4. 当调用SenderBean类sendMessage方法时,AbstractApplicationContext调用publishEvent方法。

5. AbstractApplicationContext类的publishEvent方法调用ApplicationEventMulticasterImpl类的onApplicationEvent方法。

6. ApplicationEventMulticasterImpl通知Set容器中所有的ApplicationListener对象,调用它们的onApplicationEvent方法。

从以上的过程可以看出,ApplicationContext将事件通知所有的ApplicationListener。如果
ApplicationListener的子类(如MessageListener)只想接受指定的事件类型,需要自己编写过滤代码,如例子中的if
(event instanceof MessageEvent)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: