Spring中ApplicationEvent、ApplicationListener、InitializingBean、FactoryBean

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







發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章