Spring——IoC擴展

這些擴展其實是在初始過程中植入的鉤子或回調(diào)method源武,主要看refresh方法

@Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();
            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);
            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);
                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);
                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);
                // Initialize message source for this context.
                initMessageSource();
                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();
                // Initialize other special beans in specific context subclasses.
                onRefresh();
                // Check for listener beans and register them.
                registerListeners();
                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);
                // Last step: publish corresponding event.
                finishRefresh();
            }
            catch (BeansException ex) {
                ...
            }
        }
    }

ApplicationListener

ApplicationListener 其實是 spring 事件通知機制中核心概念博助。

在java的事件機制中盾致,一般會有三個概念:

  • event object : 事件對象,描述事件本身一些屬性
  • event source :事件源铺厨,產(chǎn)生事件的地方
  • event listener :監(jiān)聽具體的事件并作出響應(yīng)

對應(yīng)的在Spring事件傳播機制中:

  • 事件類:ApplicationEvent
  • 監(jiān)聽類:實現(xiàn)ApplicationListener 接口
  • 事件發(fā)布類偏竟,:實現(xiàn)ApplicationEventPublisherAware接口
  • 將事件類和監(jiān)聽類交給Spring容器

例子

public class UserRegisterEvent extends ApplicationEvent {

    public String name;

    public UserRegisterEvent(Object o) {
        super(o);
    }

    public UserRegisterEvent(Object o, String name) {
        super(o);
        this.name=name;
    }
}

public class UserService implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher applicationEventPublisher;

    public void setApplicationEventPublisher(ApplicationEventPublisher
    applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    public void register(String name) {
        System.out.println("用戶:" + name + " 已注冊!");
        applicationEventPublisher.publishEvent(new UserRegisterEvent(name));
    }
}

public class BonusServerListener implements
ApplicationListener<UserRegisterEvent> {
    public void onApplicationEvent(UserRegisterEvent event) {
        System.out.println("積分服務(wù)接到通知民轴,給 " + event.getSource() +
        " 增加積分...");
    }
}

// 注冊到容器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
        
    <bean id="userService" class="com.glmapper.extention.UserService"/>
    <bean id="bonusServerListener"
    class="com.glmapper.extention.BonusServerListener"/>
    
</beans>

public class MainTest {
    public static void main(String[] args) {
        ApplicationContext context =new 
        ClassPathXmlApplicationContext("beans.xml");
        UserService userService = (UserService)
        context.getBean("userService");
        //注冊事件觸發(fā)
        userService.register("glmapper");
    }
}
// 輸出
用戶:glmapper 已注冊攻柠!
積分服務(wù)接到通知球订,給 glmapper 增加積分...

FactroyBean

XML 方式的 AOP 就是通過該接口實現(xiàn)的

public interface FactoryBean<T> {
    T getObject() throws Exception;
    Class<?> getObjectType();
    boolean isSingleton();
}

Spring 在 IOC 初始化的時候,一般的Bean都是直接調(diào)用構(gòu)造方法瑰钮,而如果該Bean實現(xiàn)了FactoryBean 接口冒滩,則會調(diào)用該Bean的 getObject 方法獲取bean,這也是Spring 使用此接口構(gòu)造AOP的原因浪谴。在 IOC 調(diào)用此方法的時候开睡,返回一個代理,完成AOP代理的創(chuàng)建较店。

BeanNameAware士八、ApplicationContextAware 和 BeanFactoryAware 針對bean工廠,可以獲取上下文梁呈,可以獲取當(dāng)前bena的id

ApplicationContextAware

這個比較常用
ApplicationContextAware中只有一個setApplicationContext方法婚度。實現(xiàn)了ApplicationContextAware接口的類,可以在該Bean被加載的過程中獲取Spring的應(yīng)用上下文ApplicationContext官卡,通過ApplicationContext可以獲取
Spring容器內(nèi)的很多信息蝗茁。

public class GlmapperApplicationContext implements
ApplicationContextAware {

private  ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext
applicationContext) throws BeansException {
        this.applicationContext=applicationContext;
}

public ApplicationContext getApplicationContext(){
    return applicationContext;
}

}

BeanFactoryAware

我們知道BeanFactory是整個Ioc容器最頂層的接口,它規(guī)定了容器的基本行為寻咒。實現(xiàn)BeanFactoryAware接口就表明當(dāng)前類具體BeanFactory的能力哮翘。

BeanFactoryAware接口中只有一個setBeanFactory方法。實現(xiàn)了BeanFactoryAware接口的類毛秘,可以在該Bean被加載的過程中獲取加載該Bean的BeanFactory饭寺,同時也可以獲取這個BeanFactory中加載的其它Bean。

有一個問題叫挟,我們?yōu)槭裁葱枰ㄟ^BeanFactory的getBean來獲取Bean呢艰匙?Spring已經(jīng)提供了很多便捷的注入方式,那么通過BeanFactory的getBean來獲取Bean有什么好處呢抹恳?來看一個場景:寫死的邏輯改為由動態(tài)配置決定

public interface HelloService {
    void sayHello();
}

//英文打招呼實現(xiàn)
public class GlmapperHelloServiceImpl implements HelloService {
    public void sayHello() {
        System.out.println("Hello Glmapper");
    }
}

//中文打招呼實現(xiàn)
public class LeishuHelloServiceImpl implements HelloService {
    public void sayHello() {
        System.out.println("你好员凝,磊叔");
    }
}

// 之前做法
if (condition=="英文"){
    glmapperHelloService.sayHello();
}
if (condition=="中文"){
    leishuHelloService.sayHello();
}
// 有沒有什么方式可以動態(tài)的去決定客戶端類到底去調(diào)用哪一種語言實現(xiàn),而不是用過if-else方式來羅列呢奋献?
// 是的健霹,對于這些需要動態(tài)的去獲取對象的場景,BeanFactoryAware就可以很好的搞定瓶蚂。

public class GlmapperBeanFactory implements BeanFactoryAware {

    private BeanFactory beanFactory;

    public void setBeanFactory(BeanFactory beanFactory) throws
    BeansException {
        this.beanFactory=beanFactory;
    }
    /**
     * 提供一個execute 方法來實現(xiàn)不同業(yè)務(wù)實現(xiàn)類的調(diào)度器方案糖埋。
     * @param beanName
     */
    public void execute(String beanName){
        HelloService helloService=(HelloService)
        beanFactory.getBean(beanName);
        helloService.sayHello();
    }
}

// 
public class HelloFacade {
    private GlmapperBeanFactory glmapperBeanFactory;
    //調(diào)用glmapperBeanFactory的execute方法
    public void sayHello(String beanName){
        glmapperBeanFactory.execute(beanName);
    }
    public void setGlmapperBeanFactory(GlmapperBeanFactory beanFactory){
        this.glmapperBeanFactory = beanFactory;
    }
}
public class MainTest {
    public static void main(String[] args) {
        ApplicationContext context = new
        ClassPathXmlApplicationContext("beans.xml");
        
        HelloFacade helloFacade = (HelloFacade)
        context.getBean("helloFacade");

        GlmapperBeanFactory glmapperBeanFactory = (GlmapperBeanFactory)
        context.getBean("glmapperBeanFactory");
        
        //這里其實可以不通過set方法注入到helloFacade中,
        //可以在helloFacade中通過autowired
        //注入扬跋;這里在使用main方法來執(zhí)行驗證阶捆,所以就手動set進入了
        helloFacade.setGlmapperBeanFactory(glmapperBeanFactory);

        //這個只需要傳入不同HelloService的實現(xiàn)類的beanName,
        //就可以執(zhí)行不同的業(yè)務(wù)邏輯
        helloFacade.sayHello("glmapperHelloService");
        helloFacade.sayHello("leishuHelloService");

    }
}

優(yōu)化后只需要傳入不同HelloService的實現(xiàn)類的beanName,就可以執(zhí)行不同的業(yè)務(wù)邏輯

Bean 初始化順序相關(guān)接口

驗證Spring-Bean初始化順序洒试,先看幾個關(guān)鍵接口


  • InitializingBean
    Spirng的InitializingBean為bean提供了定義初始化方法的方式倍奢。InitializingBean是一個接口,它僅僅包含一個方法:afterPropertiesSet()垒棋。
    在spring 初始化后卒煞,執(zhí)行完所有屬性設(shè)置方法(即setXxx)將自動調(diào)用 afterPropertiesSet(), 在配置文件中無須特別的配置, 但此方式增加了bean對spring 的依賴叼架,應(yīng)該盡量避免使用

  • init-method(非接口)
    Spring雖然可以通過InitializingBean完成一個bean初始化后對這個bean的回調(diào)畔裕,但是這種方式要求bean實現(xiàn) InitializingBean接口。一但bean實現(xiàn)了InitializingBean接口乖订,那么這個bean的代碼就和Spring耦合到一起了扮饶。通常情況下我不鼓勵bean直接實現(xiàn)InitializingBean,可以使用Spring提供的init-method的功能來執(zhí)行一個bean 子定義的初始化方法乍构。

  • BeanFactoryPostProcessor接口
    可以在spring的bean創(chuàng)建之前甜无,修改bean的定義屬性。也就是說哥遮,Spring允許BeanFactoryPostProcessor在容器實例化任何其它bean之前讀取配置元數(shù)據(jù)岂丘,并可以根據(jù)需要進行修改,例如可以把bean的scope從singleton改為prototype眠饮,也可以把property的值給修改掉奥帘。可以同時配置多個BeanFactoryPostProcessor仪召,并通過設(shè)置'order'屬性來控制各個BeanFactoryPostProcessor的執(zhí)行次序寨蹋。
    注意:BeanFactoryPostProcessor是在spring容器加載了bean的定義文件之后,在bean實例化之前執(zhí)行的扔茅。接口方法的入?yún)⑹荂onfigurrableListableBeanFactory钥庇,使用該參數(shù),可以獲取到相關(guān)bean的定義信息

  • BeanPostProcessor接口
    BeanPostProcessor咖摹,可以在spring容器實例化bean之后,在執(zhí)行bean的初始化方法前后难述,添加一些自己的處理邏輯萤晴。這里說的初始化方法,指的是下面兩種:
    1)bean實現(xiàn)了InitializingBean接口胁后,對應(yīng)的方法為afterPropertiesSet
    2)在bean定義的時候店读,通過init-method設(shè)置的方法

  • InstantiationAwareBeanPostProcessor
    個接口實際上我們也是非常的熟悉,該接口在我們剖析注解配置AOP的時候是我們的老朋友攀芯,實際上屯断,注解配置的AOP是間接實現(xiàn) BeanPostProcess 接口的,而 InstantiationAwareBeanPostProcessor 就是繼承該接口的。


例子

public class SpringIoCBean implements InitializingBean, BeanPostProcessor, BeanFactoryPostProcessor, BeanFactoryAware,
        BeanNameAware, DisposableBean {

    static {
        System.out.println(" static block ");
    }

    private String hello;

    {
        System.out.println("{} block hello = " + hello);
    }

    public SpringIoCBean(){
        System.out.println("調(diào)用HelloWorld構(gòu)造器...");
    }

    public String getHello() {
        return hello;
    }

    public void setHello(String hello) {
        this.hello = hello;
        System.out.println("調(diào)用setHello()...");
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("調(diào)用BeanPostProcessor的postProcessBeforeInitialization() -- " + beanName);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("調(diào)用BeanPostProcessor的postProcessAfterInitialization() -- "+ beanName);
        return bean;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("調(diào)用InitializingBean的afterPropertiesSet()...");
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory paramConfigurableListableBeanFactory)
            throws BeansException {
        System.out.println("調(diào)用BeanFactoryPostProcessor的postProcessBeanFactory()...");

    }

    @Override
    public String toString() {
        return "HelloWorld [hello=" + hello + "]";
    }

    @Override
    public void setBeanName(String paramString) {
        System.out.println("調(diào)用BeanNameAware.setBeanName paramString:"+paramString);

    }

    @Override
    public void setBeanFactory(BeanFactory paramBeanFactory) throws BeansException {
        System.out.println("調(diào)用BeanFactoryAware.setBeanFactory");

    }

    @Override
    public void destroy() throws Exception {
        System.out.println("DisposableBean 接口 destroy方法");

    }

    public void init() throws Exception {
        System.out.println("HelloWorld類 init-method 方法");

    }

}
// 配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="springIoCBean" class="com.alicp.jetcache.test.spring.SpringIoCBean" init-method="init" >
        <property name="hello" value="bigtest" />
    </bean>

</beans>

// 測試類
@RunWith(SpringJUnit4ClassRunner.class)  //使用junit4進行測試
@ContextConfiguration({"classpath:spring-bean.xml"}) //加載配置文件
public class SpringBeanIoCTest {

    @Autowired
    private SpringIoCBean bean;

    @Test
    public void testMain(){
        System.out.println("testMain");
    }

}
// ======輸出
static block 
{} block hello = null
調(diào)用HelloWorld構(gòu)造器...
調(diào)用setHello()...
調(diào)用BeanNameAware.setBeanName paramString:springIoCBean
調(diào)用BeanFactoryAware.setBeanFactory
調(diào)用InitializingBean的afterPropertiesSet()...
HelloWorld類 init-method 方法
調(diào)用BeanFactoryPostProcessor的postProcessBeanFactory()...
調(diào)用BeanPostProcessor的postProcessBeforeInitialization() -- org.springframework.context.event.internalEventListenerProcessor
調(diào)用BeanPostProcessor的postProcessAfterInitialization() -- org.springframework.context.event.internalEventListenerProcessor
調(diào)用BeanPostProcessor的postProcessBeforeInitialization() -- org.springframework.context.event.internalEventListenerFactory
調(diào)用BeanPostProcessor的postProcessAfterInitialization() -- org.springframework.context.event.internalEventListenerFactory
調(diào)用BeanPostProcessor的postProcessBeforeInitialization() -- com.alicp.jetcache.spring.SpringBeanIoCTest
調(diào)用BeanPostProcessor的postProcessAfterInitialization() -- com.alicp.jetcache.spring.SpringBeanIoCTest
testMain
DisposableBean 接口 destroy方法

由結(jié)果得出初始化bean順序

  • 構(gòu)造函數(shù) construct

  • 初始化屬性 property

  • BeanNameAware : setBeanName

  • BeanFactoryAware : 接口執(zhí)行setBeanFactory方法

  • InitializingBean 接口執(zhí)行afterPropertiesSet方法

  • 如果在配置文件中指定了init-method殖演,那么執(zhí)行該方法

  • 如果實現(xiàn)了BeanFactoryPostProcessor 接口在 “new”其他類之前執(zhí)行 postProcessBeanFactory 方法(通過這個方法可以改變配置文件里面的屬性值的配置)// 該bean只會調(diào)用一次

  • 如果實現(xiàn)了BeanPostProcessor 接口氧秘,那么會在其他bean初始化方法之前執(zhí)行postProcessBeforeInitialization 方法,之后執(zhí)行postProcessAfterInitialization方法 // 該bean會在每個其他bean初始化前后得到調(diào)用趴久,所以可以用來過濾有關(guān)聯(lián)的bean相應(yīng)的處理

Ref:
https://juejin.im/post/5b7964d6f265da43412866c7

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末丸相,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子彼棍,更是在濱河造成了極大的恐慌灭忠,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件座硕,死亡現(xiàn)場離奇詭異弛作,居然都是意外死亡,警方通過查閱死者的電腦和手機华匾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門映琳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人瘦真,你說我怎么就攤上這事刊头。” “怎么了诸尽?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵原杂,是天一觀的道長。 經(jīng)常有香客問我您机,道長穿肄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任际看,我火速辦了婚禮咸产,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘仲闽。我一直安慰自己脑溢,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布赖欣。 她就那樣靜靜地躺著屑彻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪顶吮。 梳的紋絲不亂的頭發(fā)上社牲,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機與錄音悴了,去河邊找鬼搏恤。 笑死违寿,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的熟空。 我是一名探鬼主播藤巢,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼痛阻!你這毒婦竟也來了菌瘪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤阱当,失蹤者是張志新(化名)和其女友劉穎俏扩,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弊添,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡录淡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了油坝。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嫉戚。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖澈圈,靈堂內(nèi)的尸體忽然破棺而出彬檀,到底是詐尸還是另有隱情,我是刑警寧澤瞬女,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布窍帝,位于F島的核電站,受9級特大地震影響诽偷,放射性物質(zhì)發(fā)生泄漏坤学。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一报慕、第九天 我趴在偏房一處隱蔽的房頂上張望深浮。 院中可真熱鬧,春花似錦眠冈、人聲如沸飞苇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽玄柠。三九已至,卻和暖如春诫舅,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背宫患。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工刊懈, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓虚汛,卻偏偏與公主長得像匾浪,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子卷哩,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 參考書目:《這樣學(xué)習(xí)最有效》[美] 戴維斯 學(xué)習(xí)是有方法的蛋辈。 1.明確學(xué)習(xí)目標(biāo),減少心理內(nèi)耗 我們經(jīng)常糾結(jié)于學(xué)什么...
    麥小麥的小島閱讀 691評論 0 3
  • 知道了什么是線程将谊。線程是進程中獨立執(zhí)行的單位冷溶。線程是操作系統(tǒng)進行運算調(diào)度的最小單位,是進程中的一個實體尊浓,由cpu調(diào)...
    Komolei閱讀 241評論 0 0
  • 本書的終極目的是幫助你在生活中永久建立的健康的習(xí)慣性行為方式逞频。 如果沒有產(chǎn)出什么結(jié)果,再大的決心也...
    曾小雄_xwx閱讀 148評論 0 0
  • 親子教育根本就不是教育孩子和改變孩子栋齿,而是家長提升和成長自己苗胀,這才應(yīng)該是親子教育最根本的核心所在。 媽媽(的內(nèi)在情...
    每個人的孟母堂閱讀 180評論 0 0
  • 木子曦 年齡:17 學(xué)校:夜曦 身高:165cm 體重:42kg 愛好:無所不能...
    河檬閱讀 398評論 0 0