容器啟動過程
Spring官方關於容器啟動的科普性圖示:
其中The Spring Container節點對容器進行了初始化,產生直接可用的Fully configured system。
下圖是容器啟動的各個階段圖示:
其中提到的相關介面和類的關係如下圖:
以上類圖中BeanDefinitionRegistry
依賴BeanDefinition
,其他都是實現關係。
BeanFactoryPostProcessor容器擴充套件機制(第一階段後)
該機制允許我們在容器例項化相應物件之前,對註冊到容器的 BeanDefinition 所儲存的資訊做相應的修改。也就是在容 器實現的第一階段最後加入一道工序。
BeanFactoryPostProcessor註冊方式
BeanFactory
硬編碼註冊BeanFactoryPostProcessor
:
// 宣告將被後處理的BeanFactory例項
ConfigurableListableBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("..."));
// 宣告要使用的BeanFactoryPostProcessor
PropertyPlaceholderConfigurer propertyPostProcessor = new PropertyPlaceholderConfigurer();
propertyPostProcessor.setLocation(new ClassPathResource("..."));
// 執行後處理操作
propertyPostProcessor.postProcessBeanFactory(beanFactory);
複製程式碼
ApplicationContext
配置檔案註冊BeanFactoryPostProcessor
:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>conf/jdbc.properties</value>
<value>conf/mail.properties</value>
</list>
</property>
</bean>
複製程式碼
各常用實現類說明
1. PropertyPlaceholderConfigurer
將XML配置檔案與具體引數property分離,在XML中使用佔位符匹配properties檔案中的具體引數,如以下形式:
//XML資料來源配置
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="url">
<value>${jdbc.url}</value>
</property>
<property name="driverClassName">
<value>${jdbc.driver}</value>
</property>
<property name="username">
<value>${jdbc.username}</value>
</property>
<property name="password">
<value>${jdbc.password}</value>
</property>
</bean>
複製程式碼
//properties檔案對應引數
jdbc.url=jdbc:mysql://server/MAIN?useUnicode=true&characterEncoding=ms932&failOverReadOnly=false&roundRobinLoadBalance=true
jdbc.driver=com.mysql.jdbc.Driver
jdbc.username=your username=your password jdbc.password
複製程式碼
PropertyPlaceholderConfigurer
還會檢查Java的System
類中的Properties
,可以通過setSystemPropertiesMode()
或者setSystemPropertiesModeName()
來控制是否載入或者覆蓋System
相應Properties
的行為。其提供三種模式:
public class PropertyPlaceholderConfigurer extends PlaceholderConfigurerSupport {
//不使用System的Properties配置項
public static final int SYSTEM_PROPERTIES_MODE_NEVER = 0;
//預設模式。properties中找不到對應引數(配置項),則去System的Properties找。
public static final int SYSTEM_PROPERTIES_MODE_FALLBACK = 1;
//優先使用System的Properties配置項
public static final int SYSTEM_PROPERTIES_MODE_OVERRIDE = 2;
}
複製程式碼
2. PropertyOverrideConfigurer
可以通過PropertyOverrideConfigurer
對容器中配置的任何你想處理的bean
定義的property
資訊(不需要使用佔位符)進行覆蓋替換。
例如,對於上文的XML資料來源配置,作如下配置:
註冊PropertyOverrideConfigurer
:
<bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
<property name="location" value="pool-adjustment.properties"/>
</bean>
複製程式碼
pool-adjustment.properties
內容:
#鍵引數值對格式為:beanName.propertyName=value
dataSource.minEvictableIdleTimeMillis=1000
dataSource.maxActive=50
複製程式碼
即可替換dataSource
的對應引數。
未使用PropertyOverrideConfigurer進行設定的引數依舊使用bean定義中的引數;多個引數對同一個property值進行設定時,以最後一個為準
3. CustomEditorConfigurer
通過XML定義的bean
以及其property
都需要由String轉換成對應的各式型別物件,這個工作即是由JavaBean
的PropertyEditor
來完成(Spring也提供了自身實現的一些PropertyEditor
,大多位於org.springframework.beans.propertyeditors
)。
部分PropertyEditor
(容器預設載入):
- StringArrayPropertyEditor:將符合CSV格式的字串轉換成
String[]
陣列的形式,預設是以逗號(,)分隔的字串。 - ClassEditor:根據
String
型別的class
名稱,直接將其轉換成相應的Class
物件。 - FileEditor:對應
java.io.File
型別的PropertyEditor
,負責資源定位。 - LocaleEditor:針對
java.util.Locale
型別的PropertyEditor
。 - PatternEditor:針對Java SE 1.4之後才引入的
java.util.regex.Pattern
的PropertyEditor
。
自定義PropertyEditor
:
兩種方式:
- 直接實現
java.beans.PropertyEditor
- 繼承
java.beans.PropertyEditorSupport
,只需要實現setAsText(String)
方法。
如下為定製日期格式的PropertyEditorSupport
實現:
public class DatePropertyEditor extends PropertyEditorSupport {
private String datePattern;
@Override
public void setAsText(String text) throws IllegalArgumentException {
DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(getDatePattern());
Date dateValue = dateTimeFormatter.parseDateTime(text).toDate();
setValue(dateValue);
}
public String getDatePattern() {
return datePattern;
}
public void setDatePattern(String datePattern) {
this.datePattern = datePattern;
}
public DatePropertyEditor(String datePattern){
this.datePattern = datePattern;
}
}
複製程式碼
通過CustomEditorConfigurer
註冊自定義的PropertyEditor
- 容器為
BeanFactory
時,硬編碼註冊:
XmlBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("……"));
CustomEditorConfigurer ceConfigurer = new CustomEditorConfigurer();
Map customerEditors = new HashMap();
customerEditors.put(java.util.Date.class, new DatePropertyEditor("yyyy/MM/dd"));
ceConfigurer.setCustomEditors(customerEditors);
ceConfigurer.postProcessBeanFactory(beanFactory);
複製程式碼
- 容器為
ApplicationContext
時,作為bean
註冊:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
//Spring2.0前使用customEditors
<property name="customEditors">
<map>
<entry key="java.util.Date">
<ref bean="datePropertyEditor"/>
</entry>
</map>
</property>
</bean>
<bean id="datePropertyEditor" class="...DatePropertyEditor">
<property name="datePattern">
<value>yyyy/MM/dd</value>
</property>
</bean>
複製程式碼
Spring2.0後提倡使用propertyEditorRegistrars
屬性來指定自定義的PropertyEditor
:
需額外實現PropertyEditorRegistrar
:
public class DatePropertyEditorRegistrar implements PropertyEditorRegistrar {
private PropertyEditor propertyEditor;
public void registerCustomEditors(PropertyEditorRegistry peRegistry) {
peRegistry.registerCustomEditor(java.util.Date.class, getPropertyEditor());
}
public PropertyEditor getPropertyEditor() {
return propertyEditor;
}
public void setPropertyEditor(PropertyEditor propertyEditor) {
this.propertyEditor = propertyEditor;
}
}
複製程式碼
此時的bean
註冊:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
//多個PropertyEditorRegistrar可以在list裡一一指定
<list>
<ref bean="datePropertyEditorRegistrar"/>
</list>
</property>
</bean>
//Spring2.0後提倡使用DatePropertyEditor
<bean id="datePropertyEditorRegistrar" class="...DatePropertyEditorRegistrar">
<property name="propertyEditor">
<ref bean="datePropertyEditor"/>
</property>
</bean>
<bean id="datePropertyEditor" class="...DatePropertyEditor">
<property name="datePattern">
<value>yyyy/MM/dd</value>
</property>
</bean>
複製程式碼
BeanFactoryPostProcessor工作機制類圖:
其中,左側部分為BeanFactoryPostProcessor
及其子類,右側為`PropertyEditor及其子類,下方為自定義CostomEditor序號產生器制實現。
bean生命週期(第二階段)
當請求方通過BeanFactory
的getBean()
方法來請求某個物件例項的時候,才有可能觸發Bean例項化階段的活動。
- 客戶端物件顯式呼叫
- 容器內部隱式呼叫
- 對於 BeanFactory 來說,物件例項化預設採用延遲初始化。當初始化A物件時,會隱式初始化A的依賴物件B。
- ApplicationContext 啟動之後會例項化所有的bean定義。當初始化A物件時,會隱式初始化A的依賴物件B。
bean
例項化過程:
org.springframework.beans.factory.support.AbstractBeanFactory
檢視getBean()
;org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory
檢視createBean()
;
getBean()
大致邏輯:(待閱原始碼)
Bean的例項化與 BeanWrapper
可以通過反射或者CGLIB動態位元組碼生成來初始化相應的bean例項或者動態生成其子類。
spring預設使用CglibSubclassingInstantiationStrategy
生產被BeanWrapperImpl
包裝的目標類的bean
。
InstantiationStrategy類圖:
BeanWrapper和他的爸爸們:
各色的 Aware 介面
當物件例項化完成並且相關屬性以及依賴設定完成之後,Spring容器會檢查當前物件例項是否實現了一系列的以 Aware 命名結尾的介面定義。如果是,則將這些 Aware 介面定義中規定的依賴注入給當前物件例項。
BeanFactory
對應的Aware
:
ApplicationContext
對應的Aware
:
BeanPostProcessor
BeanPostProcessor存在於物件例項化階段。
介面定義如下:
package org.springframework.beans.factory.config;
public interface BeanPostProcessor {
//例項化前執行
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
//例項化後執行
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
複製程式碼
例如ApplicationContextAwareProcessor,則是對ApplicationContext
對應的Aware
進行檢測執行對應操作的BeanPostProcessor
實現類,其postProcessBeforeInitialization
方法如下:
public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
AccessControlContext acc = null;
//檢測此bean是否實現以下Aware中的一個或多個
if (System.getSecurityManager() != null &&
(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||
bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||
bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)) {
//獲取當前applicationContext的AccessControlContext
acc = this.applicationContext.getBeanFactory().getAccessControlContext();
}
if (acc != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
invokeAwareInterfaces(bean);
return null;
}, acc);
}
else {
invokeAwareInterfaces(bean);
}
return bean;
}
複製程式碼
自定義 BeanPostProcessor
- 標註需要進行處理的實現類(可定義並實現標記介面(Aware))
- 實現相應的
BeanPostProcessor
對符合條件的Bean例項進行處理 - 將自定義的
BeanPostProcessor
註冊到容器,註冊方式如下:
- 對於 BeanFactory 型別的容器,採用硬編碼
ConfigurableBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource(...));
beanFactory.addBeanPostProcessor(new CustomPostProcessor());
複製程式碼
- 對於 ApplicationContext 容器,直接XML註冊
<beans>
<bean id="customPostProcessor" class="package.name.CustomPostProcessor">
<!--如果需要,注入必要的依賴-->
</bean>
...
</beans>
複製程式碼
InitializingBean 和 init-method
org.springframework.beans.factory.InitializingBean
是容器內部廣泛使用的一個物件生命週期標識介面,用於在BeanPostProcessor
的前置處理執行後進一步編輯實現該介面的bean,如下:
public interface InitializingBean {
/**
* Invoked by the containing {@code BeanFactory} after it has set all bean properties
* and satisfied {@link BeanFactoryAware}, {@code ApplicationContextAware} etc.
* <p>This method allows the bean instance to perform validation of its overall
* configuration and final initialization when all bean properties have been set.
* @throws Exception in the event of misconfiguration (such as failure to set an
* essential property) or if initialization fails for any other reason
*/
void afterPropertiesSet() throws Exception;
}
複製程式碼
實際開發中使用 <bean>
的 init-method
屬性來代替上述方式。一般用於整合第三方庫。
DisposableBean 與 destroy-method
與InitializingBean
和init-method
對應,用於執行singleton
型別的物件銷燬操作。
為該例項註冊一個用於物件銷燬的回撥(Callback),以便在這些singleton型別的物件例項銷燬之前,執行銷燬邏輯。
例如Spring註冊的資料庫連線池:
<!--銷燬方法為BasicDataSource自定義的close方法-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="url">
<value>${jdbc.url}</value>
</property>
<property name="driverClassName">
<value>${jdbc.driver}</value>
</property>
<property name="username">
<value>${jdbc.username}</value>
</property>
<property name="password">
<value>${jdbc.password}</value>
</property>
...
</bean>
複製程式碼
對BeanFactory
:
應在程式退出或者其他業務場景呼叫ConfigurableBeanFactory
的destroySingletons()
方法(處理所有實現DisposableBean
介面和註冊了destroy-method
方法的類)銷燬容器管理的所有singleton型別的物件例項。
/**
* BeanFactory銷燬單例例項方法呼叫。
*/
public class ApplicationLauncher {
public static void main(String[] args) {
BasicConfigurator.configure();
BeanFactory container = new XmlBeanFactory(new ClassPathResource("..."));
BusinessObject bean = (BusinessObject) container.getBean("...");
bean.doSth();
((ConfigurableListableBeanFactory) container).destroySingletons();
// 應用程式退出,容器關閉
}
}
複製程式碼
對ApplicationContext
:
AbstractApplicationContext
為我們提供了registerShutdownHook()
方法,該方法底層使用標準的Runtime
類的addShutdownHook()
方式來呼叫相應bean物件的銷燬邏輯。
/**
* 使用 registerShutdownHook() 方法註冊並觸發物件銷燬邏輯回撥行為
*/
public class ApplicationLauncher {
public static void main(String[] args) {
BasicConfigurator.configure();
BeanFactory container = new ClassPathXmlApplicationContext("...");
((AbstractApplicationContext) container).registerShutdownHook();
BusinessObject bean = (BusinessObject) container.getBean("...");
bean.doSth();
// 應用程式退出,容器關閉
}
}
複製程式碼