一、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()方法所返回的對象。