轉眼間畢業已經兩年多了,工作中一直也在使用Spring,中途也陸續的看過其中的用法以及部分的實現原理。 最近工作之餘想整體的看一下Spring的具體原理,研究下源碼,理解下這個經典框架的設計思路,以及其中的設計模式。 不過開始看了一週後,感覺也是似懂非懂,回想起來總是不知道該從哪說起。
爲了防止這次看了又跟沒看一樣的慘劇發生,所以決定在看的過程中,記錄一下相關細節。 說實話準備開始寫這篇文章時,我還是有點不知道從哪說起,不過還是決定硬着頭皮寫吧,總要嘗試一下的嘛。
這篇文章主要說明 Spring IOC 相關的源代碼實現,使用 xml 配置的方式,雖然大家在使用中基本上不會使用這種方式,最起碼不是完全使用 XML 配置,不過從研究源代碼的角度,這種方式無疑是最合適的,理解了xml配置的方式,註解的實現基本上原理也是類似的過程,也許在過幾天我抽空也會寫篇文章講解下註解實現的相關源代碼。
由於文章篇幅較長,這裏分爲上下兩篇,此篇爲上篇,包含目錄如下:
Spring容器的功能加載
ApplicationContext bf = new ClassPathXmlApplicationContext("beans.xml");
如上面這行代碼,我們使用ApplicationCdontext方式加載XML。以ClassPathXmlApplicationContext作爲切入點,開始對整體功能進行分析。
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super(parent);
// 設置路徑
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
如上,設置路徑是必不可少的步驟。
容器啓動流程
設置好了路徑之後,便可以根據路徑做配置文件的解析以及各種功能的實現了。而Spring容器的整個啓動過程幾乎全部都在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.
// 初始化BeanFactory,並進行XML文件讀取
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
// 對BeanFactory進行各種功能填充
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
// 子類覆蓋方法做額外的處理
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
// 激活各種BeanFactory處理器
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
// 註冊攔截Bean創建的Bean處理器,此處只是註冊,真正的調用在getBean時發生
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
// 爲上下文初始化Message源,國際化處理
initMessageSource();
// Initialize event multicaster for this context.
// 初始化應用消息廣播器,並放入"applicationEventMulticaster"bean中
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
// 留給子類來初始化其他的Bean
onRefresh();
// Check for listener beans and register them.
// 在所有註冊的bean中查找listener bean,註冊到消息廣播器中
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
// 初始化剩下的所有單實例bean(非延遲加載)
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
// 完成刷新過程,通知生命週期處理器lifeCycleProcessor刷新過程,同時發出ContextRefreshEvent通知別人
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
流程概括
下面主要概括一下上面初始化步驟,並簡要解釋一下它爲我們提供的功能。
(1) 初始化前的準備工作,例如對系統屬性以及環境變量進行準備和驗證。
在某些情況下,項目的使用需要讀取某些系統變量,而這個變量可能影響着系統的正確性,那麼prepareRefresh這些準備函數就顯得非常必要,它可以在Spring啓動前對必須的變量進行驗證。
(2) 初始化BeanFactory,並進行XML文件讀取
這一步驟會複用BeanFactory中的配置文件讀取解析以及其他功能,這一步之後,其實ClasspathXmlApplicationContext實際上就已經包含了BeanFactory所提供的功能,也就是可以進行Bean的提取等基礎操作了。
(3) 對BeanFactory進行各種功能補充
@Qualifier與@Autowired應該是大家非常熟悉的註解,那麼這兩個註解正是在這一步驟中增加的支持
(4) 子類覆蓋方法做額外的處理
Spring之所以強大,爲世人所推崇,除了它功能上爲大家提供了便利外,還有一方面是它的完美架構,開放式的架構讓使用它的程序員很容易根據實際業務做相應的功能擴展。這種開放式的設計在Spring中隨處可見,例如本例中postProcessBeanFactory函數就是提供的空函數用來讓程序員在業務上做進一步的擴展。
(5) 激活各種BeanFactory處理器。
(6) 註冊各種攔截Bean創建的bean處理器,這裏只是註冊,真正的調用在getBean的時候。
(7) 爲上下文初始化Message源,即對不同的語言消息體進行國際化處理。
(8) 初始化應用消息廣播器,並放入applicationEventMulticaster
中。
(9) 留給子類來初始化其他的bean。
(10) 在所有註冊的bean中查找 listener bean ,註冊到消息廣播器中。
(11)初始化剩下的單實例(非惰性加載)bean。
(12) 完成刷新過程,通知生命週期處理器 lifecycleProcessor
刷新過程,同時發出 ContextRefreshEvent通知別人。
一探究竟
上面我們分析了Spring容器啓動要經歷的基本流程,接下來我們來一起探究具體每一步都做了什麼事情。
環境準備
prepareRefresh函數主要是做準備工作,例如對系統屬性的以及環境變量的初始化以及驗證。
/**
* Prepare this context for refreshing, setting its startup date and
* active flag as well as performing any initialization of property sources.
*/
protected void prepareRefresh() {
this.startupDate = System.currentTimeMillis();
this.closed.set(false);
this.active.set(true);
if (logger.isInfoEnabled()) {
logger.info("Refreshing " + this);
}
// Initialize any placeholder property sources in the context environment
// 留給子類覆蓋
initPropertySources();
// Validate that all properties marked as required are resolvable
// 驗證所需要的屬性文件是否都已放入環境中
// see ConfigurablePropertyResolver#setRequiredProperties
getEnvironment().validateRequiredProperties();
// Allow for the collection of early ApplicationEvents,
// to be published once the multicaster is available...
this.earlyApplicationEvents = new LinkedHashSet<ApplicationEvent>();
}
我在第一次看到這個函數時,覺得好像沒什麼用,因爲能看出來最後兩句代碼是關鍵,可能卻沒有什麼邏輯,initPropertySources 是空的,而且 getEnvironment().validateRequiredProperties() 也因爲沒有需要驗證的屬性而沒有任何處理。其實不然,是因爲沒有徹底理解纔會有上面的錯覺,這個函數如果用好了,作用是非常大的。下面先說下每個函數的作用,再結合一個例子我們一起看下。
(1) initPropertySources 正符合 Spring 的開放式設計,給用戶最大擴展 Spring 的能力。用戶可以根據自身的需要重寫 initPropertySources 方法,並在方法中進行個性化的屬性處理及設置。
(2) validateRequiredProperties 則是對屬性的驗證,那麼如何驗證呢?
假如,現在有這樣一個需求,工程在運行過程中,用到的某個設置(例如PATH)是從系統環境變量中取得的,如果用戶沒有在系統環境變量中配置這個屬性,那麼運行啓動肯定會報錯。這一要求在 Spring 中可以這樣做,你可以直接修改 Spring 的源碼,例如修改ClasspathXmlApplicationContext。當然最好的方法還是對 Spring 源碼進行擴展,如下:
public class MyClassPathXmlApplicationContext extends ClassPathXmlApplicationContext{
public MyClassPathXmlApplicationContext(String configLocation) throws BeansException {
super(configLocation);
}
@Override
protected void initPropertySources() {
// 添加驗證要求
getEnvironment().setRequiredProperties("PATH");
}
}
這裏自定義了繼承 ClassPathXmlApplicationContext
的 MyClassPathXmlApplicationContext
並重寫了 initPropertySources
方法,在方法中添加了我們的個性化需求,那麼在驗證的時候也就是程序走到 getEnvironment().validateRequiredProperties()
的時候,如果系統系統檢測並沒有需要的PATH
環境變量,那麼將拋出異常。當然我們需要在使用的時候替換ClassPathXmlApplicationContext
:
@Test
public void testInitProperties() {
ApplicationContext applicationContext = new MyClassPathXmlApplicationContext("beans.xml");
Dog dog = (Dog) applicationContext.getBean("dog");
}
加載 BeanFactory
obtainFreshBeanFactory
方法從字面上理解就是獲取 BeanFactory
。 上面也有提過 ApplicatioContext
是對BeanFactory
的功能上的擴展,不但包含了 BeanFactory
的全部功能更在其基礎上添加了大量的擴展應用,那麼obtainFreshBeanFactory
正是實現 BeanFactory
的地方,也就是說經過了這個函數後 ApplicationContext
就已經擁有了 BeanFactory
的全部功能。
/**
* Tell the subclass to refresh the internal bean factory.
* @return the fresh BeanFactory instance
* @see #refreshBeanFactory()
* @see #getBeanFactory()
*/
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
// 初始化BeanFactory,並進行XML文件讀取,將得到的BeanFactory記錄到當前實體的屬性中
refreshBeanFactory();
// 返回當前實體的BeanFactory屬性
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
/**
* This implementation performs an actual refresh of this context's underlying
* bean factory, shutting down the previous bean factory (if any) and
* initializing a fresh bean factory for the next phase of the context's lifecycle.
*/
// 此方法爲核心實現
@Override
protected final void refreshBeanFactory() throws BeansException {
// 關閉已經存在的beanFactory
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
// 創建DefaultListableBeanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
// 爲了序列化指定id,如果需要的話讓其從id反序列化爲BeanFactory對象
beanFactory.setSerializationId(getId());
// 定製beanFactory,設置相關屬性,包括是否允許覆蓋同名稱不同定義的對象以及循環依賴,以及設置@Autowired和@Qualifier註解解析器 QualifierAnnotationAutowireCandidateResolver
customizeBeanFactory(beanFactory);
// 初始化DocumentReader,並進行XML文件讀取及解析
loadBeanDefinitions(beanFactory);
// 使用全局變量記錄BeanFactroy類實例
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
定製 BeanFactory
上面已經提到這裏開始了對 BeanFactory 的擴展,再基本容器的基礎上,增加了是否允許覆蓋是否允許擴展的設置並提供了註解 @Qualifier 和 @Autowired 的支持。
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
// 如果allowBeanDefinitionOverriding 屬性不爲空,設置給beanFactory相應的屬性,此屬性的含義:是否允許覆蓋同名稱的不同定義的對象
if (this.allowBeanDefinitionOverriding != null) {
beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// 此屬性的含義:是否允許bean之間存在循環依賴
if (this.allowCircularReferences != null) {
beanFactory.setAllowCircularReferences(this.allowCircularReferences);
}
}
看了代碼,我們可以得知對於上面是否允許覆蓋的兩個屬性只是判斷了是否爲空,如果不爲空要進行設置,但是並沒有看到在哪裏設置,究竟這個設置是在哪裏呢?其實還是和上面一樣,提現了Spring的高度擴展性,使用子類覆蓋方法,例如:
@Override
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
super.setAllowBeanDefinitionOverriding(false);
super.setAllowCircularReferences(false);
super.customizeBeanFactory(beanFactory);
}
加載 BeanDefinition
再上一個步驟,我們看到已經初始化了 DefaultListableFactory
, 這是實現配置文件加載的第一步,還需要XmlBeanDefinitionReader
來讀取XML。那麼接下來的步驟首先要做的就是初始化 XmlBeanDefinitionReader
。
/**
* Loads the bean definitions via an XmlBeanDefinitionReader.
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
* @see #initBeanDefinitionReader
* @see #loadBeanDefinitions
*/
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
// 爲指定beanFactory創建XmlBeanDefinitionReader
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
// 爲beanDefinitionReader進行環境變量的設置
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
// 對beanDefinitionReader進行設置,可以覆蓋
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
在初始化了 DefaultListableBeanFactory
和 XmlBeanDefinitionReader
後就可以進行配置文件的讀取了。
/**
* Load the bean definitions with the given XmlBeanDefinitionReader.
* <p>The lifecycle of the bean factory is handled by the {@link #refreshBeanFactory}
* method; hence this method is just supposed to load and/or register bean definitions.
* @param reader the XmlBeanDefinitionReader to use
* @throws BeansException in case of bean registration errors
* @throws IOException if the required XML document isn't found
* @see #refreshBeanFactory
* @see #getConfigLocations
* @see #getResources
* @see #getResourcePatternResolver
*/
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}
使用 XmlBeanDefinitionReader
的 loadBeanDefinitions
方法進行配置文件的加載註冊。因爲 XmlBeanDefinitionReader
在創建時已經將初始化好的 DefaultListableBeanFactory
註冊進去了,所以XmlBeanDefinitionReader
所讀取的 BeanDefinitionHolder
都會註冊到 DefaultListableBeanFactory
中,也就是經過此步驟,類型 DefaultListableBeanFactory
的實例變量 beanFactory 已經包含了所有解析好的配置。
功能擴展
進入函數 prepareBeanFactory 之前,Spring 已經完成了對配置的解析,而 ApplicationContext 在功能上的擴展就在此展開。
/**
* Configure the factory's standard context characteristics,
* such as the context's ClassLoader and post-processors.
* @param beanFactory the BeanFactory to configure
*/
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// 設置beanFactory的classloader爲當前context的classLoader
beanFactory.setBeanClassLoader(getClassLoader());
// 設置beanFactory的表達式語言處理器,spring3開始增加了表達式語言的支持,默認可以使用#{bean.xxx}的形式調用相關屬性值。
beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
// 爲beanFactory增加一個默認的propertyEditor,這個主要是對bean的屬性設置等管理的一個工具
beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));
// 添加beanPostProcessor
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
// 設置幾個忽略自動裝配的接口
beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);
// BeanFactory interface not registered as resolvable type in a plain factory.
// MessageSource registered (and found for autowiring) as a bean.
// 設置幾個自動裝配的特殊規則
beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
beanFactory.registerResolvableDependency(ResourceLoader.class, this);
beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
beanFactory.registerResolvableDependency(ApplicationContext.class, this);
// Register early post-processor for detecting inner beans as ApplicationListeners.
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));
// Detect a LoadTimeWeaver and prepare for weaving, if found.
// 增加對AspectJ的支持
if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
// Set a temporary ClassLoader for type matching.
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}
// 添加默認的系統環境bean
if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
}
if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
}
if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
}
}
上面函數中主要進行了幾個方面的擴展:
- 增加對SPEL語言的支持
- 增加對屬性編輯器的支持;
- 增加對一些內置類,比如 EnvironmentAware、MessageSourceAware 的信息注入。
- 設置了依賴功能可忽略的接口;
- 註冊一些固定依賴的屬性;
- 增加AspectJ的支持;
- 將相關環境變量以及系統屬性以單例模式註冊;
上面我們總結了這一階段的幾個步驟,但是對於具體含義可能並不理解,下面將對各個步驟進行分析;
增加SPEL語言支持
Spring 表達式語言全稱爲“Spring Expression Language”,縮寫爲"SPEL",能在運行時構建複雜表達式、存取對象屬性、對象方法調用等,並且能與Spring功能完美整合,比如能用來配置bean定義。SPEL是單獨模塊,只依賴於core 模塊,不依賴於其他模塊,可以單獨使用。SpEL 使用#{...} 作爲界定符。這裏只是爲了喚醒大家的記憶幫助我們來理解源碼,有興趣的可以進一步深入研究。
在源碼中通過代碼 beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()))
註冊語言解析器,就可以對SPEL進行解析了,那麼註冊解析器後Spring又是在什麼時候調用解析器的呢?
Spring 在 bean進行初始化的時候會有屬性填充的一步,而這一步會調用 AbstractAutowireCapableBeanFactory
類的 applyPropertyValues
函數來完成功能。而就在這個函數中,會通過構造 BeanDefinitionValueResolver
類型實例valueResolver 來進行屬性值的解析。同時,也就是這個步驟中一般通過AbstractBeanFactory 中的evaluateBeanDefinitionString 方法去完成SPEL的解析。
/**
* Evaluate the given String as contained in a bean definition,
* potentially resolving it as an expression.
* @param value the value to check
* @param beanDefinition the bean definition that the value comes from
* @return the resolved value
* @see #setBeanExpressionResolver
*/
protected Object evaluateBeanDefinitionString(String value, BeanDefinition beanDefinition) {
if (this.beanExpressionResolver == null) {
return value;
}
Scope scope = (beanDefinition != null ? getRegisteredScope(beanDefinition.getScope()) : null);
return this.beanExpressionResolver.evaluate(value, new BeanExpressionContext(this, scope));
}
當調用這個方法的時候,會判斷是否存在語言解析器,如果存在則調用解析器的方法進行解析,解析的過程是在Spring的epression 的包內,這裏不做過多解釋。我們通過對 evaluateBeanDefinitionString 方法的調用層次可以看出,應用語言解析器的調用主要是在解析依賴注入bean的時候,以及在完成bean的初始化和屬性獲取後進行屬性填充的時候。
增加屬性註冊編輯器
在Spring DI的時候可以把普通屬性注入進來,但是像Date類型就無法被識別,例如:
public class UserManager {
private Date dateValue;
@Override
public String toString() {
return "UserManager{" +
"dateValue=" + dateValue +
'}';
}
public Date getDateValue() {
return dateValue;
}
public void setDateValue(Date dateValue) {
this.dateValue = dateValue;
}
// 上面代碼中需要對日期進行屬性注入
<bean id="userManager" class="com.ccgogoing.domain.UserManager">
<property name="dateValue">
<value>2019-01-27</value>
</property>
</bean>
// 測試代碼
@Test
public void testDate() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
UserManager userManager = (UserManager) applicationContext.getBean("userManager");
System.out.println(userManager);
}
如果直接這樣使用,程序會報錯,類型轉換失敗。因爲在UserManager中的dateValue 屬性是Date類型的,而在XML中配置的確實String類型的,因此當然會報錯。
Spring針對此問題提供了兩種解決辦法:
-
使用自定義屬性編輯器
(1) 編寫自定義的屬性編輯器
public class DatePropertyEditor extends PropertyEditorSupport {
private String format = "yyyy-MM-dd";
public void setFormat (String format) {
this.format = format;
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
System.out.println("text = " + text);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
try {
Date d = simpleDateFormat.parse(text);
this.setValue(d);
} catch (ParseException e) {
e.printStackTrace();
}
super.setAsText(text);
}
}
(2) 將自定義屬性編輯器註冊到Spring中
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="java.util.Date">
<bean class="com.ccgogoing.extend.DatePropertyEditor">
<property name="format" value="yyyy-MM-dd"/>
</bean>
</entry>
</map>
</property>
</bean>
在配置文件中引入類型爲 org.springframework.beans.factory.config.CustomEditorConfigurer
的bean,並在屬性 customEditors 中加入自定義的屬性編輯器,其中key 爲屬性編輯器所對應的類型。 通過這樣的配置,當spring 注入bean 的屬性時,一旦碰到Date類型就會調用自定義的DatePropertyEditor解析器進行解析,並用解析結果代替配置屬性進行注入。
-
註冊Spring自帶的屬性編輯器CustomDateEditor
具體代碼如下:
// 定義屬性編輯器 public class DatePropertyEditorRegistrar implements PropertyEditorRegistrar { @Override public void registerCustomEditors(PropertyEditorRegistry registry) { registry.registerCustomEditor(Date.class,new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"),true)); } } // 註冊到Spring中 <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <property name="propertyEditorRegistrars"> <list> <bean class="com.ccgogoing.extend.DatePropertyEditorRegistrar"/> </list> </property> </bean>
我們瞭解了自定義屬性編輯器的作用,但是與 beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()))
並無聯繫,因爲在註冊自定義屬性編輯器的時候使用的是 PropertyEditorRegistry
的 registerCustomEditor 方法,而這裏使用的是 ConfigurableListableBeanFactory
的 addPropertyEditorRegistrar 方法。 我們不妨深入探索一下 ResourceEditorRegistrar
的內部實現,在 ResourceEditorRegistrar
中,我們最關心的方法是 registerCustomEditors
.
/**
* Populate the given {@code registry} with the following resource editors:
* ResourceEditor, InputStreamEditor, InputSourceEditor, FileEditor, URLEditor,
* URIEditor, ClassEditor, ClassArrayEditor.
* <p>If this registrar has been configured with a {@link ResourcePatternResolver},
* a ResourceArrayPropertyEditor will be registered as well.
* @see org.springframework.core.io.ResourceEditor
* @see org.springframework.beans.propertyeditors.InputStreamEditor
* @see org.springframework.beans.propertyeditors.InputSourceEditor
* @see org.springframework.beans.propertyeditors.FileEditor
* @see org.springframework.beans.propertyeditors.URLEditor
* @see org.springframework.beans.propertyeditors.URIEditor
* @see org.springframework.beans.propertyeditors.ClassEditor
* @see org.springframework.beans.propertyeditors.ClassArrayEditor
* @see org.springframework.core.io.support.ResourceArrayPropertyEditor
*/
@Override
public void registerCustomEditors(PropertyEditorRegistry registry) {
ResourceEditor baseEditor = new ResourceEditor(this.resourceLoader, this.propertyResolver);
doRegisterEditor(registry, Resource.class, baseEditor);
doRegisterEditor(registry, ContextResource.class, baseEditor);
doRegisterEditor(registry, InputStream.class, new InputStreamEditor(baseEditor));
doRegisterEditor(registry, InputSource.class, new InputSourceEditor(baseEditor));
doRegisterEditor(registry, File.class, new FileEditor(baseEditor));
if (pathClass != null) {
doRegisterEditor(registry, pathClass, new PathEditor(baseEditor));
}
doRegisterEditor(registry, Reader.class, new ReaderEditor(baseEditor));
doRegisterEditor(registry, URL.class, new URLEditor(baseEditor));
ClassLoader classLoader = this.resourceLoader.getClassLoader();
doRegisterEditor(registry, URI.class, new URIEditor(classLoader));
doRegisterEditor(registry, Class.class, new ClassEditor(classLoader));
doRegisterEditor(registry, Class[].class, new ClassArrayEditor(classLoader));
if (this.resourceLoader instanceof ResourcePatternResolver) {
doRegisterEditor(registry, Resource[].class,
new ResourceArrayPropertyEditor((ResourcePatternResolver) this.resourceLoader, this.propertyResolver));
}
}
/**
* Override default editor, if possible (since that's what we really mean to do here);
* otherwise register as a custom editor.
*/
private void doRegisterEditor(PropertyEditorRegistry registry, Class<?> requiredType, PropertyEditor editor) {
if (registry instanceof PropertyEditorRegistrySupport) {
((PropertyEditorRegistrySupport) registry).overrideDefaultEditor(requiredType, editor);
}
else {
registry.registerCustomEditor(requiredType, editor);
}
}
在doRegisterEditor
方法中,可以看到自定義屬性編譯器中使用的關鍵代碼 registry.registerCustomEditor(requiredType, editor)
; 回過頭來看,其實 ResourceEditorRegistrar
類其實無非就是註冊了一系列的常用類型的屬性編輯器。例如,代碼 doRegisterEditor(registry, Class.class, new ClassEditor(classLoader))
實現的功能就是註冊Class類對應的屬性編輯器。那麼,註冊之後,一旦某個實體bean中存在一些Class類型的屬性,那麼Spring會調用ClassEditor將配置中定義的String類型轉換爲Class類型並進行賦值。
分析到這裏,依然有個疑問那就是 beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()))
僅僅是註冊了 ResourceEditorRegistrar 實例,但是卻沒有調用 registerCustomEditors
進行註冊 ,那麼到底什麼時候進行這些屬性編輯器的註冊呢?進一步我們查看ResourceEditorRegistrar#registerCustomEditors 的調用棧:
可以看到一個比較熟悉的方法在調用,那就是 AbstractBeanFactory#initBeanWrapper
,這是在bean 初始化時使用的一個方法,主要是將BeanDefinition 轉換爲BeanWrapper 後用於對屬性的填充。到此邏輯已經明瞭,在bean的初始化後,會調用 ResourceEditorRegistrar#registerCustomEditors
方法進行批量通用屬性編輯器註冊。註冊後,在屬性填充時,Spring便可以使用這些屬性編輯器進行屬性的解析注入了。
既然提到了BeanWrapper, 這裏需要強調下,Spring中用於封裝Bean的是BeanWrapper類型,而它又間接繼承了 PropertyEditorRegistry ,也就是我們上面自定義的屬性編輯器註冊時的方法參數 PropertyEditorRegistry registry ,其實大部分情況下都是BeanWrapper,對於BeanWrapper 在 Spring 中的默認實現爲 BeanWrapperImpl
, 而 BeanWrapperImpl 除了實現 BeanWrapper 接口外還間接繼承了 PropertyEditorRegistrySupport
,在 PropertyEditorRegistrySupport 中有這樣一個方法:
/**
* Actually register the default editors for this registry instance.
*/
private void createDefaultEditors() {
this.defaultEditors = new HashMap<Class<?>, PropertyEditor>(64);
// Simple editors, without parameterization capabilities.
// The JDK does not contain a default editor for any of these target types.
this.defaultEditors.put(Charset.class, new CharsetEditor());
this.defaultEditors.put(Class.class, new ClassEditor());
this.defaultEditors.put(Class[].class, new ClassArrayEditor());
this.defaultEditors.put(Currency.class, new CurrencyEditor());
this.defaultEditors.put(File.class, new FileEditor());
this.defaultEditors.put(InputStream.class, new InputStreamEditor());
this.defaultEditors.put(InputSource.class, new InputSourceEditor());
this.defaultEditors.put(Locale.class, new LocaleEditor());
if (pathClass != null) {
this.defaultEditors.put(pathClass, new PathEditor());
}
this.defaultEditors.put(Pattern.class, new PatternEditor());
this.defaultEditors.put(Properties.class, new PropertiesEditor());
this.defaultEditors.put(Reader.class, new ReaderEditor());
this.defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor());
this.defaultEditors.put(TimeZone.class, new TimeZoneEditor());
this.defaultEditors.put(URI.class, new URIEditor());
this.defaultEditors.put(URL.class, new URLEditor());
this.defaultEditors.put(UUID.class, new UUIDEditor());
if (zoneIdClass != null) {
this.defaultEditors.put(zoneIdClass, new ZoneIdEditor());
}
// Default instances of collection editors.
// Can be overridden by registering custom instances of those as custom editors.
this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class));
this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class));
this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class));
this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class));
this.defaultEditors.put(SortedMap.class, new CustomMapEditor(SortedMap.class));
// Default editors for primitive arrays.
this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor());
this.defaultEditors.put(char[].class, new CharArrayPropertyEditor());
// The JDK does not contain a default editor for char!
this.defaultEditors.put(char.class, new CharacterEditor(false));
this.defaultEditors.put(Character.class, new CharacterEditor(true));
// Spring's CustomBooleanEditor accepts more flag values than the JDK's default editor.
this.defaultEditors.put(boolean.class, new CustomBooleanEditor(false));
this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true));
// The JDK does not contain default editors for number wrapper types!
// Override JDK primitive number editors with our own CustomNumberEditor.
this.defaultEditors.put(byte.class, new CustomNumberEditor(Byte.class, false));
this.defaultEditors.put(Byte.class, new CustomNumberEditor(Byte.class, true));
this.defaultEditors.put(short.class, new CustomNumberEditor(Short.class, false));
this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, true));
this.defaultEditors.put(int.class, new CustomNumberEditor(Integer.class, false));
this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true));
this.defaultEditors.put(long.class, new CustomNumberEditor(Long.class, false));
this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true));
this.defaultEditors.put(float.class, new CustomNumberEditor(Float.class, false));
this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true));
this.defaultEditors.put(double.class, new CustomNumberEditor(Double.class, false));
this.defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, true));
this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, true));
this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, true));
// Only register config value editors if explicitly requested.
if (this.configValueEditorsActive) {
StringArrayPropertyEditor sae = new StringArrayPropertyEditor();
this.defaultEditors.put(String[].class, sae);
this.defaultEditors.put(short[].class, sae);
this.defaultEditors.put(int[].class, sae);
this.defaultEditors.put(long[].class, sae);
}
}
通過這個方法我們已經知道了在Spring中定義了一系列常用的屬性編輯器使我們可以方便的進行配置。 如果我們定義的bean中有某個屬性不在上面的類型中,我們才需要進行個性化的屬性編輯器的註冊。
添加 ApplicationContextAwareProcessor 處理器
繼續跟蹤 org.springframework.context.support.AbstractApplicationContext#prepareBeanFactory
方法的主線,接下來的 beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this))
其實就是註冊個BeanPostProcessor,而真正的邏輯還是在 ApplicationContextAwareProcessor 中。
ApplicationContextAwareProcessor 實現了 BeanPostProcessor 接口,在這裏需要講一下,在bean實例化的時候,也就是Spring 激活bean 的 init-method 前後,會調用BeanPostProcessor 的 postProcessBeforeInitialization 和 postProcessAfterInitialization 方法。同樣對於 ApplicationContextAwareProcessor 我們同樣關注於這兩個方法。
在 postProcessAfterInitialization 方法中並沒有做什麼邏輯處理。
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
那麼,我們着重看下 postProcessBeforeInitialization
@Override
public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
AccessControlContext acc = null;
if (System.getSecurityManager() != null &&
(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||
bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||
bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)) {
acc = this.applicationContext.getBeanFactory().getAccessControlContext();
}
if (acc != null) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
invokeAwareInterfaces(bean);
return null;
}
}, acc);
}
else {
invokeAwareInterfaces(bean);
}
return bean;
}
private void invokeAwareInterfaces(Object bean) {
if (bean instanceof Aware) {
if (bean instanceof EnvironmentAware) {
((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
}
if (bean instanceof EmbeddedValueResolverAware) {
((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
}
if (bean instanceof ResourceLoaderAware) {
((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
}
if (bean instanceof ApplicationEventPublisherAware) {
((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
}
if (bean instanceof MessageSourceAware) {
((MessageSourceAware) bean).setMessageSource(this.applicationContext);
}
if (bean instanceof ApplicationContextAware) {
((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
}
}
}
postProcessBeforeInitialization 方法中調用了 invokeAwareInterfaces。 從invokeAwareInterfaces 方法中,我們或多或少已經瞭解了Spring 的用意,實現這些Aware接口的bean在被初始化之後,可以取得一些對應的資源。
設置忽略依賴
當 Spring 上一步將 ApplicationContextAwareProcessor註冊後,那麼在 invokeAwareInterfaces 方法中間接調用的 Aware 類 已經不是普通的bean了,如 MessageSourceAware 、ResourceLoaderAware 等,那麼當前需要在 Spring 做bean的依賴注入的時候忽略它們,而 ignoreDependencyInterface 的作用正是如此。
// 設置幾個忽略自動裝配的接口
beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);
註冊依賴
Spring 中有了忽略依賴的功能,必不可少的也會有註冊依賴的功能。
beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
beanFactory.registerResolvableDependency(ResourceLoader.class, this);
beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
beanFactory.registerResolvableDependency(ApplicationContext.class, this);
當註冊了依賴解析後,例如當註冊了對BeanFactory.class 的解析依賴後,當bean的屬性注入的時候,一旦檢測到屬性爲BeanFactory類型將會把beanFactory的實例注入進去。
由於Spring容器啓動這篇文章篇幅較長,本篇暫時介紹到這裏,下一篇文章會接着分析BeanFactory的後處理、初始化非延遲加載單例、finishRefresh等方法的具體細節。
請務必點擊閱讀此文的下篇 《Spring容器基本實現之源碼分析-下篇 傳送門》 ,看完後必定會對 Spring 的整體把控會有更深一步的理解。