一、ApplicationEvent&ApplicationListener
Spring 3.0中提供了很多类似*Aware的类,其中ApplicationContextAware接口可以实现我们在初始化bean的时候给bean注入ApplicationConxt(Spring上下文对象)对象。ApplicationContextAware接口提供了publishEvent方法,实现了Observe(观察者)设计模式的传播机制,实现了对bean的传播。通过ApplicationContextAware我们可以把系统中所有ApplicationEvent传播给系统中所有的ApplicationListener。因此,我们只需要构造好我们自己的ApplicationEvent和ApplicationListener,就可以在系统中实现相应的监听器。
下面以增加学生的示例来演示如何构造Spring的监听器,StudentAddEvent是监听的事件对象,StudentAddListener是事件的监听器(负责处理接收到的监听事件),StudentAddBean负责触发StudentAddEvent事件。具体步骤如下:
1、定义StudentAddEvent监听事件
package com.trs.spring.event;
import org.springframework.context.ApplicationEvent;
/**
* 增加学生的监听事件
*/
public class StudentAddEvent extends ApplicationEvent {
private static final long serialVersionUID = 20L;
private String m_sStudentName;
public StudentAddEvent(Object source, String _sStudentName) {
super(source);
this.m_sStudentName = _sStudentName;
}
public String getStudentName() {
return m_sStudentName;
}
}
2、定义StudentAddListener监听器
package com.trs.spring.event;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
public class StudentAddListener implements ApplicationListener {
public void onApplicationEvent(ApplicationEvent _event) {
if (!(_event instanceof StudentAddEvent)) {
return;
}
StudentAddEvent studentAddEvent = (StudentAddEvent) _event;
System.out.println("增加了学生:::" + studentAddEvent.getStudentName());
}
}
3、定义StudentAddBean触发StudentAddEvent事件
<span style="font-size: 18px;">package com.trs.spring.event;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class StudentAddBean implements ApplicationContextAware {
private ApplicationContext m_applicationContext = null;
</span><span style="font-size:18px;"> public void setApplicationContext(ApplicationContext _applicationContext)
throws BeansException {
this.m_applicationContext = _applicationContext;
}
public void addStudent(String _sStudentName) {
StudentAddEvent aStudentEvent = new StudentAddEvent(
m_applicationContext, _sStudentName);
m_applicationContext.publishEvent(aStudentEvent);
}
public static void main(String[] args) {
String[] xmlCo</span><span style="font-size: 18px;">nfig = new String[] { "applicationContext.xml" };
ApplicationContext context = new ClassPathXmlApplicationContext(
xmlConfig);
StudentAddBean studentBean = (StudentAddBean) context
.getBean("StudentAddBean");
studentBean.addStudent("我是第一个学生");
studentBean.addStudent("第二个学生已经添加");
}
} </span>
4、applicationContext.xml配置文件
<bean id="StudentAddBean" class="com.trs.spring.event.StudentAddBean"></bean>
<bean id="StudentAddListener" class="com.trs.spring.event.StudentAddListener"></bean>
5、说明
ApplicationContext在运行期会自动检测到所有实现了ApplicationListener的bean对象,并将其作为事件接收对象。当ApplicationContext的publishEvent方法被触发时,每个实现了ApplicationListener接口的bean都会收到ApplicationEvent对象,每个ApplicationListener可根据事件类型只接收处理自己感兴趣的事件,比如上面的StudentAddListener只接收StudentAddEvent事件。
6、执行StudentAddBean的main函数,结果如下:增加了学生:::我是第一个学生
增加了学生:::第二个学生已经添加
二、InitializingBean
InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候会执行该方法。
测试程序如下:
import org.springframework.beans.factory.InitializingBean;
public class TestInitializingBean implements InitializingBean{
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("ceshi InitializingBean");
}
public void testInit(){
System.out.println("ceshi init-method");
}
}
配置文件:
<bean id="testInitializingBean" class="com.TestInitializingBean" ></bean>
main函数如下:
public class Main {
public static void main(String[] args){
ApplicationContext context = new FileSystemXmlApplicationContext("/src/main/java/com/beans.xml");
}
}
运行main函数,打印如下结果:
ceshi InitializingBean
这说明在spring初始化bean的时候,如果bean实现了InitializingBean接口,会自动调用afterPropertiesSet方法。
思考一、实现InitializingBean接口与在配置文件中指定init-method有什么不同?
修改配置文件:
<bean id="testInitializingBean" class="com.TestInitializingBean" init-method="testInit"></bean>
运行main函数,打印如下结果:
ceshi InitializingBean
ceshi init-method
由结果可看出,在spring初始化bean的时候,如果该bean是实现了InitializingBean接口,并且同时在配置文件中指定了init-method,系统则是先调用afterPropertiesSet方法,然后在调用init-method中指定的方法。
思考二、这方式在spring中是怎么实现的?
通过查看spring的加载bean的源码类(AbstractAutowireCapableBeanFactory)可看出其中奥妙
AbstractAutowireCapableBeanFactory类中的invokeInitMethods讲解的非常清楚,源码如下:
<span style="font-size:18px;">protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)
throws Throwable {
//判断该bean是否实现了实现了InitializingBean接口,如果实现了InitializingBean接口,则只掉调用bean的afterPropertiesSet方法
boolean isInitializingBean = (bean instanceof InitializingBean);
if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
if (logger.isDebugEnabled()) {
logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
}
if (System.getSecurityManager() != null) {
try {
AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
public Object run() throws Exception {
//直接调用afterPropertiesSet
((InitializingBean) bean).afterPropertiesSet();
return null;
}
},getAccessControlContext());
} catch (PrivilegedActionException pae) {
throw pae.getException();
}
}
else {
//直接调用afterPropertiesSet
((InitializingBean) bean).afterPropertiesSet();
}
}
if (mbd != null) {
String initMethodName = mbd.getInitMethodName();
//判断是否指定了init-method方法,如果指定了init-method方法,则再调用制定的init-method
if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
!mbd.isExternallyManagedInitMethod(initMethodName)) {
//进一步查看该方法的源码,可以发现init-method方法中指定的方法是通过反射实现
invokeCustomInitMethod(beanName, bean, mbd);
}
}
}</span>
总结:1:spring为bean提供了两种初始化bean的方式,实现InitializingBean接口,实现afterPropertiesSet方法,或者在配置文件中同过init-method指定,两种方式可以同时使用
2:实现InitializingBean接口是直接调用afterPropertiesSet方法,比通过反射调用init-method指定的方法效率相对来说要高点。但是init-method方式消除了对spring的依赖
3:如果调用afterPropertiesSet方法时出错,则不调用init-method指定的方法。
三、FactoryBean
Spring中有两种类型的Bean,一种是普通Bean,另一种是工厂Bean,即FactoryBean,这两种Bean都被容器管理,但工厂Bean跟普通Bean不同,其返回的对象不是指定类的一个实例,其返回的是该FactoryBean的getObject方法所返回的对象。在Spring框架内部,有很多地方有FactoryBean的实现类,它们在很多应用如(Spring的AOP、ORM、事务管理)及与其它第三框架(ehCache)集成时都有体现,下面简单分析FactoryBean的用法。
以下SimpleFactoryBean类实现了FactoryBean接口中的三个方法。 并将该类配置在XML中。
<span style="font-size:18px;">public class SimpleFactoryBean implements FactoryBean {
private boolean flag;
public Object getObject() throws Exception {
if (flag) {
return new Date();
}
return new String("false");
}
@SuppressWarnings("unchecked")
public Class getObjectType() {
return flag ? Date.class : String.class;
}
public boolean isSingleton() {
return false;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
} </span>
<span style="font-size:18px;"> <bean id="factoryBeanOne" class="com.study.demo.factorybean.SimpleFactoryBean" >
<property name="flag">
<value>true</value>
</property>
</bean>
<bean id="factoryBeanTwo" class="com.study.demo.factorybean.SimpleFactoryBean" >
<property name="flag">
<value>false</value>
</property>
</bean></span>
<span style="font-size:18px;">public class MainTest {
public static void main(String[] args) {
Resource res = new ClassPathResource("bean.xml");
BeanFactory factory = new XmlBeanFactory(res);
System.out.println(factory.getBean("factoryBeanOne").getClass());
System.out.println(factory.getBean("factoryBeanTwo").getClass());
}
} </span>
通过简单的测试可知,该类输出如下: class java.util.Date
class java.lang.String
也就是说,容器通过getBean方法返回的不是FactoryBean本身,而是FactoryBean实现类中getObject()方法所返回的对象。