Spring原始碼解析之基礎應用(二)

北洛發表於2020-10-18

方法注入

在spring容器中,大部分bean的作用域(scope)是單例(singleton)的,少部分bean的作用域是原型(prototype),如果一個bean的作用域是原型,我們A bean的作用域是原型,B bean中以@Autowired的方式注入A,那麼B在A中依舊是單例。我們可以讓B類實現ApplicationContextAware介面,這個介面會注入一個ApplicationContext物件,如果我們有需要A bean,則從ApplicationContextAware物件中獲取。

package org.example.service;


import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("prototype")
public class CommandService {
}

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class CommandManager implements ApplicationContextAware {
    private ApplicationContext applicationContext;
    @Autowired
    private CommandService commandService;

    public CommandService getCommandService() {
        return commandService;
    }

    //方法注入
    public CommandService createCommandService() {
        return applicationContext.getBean(CommandService.class);
    }

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

package org.example.config;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan("org.example.service")
public class MyConfig {
}

  

為了驗證筆者之前的說法,筆者特意在CommandManager類中注入一個原型的commandService的bean,也提供了方法注入,從applicationContext中獲取CommandService物件。現在,我們編寫測試用例,CommandManager有兩種獲取CommandService的方式,一種是getCommandService(),一種是createCommandService(),我們的目標是希望每次獲取CommandService物件都是新建立的物件。

    @Test
    public void test05() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig.class);
        CommandManager commandManager = ac.getBean(CommandManager.class);
        //獲取@Autowired注入的CommandService
        System.out.println(commandManager.getCommandService());
        System.out.println(commandManager.getCommandService());
        //獲取方法注入的CommandService
        System.out.println(commandManager.createCommandService());
        System.out.println(commandManager.createCommandService());
    }

  

執行結果:

org.example.service.CommandService@351d0846
org.example.service.CommandService@351d0846
org.example.service.CommandService@77e4c80f
org.example.service.CommandService@35fc6dc4

 

除了像上面那樣從環境上下文獲取CommandService物件之外,我們還可以藉助@Lookup註解:

package org.example.service;

import org.springframework.beans.factory.annotation.Lookup;
import org.springframework.stereotype.Component;

@Component
public abstract class CommandManager1 {
    @Lookup
    public abstract CommandService createCommandService();
}


import org.springframework.beans.factory.annotation.Lookup;
import org.springframework.stereotype.Component;

@Component
public class CommandManager2 {
    @Lookup
    public CommandService createCommandService() {
        return null;
    }
}

  

測試用例:

    @Test
    public void test06() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig.class);
        CommandManager1 commandManager1 = ac.getBean(CommandManager1.class);
        System.out.println(commandManager1.createCommandService());
        System.out.println(commandManager1.createCommandService());
        CommandManager2 commandManager2 = ac.getBean(CommandManager2.class);
        System.out.println(commandManager2.createCommandService());
        System.out.println(commandManager2.createCommandService());
    }

  

執行結果:

org.example.service.CommandService@6193932a
org.example.service.CommandService@647fd8ce
org.example.service.CommandService@159f197
org.example.service.CommandService@78aab498

  

不管是CommandManager1還是CommandManager2,spring在對這兩個類生成代理物件,當呼叫被@Lookup註解標記的方法時,產生新的原型物件返回。

生命週期

從spring2.5開始,我們有三種選項來控制bean生命週期行為:

  1. InitializingBean和DisposableBean回撥介面。
  2. 自定義init()和destroy()方法。
  3. @PostConstruct和@PreDestroy註解。 

我們可以使用上面三種選項來控制bean在不同生命週期時機進行函式回撥,比如當一些dao在spring建立好之後,在回撥方法裡執行熱點資料載入。下面的A類,我們按照第一和第三個選項,實現InitializingBean、DisposableBean介面,並標註@PostConstruct、@PreDestroy方法,分別是aaa()、bbb()。然後,我們在配置類MyConfig2通過@Bean註解獲取一個型別為A的bean,並且按照第二個選項,在@Bean標註了init()和destroy()方法,分別是ccc()、ddd()。

package org.example.beans;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

public class A implements InitializingBean, DisposableBean {
    @Override
    public void destroy() throws Exception {
        System.out.println("A destroy...");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("A afterPropertiesSet...");
    }

    @PostConstruct
    public void aaa() {
        System.out.println("A aaa...");
    }

    @PreDestroy
    public void bbb() {
        System.out.println("A bbb...");
    }

    public void ccc() {
        System.out.println("A ccc...");
    }

    public void ddd() {
        System.out.println("A ddd...");
    }
}

package org.example.config;

import org.example.beans.A;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("org.example.service")
public class MyConfig2 {
    @Bean(initMethod = "ccc", destroyMethod = "ddd")
    public A getA() {
        return new A();
    }
}

  

測試用例:

    @Test
    public void test07() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig2.class);
        ac.close();
    }

  

執行結果:

A aaa...
A afterPropertiesSet...
A ccc...
A bbb...
A destroy...
A ddd...

  

從上面的執行結果,我們可以得出:如果存在不同的初始化方法,bean的回撥順序是:

  1. 先執行@PostConstruct註解所標註的方法。
  2. 再執行InitializingBean介面所實現的afterPropertiesSet()方法
  3. 最後執行自定義的init()方法。

銷燬方法也是一樣的順序:

  1. 先執行@PreDestroy註解所標註的方法。
  2. 在執行DisposableBean介面所實現的destroy()方法。
  3. 最後執行自定義的destroy()方法。

 

服務啟動關閉回撥

之前我們學習了bean的生命週期回撥,我們可以在bean的不同生命週期裡執行不同的方法,如果我們想針對整個服務進行生命週期回撥呢?比如要求在spring容器初始化好所有的bean之後、或者在關閉spring容器時進行方法回撥。spring提供了LifeCycle介面來幫助我們實現針對服務的生命週期回撥。LifeCycle提供了三個介面:

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}

  

當isRunning()返回為false時,start()會在初始化完所有的bean之後,進行回撥。比如我們可以在初始化好所有的bean之後,監聽一個訊息佇列,並處理佇列裡的訊息。當isRunning()返回為true時,stop()會在容器關閉時回撥stop()方法。

來看下面的例子,TestLiftCycle的isRunning()預設返回false,所以在bean初始化後,允許spring容器回撥start(),在start()方法中,將isRun置為true,允許在spring容器關閉時,回撥stop()方法。

package org.example.service;

import org.springframework.context.Lifecycle;
import org.springframework.stereotype.Component;

@Component
public class TestLiftCycle implements Lifecycle {
    private boolean isRun = false;

    @Override
    public void start() {
        isRun = true;
        System.out.println("TestLiftCycle start...");
    }

    @Override
    public void stop() {
        System.out.println("TestLiftCycle stop...");
    }

    @Override
    public boolean isRunning() {
        return isRun;
    }
}

  

要回撥TestLiftCycle的start()和stop(),在測試用例裡需要顯式呼叫容器的start()和stop()方法。如果專案是部署在類似Tomcat、JBoss的微博伺服器上,我們可以省去容器顯式呼叫stop()方法,因為Tweb伺服器在結束程式的時候,會回撥spring容器的stop(),進而回撥到我們編寫的stop(),但是start()必須顯式呼叫,不管是否部署在web伺服器上。

測試用例:

    @Test
    public void test08() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig.class);
        ac.start();
        ac.stop();
    }

  

執行結果:

TestLiftCycle start...
TestLiftCycle stop...

  

為了方便程式設計師偷懶,spring還提供了SmartLifecycle介面,這個介面不需要我們顯式呼叫容器的start()方法,也可以在初始化所有的bean之後回撥start()。下面,我們來看下SmartLifecycle介面:

public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);
}

 

SmartLifecycle擴充套件了Lifecycle和Phased兩個介面,Phased要求實現int getPhase()介面,返回的數值越低越優先執行。boolean isAutoStartup()代表是否自動執行SmartLifecycle的start()方法,如果為true會自動呼叫start(),為false的話,需要顯式呼叫容器的start()才會回撥SmartLifecycle的start()。SmartLifecycle擴充套件了原先Lifecycle的stop()方法,當我們執行完stop(Runnable callback)中的業務,需要執行callback.run(),這將啟用非同步關閉,否則要等待30s才會關閉容器。當然,這30s是可以修改的:

<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
    <!-- timeout value in milliseconds -->
    <property name="timeoutPerShutdownPhase" value="10000"/>
</bean>

 

下面,我們來測試TestSmartLifecycle,這裡的isRunning()依舊和Lifecycle一樣,只是關閉容器時不會再回撥TestSmartLifecycle的stop(),轉而回撥stop(Runnable callback)。

package org.example.service;

import org.springframework.context.SmartLifecycle;
import org.springframework.stereotype.Component;

@Component
public class TestSmartLifecycle implements SmartLifecycle {
    private boolean isRun = false;

    @Override
    public void start() {
        isRun = true;
        System.out.println("TestSmartLifecycle start...");
    }

    @Override
    public boolean isAutoStartup() {
        return true;
    }

    @Override
    public void stop(Runnable callback) {
        System.out.println("TestSmartLifecycle stop callback...");
        //執行callback.run()可以讓容器立即關閉,否則容器會等待30s再關閉
        callback.run();
    }

    @Override
    public int getPhase() {
        return 0;
    }


    @Override
    public void stop() {
    }

    @Override
    public boolean isRunning() {
        return isRun;
    }

}

  

測試用例:

    @Test
    public void test09() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig.class);
        ac.stop();
    }

  

執行結果:

TestSmartLifecycle start...
TestSmartLifecycle stop callback...

  

 

相關文章