Spring源碼分析(一)基本介紹
摘要:本文結合《Spring源碼深度解析》來分析Spring 5.0.6版本的源代碼。若有描述錯誤之處,歡迎指正。
前言
作為一名開發人員,閱讀源碼是一個很好的學習方式。本文將結合《Spring源碼深度解析》來分析Spring 5.0.6版本的源代碼,若有描述錯誤之處,歡迎指正。
Spring是2003年興起的一個輕量級Java開源框架,旨在解決企業應用開發的復雜性。Spring發展至今,衍生出非常豐富的模塊,並應用在多種場景,比如:桌面應用,Web應用等。Spring的模塊化可以允許你只使用需要的模塊,而不必全部引入。
目錄
一、整體架構
1. 核心容器
2. 數據訪問/集成
3. Web
4. AOP
5. Test
二、設計理念
三、使用場景
1. 典型的Spring web應用程序
2. Spring中間層使用第三方web框架
3. 遠程調用
4. EJBs-包裝現存POJOs
一、整體架構
Spring框架是一個分層架構,他包含一系列的功能要素,並被分為大約20個模塊,如下圖所示(很遺憾,並沒有找到Spring5的架構圖,下圖是Spring4的,但結合Spring5的源碼來看,該圖還是能夠體現Spring5的核心模塊)
這些模塊被總結為以下幾部分。
1. 核心容器
Core Container(核心容器)包含有Core、Beans、Context和Expression Language模塊。Core和Beans模塊是框架的基礎部分,提供IoC(控制反轉)和DI(依賴注入)特性。這里的基礎概念是BeanFactory,它提供對Factory模式的經典實現來消除對程序性單例模式的需要,並真正地允許你從程序邏輯中分離出依賴關系和配置。
- Core模塊主要包含Spring框架基本的核心工具類,Spring的其他組件都要使用到這個包里的類,Core模塊是其他組件的基本核心。當然你也可以在自己的應用系統中使用這些工具類。
- Beans模塊是所有應用都要用到的,它包含訪問配置文件、創建和管理Bean以及進行Inversion of Control/Dependency Injection(IoC/DI)操作相關的所有類。
- Context模塊構建於Core和Beans模塊基礎之上,提供了一種類似於JNDI注冊器的框架式的對象訪問方法。Context模塊繼承了Beans的特性,為Spring核心提供了大量擴展,添加了對國際化(例如資源綁定)、事件傳播、資源加載和對Context的透明創建的支持。Context同時也支持J2EE的一些特性,例如EJB、JMX和基礎的遠程處理。ApplicationContext接口是Context模塊的關鍵。
- Expression Language 模塊提供了一個強大的表達式語言用於在運行時查詢和操縱對象。它是JSP2.1規范中定義的unifed expression language的一個擴展。該語言支持設置/獲取屬性的值,屬性的分配,方法的調用,訪問數組上下文(accession the context of arrays),容器和索引器,邏輯和算數運算符,命名變量以及從Spring的IoC容器中根據名稱檢索對象。它也支持list投影,選擇和一般的list聚合。
2. 數據訪問/集成
Data Access/Integration(數據訪問/集成)層包含有JDBC、ORM、OXM、JMS和Transaction模塊,其中:
- JDBC模塊提供了一個JDBC抽象層,它可以消除冗長的JDBC編碼和解析數據庫廠商特有的錯誤代碼。這個模塊包含了Spring對JDBC數據訪問進行封裝的所有類。
- ORM(Object Relational Mapping對象關系映射)模塊為流行的對象-關系映射API,如JPA、JDO、Hibernate、iBatis等,提供了一個交互層。利用ORM封裝包,可以混合使用所有Spring提供的特性進行O/R映射。如前邊提到的簡單聲明性事務管理。
Spring框架插入了若干個ORM框架,從而提供了ORM的對象關系工具,其中包括JDO、Hibernate和iBatisSQL Map。所有這些都遵從Spring的通用事務和DAO異常層次結構。
- OXM模塊提供了一個對Object/XML映射實現的抽象層,Object/XML映射實現包括JAXB、Castor、XMLBeans、JiBX和XStream。
- JMS(Java Messaging Service)模塊主要包含了一些制造和消費消息的特性。
- Transaction模塊支持編程和聲明性的事務管理,這些事務類必須實現特定的接口,並且對所有的POJO都適用。
3. Web
Web上下文模塊建立在應用程序上下文模塊之上,為基於Web的應用程序提供了上下文。所以Spring框架支持與Jakarta Struts的集成。Web模塊還簡化了處理多部分請求以及將請求參數綁定到域對象的工作。Web層包含了Web、Web-Servlet、Web-Struts和Web-Porlet模塊,具體說明如下。
- Web模塊提供了基礎的面向Web的集成特性。例如,多文件上傳,使用servlet listeners初始化IoC容器以及一個面向Web的應用上下文。它還包含Spring遠程支持中Web的相關部分。
- Web-Servlet模塊(web.servlet.jar)包含Spring的model-view-controller(MVC)的實現。Spring的MVC框架使得模型范圍內的代碼和web forms之間能夠清楚地分離開來,並與Spring框架的其他特性集成在一起。
- Web-Struts模塊提供了對Struts的支持,使得類在Spring應用中能夠與一個典型的Struts Web層集成在一起,注意,該支持在Spring3.0中是deprecated的。
- Web-Porlet模塊提供了用於Portlet環境和Web-Servlet模塊的MVC的實現。
4. AOP
AOP模塊提供了一個符合AOP聯盟標准的面向切面編程的實現,它讓你可以定義例如方法攔截器和切點。從而將邏輯代碼分開,降低它們之間的耦合性。利用source-level的元數據功能,還可以將各種行為信息合並到你的代碼中,這有點像.Net技術中的attribute概念。
通過配置管理特性,SpringAOP模塊直接將面向切面的編程功能集成到了Spring框架中,所以可以很容易地使Spring框架管理的任何對象支持AOP。Spring AOP模塊為基於Spring的應用程序中的對象提供了事務管理服務。通過使用Spring AOP,不用依賴EJB組件,就可以將聲明性事務管理集成到應用程序中。
- Aspects模塊提供了對AspectJ(一個面向切面的框架,它擴展了Java語言)的集成支持。
- Instrumentation模塊提供了class instrumentation 支持和classloader實現,使得可以在特定的應用服務器上使用。
5. Test
Test模塊支持使用JUnit和TestNG對Spring組件進行測試。
二、設計理念
Spring是面向Bean的編程(BOP:Bean Oriented Programming),Bean在Spring中才是真正的主角。Bean在Spring中作用就像Object對OOP的意義一樣,沒有對象的概念就像沒有面向對象編程,Spring中沒有Bean也就沒有Spring存在的意義。Spring提供了IoC 容器通過配置文件或者注解的方式來管理對象之間的依賴關系。
控制反轉(Inversion of Control,縮寫為IoC),是面向對象編程中的一種設計原則,可以用來減低計算機代碼之間的耦合度。其中最常見的方式叫做依賴注入(Dependency Injection,簡稱DI),還有一種方式叫“依賴查找”(Dependency Lookup)。通過控制反轉,對象在被創建的時候,由一個調控系統內所有對象的外界實體,將其所依賴的對象的引用傳遞給它。也可以說,依賴被注入到對象中。三、使用場景
前面描述的模塊使得Spring成為許多場景中的合理選擇,從在資源受限設備上運行的嵌入式應用程序到使用Spring事務管理功能和Web框架集成的全面的企業應用程序。
1. 典型的Spring web應用程序
2. Spring中間層使用第三方web框架
有時情況並不允許你完全切換到一個不同的框架。Spring框架不是一個要么使用全部特性要么什么都用不了的解決方案,不強制使用其中的每個功能。現存的前端如Struts,Tapestry,JSF或其他UI框架都可以同基於Spring的中間層整合在一起,從而使你能夠使用Spring事務功能。你只需要使用ApplicationContext連接你的業務邏輯以及通過WebApplicationContext整合你的web層。
3. 遠程調用
你可以使用Spring的Hessian-,Rmi-或HttpInvokerProxyFactoryBean類來通過web服務訪問現存的代碼。遠程訪問現存應用程序並不困難。
4. EJBs-包裝現存POJOs
Spring框架還為企業JavaBeans提供了一個訪問抽象層,使你能夠重用現有的POJO,並將其包裝在無狀態會話bean中,以便在可能需要聲名式安全的可擴展,故障安全的web應用程序中使用。
Spring源碼分析(二)容器基本用法
摘要:本文結合《Spring源碼深度解析》來分析Spring 5.0.6版本的源代碼。若有描述錯誤之處,歡迎指正。
在正式分析Spring源碼之前,我們有必要先來回顧一下Spring中最簡單的用法。盡管我相信您已經對這個例子非常熟悉了。
Bean是Spring中最核心的概念,因為Spring就像是個大水桶,而Bean就像是水桶中的水,水桶脫離了水也就沒什么用處了,那么我們先看看Bean的定義。
public class MySpringBean { private String str = "mySpringBean"; public String getStr() { return str; } public void setStr(String str) { this.str = str; } }
很普通,Bean沒有任何特別之處。的確,Spring的目的就是讓我們的Bean能成為一個純粹的POJO,這也是Spring所追求的。接下來看看配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="mySpringBean" class="org.cellphone.uc.MySpringBean"/> </beans>
在上面的配置中我們看到了Bean的聲明方式,接下來看測試代碼:
public class BeanFactoryTest { @Test public void testSimpleLoad() { BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring/spring-test.xml")); MySpringBean bean = (MySpringBean) beanFactory.getBean("mySpringBean"); Assert.assertEquals("testSimpleLoad", "mySpringBean", bean.getStr()); } }
XmlBeanFactory從Spring 3.1版本開始就被廢棄了,但源碼中未說明廢棄的原因......
直接使用BeanFactory作為容器對於Spring的使用來說並不多見,因為在企業級的應用中大多數都會使用ApplicationContext(后續再介紹兩者之間的差異),這里只是用於測試,讓讀者更快更好地分析Spring的內部原理。
通過上面一行簡單的代碼就拿到了MySpringBean實例,但這行代碼在Spring中卻執行了非常多的邏輯。接下來就來深入分析BeanFactory.getBean方法的實現原理。
Spring源碼分析(三)容器核心類
摘要:本文結合《Spring源碼深度解析》來分析Spring 5.0.6版本的源代碼。若有描述錯誤之處,歡迎指正。
在上一篇文章中,我們熟悉了容器的基本用法。在這一篇,我們開始分析Spring的源碼。但是在正式開始熟悉源碼之前,有必要了解一下Spring中最核心的兩個類。
1. DefaultListableBeanFactory
XmlBeanFactory繼承自DefaultListableBeanFactory,而DefaultListableBeanFactory是整個bean加載的核心部分,是Spring注冊及加載bean的默認實現,而對於XmlBeanFactory與DefaultListableBeanFactory不同的地方其實是在XmlBeanFactory中使用了自定義的XML讀取器XmlBeanDefinitionReader,實現了個性化的BeanDefinitionReader讀取,DefaultListableBeanFactory繼承了AbstractAutowireCapableBeanFactory並實現了ConfigurableListableBeanFactory以及BeanDefinitionRegistry接口。以下是DefaultListableBeanFactory的類圖:
從上面的類圖中,我們可以清晰地從全局角度了解DefaultListableBeanFactory的脈絡。接下來先了解一下上面類圖中各個類的作用。
AliasRegistry | 定義對alias的簡單增刪改查等操作 |
SimpleAliasRegistry | 主要使用map作為alias的緩存,並對接口AliasRegistry進行實現 |
SingletonBeanRegistry | 定義對單例的注冊及獲取 |
BeanFactory | 定義獲取bean及bean的各種屬性 |
DefaultSingletonBeanFactory | 對接口SingletonBeanRegistry各函數的實現 |
HierarchicalBeanFactory | 繼承BeanFactory,也就是在BeanFactory定義的功能的基礎上增加了對parentFactory的支持 |
BeanDefinitionRegistry | 定義對BeanDefinition的各種增刪改操作 |
FactoryBeanRegistrySupport | 在DefaultSingletonBeanRegistry的基礎上增加了對FactoryBean的特殊處理功能 |
ConfigurableBeanFactory | 提供配置Factory的各種方法 |
ListableBeanFactory | 根據各種條件獲取bean的配置清單 |
AbstractBeanFactory | 綜合FactoryBeanRegistrySupport和ConfigurableBeanFactory的功能 |
AutowireCapableBeanFactory | 提供創建bean、自動注入,初始化以及應用bean的后處理器 |
AbstractAutowireCapableBeanFactory | 綜合AbstractBeanFactory並對接口AutowireCapableBeanFactory進行實現 |
ConfigurableListableBeanFactory | BeanFactory配置清單,指定忽略類型及接口等 |
DefaultListableBeanFactory | 綜合上面所有功能,主要是對Bean注冊后的處理 |
XmlBeanFactory對DefaultListableBeanFactory進行了擴展,主要用於從XML文檔中讀取BeanDefinition,對於注冊及獲取Bean都是使用從父類DefaultListableBeanFactory繼承的方法去實現,而唯獨與父類不同的個性化實現就是增加了XmlBeanDefinitionReader類型的reader屬性。在XmlBeanFactory中主要使用reader屬性對資源文件進行讀取和注冊。
2. XmlBeanDefinitionReader
XML配置文件的讀取時Spring的重要功能,因為Spring的大部分功能都是以配置作為切入點的,那么我們可以從XmlBeanDefinitionReader中梳理一下資源文件讀取、解析及注冊的大致脈絡。首先我們看看各個類的功能。
ResourceLoader | 定義資源加載器,主要應用於根據給定的資源文件地址返回對應的Resource |
BeanDefinitionReader | 主要定義資源文件讀取並轉換為BeanDefinition的各個功能 |
EnvironmentCapable | 定義獲取Environment方法 |
DocumentLoader | 定義從資源文件加載到轉換為Document的功能 |
AbstractBeanDefinitionReader | 對EnvironmentCapable、BeanDefinitionReader類定義的功能進行實現 |
BeanDefinitionDocumentReader | 定義讀取Document並注冊BeanDefiniton功能 |
BeanDefinitionParserDelegate | 定義解析Element的各種方法 |
通過以上分析,我們可以梳理出整個XML配置文件讀取的大致流程,如下圖所示:
在XmlBeanDifinitonReader中主要包含以下幾個步驟的處理:
1)通過繼承自AbstractBeanDefinitionReader中的方法,來使用ResourceLoader將資源文件路徑轉換為對應的Resource文件。
2)通過DocumentLoader對Resource文件進行轉換,將Resource文件轉換為Document文件。
3)通過實現接口BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader類對Document進行解析,並使用BeanDefinitionParserDelegate對Element進行解析。
Spring源碼分析(四)容器的基礎XmlBeanFactory
摘要:本文結合《Spring源碼深度解析》來分析Spring 5.0.6版本的源代碼。若有描述錯誤之處,歡迎指正。
經過Spring源碼分析(二)容器基本用法和Spring源碼分析(三)容器核心類兩篇文章,我們已經對Spring的容器功能有了一個大致的了解,盡管你可能還很迷糊,但是不要緊,接下來我們會詳細探索每個步驟的實現。首先要深入分析的是以下功能的代碼實現:
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring/spring-test.xml"));
通過XmlBeanFactory初始化時序圖,我們看下上面代碼的執行邏輯:
時序圖從BeanFactoryTest測試類開始,通過時序圖我們可以一目了然地看到整個邏輯處理順序。先調用了ClassPathResource的構造函數來構造Resource資源文件的實例對象,后續的資源處理就可以用Resource提供的各種服務來操作了,當我們有了Resource后就可以進行XmlBeanFactory的初始化了。那么Resource文件是如何封裝的呢?
1. 配置文件封裝
Spring的配置文件讀取是通過ClassPathResource進行封裝的,如new ClassPathResource("spring/spring-test.xml"),那么ClassPathResource完成了什么功能呢?
在Java中,將不同來源的資源抽象成URL,通過注冊不同的handler(URLStreamHander)來處理不同來源的資源的讀取邏輯,一般handler的類型使用不同的前綴(協議,Protocol)來識別,如“file:”、"http:"、"jar:"等,然而URL沒有默認定義相對Classpath或ServletContext等資源的handler,雖然可以注冊自己的URLStreamHandler來解析特定的URL前綴(協議),比如“classpath:”,然而這需要了解URL的實現機制,而且URL也沒有提供一些基本的方法,如檢查當前資源是否存在、檢查當前資源是否可讀等方法。因而Spring對其內部使用到的資源實現了自己的抽象結構:Resource接口來封裝底層資源。
public interface InputStreamSource { InputStream getInputStream() throws IOException; }
public interface Resource extends InputStreamSource { boolean exists(); default boolean isReadable() { return true; } default boolean isOpen() { return false; } default boolean isFile() { return false; } URL getURL() throws IOException; URI getURI() throws IOException; File getFile() throws IOException; default ReadableByteChannel readableChannel() throws IOException { return Channels.newChannel(getInputStream()); } long contentLength() throws IOException; long lastModified() throws IOException; Resource createRelative(String relativePath) throws IOException; @Nullable String getFilename(); String getDescription(); }
InputStreamSource封裝任何能返回InputStream的類,比如File、Classpath下的資源和Byte Array等。它只有一個方法定義:getInputStream(),該方法返回一個新的InputStream對象。
Resource接口抽象了所有Spring內部使用到的底層資源:File、URL、Classpath等。首先,它定義了3個判斷當前資源狀態的方法:存在性(exists)、可讀性(isReadable)、是否處於打開狀態(isOpen)。另外,Resource接口還提供了不同資源到URL、URI、File類型的轉換,以及獲取lastModified屬性、文件名(不帶文件信息的文件名,getFilename())的方法。為了便於操作,Resource還提供了基於當前資源創建一個相對資源的方法:createRelative()。在錯誤處理中需要詳細地打印出錯的資源文件,因而Resource還提供了getDescription()方法用於在錯誤處理中的打印信息。
對不同來源的資源文件都有相應的Resource實現:文件(FileSystemResource)、Classpath資源(ClasspathResource)、URL資源(URLResource)、InputStream資源(InputStreamResource)、Byte數組(ByteArrayResource)等。相關類圖如下圖所示:
在日常的開發工作中,資源文件的加載也是經常用到的,可以直接使用Spring提供的類,比如在希望加載文件時可以使用以下代碼:
Resource resource = new ClassPathResource("spring/spring-test.xml.xml"); InputStream inputStream = resource.getInputStream();
得到inputStream后,我們可以按照以前的開發方式進行實現了,並且我們已經可以利用Resource及其子類為我們提供好的諸多特性。
有了Resource接口便可以對所有資源文件進行統一處理。至於實現,其實是非常簡單的,以getInputStream為例,ClassPathResource中的實現方式便是通過class或者classLoader提供的底層方法進行調用,而對於FileSystemResource的實現其實更簡單,直接使用FileInputStream對文件進行實例化。
ClassPathResource.java
/** * This implementation opens an InputStream for the given class path resource. * @see java.lang.ClassLoader#getResourceAsStream(String) * @see java.lang.Class#getResourceAsStream(String) */ @Override public InputStream getInputStream() throws IOException { InputStream is; if (this.clazz != null) { is = this.clazz.getResourceAsStream(this.path); } else if (this.classLoader != null) { is = this.classLoader.getResourceAsStream(this.path); } else { is = ClassLoader.getSystemResourceAsStream(this.path); } if (is == null) { throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist"); } return is; }
FileSystemResource.java
/** * This implementation opens a NIO file stream for the underlying file. * @see java.io.FileInputStream */ @Override public InputStream getInputStream() throws IOException { try { return Files.newInputStream(this.file.toPath()); } catch (NoSuchFileException ex) { throw new FileNotFoundException(ex.getMessage()); } }
當通過Resource相關類完成了對配置文件進行封裝后配置文件的讀取工作就全權交給XmlBeanDefinitionReader來處理了。
了解了Spring中將配置文件封裝為Resource類型的實例方法后,我們就可以繼續探討XmlBeanFactory的初始化方法了,XmlBeanFactory初始化有許多方法,Spring中提供了很多的構造函數,在這里分析的是使用Resource實例作為構造函數參數的方法,代碼如下:
/** * Create a new XmlBeanFactory with the given resource, * which must be parsable using DOM. * @param resource the XML resource to load bean definitions from * @throws BeansException in case of loading or parsing errors */ public XmlBeanFactory(Resource resource) throws BeansException { // 調用XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory)構造方法 this(resource, null); } /** * Create a new XmlBeanFactory with the given input stream, * which must be parsable using DOM. * @param resource the XML resource to load bean definitions from * @param parentBeanFactory parent bean factory * @throws BeansException in case of loading or parsing errors */ // parentBeanFactory為父類BeanFactory用於factory合並,可以為空 public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); }
上面函數的代碼中,this.reader.loadBeanDefinitions(resource)才是資源加載的真正實現,也是我們分析的重點之一。我們可以看到時序圖中提到的XmlBeanDefinitionReader加載數據就是這里完成的,但是在XmlBeanDefinitionReader加載數據前還有一個調用父類構造函數初始化的過程:super(parentBeanFactory),跟蹤代碼到父類AbstractAutowireCapableBeanfactory的構造函數中:
/** * Create a new AbstractAutowireCapableBeanFactory. */ public AbstractAutowireCapableBeanFactory() { super(); ignoreDependencyInterface(BeanNameAware.class); ignoreDependencyInterface(BeanFactoryAware.class); ignoreDependencyInterface(BeanClassLoaderAware.class); }
這里有必要提及下ignoreDependencyInterface方法。ignoreDependencyInterface的主要功能,是忽略給定接口的自動裝配功能,那么,這樣做的目的是什么呢?會產生什么樣的效果呢?
舉例來說,當A中有屬性B,那么當Spring在獲取A的Bean的如果其屬性B還沒有初始化,那么Spring會自動初始化B,這也是Spring提供的一個重要特性。但是,某些情況下B不會被初始化,其中的一種情況就是B實現了BeanNameAware接口。Spring中是這樣介紹的,自動裝配的時候,忽略給定的依賴接口,典型的應用是通過其他方式解析Application上下文注冊依賴,類似於BeanFactory通過BeanFactoryAware進行注入或者ApplicationContext通過ApplicationContextAware進行注入。
2. 加載Bean
之前提到的在XmlBeanFactory構造函數中調用了XmlBeanDefinitionReader類型的reader屬性提供的方法this.reader.loadBeanDefinitions(resource),而這句代碼則是整個資源加載的切入點,我們先來看看這個方法的時序圖,如下圖所示:
看到上圖我們才知道,原來繞了這么久還沒有切入正題,還一直在為加載XML文件和解析注冊Bean在做准備工作。從上面的時序圖中我們嘗試梳理整個的處理過程如下:
- 封裝資源文件。當進入XmlBeanDefinitionReader后首先對參數Resource使用EncodeResource類進行封裝。
- 獲取輸入流。從Resource中獲取對應的InputStream並構造InputSource。
- 通過構造的InputSource實例和Resource實例繼續調用函數doLoadBeanDefinitions。
我們來看一下loadBeanDefinitions函數具體的實現過程:
/** * Load bean definitions from the specified XML file. * @param resource the resource descriptor for the XML file * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors */ @Override public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource)); }
那么EncodeResource的作用是什么呢?通過名稱,我們可以大致推斷這個類主要是用於對資源文件的編碼進行處理。其中的主要邏輯體現在getReader()方法中,當設置了編碼屬性的時候Spring會使用相應的編碼作為輸入流的編碼。
/** * Open a {@code java.io.Reader} for the specified resource, using the specified * {@link #getCharset() Charset} or {@linkplain #getEncoding() encoding} * (if any). * @throws IOException if opening the Reader failed * @see #requiresReader() * @see #getInputStream() */ public Reader getReader() throws IOException { if (this.charset != null) { return new InputStreamReader(this.resource.getInputStream(), this.charset); } else if (this.encoding != null) { return new InputStreamReader(this.resource.getInputStream(), this.encoding); } else { return new InputStreamReader(this.resource.getInputStream()); } }
上面代碼構造了一個有編碼(encoding)的InputStreamReader。當構造好encodeResource對象后,再次轉入了可復用方法loadBeanDefinitions(new EncodedResource(resource))。
這個方法內部才是真正的數據准備階段,也就是時序圖鎖描述的邏輯:
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { Assert.notNull(encodedResource, "EncodedResource must not be null"); if (logger.isDebugEnabled()) { logger.debug("Loading XML bean definitions from " + encodedResource.getResource()); } // 通過屬性來記錄已經加載的資源 Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet<>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } try { // 從encodedResource中獲取已經封裝的Resource對象並再次從Resource中獲取其中的inputStream InputStream inputStream = encodedResource.getResource().getInputStream(); try { // InputSource這個類並不是來自於Spring,他的全路徑是org.xml.sax.InputSource InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } // 真正進入了邏輯核心部分 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { // 關閉輸入流 inputStream.close(); } } catch (IOException ex) { throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); } finally { currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } }
我們再次准備一下數據准備階段的邏輯,首先對傳入的resource參數做封裝,目的是考慮到Resource可能存在編碼要求的情況,其次,通過SAX讀取XML文件的方式來准備InputSource對象,最后將准備的數據通過參數傳入真正的核心處理部分doLoadBeanDefinitions(inputSource, encodedResource.getResource())。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { Document doc = doLoadDocument(inputSource, resource); return registerBeanDefinitions(doc, resource); } catch (BeanDefinitionStoreException ex) { throw ex; } catch (SAXParseException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); } catch (SAXException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", ex); } catch (ParserConfigurationException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, ex); } catch (IOException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, ex); } catch (Throwable ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, ex); } }
上面的代碼只做了兩件事,每一件都是必不可少的。
- 加載XML文件,並得到對應的Document。
- 根據返回的Document注冊Bean信息。
這兩個步驟支撐着整個Spring容器部分的實現基礎,尤其是第二部對配置文件的解析,邏輯非常復雜,下一節里面先從獲取Document講起。
Spring源碼分析(五)獲取Document
摘要:本文結合《Spring源碼深度解析》來分析Spring 5.0.6版本的源代碼。若有描述錯誤之處,歡迎指正。
這一篇開始進行Document加載了,XmlBeanFactoryReader類對於文檔讀取並沒有親歷親為,而是委托給了DocumentLaoder去執行,DocumentLoader是個接口,真正調用的是DefaultDocumentLoader,解析代碼如下:
/** * Load the {@link Document} at the supplied {@link InputSource} using the standard JAXP-configured * XML parser. */ @Override public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); if (logger.isDebugEnabled()) { logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]"); } DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); return builder.parse(inputSource); }
對於這部分代碼其實並沒有太多可以描述的,因為通過SAX解析XML文檔的套路都差不多,Spring在這里並沒有什么特殊的地方,同樣首先創建DocumentBuilderFactory,再通過DocumentBuilderFactory創建DocumentBuilder,進而解析inputSource來返回Document對象。這里有必要提及一下EntityResolver,對於參數entityResolver,傳入的是通過getEntityResolver() 函數獲取的返回值,如下代碼:
protected EntityResolver getEntityResolver() { if (this.entityResolver == null) { // Determine default EntityResolver to use. ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader != null) { this.entityResolver = new ResourceEntityResolver(resourceLoader); } else { this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader()); } } return this.entityResolver; }
那么,EntityResolver到底是做什么用的呢?
EntityResolver用法
在loadDocument方法中涉及一個參數EntityResolver,何為EntitiResolver?官網這樣解釋:如果SAX應用程序需要實現自定義處理外部實體,則必須實現此接口並使用setEntityResolver方法向SAX驅動器注冊一個實例。也就是說,對於解析一個XML,SAX首先讀取該XML文檔上的聲明,根據聲明去尋找相應的DTD定義,以便對文檔進行一個驗證。默認的尋找規則,即通過網絡(實現上就是聲明的DTD的URL地址)來下載相應的DTD聲明,並進行認證。下載的過程漫長,而且當網絡中斷或不可用的時候,這里會報錯,就是因為相應的DTD聲明沒有被找到的原因。
enntityResolver的作用是項目本身就可以提供一個如何尋找DTD聲明的方法,即由程序來實現尋找DTD聲明的過程,比如我們將DTD文件放到項目中某處,在實現時直接將此文檔讀取並返回給SAX即可。這樣就避免了通過網絡來尋找相應的聲明。
首先看enntityResolver的接口方法聲明:
public abstract InputSource resolveEntity (String publicId, String systemId) throws SAXException, IOException;
這里,它接受兩個參數publicId和systemId,並返回一個inputSource對象。這里我們以特定配置文件來進行講解。
(1)如果我們在解析驗證模式為XSD的配置文件,代碼如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> ...... </beans>
讀取到以下兩個參數。
- publicId:null
- systemId:http://www.springframework.org/schema/beans/spring-beans.xsd
(2)如果我們在解析驗證模式為DTD的配置文件,代碼如下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//Spring//DTD BEAN 2.0//EN" "http://www.springframework.org/schema/beans/spring-beans.xsd"> <beans"> ...... </beans>
讀取到以下兩個參數:
- publicId:-//Spring//DTD BEAN 2.0//EN
- systemId:http://www.springframework.org/schema/beans/spring-beans.xsd
之前已經提到過,驗證文件默認的加載方式是通過URL進行網絡下載,這樣會造成延時,用戶體驗也不好,一般的做法是將驗證文件放置在自己的工程里,那么怎么做才能將這個URL轉換為自己工程里對應的地址文件呢?我們以加載DTD文件為例來看看Spring中是如何實現的。根據之前Spring中通過getEntityResolver()方法對EntityResolver的獲取,我們知道,Spring中使用DelegatingEntityResolver類為EntityResolver的實現類,resolveEntity實現方法如下:
@Override @Nullable public InputSource resolveEntity(String publicId, @Nullable String systemId) throws SAXException, IOException { if (systemId != null) { if (systemId.endsWith(DTD_SUFFIX)) { // 如果是dtd從這里解析 return this.dtdResolver.resolveEntity(publicId, systemId); } else if (systemId.endsWith(XSD_SUFFIX)) { // 通過調用META-INF/Spring.schemas解析 return this.schemaResolver.resolveEntity(publicId, systemId); } } return null; }
我們可以看到,對不同的驗證模式,Spring使用了不同的解析器解析。這里簡單描述一下原理,比如加載DTD類型的BeanDtdResolver的resolveEntity是直接截取systemId最后的xx.dtd然后去當前路徑下尋找,而加載XSD類型的PluggableSchemaResolver類的resolveEntity是默認到META-INF/Spring.schemas文件中找到systemId所對應的XSD文件並加載。下面是BeansDtdResolver的源碼:
@Override @Nullable public InputSource resolveEntity(String publicId, @Nullable String systemId) throws IOException { if (logger.isTraceEnabled()) { logger.trace("Trying to resolve XML entity with public ID [" + publicId + "] and system ID [" + systemId + "]"); } if (systemId != null && systemId.endsWith(DTD_EXTENSION)) { int lastPathSeparator = systemId.lastIndexOf('/'); int dtdNameStart = systemId.indexOf(DTD_NAME, lastPathSeparator); if (dtdNameStart != -1) { String dtdFile = DTD_NAME + DTD_EXTENSION; if (logger.isTraceEnabled()) { logger.trace("Trying to locate [" + dtdFile + "] in Spring jar on classpath"); } try { Resource resource = new ClassPathResource(dtdFile, getClass()); InputSource source = new InputSource(resource.getInputStream()); source.setPublicId(publicId); source.setSystemId(systemId); if (logger.isDebugEnabled()) { logger.debug("Found beans DTD [" + systemId + "] in classpath: " + dtdFile); } return source; } catch (IOException ex) { if (logger.isDebugEnabled()) { logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in classpath", ex); } } } } // Use the default behavior -> download from website or wherever. return null; }
Spring源碼分析(六)解析和注冊BeanDefinitions
摘要:本文結合《Spring源碼深度解析》來分析Spring 5.0.6版本的源代碼。若有描述錯誤之處,歡迎指正。
當把文件轉換為Document后,接下來的提取及注冊bean就是我們的重頭戲。繼續上一篇的分析,當程序已經擁有XML文檔文件的Document實例對象時,就會被引入XmlBeanDefinitionReader的這個方法。
/** * Register the bean definitions contained in the given DOM document. * Called by {@code loadBeanDefinitions}. * <p>Creates a new instance of the parser class and invokes * {@code registerBeanDefinitions} on it. * @param doc the DOM document * @param resource the resource descriptor (for context information) * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of parsing errors * @see #loadBeanDefinitions * @see #setDocumentReaderClass * @see BeanDefinitionDocumentReader#registerBeanDefinitions */ public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { // 使用DefaultBeanDefinitionDocumentReader實例化BeanDefinitionDocumentReader BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); // 實例化BeanDefinitionReader時候會將BeanDefinitionRegistry傳入,默認使用繼承自DefaultListableBeanFactory的子類 int countBefore = getRegistry().getBeanDefinitionCount(); // 加載及注冊bean documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); // 記錄本次加載的BeanDefinition個數 return getRegistry().getBeanDefinitionCount() - countBefore; }
其中的參數doc是通過上一節loadDocument加載轉換出來的。在這個方法中很好地應用了面向對象單一職責的原則,將邏輯處理委托給單一的類進行處理,而這個邏輯處理類就是BeanDefinitionDocumentReader。BeanDefinitionDocumentReader是一個接口,而實例化的工作是在createBeanDefinitionDocumentReader()中完成的,而通過此方法, BeanDefinitionDocumentReader真正的類型其實已經是DefaultBeanDefinitionDocumentReader了,進入DefaultBeanDefinitionDocumentReader后,發現這個方法的重要目的之一就是提取root,以便再次將root作為參數繼續BeanDefinition的注冊。
/** * This implementation parses bean definitions according to the "spring-beans" XSD * (or DTD, historically). * <p>Opens a DOM Document; then initializes the default settings * specified at the {@code <beans/>} level; then parses the contained bean definitions. */ @Override public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); doRegisterBeanDefinitions(root); }
經過艱難險阻,磕磕絆絆,我們終於到了核心邏輯的底部doRegisterBeanDefinitions(root),至少我們在這個方法中看到了希望。
如果說以前一直是XML加載解析的准備階段,那么doRegisterBeanDefinitions算是真正地開始進行解析了,我們期待的核心部分真正開始了。
/** * Register each bean definition within the given root {@code <beans/>} element. */ @SuppressWarnings("deprecation") // for Environment.acceptsProfiles(String...) protected void doRegisterBeanDefinitions(Element root) { // Any nested <beans> elements will cause recursion in this method. In // order to propagate and preserve <beans> default-* attributes correctly, // keep track of the current (parent) delegate, which may be null. Create // the new (child) delegate with a reference to the parent for fallback purposes, // then ultimately reset this.delegate back to its original (parent) reference. // this behavior emulates a stack of delegates without actually necessitating one. // 專門處理解析 BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(getReaderContext(), root, parent); if (this.delegate.isDefaultNamespace(root)) { // 處理profile屬性 String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); // We cannot use Profiles.of(...) since profile expressions are not supported // in XML config. See SPR-12458 for details. if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { if (logger.isDebugEnabled()) { logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + getReaderContext().getResource()); } return; } } } /* 這里是模板方法模式 */ // 解析前處理,留給子類實現 preProcessXml(root); parseBeanDefinitions(root, this.delegate); // 解析后處理,留給子類實現 postProcessXml(root); this.delegate = parent; }
通過上面的代碼我們看到了處理流程,首先是對profile的處理,然后開始進行解析,可以當我們跟進preProcessXml(root)和postProcessXml(root)發現代碼是空的,既然是空的寫着還有什么用呢?就像面向對象設計方法學中常說的一句話,一個類要么是面向繼承設計的,要么就用final修飾。在DefaultBeanDefinitionDocumentReader中並沒有用final修飾,所以它是面向繼承設計的。這兩個方法正是為子類而設計的,如果讀者有了解過設計模式,可以很快速地反應出這是模板方法模式,如果繼承自DefaultBeanDefinitionDocumentReader的子類需要在Bean解析前后做一些處理的話,那么只需要重寫這兩個方法就可以了。
1. profile屬性的使用
我們注意到在注冊bean的最開始是對PROFILE_ATTRIBUTE屬性的解析,可能對於我們來說,profile屬性並不是很常用。讓我們先了解一下這個屬性。
分析profile前我們先了解下profile的用法,官方實例代碼片段如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <beans profile="dev"> ...... </beans> <beans profile="production"> ...... </beans> </beans>
集成到Web環境中時,在web.xml中加入以下代碼:
<context-param> <param-name>Spring.profiles.active</param-name> <param-value>dev</param-value> </context-param>
有了這個特性我們就可以在配置文件中部署兩套配置來適用於生產環境和開發環境,這樣可以方便的進行切換開發、部署環境,最常用的就是更換不同的數據庫。
了解了profile的使用再來分析代碼會清晰很多,首先程序會獲取beans節點是否定義了profile屬性,如果定義了則會需要到環境變量中去尋找,所以這里首先斷言environment不可能為空,因為profile是可以同時指定多個的,需要程序對其拆分,並解析每個profile是都符合環境變量中所定義的,不定義則不會浪費性能去解析。
2. 解析並注冊BeanDefinition
處理了profile后就可以進行XML的讀取了,跟蹤代碼進入parseBeanDefinitions(root, this.delegate)。
/** * Parse the elements at the root level in the document: * "import", "alias", "bean". * @param root the DOM root element of the document */ protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { // 對beans的處理 if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { // 對bean的處理 parseDefaultElement(ele, delegate); } else { // 對bean的處理 delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
上面的代碼看起來邏輯還是蠻清晰的,因為在Spring的XML配置里面有兩大類Bean聲明,一個是默認的,如:
<bean id="mySpringBean" class="org.cellphone.uc.MySpringBean"/>
另一類就是自定義的,如:
<tx:annotation-driven/>
而這兩種的讀取及解析差別是非常大的,如果采用Spring默認的配置,Spring當然知道該怎么做,但是如果是自定義的,那么就需要用戶實現一些接口和配置了。對於根節點或者子節點如果是默認命名空間的話則采用parseDefaultElement方法進行解析,否則使用delegate.parseCustomElement(ele)方法對自定義命名空間進行解析。而判斷是否默認命名空間還是自定義命名空間的辦法是使用node.getNamespaceURI()獲取命名空間,並與Spring中固定的命名空間http://www.springframework.org/schema/beans進行比對。如果一致則認為是默認,否則就認為是自定義。而對於默認標簽解析與自定義標簽解析我們將會在下一篇中進行討論。
Spring源碼分析(七)bean標簽的解析及注冊
摘要:本文結合《Spring源碼深度解析》來分析Spring 5.0.6版本的源代碼。若有描述錯誤之處,歡迎指正。
在上一篇中提到過Spring中的標簽包括默認標簽和自定義標簽兩種,而兩種標簽的用法以及解析方式存在着很大的不同。本節開始詳細分析默認標簽的解析過程。
默認標簽的解析是在parseDefaultElement函數中進行的,函數中的功能邏輯一目了然,分別對4種不同標簽(import、alias、bean和beans)做了不同的處理。
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { // 對import標簽的處理 if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } // 對alias標簽的處理 else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } // 對bean標簽的處理 else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } // 對beans標簽的處理 else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele); } }
在4種標簽中,對bean標簽的解析最為復雜也最為重要,所以從此標簽開始深入分析,如果能理解這個標簽的解析過程,其他標簽的解析就迎刃而解了。首先看看函數processBeanDefinition(ele, delegate)。
/** * Process the given bean element, parsing the bean definition * and registering it with the registry. */ protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // Register the final decorated instance. BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex); } // Send registration event. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
乍一看,似乎一頭霧水,沒有以前的函數那樣清晰的邏輯。大致的邏輯總結如下。
(1)首先委托BeanDefinitionDelegate類的parseBeanDefinitionElement方法進行元素解析,返回BeanDefinitionHolder類型的實例bdHolder,經過這個方法后,bdHolder實例已經包含我們配置文件中配置的各種屬性了,例如class、name、id、alias之類的屬性。
(2)當返回的dbHolder不為空的情況下若存在默認標簽的子節點下再有自定義屬性,還需要再次對自定義標簽進行解析。
(3)解析完成后,需要對解析后的bdHolder進行注冊,同樣,注冊操作委托給了BeanDefinitionReaderUtils的registerBeanDefinition方法。
(4)最后發出響應事件,通知相關的監聽器,這個bean已經加載完成了。
配合時序圖,可能會更容易理解。
解析BeanDefinition
下面我們就針對各個操作做具體分析。首先我們從元素解析及信息提取開始,也就是BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele),進入BeanDefinitionDelegate類的parseBeanDefinitionElement方法。
/** * Parses the supplied {@code <bean>} element. May return {@code null} * if there were errors during parse. Errors are reported to the * {@link org.springframework.beans.factory.parsing.ProblemReporter}. */ @Nullable public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) { return parseBeanDefinitionElement(ele, null); } /** * Parses the supplied {@code <bean>} element. May return {@code null} * if there were errors during parse. Errors are reported to the * {@link org.springframework.beans.factory.parsing.ProblemReporter}. */ @Nullable public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) { // 解析id屬性 String id = ele.getAttribute(ID_ATTRIBUTE); // 解析name屬性 String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); // 分割name屬性 List<String> aliases = new ArrayList<>(); if (StringUtils.hasLength(nameAttr)) { String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS); aliases.addAll(Arrays.asList(nameArr)); } String beanName = id; if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) { beanName = aliases.remove(0); if (logger.isDebugEnabled()) { logger.debug("No XML 'id' specified - using '" + beanName + "' as bean name and " + aliases + " as aliases"); } } if (containingBean == null) { checkNameUniqueness(beanName, aliases, ele); } AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); if (beanDefinition != null) { if (!StringUtils.hasText(beanName)) { try { // 如果不存在beanName,那么根據Spring中提供的命名規則為當前bean生成對應的beanName if (containingBean != null) { beanName = BeanDefinitionReaderUtils.generateBeanName( beanDefinition, this.readerContext.getRegistry(), true); } else { beanName = this.readerContext.generateBeanName(beanDefinition); // Register an alias for the plain bean class name, if still possible, // if the generator returned the class name plus a suffix. // This is expected for Spring 1.2/2.0 backwards compatibility. String beanClassName = beanDefinition.getBeanClassName(); if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) { aliases.add(beanClassName); } } if (logger.isDebugEnabled()) { logger.debug("Neither XML 'id' nor 'name' specified - " + "using generated bean name [" + beanName + "]"); } } catch (Exception ex) { error(ex.getMessage(), ele); return null; } } String[] aliasesArray = StringUtils.toStringArray(aliases); return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } return null; }
以上便是對默認標簽解析的全過程了。當然,對Spring的解析猶如洋蔥剝皮一樣,一層一層地進行,盡管現在只能看到對屬性id以及name的解析,但是很慶幸,思路我們已經了解了。在開始對屬性展開全面解析前,Spring在外層又做了一個當前層的功能架構,在當前層完成的主要工作包括如下內容。
(1)提取元素中的id以及name屬性。
(2)進一步解析其他所有屬性並統一封裝至GenericBeanDefinition類型的實例中。
(3)如果檢測到bean沒有指定beanName,那么使用默認規則為此Bean生成beanName。
(4)將獲取到的信息封裝到BeanDefinitionHolder的實例中。
我們進一步查看步驟(2)中對標簽其他屬性的解析過程。
/** * Parse the bean definition itself, without regard to name or aliases. May return * {@code null} if problems occurred during the parsing of the bean definition. */ @Nullable public AbstractBeanDefinition parseBeanDefinitionElement( Element ele, String beanName, @Nullable BeanDefinition containingBean) { this.parseState.push(new BeanEntry(beanName)); String className = null; // 解析class屬性 if (ele.hasAttribute(CLASS_ATTRIBUTE)) { className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); } String parent = null; // 解析parent屬性 if (ele.hasAttribute(PARENT_ATTRIBUTE)) { parent = ele.getAttribute(PARENT_ATTRIBUTE); } try { // 創建用於承載屬性的AbstractBeanDefinition類型的GenericBeanDefinition AbstractBeanDefinition bd = createBeanDefinition(className, parent); // 硬編碼解析默認bean的各種屬性 parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); // 提取description bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); // 解析元數據 parseMetaElements(ele, bd); // 解析lookup-method屬性 parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); // 解析replaced-method parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); // 解析構造函數參數 parseConstructorArgElements(ele, bd); // 解析property子元素 parsePropertyElements(ele, bd); // 解析qualifier子元素 parseQualifierElements(ele, bd); bd.setResource(this.readerContext.getResource()); bd.setSource(extractSource(ele)); return bd; } catch (ClassNotFoundException ex) { error("Bean class [" + className + "] not found", ele, ex); } catch (NoClassDefFoundError err) { error("Class that bean class [" + className + "] depends on not found", ele, err); } catch (Throwable ex) { error("Unexpected failure during bean definition parsing", ele, ex); } finally { this.parseState.pop(); } return null; }
終於,bean標簽的所有屬性,不論是常用的還是不常用的我們都看到了,盡管有些復雜的屬性還需要進一步的解析,不過絲毫不會影響我們興奮的心情。接下來,我們繼續一些復雜標簽屬性的解析。
1. 創建用於承載屬性的BeanDefinition
BeanDefinition是一個接口,在Spring中存在三種實現:RootBeanDefinition、ChildBeanDefinition以及GenericBeanDefinition。三種實現均繼承了AbstractBeanDefinition,其中BeanDefinition是配置文件<bean>元素標簽在容器中的內部表現形式。<bean>元素標簽擁有class、scope、lazy-init等配置屬性,BeanDefinition則提供了相應的beanClass、scope、lazyInit屬性,BeanDefinition和<bean>屬性是一一對應的。其中RootBeanDefinition是最常用的實現類,它對應一般的<bean>元素標簽,GenericBeanDefinition是自2.5版本以后新加入的bean文件配置屬性定義類,是一站式服務類。
在配置文件中可以定義父<bean>和子<bean>,父<bean>用RootBeanDefinition表示,而子<bean>用ChildBeanDefinition表示,而沒有父<bean>的<bean>就使用RootBeanDefinition表示。AbstractBeanDefinition對兩者共同的類信息進行抽象。
Spring通過BeanDefinition將配置文件中的<bean>配置信息轉換為容器的內部表示,並將這些BeanDefinition注冊到BeanDefinitionRegistry中。Spring容器的BeanDefinitionRegistry就像是Spring配置信息的內存數據庫,主要是以map的形式保存,后續操作直接從BeanDefinitionResistry中讀取配置信息。它們之間的關系如下圖所示:
因此,要解析屬性首先要創建用於承載屬性的實例,也就是創建GenericBeanDefinition類型的實例。而代碼createBeanDefinition(className, parent)的作用就是實現此功能。
/** * Create a bean definition for the given class name and parent name. * @param className the name of the bean class * @param parentName the name of the bean's parent bean * @return the newly created bean definition * @throws ClassNotFoundException if bean class resolution was attempted but failed */ protected AbstractBeanDefinition createBeanDefinition(@Nullable String className, @Nullable String parentName) throws ClassNotFoundException { return BeanDefinitionReaderUtils.createBeanDefinition( parentName, className, this.readerContext.getBeanClassLoader()); } /** * Create a new GenericBeanDefinition for the given parent name and class name, * eagerly loading the bean class if a ClassLoader has been specified. * @param parentName the name of the parent bean, if any * @param className the name of the bean class, if any * @param classLoader the ClassLoader to use for loading bean classes * (can be {@code null} to just register bean classes by name) * @return the bean definition * @throws ClassNotFoundException if the bean class could not be loaded */ public static AbstractBeanDefinition createBeanDefinition( @Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException { GenericBeanDefinition bd = new GenericBeanDefinition(); // parentName可能為空 bd.setParentName(parentName); if (className != null) { if (classLoader != null) { // 如果classLoader不為空,則使用以傳入的classLoader加載類對象,否則只是記錄className bd.setBeanClass(ClassUtils.forName(className, classLoader)); } else { bd.setBeanClassName(className); } } return bd; }
2. 解析各種屬性
當我們創建了bean信息的承載實例后,便可以進行bean信息的各種屬性解析了,首先我們進入parseBeanDefinitionAttributes方法。parseBeanDefinitionAttributes方法是對element所有元素屬性進行解析:
/** * Apply the attributes of the given bean element to the given bean * definition. * @param ele bean declaration element * @param beanName bean name * @param containingBean containing bean definition * @return a bean definition initialized according to the bean element attributes */ public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName, @Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) { // 解析singleton屬性 // 檢查當前bean是否有singleton屬性,有則提示應該使用scope屬性,並拋出異常 if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) { error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele); } // 解析scope屬性 else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) { // 獲取並設置scope屬性值 bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE)); } else if (containingBean != null) { // Take default from containing bean in case of an inner bean definition. // 在嵌入beanDefinition情況下且沒有單獨指定scope屬性則使用父類默認的屬性 bd.setScope(containingBean.getScope()); } // 解析abstract屬性 if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) { bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE))); } // 解析lazy-init屬性 String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE); if (DEFAULT_VALUE.equals(lazyInit)) { lazyInit = this.defaults.getLazyInit(); } // 若沒有設置或設置成其他字符都會被設置為false bd.setLazyInit(TRUE_VALUE.equals(lazyInit)); // 解析autowire屬性 String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE); bd.setAutowireMode(getAutowireMode(autowire)); // 解析depends-on屬性 if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) { String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE); bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS)); } // 解析autowire-candidate屬性 String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE); if ("".equals(autowireCandidate) || DEFAULT_VALUE.equals(autowireCandidate)) { String candidatePattern = this.defaults.getAutowireCandidates(); if (candidatePattern != null) { String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern); bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName)); } } else { bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate)); } // 解析primary屬性 if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) { bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE))); } // 解析init-method屬性 if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) { String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE); bd.setInitMethodName(initMethodName); } else if (this.defaults.getInitMethod() != null) { bd.setInitMethodName(this.defaults.getInitMethod()); bd.setEnforceInitMethod(false); } // 解析destroy-method屬性 if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) { String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE); bd.setDestroyMethodName(destroyMethodName); } else if (this.defaults.getDestroyMethod() != null) { bd.setDestroyMethodName(this.defaults.getDestroyMethod()); bd.setEnforceDestroyMethod(false); } // 解析factory-method屬性 if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) { bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE)); } // 解析factory-bean屬性 if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) { bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE)); } return bd; }
我們可以清楚地看到Spring完成了對所有bean屬性的解析,這些屬性中有很多是我們經常使用的,同時我相信也一定會有或多或少的屬性是讀者不熟悉或是沒有使用過的,感興趣的讀者可以查閱相關資料進一步了解每個屬性。
3. 解析子元素meta
在開始解析元數據的分析前,我們先回顧下元數據meta屬性的使用。
<bean id="mySpringBean" class="org.cellphone.uc.MySpringBean"> <meta key="str" value="I'm a test attribute."/> </bean>
這段代碼並不會直接體現在MySpringBean的屬性當中,而是一個額外的聲明,當需要使用里面的信息的時候可以通過BeanDefinition的getAttribute(key)方法進行獲取。
對meta屬性的解析代碼如下:
public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) { // 獲取當前節點的所有子元素 NodeList nl = ele.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); // 提取meta if (isCandidateElement(node) && nodeNameEquals(node, META_ELEMENT)) { Element metaElement = (Element) node; String key = metaElement.getAttribute(KEY_ATTRIBUTE); String value = metaElement.getAttribute(VALUE_ATTRIBUTE); // 使用key、value構造BeanMetadataAttribute BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value); attribute.setSource(extractSource(metaElement)); // 記錄信息 attributeAccessor.addMetadataAttribute(attribute); } } }
4. 解析子元素lookup-method
同樣,子元素lookup-method似乎並不是很常用,但是在某些時候它的確是非常有用的屬性,通常我們稱它為獲取器注入。應用《Spring in Action》中的一句話:獲取器注入是一種特殊的方法注入,它是把一個方法聲明為返回某種類型的bean,但實際要返回的bean是在配置文件里面配置的,此方法可用在設計有些可插拔的功能上,接觸程序依賴。我們看看具體的應用。
(1)首先創建一個父類:
public class User { public void showMe() { System.out.println("I am a user"); } }
(2)創建其子類並覆蓋showMe方法:
public class Teacher extends User { @Override public void showMe() { System.out.println("I am a teacher"); } }
(3)創建調用方法:
public abstract class GetBeanTest { public void showMe() { this.getBean().showMe(); } public abstract User getBean(); }
(4)創建測試方法:
public class Main { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("spring/lookup-test.xml"); GetBeanTest getBeanTest = (GetBeanTest) context.getBean("getBeanTest"); getBeanTest.showMe(); } }
到現在為止,除了配置文件外,整個測試方法就完成了,如果之前沒有接觸過獲取器注入的讀者可能會有疑問:抽象方法還沒有被實現,怎么可以直接調用呢?答案就在Spring為我們提供的獲取器中,我們看看配置文件是怎么配置的。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="getBeanTest" class="org.cellphone.uc.GetBeanTest"> <lookup-method name="getBean" bean="teacher"/> </bean> <bean id="teacher" class="org.cellphone.uc.Teacher"/> </beans>
在配置文件中,我們看到了源碼解析中提到的lookup-method子元素,這個配置完成的功能是動態地將teacher所代表的bean作為getBean的返回值,運行測試方法我們會看到控制台上的輸出:
I am a teacher
當我們的業務變更或者在其他情況下,teacher里面的業務邏輯已經不再符合我們的業務要求,需要進行替換怎么辦呢?這時我們需要增加新的邏輯類:
public class Student extends User { @Override public void showMe() { System.out.println("I am a student"); } }
同時修改配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="getBeanTest" class="org.cellphone.uc.GetBeanTest"> <lookup-method name="getBean" bean="student"/> </bean> <bean id="teacher" class="org.cellphone.uc.Teacher"/> <bean id="student" class="org.cellphone.uc.Student"/> </beans>
再次運行測試類,你會發現不一樣的結果:
I am a student
至此,我們已經初步了解了lookup-method子元素所提供的大致功能,相信這時再次去看它的屬性提取源碼會覺得更有針對性。
/** * Parse lookup-override sub-elements of the given bean element. */ public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) { NodeList nl = beanEle.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); // 僅當在bean下面有子元素下且為lookup-method時有效 if (isCandidateElement(node) && nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) { Element ele = (Element) node; // 獲取要修飾的方法 String methodName = ele.getAttribute(NAME_ATTRIBUTE); // 獲取配置返回的bean String beanRef = ele.getAttribute(BEAN_ELEMENT); LookupOverride override = new LookupOverride(methodName, beanRef); override.setSource(extractSource(ele)); overrides.addOverride(override); } } }
上面的代碼很熟悉,似乎與parseMetaElements的代碼大同小異,最大的區別就是在if判斷中的節點名稱在這里被修改為LOOKUP_METHOD_ELEMENT。還有,在數據存儲上面通過使用LookupOverride類型的實體類來進行數據承載並記錄在AbstractBeanDefinition中的methodOverrides屬性中。
5. 解析子元素replaced-method
這個方法主要是對bean中replaced-method子元素的提取,在開始提取分析之前我們還是預先介紹下這個元素的用法。
方法替換:可以在運行時用新的方法替換現有的方法。與之前的lookup-method不同的是,replaced-method不但可以動態地替換返回實體bean,而且還能動態地更改原有方法的邏輯。我們來看看使用示例。
(1)在changeMe中完成某個業務邏輯:
public class UserChange { public void changeMe() { System.out.println("change me"); } }
(2)在運營一段時間后需要改變原有的業務邏輯:
public class UserChangeReplacer implements MethodReplacer { @Override public Object reimplement(Object obj, Method method, Object[] args) throws Throwable { System.out.println("我替換了原有的方法"); return null; } }
(3)使替換后的類生效:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="userChange" class="org.cellphone.uc.UserChange"> <replaced-method name="changeMe" replacer="replacer"/> </bean> <bean id="replacer" class="org.cellphone.uc.UserChangeReplacer"/> </beans>
(4)測試:
public class ReplaceMain { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("spring/replace-test.xml"); UserChange change = (UserChange) context.getBean("userChange"); change.changeMe(); } }
好了,運行測試類就可以看到預期的效果了,控制台成功打印出“我替換了原有的方法”,也就是說我們做到了動態替換原有方法,知道了這個元素的用法,我們再次來看元素的提取過程:
/** * Parse replaced-method sub-elements of the given bean element. */ public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) { NodeList nl = beanEle.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); // 僅當在bean下面有子元素下且為replaced-method時有效 if (isCandidateElement(node) && nodeNameEquals(node, REPLACED_METHOD_ELEMENT)) { Element replacedMethodEle = (Element) node; // 提取要替換的舊的方法 String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE); // 提取對應的新的替換方法 String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE); ReplaceOverride replaceOverride = new ReplaceOverride(name, callback); // Look for arg-type match elements. List<Element> argTypeEles = DomUtils.getChildElementsByTagName(replacedMethodEle, ARG_TYPE_ELEMENT); for (Element argTypeEle : argTypeEles) { // 記錄參數 String match = argTypeEle.getAttribute(ARG_TYPE_MATCH_ATTRIBUTE); match = (StringUtils.hasText(match) ? match : DomUtils.getTextValue(argTypeEle)); if (StringUtils.hasText(match)) { replaceOverride.addTypeIdentifier(match); } } replaceOverride.setSource(extractSource(replacedMethodEle)); overrides.addOverride(replaceOverride); } } }
我們可以看到無論是lookup-method還是replaced-method都是構造了一個MethodOverride,並最終記錄在了AbstractBeanDefinition中的methodOverrides屬性中。而這個屬性如何使用以完成它所提供的功能我們會在后續的章節進行詳細介紹。
6. 解析子元素constructor-arg
對構造函數的解析式非常常用,也是非常復雜的,下面舉個簡單的小例子:
<beans> <!-- 默認情況是按照參數的順序注入,當指定index索引后就可以改變注入參數的順序 --> <bean id= "helloBean" class="com.HelloBean"> <constructor-arg index="0"> <value>Hello</value> </cibstructor-arg> <constructor-arg index="1"> <value>World</value> </cibstructor-arg> </bean> </beans>
上面的配置是Spring構造函數配置中最基礎的配置,實現的功能就是對HelloBean自動尋找對應的構造函數,並在初始化的時候將設置的參數傳入。那么讓我們來看看具體的XML解析過程。
對於constructor-arg子元素的解析,Spring是通過parseConstructorArgElements函數來實現的,具體的代碼如下:
/** * Parse constructor-arg sub-elements of the given bean element. */ public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) { NodeList nl = beanEle.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (isCandidateElement(node) && nodeNameEquals(node, CONSTRUCTOR_ARG_ELEMENT)) { // 解析constructor-arg parseConstructorArgElement((Element) node, bd); } } }
這個結構中遍歷所有子元素,也就是提取所有constructor-arg,然后進行解析,具體的解析被放置在了另個函數parseConstructorArgElement中,
/** * Parse a constructor-arg element. */ public void parseConstructorArgElement(Element ele, BeanDefinition bd) { // 提取index屬性 String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE); // 提取type屬性 String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE); // 提取name屬性 String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); if (StringUtils.hasLength(indexAttr)) { try { int index = Integer.parseInt(indexAttr); if (index < 0) { error("'index' cannot be lower than 0", ele); } else { try { this.parseState.push(new ConstructorArgumentEntry(index)); // 解析ele對應的屬性元素 Object value = parsePropertyValue(ele, bd, null); ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value); if (StringUtils.hasLength(typeAttr)) { valueHolder.setType(typeAttr); } if (StringUtils.hasLength(nameAttr)) { valueHolder.setName(nameAttr); } valueHolder.setSource(extractSource(ele)); // 不允許重復指定相同參數 if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) { error("Ambiguous constructor-arg entries for index " + index, ele); } else { bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder); } } finally { this.parseState.pop(); } } } catch (NumberFormatException ex) { error("Attribute 'index' of tag 'constructor-arg' must be an integer", ele); } } else { // 沒有index屬性則自動尋找 try { this.parseState.push(new ConstructorArgumentEntry()); Object value = parsePropertyValue(ele, bd, null); ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value); if (StringUtils.hasLength(typeAttr)) { valueHolder.setType(typeAttr); } if (StringUtils.hasLength(nameAttr)) { valueHolder.setName(nameAttr); } valueHolder.setSource(extractSource(ele)); bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder); } finally { this.parseState.pop(); } } }
上面一段代碼涉及的邏輯並不復雜,首先是提取constructor-arg上必要的屬性(index、type、name)。
如果配置中指定了index屬性,那么操作步驟如下:
(1)解析constructor-arg的子元素。
(2)使用ConstructorArgumentValues.ValueHolder來封裝解析出來的元素。
(3)將type、name和index屬性一並封裝在ConstructorArgumentValues.ValueHolder中並添加至當前BeanDefinition的constructorArgumentValues的indexedArgumentValues屬性中。
如果沒有指定index屬性,那么操作步驟如下:
(1)解析constructor-arg的子元素。
(2)使用ConstructorArgumentValues.ValueHolder來封裝解析出來的元素。
(3)將type、name和index屬性一並封裝在ConstructorArgumentValues.ValueHolder中並添加至當前BeanDefinition的constructorArgumentValues的genericArgumentValues屬性中。
可以看到,對於是否定義index屬性,Spring的處理流程是不同的,關鍵在於屬性信息被保存的位置。
了解整個流程后,繼續嘗試了解解析構造函數配置中子元素的過程,進入parsePropertyValue:
/** * Get the value of a property element. May be a list etc. * Also used for constructor arguments, "propertyName" being null in this case. */ @Nullable public Object parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) { String elementName = (propertyName != null ? "<property> element for property '" + propertyName + "'" : "<constructor-arg> element"); // Should only have one child element: ref, value, list, etc. // 一個屬性只能對應一種類型:ref、value、list等 NodeList nl = ele.getChildNodes(); Element subElement = null; for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); // 對應description或者meta不處理 if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) && !nodeNameEquals(node, META_ELEMENT)) { // Child element is what we're looking for. if (subElement != null) { error(elementName + " must not contain more than one sub-element", ele); } else { subElement = (Element) node; } } } // 解析constructor-arg上的ref屬性 boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE); // 解析constructor-arg上的value屬性 boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE); if ((hasRefAttribute && hasValueAttribute) || ((hasRefAttribute || hasValueAttribute) && subElement != null)) { /** * 在constructor-arg上不存在: * 1. 同時既有ref屬性又有value屬性 * 2. 存在ref屬性或者value屬性且又有子元素 */ error(elementName + " is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele); } if (hasRefAttribute) { // ref屬性的處理,使用RuntimeBeanReference封裝對應的ref名稱 String refName = ele.getAttribute(REF_ATTRIBUTE); if (!StringUtils.hasText(refName)) { error(elementName + " contains empty 'ref' attribute", ele); } RuntimeBeanReference ref = new RuntimeBeanReference(refName); ref.setSource(extractSource(ele)); return ref; } else if (hasValueAttribute) { // value屬性的處理,使用TypedStringValue封裝 TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE)); valueHolder.setSource(extractSource(ele)); return valueHolder; } else if (subElement != null) { // 解析子元素 return parsePropertySubElement(subElement, bd); } else { // Neither child element nor "ref" or "value" attribute found. // 既沒有ref也沒有value也沒有子元素,Spring蒙圈了 error(elementName + " must specify a ref or value", ele); return null; } }
從代碼上看,對構造函數中屬性元素的解析經歷了一下幾個過程。
(1)略過descrition或者meta。
(2)提取constructor-arg上的ref和value屬性,以便於根據規則驗證正確性,其規則為在constructor-arg上不存在以下情況。
-
同時既有ref屬性又有value屬性。
-
存在ref屬性或者value屬性且又有子元素。
(3)ref屬性的處理。使用RuntimeBeanReference封裝對應的ref名稱,如:
<constructor-arg ref = "hello" />
(4)value屬性的處理。使用TypedStringValue封裝,如:
<constructor-arg value = "hello" />
(5)子元素的處理。如:
<constructor-arg> <map> <entry key = "key" value = "value" /> </map> </constructor-arg>
而對於子元素的處理,例如這里提到的在構造函數中又嵌入了子元素map是怎么實現的呢?parsePropertySubElement中實現了對各種子元素的分類處理。
@Nullable public Object parsePropertySubElement(Element ele, @Nullable BeanDefinition bd) { return parsePropertySubElement(ele, bd, null); } /** * Parse a value, ref or collection sub-element of a property or * constructor-arg element. * @param ele subelement of property element; we don't know which yet * @param defaultValueType the default type (class name) for any * {@code <value>} tag that might be created */ @Nullable public Object parsePropertySubElement(Element ele, @Nullable BeanDefinition bd, @Nullable String defaultValueType) { if (!isDefaultNamespace(ele)) { return parseNestedCustomElement(ele, bd); } else if (nodeNameEquals(ele, BEAN_ELEMENT)) { BeanDefinitionHolder nestedBd = parseBeanDefinitionElement(ele, bd); if (nestedBd != null) { nestedBd = decorateBeanDefinitionIfRequired(ele, nestedBd, bd); } return nestedBd; } else if (nodeNameEquals(ele, REF_ELEMENT)) { // A generic reference to any name of any bean. String refName = ele.getAttribute(BEAN_REF_ATTRIBUTE); boolean toParent = false; if (!StringUtils.hasLength(refName)) { // A reference to the id of another bean in a parent context. // 解析parent refName = ele.getAttribute(PARENT_REF_ATTRIBUTE); toParent = true; if (!StringUtils.hasLength(refName)) { error("'bean' or 'parent' is required for <ref> element", ele); return null; } } if (!StringUtils.hasText(refName)) { error("<ref> element contains empty target attribute", ele); return null; } RuntimeBeanReference ref = new RuntimeBeanReference(refName, toParent); ref.setSource(extractSource(ele)); return ref; } // 對idref元素的解析 else if (nodeNameEquals(ele, IDREF_ELEMENT)) { return parseIdRefElement(ele); } // 對value子元素的解析 else if (nodeNameEquals(ele, VALUE_ELEMENT)) { return parseValueElement(ele, defaultValueType); } // 對null子元素的解析 else if (nodeNameEquals(ele, NULL_ELEMENT)) { // It's a distinguished null value. Let's wrap it in a TypedStringValue // object in order to preserve the source location. TypedStringValue nullHolder = new TypedStringValue(null); nullHolder.setSource(extractSource(ele)); return nullHolder; } else if (nodeNameEquals(ele, ARRAY_ELEMENT)) { // 解析array子元素 return parseArrayElement(ele, bd); } else if (nodeNameEquals(ele, LIST_ELEMENT)) { // 解析list子元素 return parseListElement(ele, bd); } else if (nodeNameEquals(ele, SET_ELEMENT)) { // 解析set子元素 return parseSetElement(ele, bd); } else if (nodeNameEquals(ele, MAP_ELEMENT)) { // 解析map子元素 return parseMapElement(ele, bd); } else if (nodeNameEquals(ele, PROPS_ELEMENT)) { // 解析props子元素 return parsePropsElement(ele); } else { error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele); return null; } }
可以看到,在上面的函數中實現了所有可支持的子類的分類處理。
7. 解析子元素property
parsePropertyElements函數完成了對property屬性的提取,property使用方式如下:
<bean id = "test" class = "test.TestClass"> <property name = "testStr" value = "testStr" /> </bean>
具體的解析過程如下:
/** * Parse property sub-elements of the given bean element. */ public void parsePropertyElements(Element beanEle, BeanDefinition bd) { NodeList nl = beanEle.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) { parsePropertyElement((Element) node, bd); } } }
有了之前分析構造函數的經驗,這個函數並不難理解,無非是提取所有property的子元素,然后調用parsePropertyElement處理,parsePropertyElement代碼如下:
/** * Parse a property element. */ public void parsePropertyElement(Element ele, BeanDefinition bd) { String propertyName = ele.getAttribute(NAME_ATTRIBUTE); if (!StringUtils.hasLength(propertyName)) { error("Tag 'property' must have a 'name' attribute", ele); return; } this.parseState.push(new PropertyEntry(propertyName)); try { // 不允許多次對同一屬性配置 if (bd.getPropertyValues().contains(propertyName)) { error("Multiple 'property' definitions for property '" + propertyName + "'", ele); return; } Object val = parsePropertyValue(ele, bd, propertyName); PropertyValue pv = new PropertyValue(propertyName, val); parseMetaElements(ele, pv); pv.setSource(extractSource(ele)); bd.getPropertyValues().addPropertyValue(pv); } finally { this.parseState.pop(); } }
可以看到上面函數與構造函數注入方式不同的是將返回值使用PropertyValue進行封裝,並記錄在BeanDefinition中的propertyValues屬性中。
8. 解析子元素qualifier
對於qualifier元素的獲取,我們接觸更多的是注解的方式,在使用Spring框架中進行自動注入時,Spring容器中匹配的候選Bean數目必須有且只有一個。當找不到一個匹配的Bean時,Spring容器將拋出BeanCreationException異常,並指出必須至少擁有一個匹配的Bean。
Spring允許我們通過Qualifier指定注入Bean的名稱,這樣歧義就消除了,而對於配置方式使用如下:
<bean id="userChange" class="org.cellphone.uc.UserChange"> <qualifier type="org.springframework.beans.factory.annotation.Qualifier" value="uc"/> </bean>
其解析過程與之前大同小異,這里不再重復敘述。