1. 程式人生 > >Spring整理系列(06)——spring中Aware結尾介面

Spring整理系列(06)——spring中Aware結尾介面

一、關於spring中Aware結尾介面介紹:

Spring中提供一些Aware結尾相關介面,像是BeanFactoryAware、 BeanNameAware、ApplicationContextAware、ResourceLoaderAware、ServletContextAware等等。

實現這些 Aware介面的Bean在被例項化
之後,可以取得一些相對應的資源,例如實現BeanFactoryAware的Bean在例項化後,Spring容器將會注入BeanFactory的例項,而實現ApplicationContextAware的Bean,在Bean被例項化後,將會被注入 ApplicationContext的例項等等。

通過重寫setter方法,當前bean被例項化後實現相關例項的注入。

二、以BeanNameAware、ApplicationContextAware介面舉例說明:

<bean name ="myContext" class="com.jsun.test.springDemo.aware.MyApplicationContext"></bean>
//實現BeanNameAware介面,並重寫setBeanName()方法,讓Bean獲取自己在BeanFactory配置中的名字(根據情況是id或者name)
//實現ApplicationContextAware介面,並重寫setApplicationContext()方法
public class MyApplicationContext implements BeanNameAware,ApplicationContextAware{ private String beanName; //注入的beanName即為MyApplicationContext在BeanFactory配置中的名字(根據情況是id或者name) @Override public void setBeanName(String beanName) { this.beanName = beanName; System.out.println("MyApplicationContext beanName:"
+beanName); } @Override public void setApplicationContext(ApplicationContext context) throws BeansException { //通過重寫的介面方法,獲取spring容器例項context,進而獲取容器中相關bean資源 System.out.println(context.getBean(this.beanName).hashCode()); } }
    @Test
    public void testScope(){
        //單元測試再次獲取bean,並輸出bean的hashCode
        System.out.println(super.getBean("myContext").hashCode());
    }

上面輸出結果:

資訊: Loading XML bean definitions from URL [file:/E:/workspace/springDemo/target/classes/spring-ioc.xml]
MyApplicationContext beanName:myContext
1663216960
1663216960
八月 07, 2016 4:25:12 下午 org.springframework.context.support.ClassPathXmlApplicationContext doClose
資訊: Closing org.springframework.context.support.ClassPathXmlApplicationContext@5531a519: startup date [Sun Aug 07 16:25:11 CST 2016]; root of context hierarchy

注意:除了通過實現Aware結尾介面獲取spring內建物件,也可以通過@Autowired註解直接注入相關物件,如下:
(如果需要用到靜態方法中,如工具方法,還是採用實現介面的方式)

@Autowired
private MessageSource messageSource; 

@Autowired
private ResourceLoader resourceLoader; 

@Autowired
private ApplicationContext applicationContext;

三、實際專案應用遇到的情況:

1、專案support作為基本的支援包,提供了獲取spring容器中bean的公共方法,該方法所屬bean被spring容器管理

1)、support專案spring-context.xml配置檔案:

    <!-- 啟動自動掃描 -->
    <context:component-scan base-package="com.test.spring.support"></context:component-scan>

2)、建立TestBean類,被spring容器管理,相當於業務bean:

package com.test.spring.support;

import org.springframework.stereotype.Component;

/** 
 * @date 建立時間:2016年8月8日 上午11:07:42 
 * @Description: support包中測試bean,相當於例項專案業務bean
 */
@Component("testBean")
public class TestBean {
    public TestBean(){
        System.out.println("TestBean 例項化。");
    }
}

3)、建立SpringContextHolder類,被spring容器管理,同時,該類持有spring容器例項,提供靜態公共方法,獲取spring容器中管理的bean,如獲取TestBean的例項bean:

package com.test.spring.support;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

/**
 * 以靜態變數儲存Spring ApplicationContext, 可在任何程式碼任何地方任何時候取出ApplicaitonContext.
 * 
 */
@Service()
@Lazy(false)
public class SpringContextHolder implements ApplicationContextAware, DisposableBean {

    private static ApplicationContext applicationContext = null;

    /**
     * 實現ApplicationContextAware介面, 注入Context到靜態變數中.
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        if (SpringContextHolder.applicationContext != null) {
            System.out.println("SpringContextHolder中的ApplicationContext被覆蓋, 原有ApplicationContext為:" + SpringContextHolder.applicationContext);
        }
        System.out.println("Spring容器啟動,將容器例項注入到SpringContextHolder例項bean中");
        SpringContextHolder.applicationContext = applicationContext;
    }

    /**
     * 實現DisposableBean介面,重寫destroy方法,相當於destroy-method,bean被銷燬的時候被呼叫,
     * 實現在Context關閉時清理靜態變數的目的
     * 令:還有InitializingBean介面,重寫afterPropertiesSet方法,相當於init-method,bean被例項化的時候被呼叫
     */
    @Override
    public void destroy() throws Exception {
        applicationContext = null;
    }

    /**
     * 取得儲存在靜態變數中的ApplicationContext.
     */
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    /**
     * 從靜態變數applicationContext中取得Bean, 自動轉型為所賦值物件的型別.
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) {
        return (T) applicationContext.getBean(name);
    }

    /**
     * 從靜態變數applicationContext中取得Bean, 自動轉型為所賦值物件的型別.
     */
    public static <T> T getBean(Class<T> requiredType) {
        return applicationContext.getBean(requiredType);
    }
}

4)、建立單元測試類TestSpringContextHolder和單元測試方法,載入啟動spring容器,並通過上面第三步SpringContextHolder提供的靜態方法獲取上面第二步被spring容器管理的TestBean的例項bean:

package com.test.spring.support;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.BeansException;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
 * 單元測試類
 */
public class TestSpringContextHolder{

    @Test
    public void testGetBean(){
        //獲取bean
        SpringContextHolder.getBean("testBean");
    }

    //以下為容器例項宣告及初始化、銷燬
    private ClassPathXmlApplicationContext context;

    @Before
    public void before(){
        try {
            // xml檔案用逗號或者空格符隔開,均可載入
            context = new ClassPathXmlApplicationContext("spring-context.xml");
            context.start();
        } catch (BeansException e) {
            e.printStackTrace();
        }
    }
    @After
    public void after(){
        context.destroy();
    }
}

5)、執行單元測試,結果如下:

資訊: Loading XML bean definitions from class path resource [spring-context.xml]
Spring容器啟動,將容器例項注入到SpringContextHolder例項bean中
TestBean 例項化。
。。。ClassPathXmlApplicationContext doClose
資訊: Closing 。。ClassPathXmlApplicationContext@90f405e。。

上述結果可以得知,通過SpringContextHolder提供的靜態方法可以獲取spring容器中管理的bean物件。

2、專案biz執行過程中,需要使用support包提供的公共方法獲取當前biz專案的某個bean,作為臨時注入bean使用
1)、biz專案spring-context.xml檔案:

    <!-- 啟動自動掃描,base-package 如果多個,用“,”分隔 -->
    <context:component-scan base-package="com.test.spring.biz"></context:component-scan>

2)、biz專案中的業務bean:

package com.test.spring.biz;

import org.springframework.stereotype.Component;

/** 
 * @date 建立時間:2016年8月8日 上午11:29:53 
 * @Description: 業務bean
 */
@Component("bizTmpBean")
public class BizTmpBean {

    public void sayHello(){
        System.out.println("bizTmpBean say hello");
    }
}

3)、biz專案中的公共靜態類,提供靜態方法,通過呼叫support專案靜態類SpringContextHolder提供的方法獲取biz專案中spring容器管理的bean:

package com.test.spring.biz;

import org.springframework.stereotype.Service;

import com.test.spring.support.SpringContextHolder;

/** 
 * @Description: biz專案中,全域性靜態類,通過support專案包提供的公共靜態方法,獲取臨時bean,呼叫臨時bean的方法
 */
@Service
public class Global {
    public static void globalGetBean(){
        //獲取臨時bean,呼叫臨時bean的方法
        try{
            BizTmpBean bizTmpBean = SpringContextHolder.getBean("bizTmpBean");

            bizTmpBean.sayHello();
        }catch(NullPointerException e){
            System.out.println("靜態獲取臨時bean失敗");
            e.printStackTrace();
        }
    }
}

4)、建立biz專案的單元測試類TestGlobal,用於驗證biz專案的靜態類Global是否可以臨時獲取到某個bean:

package com.test.spring.biz;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.BeansException;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/** 
 * @date 建立時間:2016年8月8日 上午10:49:13 
 * @Description: biz專案中單元測試類
 */
public class TestGlobal {

    //單元測試方法
    @Test
    public void testGlobal(){
        //單元測試,biz專案全域性靜態類獲取臨時bean
        Global.globalGetBean();
    }

    //以下為容器例項宣告及初始化、銷燬
    private ClassPathXmlApplicationContext context;

    @Before
    public void before(){
        try {
            // xml檔案用逗號或者空格符隔開,均可載入
            context = new ClassPathXmlApplicationContext("spring-context.xml");
            context.start();
        } catch (BeansException e) {
            e.printStackTrace();
        }
    }
    @After
    public void after(){
        context.destroy();
    }
}

5)、執行結果如下,丟擲空指標異常:

資訊: Loading XML bean definitions from class path resource [spring-context.xml]
靜態獲取臨時bean失敗
java.lang.NullPointerException
    at com.test.spring.support.SpringContextHolder.getBean(SpringContextHolder.java:51)
    at com.test.spring.biz.Global.globalGetBean(Global.java:15)
    at com.test.spring.biz.TestGlobal.testGlobal(TestGlobal.java:18)
。。。
。。。ClassPathXmlApplicationContext doClose
資訊: Closing org.springframework.context.support.ClassPathXmlApplicationContext。。。

3、上面問題產生的原因是:support中一個bean類SpringContextHolder通過實現ApplicationContextAware介面獲取spring容器中管理的bean,但它只能從自己所在的spring容器中獲取(SpringContextHolder的bean也是被spring容器管理的),現在想通過它從biz的spring容器中獲取bean,就出現了誇spring容器的問題,臨時性bean將注入失敗。

解決的辦法,很簡單,就是讓biz的spring管理support提供公共方法的那個bean類,即讓biz專案對SpringContextHolder進行自動掃描。

biz專案spring-context.xml檔案更改:

    <!-- 啟動自動掃描,新增SpringContextHolder類所在包com.test.spring.support的掃描,base-package 如果多個,用“,”分隔 -->
    <context:component-scan base-package="com.test.spring.support,com.test.spring.biz"></context:component-scan>

執行結果如下:

資訊: Loading XML bean definitions from class path resource [spring-context.xml]
Spring容器啟動,將容器例項注入到SpringContextHolder例項bean中
TestBean 例項化。
bizTmpBean say hello
八月 08, 2016 2:13:01 下午 org.springframework.context.support.ClassPathXmlApplicationContext doClose
資訊: Closing org.springframework.context.support.ClassPathXmlApplicationContext@484adff7: startup date [Mon Aug 08 14:13:01 CST 2016]; root of context hierarchy