1. 程式人生 > >Spring 5 設計模式 - bean生命週期和使用的模式

Spring 5 設計模式 - bean生命週期和使用的模式

Spring 5 設計模式 - bean生命週期和使用的模式

Spring容器管理的每個bean都有自己的生命週期和scope。

生命週期和階段

Spring載入bean以後,Spring容器處理bean的生成和例項化。可以把生命週期分成三個階段:

  • 初始化階段
  • 使用階段
  • 銷燬階段
    three phases

Spring管理的bean在每個階段執行的操作,都是依賴於配置的。

初始化階段

首先,從XML檔案、註解和Java配置載入全部配置。本階段完成以後,程式才可以使用。在本階段,生成bean,併為bean分配資源。Spring使用ApplicationContext載入bean配置,程式上下文增加完成,本階段也就執行完了。
Spring提供了ApplicationContext的多個實現,以載入配置檔案:

ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

或者

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

initialization phase

初始化階段又可分成兩步:

  • 載入bean定義
  • 初始化bean例項

載入bean定義

在這一步,所有的配置檔案 - 帶@Configuration註解的類和XML檔案 - 都被處理了。對於基於註解的配置,所有使用@Components註解的類都被掃描,來載入bean定義。所有的XML檔案被合併,bean的定義被加到BeanFactory。每個bean都用它的ID做索引。Spring提供了多個BeanFactoryPostProcessor,它被呼叫以解決執行時依賴(比如從外部屬性檔案讀值)。Spring程式裡的BeanFactoryPostProcessor可以修改任何bean的定義。
loads

可以看到,首先載入bean定義,然後呼叫BeanFactoryProcessor修改某些bean的定義。讓我們看一個例子:

@Configuration
public class AppConfig {
    @Bean
    public TransferService transferService() { 
        //
    }
    @Bean
    public AccountRepository accountRepository(DataSource dataSource) {
        //
    }
}


@Configuration
public class InfraConfig {
    @Bean
    public DataSource dataSource () {
        //
    }
}

這些Java配置由ApplicationContext載入到容器,使用他們的ID做索引,如下圖所示:
modify bean

Spring bean被索引之後放進BeanFactory,然後,BeanFactory被當作一個引數傳入BeanFactoryPostProcessor的postProcess()方法。BeanFactoryPostProcessor能修改一些bean的定義;這依賴於bean的配置。我們看看BeanFactoryPostProcessor是如何工作的“

  • 在實際增加bean之前,BeanFactoryPostProcessor處理bean的定義或者配置元資料
  • BeanFactoryPostProcessor有幾個實現,比如讀屬性檔案、註冊自定義scope
  • 可以實現自己的BeanFactoryPostProcessor
  • 如果你在一個容器內定義BeanFactoryPostProcessor,它就只作用於這個容器內的bean定義
public interface BeanFactoryPostProcessor {
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory);
}

我們看看它怎麼讀屬性檔案database.properties:

jdbc.driver = org.hsqldb.jdbcDriver
jdbc.url = jdbc:hsqldb:hsql://production:9002
jdbc.username = [email protected]

對應的配置類:

@Configuration
@PropertySource("classpath:/config/database.properties")
public class InfraConfig {
    @Bean
    public DataSource dataSource(
            @Value("${jdbc.driver}") String driver,
            @Value("${jdbc.url}") String url,
            @Value("${jdbc.user}") String user,
            @Value("${jdbc.password}") String pwd) {
        DataSource ds = new BasicDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUser(user);
        ds.setPassword(pwd);
        return ds;
    }
}

對於@Value,使用PropertySourcesPlaceholderConfigurer處理,它是一個BeanFactoryPostProcessor。

初始化bean例項

把bean的定義載入到BeanFactory之後,Spring IoC容器例項化bean。見下圖:
instantiates the beans

  • bean以正確的順序和它的依賴注入被生成,除非它是延遲的
  • 有多個BeanPostProcessor,可以修改bean例項
  • 執行完本階段,bean已經初始化完畢,準備使用了。除了prototype bean,都通過ID跟蹤,直到被銷燬

使用BeanPostProcessor自定義bean

BeanPostProcessor是個重要的擴充套件點。可以用來修改bean的視力。可以寫自己的BeanPostProcessor來增加自定義的post-processor。Spring提供了它的幾個實現。我們先看看BeanPostProcessor介面的定義:

public interface BeanPostProcessor {
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

可以實現這兩個方法實現自定義邏輯。可以在Spring容器裡增加多個BeanPostProcessor的實現,來增加自定義邏輯。也可以管理這些BeanPostProcessor的執行順序。BeanPostProcessor在bean已經在Spring容器中例項化之後工作。
Spring容器在容器初始化方法(Initializing Bean的afterPropertiesSet()和bean的init方法)執行之前呼叫postProcessBeforeInitialization()方法。並在bean初始化之後呼叫postProcessAfterInitialization()方法。Spring AOP使用post-processor提供proxy-wrapping邏輯(代理設計模式)。Spring的ApplicationContext自動探測實現了BeanPostProcessor介面的bean,註冊成post-processors。這些bean在其他任何增加的時候被呼叫。

我們自定義一個post-processor bean:

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("In After bean Initialization method.Bean name is" + beanName);
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("In Before bean Initialization method. Bean name is" + beanName);
        return bean;
    }
}

初始化擴充套件點

BPPs

Spring使用post-processor bean(BPPs)呼叫init方法(@PostConstruct)。上圖是初始化和BPPs之間的關係圖。
使用Java配置,可以這樣寫:

    @Bean(initMethod = "populateCache")
    public AccountRepository accountRepository(){
        return new JdbcAccountRepository();
    }

使用JSR-250註解,可以這樣寫:

    @PostConstruct
    void populateCache(){
        System.out.println("Called populateCache() method");
    }

第一階段的概覽圖是這樣的:
configuration life cycle

bean的使用階段

Spring程式的bean,大部分時間都在本階段。我們先看看如何從上下文獲取bean:

    
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

    TransferService transferService = context.getBean(TransferService.class);
    transferService.transfer("A", "B", 3000.1);

wrapped in a proxy

可以看到,在初始化階段,bean被包裝進一個動態代理。給bean透明地增加行為。這是一個Decorator和Proxy模式的實現。
Spring 使用兩種型別的代理:

  • JDK:由JDK實現,被代理的類需要實現一個介面
  • CGLib:不實現介面的時候時候。它不能用於final類和方法。
    both the proxies

銷燬階段

在本階段,Spring釋放資源。關閉上下文的時候,銷燬階段完成。
我們看看,呼叫applicationContext.close()發生了什麼:

  • 任何實現了DisposableBean的bean被回撥,bean例項被銷燬
  • 上下文銷燬自己,該上下文不能被重新使用
  • GC實際上銷燬物件和記憶

比如一個JdbcAccountRepository類:

public class JdbcAccountRepository implements AccountRepository {
    @Override
    public Account findByAccountId(Long accountId) {
        return new Account(accountId, "Arnav Rajput", new Amount(3000.0));
    }
    void clearCache(){
        System.out.println("Called clearCache() method");
    }
}

這樣配置destroy方法:

    @Bean (destroyMethod="clearCache")
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository();
    }

更簡單地,也可以使用一個註解:

public class JdbcAccountRepository {
    @PreDestroy
    void clearCache() {
        // close files, connections...
        // remove external resources...
    }
}

理解bean scopes

在Spring容器裡,每個bean都有一個scope。
Spring的程式上下文總是使用singleton scope增加bean。
有時候,需要在物件裡儲存狀態。比如這樣一個需求,宣告bean的scope為singleton是不安全的,因為以後重用的時候可能導致不期望的問題。Spring為了解決這樣的需求,提供了prototype scope。

singleton

singleton

看上圖,一個IoC容器內,同一個例項accountRepository,被注入其他合作bean。Spring使用快取儲存所有的singleton bean例項,通過該快取,所有的合作bean獲取依賴的物件。

prototype

prototype

Spring的任何定義為prototype scope的bean,每當注入合作bean的時候都生成新的例項。看上圖,accountRepository有多個例項。

session

在web環境中使用,每個使用者session增加一個例項。

request

在web環境中使用,每個請求增加一個例項。

其他scopes

  • WebSocket
  • Refresh
  • Thread

Spring也支援自定義scope。

自定義

先實現Scope介面,增加一個類:

import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;

public class MyThreadScope implements Scope {
    private final ThreadLocal<Object> myThreadScope =
            new ThreadLocal<Object>() {
                protected Map<String, Object> initialValue() {
                    System.out.println("initialize ThreadLocal");
                    return new HashMap<String, Object>();
                }
            };

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        Map<String, Object> scope = (Map<String, Object>) myThreadScope.get();
        System.out.println("getting object from scope.");
        Object object = scope.get(name);
        if (object == null) {
            object = objectFactory.getObject();
            scope.put(name, object);
        }
        return object;
    }

    @Override
    public String getConversationId() {
        return null;
    }

    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
    }

    @Override
    public Object remove(String name) {
        System.out.println("removing object from scope.");
        @SuppressWarnings("unchecked")
        Map<String, Object> scope = (Map<String, Object>) myThreadScope.get();
        return scope.remove(name);
    }

    @Override
    public Object resolveContextualObject(String name) {
        return null;
    }
}

然後

    @Bean
    public CustomScopeConfigurer configurer() {
        CustomScopeConfigurer csConfigurer = new CustomScopeConfigurer();
        csConfigurer.addScope("MyThreadScope", new MyThreadScope());
        return csConfigurer;
    }