Spring 5 設計模式 - bean生命週期和使用的模式
Spring 5 設計模式 - bean生命週期和使用的模式
Spring容器管理的每個bean都有自己的生命週期和scope。
生命週期和階段
Spring載入bean以後,Spring容器處理bean的生成和例項化。可以把生命週期分成三個階段:
- 初始化階段
- 使用階段
- 銷燬階段
Spring管理的bean在每個階段執行的操作,都是依賴於配置的。
初始化階段
首先,從XML檔案、註解和Java配置載入全部配置。本階段完成以後,程式才可以使用。在本階段,生成bean,併為bean分配資源。Spring使用ApplicationContext載入bean配置,程式上下文增加完成,本階段也就執行完了。
Spring提供了ApplicationContext的多個實現,以載入配置檔案:
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
或者
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
初始化階段又可分成兩步:
- 載入bean定義
- 初始化bean例項
載入bean定義
在這一步,所有的配置檔案 - 帶@Configuration註解的類和XML檔案 - 都被處理了。對於基於註解的配置,所有使用@Components註解的類都被掃描,來載入bean定義。所有的XML檔案被合併,bean的定義被加到BeanFactory。每個bean都用它的ID做索引。Spring提供了多個BeanFactoryPostProcessor,它被呼叫以解決執行時依賴(比如從外部屬性檔案讀值)。Spring程式裡的BeanFactoryPostProcessor可以修改任何bean的定義。
可以看到,首先載入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做索引,如下圖所示:
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。見下圖:
- 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;
}
}
初始化擴充套件點
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");
}
第一階段的概覽圖是這樣的:
bean的使用階段
Spring程式的bean,大部分時間都在本階段。我們先看看如何從上下文獲取bean:
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
TransferService transferService = context.getBean(TransferService.class);
transferService.transfer("A", "B", 3000.1);
可以看到,在初始化階段,bean被包裝進一個動態代理。給bean透明地增加行為。這是一個Decorator和Proxy模式的實現。
Spring 使用兩種型別的代理:
- JDK:由JDK實現,被代理的類需要實現一個介面
- CGLib:不實現介面的時候時候。它不能用於final類和方法。
銷燬階段
在本階段,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
看上圖,一個IoC容器內,同一個例項accountRepository,被注入其他合作bean。Spring使用快取儲存所有的singleton bean例項,通過該快取,所有的合作bean獲取依賴的物件。
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;
}