spring的兩大核心:IOC(依賴注入)和AOP(面向切面),IOC本質上就是一個線程安全的hashMap,put和get方法就對應IOC容器的bean的註冊和獲取,spring經過讀取xml或者使用註解配置的類生成一個BeanDefinition
放入到容器中,獲取的時候經過BeanDefinition
的配置經過asm、反射等技術完成屬性的注入最終獲取一個bean,獲取bean的方法就getBean()
,咱們無需關心實現細節,直接按照spring提供的註解或者xml配置方式使用便可。java
雖然IOC本質上是一個線程安全的hashMap,使用時直接經過getBean()
獲取(@Autowired本質也是經過getBean()
獲取),這樣在使用bean實例的時候,就不用關心bean的建立,只管用就好了,IOC會在程序啓動時,自動將依賴的對象注入到目標對象中,很是簡單,省心。可是若是不瞭解IOC中bean的註冊和獲取原理,當使用Spring沒法獲取一個bean的時候,針對拋出的異常可能一頭霧水。node
IOC容器的實現包含了兩個很是重要的過程:web
BeanDefinition
註冊到IOC中BeanDefinition
實例化bean,並從IOC中經過getBean()
獲取//spring源碼中將一個bean的BeanDefinition放入到IOC中
this.beanDefinitionMap.put(beanName, beanDefinition);
複製代碼
//Spring源碼中經過beanName獲取一個bean
public Object getBean(String name) throws BeansException {
assertBeanFactoryActive();
return getBeanFactory().getBean(name);
}
複製代碼
這兩個過程還對應了兩個很是核心的接口:spring
BeanDefinitionRegistry和BeanFactory,一個向IOC中註冊BeanDefinition
,一個從IOC獲取Bean實例對象。設計模式
讀IOC源碼必須從瞭解BeanDefinition
開始,BeanDefinition
是一個接口,不管是經過xml聲明仍是經過註解定義一個bean實例,在IOC容器中第一步老是爲其對應生成一個BeanDefinition
,裏面包含了類的全部基本信息。安全
其中AbstractBeanDefinition
實現BeanDefinition
,這個接口有兩個子接口GenericBeanDefinition
和RootBeanDefinition
,再來看看BeanDefinition中的一些方法bash
-getBeanClassName()
獲取bean的全限定名,mvc
getScope()
獲取該類的做用域,app
isLazyInit()
該類是否爲懶加載,ide
getPropertyValues()
獲取配置的屬性值列表(用於setter注入),
getConstructorArgumentValues()獲取配置的構造函數值(用於構造器注入)等bean的重要信息,若是經過註解的方式,還會包含一些註解的屬性信息,
總而言之 ,BeanDefinition
包含了咱們定義的一個類的全部信息,而後經過 BeanDefinitionRegistry
接口的registerBeanDefinition
註冊到IOC容器中,最後經過BeanDefinition
結合asm,反射等相關技術,經過BeanFactory
接口的getBean()獲取一個實例對象好像也不是什麼困難的事情了。固然這只是表層的大體原理,實際上spring在實現IOC的時候,用了大量的設計模式,好比:單例模式、模板方法、工廠模式、代理模式(AOP基本上全是)等,此外面向對象的基本原則中的單一職責、開放封閉原則等隨處可見,具體的源碼解讀仍是在以後的筆記裏介紹。
讀取源碼須要經過調試去看,Spring啓動時首先會讀取xml配置文件,xml文件能夠從當前類路徑下讀,也能夠從文件系統下讀取,如下是用於調試的簡單案例:
@Test
public void testSpringLoad() {
ClassPathXmlApplicationContext application = new ClassPathXmlApplicationContext("spring/spring-context.xml");
BankPayService bankPayService = (BankPayService) application.getBean("bankPayService");
Assert.assertNotNull(bankPayService);
}
複製代碼
xml配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd"
default-lazy-init="true">
<bean id="bankPayService" class="com.yms.manager.serviceImpl.BankPayServiceImpl"/>
<context:property-placeholder location="classpath*:app-env.properties"/>
<context:component-scan base-package="com.yms.market"/>
</beans>
複製代碼
執行測試案例,確定是成功的,打斷點開始調試,首先會進入ClassPathXmlApplicationContext
的構造函數中:
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent){
super(parent);
//獲取當前環境 裝飾傳入的路徑
setConfigLocations(configLocations);
if (refresh) {
//程序入口
refresh();
}
}
複製代碼
構造函數中最關鍵的部分是refresh()方法,該方法用於刷新IOC容器數據,該方法由AbstractApplicationContext
實現。
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// 讓子類去刷新beanFactory 進入這裏查看
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
}
}
複製代碼
在refresh
方法中主要完成了加載xml文件的環境配置、xml文件讀取,註冊BeanFactoryPostProcessor
處理器、註冊監聽器等工做,其中比較核心的是第二行代碼ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory()
;
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//加載xml配置文件,生成BeanDefinition並註冊到IOC容器中
refreshBeanFactory();
//獲取加載完xml文件以後的beanFactory對象
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
複製代碼
refreshBeanFactory和getBeanFactory都是由AbstractApplicationContext
的子類AbstractRefreshableApplicationContext
實現的,
@Override
protected final void refreshBeanFactory() throws BeansException {
//若是beanFactory不爲空 ,清除老的beanFactory
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
//建立一個beanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
//設置bean是否容許覆蓋 是否容許循環依賴
customizeBeanFactory(beanFactory);
//加載beans聲明,即讀取xml或者掃描包 生成BeanDefinition註冊到IOC
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
複製代碼
在Spring中DefaultListableBeanFactory
是一個很是重要的類,它實現了BeanDefinitionRegistry
和BeanFactory
接口,而且完成了這兩個接口的具體實現,DefaultListableBeanFactory
的類圖以下:
咱們已經知道BeanDefinitionRegistry
完成了BeanDefinition
的註冊,BeanFactory
完成了getBean()
中bean的建立,其中xml讀取和bean的 註冊的入口就是loadBeanDefinitions(beanFactory)
這個方法,loadBeanDefinitions
是一個抽象方法,由類AbstractXmlApplicationContext
實現。loadBeanDefinitions
在AbstractXmlApplicationContext
有不少個重載方法,在不通階段方法使用的參數值不一樣,接下來看看各個loadBeanDefinitions
的調用順序:
建立XmlBeanDefinitionReader
對象
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
//Spring將讀取xml操做委託給了XmlBeanDefinitionReader對象
//而且傳入DefaultListableBeanFactory將生成的beandefinition註冊到IOC中
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
//設置Spring中bean的環境
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// 設置beanDefinitionReader驗證屬性,子類能夠重寫該方法使用自定義reader對象
initBeanDefinitionReader(beanDefinitionReader);
//經過beanDefinitionReader讀取xml
loadBeanDefinitions(beanDefinitionReader);
}
複製代碼
Spring對於讀取xml文件。並非由DefaultListableBeanFactory
親力親爲,而是委託給了XmlBeanDefinitionReader
,在該類內部會將xml配置文件轉換成Resource,Spring封裝了xml文件獲取方式,咱們使用ClassPathXmlApplicationContext
讀取xml,所以Spring會經過ClassLoader獲取當前項目工做目錄,並在該目錄下查找spring-context.xml文件,固然咱們還可使用FileSystemXmlApplicationContext
從文件系統上以絕對路徑的方式讀取文件
繼續查看第二個loadBeanDefinitions(beanDefinitionReader)
:
獲取配置文件路徑集合
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
//默認返回爲空 子類能夠實現該方法 讀取指定文件
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
//獲取咱們配置的xml文件路徑集合
String[] configLocations = getConfigLocations();
if (configLocations != null)
{
reader.loadBeanDefinitions(configLocations);
}
}
複製代碼
在這個方法中,最終獲取到了咱們經過ClassPathXmlApplicationContext
對象傳進來的xml配置文件路徑,而後由進入委託對象XmlBeanDefinitionReader
的loadBeanDefinitions
方法中:
@Override
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int counter = 0;
for (String location : locations) {
//循環讀取配置的全部配置文件路徑
counter += loadBeanDefinitions(location);
}
//返回這次加載的BeanDefinition個數
return counter;
}
複製代碼
在XmlBeanDefinitionReader
中,會循環讀取配置的全部配置文件路徑,並將讀取到的bean的聲明建立成BeanDefinition
,並將這次生成的數量返回,繼續查看loadBeanDefinitions
:
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
//實際執行到這裏
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
// Can only load single resources by absolute URL.
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}
複製代碼
這個方法就是上面所說的Spring將xml配置文件封裝成Resourse,最終獲取到Resourse的過程,這部分代碼沒什麼好看的,就是找到ClassPathResource
將xml路徑放進去,而後調用loadBeanDefinitions(resources)
,再來看這個方法:
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
複製代碼
這個方法將Resource封裝城了EncodedResource
對象,這個對象有一個屬性encoding,若是設置了xml文件的編碼,在這裏讀取xml文件的時候會根據該編碼進行讀取,繼續往下看:
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
//獲取前面加載到的xml配置資源文件
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
//之因此將路徑都封裝到Resource裏面,就是使其提供一個統一的getInputStream方法
//獲取文件流對象,XmlBeanDefinitionReader無需關心xml文件怎麼來的
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
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();
}
}
}
複製代碼
spring的代碼風格好像有一個特色,凡是真正開始作事的方法入口都會以do爲前綴,通過前面一系列對xml配置文件的設置,終於來到了doLoadBeanDefinitions(inputSource, encodedResource.getResource())
,在這個方法裏Spring會讀取每個Element標籤,並根據命名空間找到對應的NameSpaceHandler
去讀取解析Node生成BeanDefinition
對象。通過一系列操做Resouse最終會被轉換成InputSource
對象,這個類也沒什麼特別的,只是除了文件流以外多了一些參數而已,好比XSD,DTD的publicId,systemId約束,文件流的編碼等,最重要的仍是InputStream
,而後來看看這個方法:
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) {
//主要是校驗文件 經過查找DTD文件約束 校驗文件格式
//讀取xml文件生成DOC
Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource);
}
複製代碼
這個方法完成兩件事情:
經過inputSource
生成Document
對象
解析Document
並將生成BeanDefinition
註冊到IOC中
文件校驗和生成DOC文檔都是一些校驗操做,若是想自定義DTD文檔讓Spring加載,後面還會細說這部份內容,暫且放下,如今主要是看看IOC的BeanDefinition
的生成過程,接下來進入registerBeanDefinitions
:
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
documentReader.setEnvironment(this.getEnvironment());
//獲取加載以前IOC容器中的BeanDefinition數量
int countBefore = getRegistry().getBeanDefinitionCount();
//具體解析 註冊
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//返回本次加載的BeanDefinition數量
return getRegistry().getBeanDefinitionCount() - countBefore;
}
複製代碼
在這個方法裏,首先建立BeanDefinitionDocumentReader
,這是個接口用於完成BeanDefinition
向IOC容器註冊的功能,Spring只提供了惟一的實現DefaultBeanDefinitionDocumentReader
,查看registerBeanDefinitions
:
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
//root 在這個測試裏就是<beans></beans>
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}
複製代碼
該方法第一步首先回去xml的根節點,在這個測試xml裏就是標籤了,而後將根節點做爲參數傳入到下面的方法中解析doRegisterBeanDefinitions
:
protected void doRegisterBeanDefinitions(Element root) {
//獲取profile環境變量
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
Assert.state(this.environment != null, "Environment must be set for evaluating profiles");
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
//判斷該root下的bean是不是前面經過web.xml或者前面設置的bean的環境值
//若是不是 不須要解析當前root標籤
if (!this.environment.acceptsProfiles(specifiedProfiles)) {
return;
}
}
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(this.readerContext, root, parent);
//解析bean所要執行的方法,該方法實際是空的 容許子類擴展 去讀取自定義的node
//屬於模板方法
preProcessXml(root);
//真正解析beans的方法
parseBeanDefinitions(root, this.delegate);
//beans解析完以後須要執行的方法,實際也是經過子類擴展 是模板方法
postProcessXml(root);
this.delegate = parent;
}
複製代碼
這個方法看起來不少,其實真正核心的只有兩部分:
讀取beans的profile屬性,判斷是否屬於被激活的組,若是不是則不解析
建立BeanDefinitionParserDelegate
,委託該類執行beans解析工做。
最後經過parseBeanDefinitions(root, this.delegate)
方法將beans的解析交給BeanDefinitionParserDelegate
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)) {
//判斷是不是xml配置的bean,若是是則調用該方法解析
parseDefaultElement(ele, delegate);
}
else {
//不然按照自定義方式解析
delegate.parseCustomElement(ele);
}
}
}
}
else {
//不然按照自定義方式解析
delegate.parseCustomElement(root);
}
}
複製代碼
這個方法很重要,這裏已經開始解析<beans>
了,首先會判斷,要解析的root是不是beans標籤,若是是再判斷子元素是不是<bean>
元素,正常來說,咱們使用spring的時候都會再<beans>
標籤下配置,因此不出意外都會走到for循環裏,而後在for循環裏判斷是不是默認命名空間的時候就會發生變化:
若是是則走parseDefaultElement(ele, delegate);
若是是<mvc:annotation-driven>、 <context:component-scan base-package="***"/>
等則會走到自定義元素解析delegate.parseCustomElement(ele)裏
自定義解析加載到最後仍是會跟加載默認命名空間的bean同樣,因此在這裏只分析自定義命名空間的解析,不過值得提一下的是自定義解析方法裏會首先根據Element的命名空間找到NamespaceHandler
,而後由該NamespaceHanler
去解析
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
//獲取元素的命名空間
String namespaceUri = getNamespaceURI(ele);
//獲取命名空間解析器
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
//解析自定義元素
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
複製代碼
因爲後面會本身實現一個NamespaceHandler
解析自定義的標籤,會專門說明Spring如何查找NamespaceHandler
以及如何解析自定義元素,這裏只是瞭解下NamespaceHandler
的概念便可,接着看Spring解析<Beans>
查看parseDefaultElement:
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
//解析 import
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
//解析alias
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
//解析bean
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// 若是仍是beans 遞歸調用
doRegisterBeanDefinitions(ele);
}
}
複製代碼
這個方法裏面就是幾個if判斷,用於解析對應的標籤,其中import alias至關因而去讀取另外一個xml文件,最後仍是會調用解析bean,因此在這裏只看解析bean的方法processBeanDefinition(ele, delegate)
:
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
//將bean的屬性都讀取到到BeanDefinitionHolder上
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
//若是bean裏面有自定義標籤 來決定是否再次解析
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 將生成的BeanDefinitionHolder註冊到IOC中
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// 發送註冊事件
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
複製代碼
在這個方法裏 ,以前分析的邏輯才逐漸清晰起來,代碼的條例也很清晰
BeanDefinitionParserDelegate
將bean標籤的屬性讀取到BeanDefinitionHolder
對象中
若是beans下還有其餘自定義標籤決定是否有必要再次解析
將BeanDefinition
註冊到IOC中
發送註冊事件
首先來看第一步,讀取node屬性到BeanDefinitionParserDelegate
中
public AbstractBeanDefinition parseBeanDefinitionElement( Element ele, String beanName, BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName));
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
//獲取class全限定名
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
try {
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
//設置beanClass或者beanClassName
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
//讀取node屬性 將配置的屬性 塞入合適的字段中
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
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);
//解析屬性(setter注入)
parsePropertyElements(ele, bd);
parseQualifierElements(ele, bd);
bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
return bd;
}
finally {
this.parseState.pop();
}
return null;
}
複製代碼
這個方法完成了讀取一個bean包括將node屬性讀入到BeanDefinition
,讀取bean的構造函數配置(是構造器注入的前提),讀取bean的屬性配置(是setter注入的前提),其實將node屬性讀取到BeanDefinition
很簡單,僅僅是一一對應而已,真正的複雜點在於讀取構造函數參數、讀取屬性值參數。
來看下面一段配置:
<bean id="userDao" class="spring.road.beans.models.UserDao"/>
<!--setter注入-->
<bean id="beanService" class="spring.road.beans.models.BeanService">
<property name="mapper" ref="userDao"/>
<property name="name" value="lijinpeng"/>
<property name="sex" value="false"/>
</bean>
<!--構造器注入-->
<bean id="person" class="spring.road.beans.models.Person">
<constructor-arg name="age" value="26"/>
<constructor-arg name="name" value="dangwendi"/>
<constructor-arg name="userDao" ref="userDao"/>
<constructor-arg name="sex" value="true"/>
</bean>
複製代碼
這段配置使用了兩種注入方式:
setter注入就是咱們經過爲屬性賦值,若是屬性值都是string類型的還很好解決,若是pojo類的屬性值不是String,而是好比像Boolean、int、Date等這些數據的時候,必需要進行數據轉換操做才能夠在getBean()
的時候將property配置的屬性經過反射注入到對應的字段裏,這好像也不是什麼困難的事情,可是若是是ref引用類型呢,這個問題該如何解決呢?Spring很巧妙的解決了這個問題,用RuntimeBeanReference
來表示ref引用的數據,用TypedStringValue
表示普通String字符串。既然一個pojo類的全部配置都會讀取到BeanDefinition
,因此在xml中配置的屬性必然也會存儲到BeanDefinition
中,繼續看源碼會發現BeanDefinition
中用MutablePropertyValues
類表示屬性集合,該類中propertyValueList
就是property集合數據,Spring用PropertyValue存儲了property的name value信息。
//在BeanDefinition類中
MutablePropertyValues getPropertyValues();
//在MutablePropertyValues類中的屬性
private final List<PropertyValue> propertyValueList;
//在PropertyValue中的屬性
private final String name;
private final Object value;
複製代碼
根據上面xml配置能夠得知value可能須要類型轉換,也多是引用ref,鑑於getBean階段沒法直接賦值,因此須要一箇中間類保存數據,在getBean()
反射階段根據類型去轉換成對象,再次查看parsePropertyElements
方法:
public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
//解析bean下的property屬性節點
if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) {
parsePropertyElement((Element) node, bd);
}
}
}
複製代碼
parsePropertyElement
解析bean下的property屬性節點
public void parsePropertyElement(Element ele, BeanDefinition bd) {
//獲取property的name 這個很簡單
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;
}
//獲取獲取property的value 這個須要用中間類表示
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();
}
}
複製代碼
山重水複疑無路,柳暗花明又一村,下面方法便是實現過程:
public Object parsePropertyValue(Element ele, BeanDefinition bd, String propertyName) {
String elementName = (propertyName != null) ?
"<property> element for property '" + propertyName + "'" :
"<constructor-arg> element";
// Should only have one child element: ref, value, list, etc.
NodeList nl = ele.getChildNodes();
Element subElement = null;
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
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;
}
}
}
//是否有ref屬性
boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
//是否有value屬性
boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);
//ref和value只能存在一個
if ((hasRefAttribute && hasValueAttribute) ||
((hasRefAttribute || hasValueAttribute) && subElement != null)) {
error(elementName +
" is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele);
}
if (hasRefAttribute) {
String refName = ele.getAttribute(REF_ATTRIBUTE);
if (!StringUtils.hasText(refName)) {
error(elementName + " contains empty 'ref' attribute", ele);
}
//若是是ref 則轉換成RuntimeBeanReference
RuntimeBeanReference ref = new RuntimeBeanReference(refName);
ref.setSource(extractSource(ele));
return ref;
}
else if (hasValueAttribute) {
//若是是String則轉換成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.
error(elementName + " must specify a ref or value", ele);
return null;
}
}
複製代碼
看有註釋行的代碼,結合上面的分析,大概能瞭解setter注入的第一階段BeanDefinition
保存屬性數據的方式了,在調用BeanFactory
的getBean()
方法時,在反射階段獲取到值對象時能夠根據類型去獲取值,若是是TypedStringValue
則只需校驗值是否應該轉換,若是須要轉換便可,至於如何轉換,若是是RuntimeBeanReference
更簡單了,直接經過getBean()
獲取就行了,請記住這兩個類型,在分析getBean()
階段屬性值解析的時候就會用到他們。
構造器注入其實比setter注入要稍微麻煩一點,之因此說麻煩其實就是要藉助相關技術去實現,由於構造器可能會有不少重載,在xml配置中若是參數順序不一樣可能會調用不一樣的構造函數,致使注入失敗,因此若是要保存構造參數值,必須匹配到惟一合適的構造函數,而且在xml配置的constructor-arg
必須按照必定規則與匹配的構造函數一一對應,才能夠在getBean()
階段注入成功。
當我本身嘗試去寫的時候,覺得只須要經過反射獲取構造函數的參數名便可,可是很不幸,經過反射拿到的參數名是諸如arg1 arg2 這樣的name,因此只能經過讀取類的字節碼文件了, 之前看過《深刻了解java虛擬機》這本書,知道能夠經過讀取字節碼文件的方式獲取參數名,可是裏面的各類索引,字段表集合啊什麼的想記住真的好難,並且個人水平還遠遠達不到那個高度,因此就用現成的吧, 我當時是用javassite實現的,看了spring的源碼,發現spring是用asm實現的,固然這個階段是在getBean()
階段實現的,之因此介紹是由於必需要先了解爲何Spring要這麼保存構造參數,後面的getBean在分析這塊源碼,仍是先來看看構造函數參數在BeanDefinition的保存吧。
spring容許在xml中經過index、name、type來指定一個參數,在BeanDefinition
中使用ConstructorArgumentValues
存儲構造函數參數集合,在ConstructorArgumentValues包含了兩個集合一個配置了索引的indexedArgumentValues
參數集合,另外一個沒有配置索引的enericArgumentValues
構造函數參數集合,而後構造函數參數值用內部類ValueHolder
表示,這個類裏包含了參數的value,類型,參數名等。
//在BeanDefinition類中
ConstructorArgumentValues getConstructorArgumentValues();
//在ConstructorArgumentValues類中
//使用了索引的構造函數參數值集合
private final Map<Integer, ValueHolder> indexedArgumentValues = new LinkedHashMap<Integer, ValueHolder>(0);
//未使用索引的構造函數參數值集合
private final List<ValueHolder> genericArgumentValues = new LinkedList<ValueHolder>();
//在ValueHolder中的屬性
private Object value;
private String type;
private String name;
private Object source;
複製代碼
構造函數參數的存儲結構分析完了,接下來看看代碼吧,其實存儲和屬性值的存儲是同樣的 ,這裏只看關鍵的代碼:
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)) {
parseConstructorArgElement((Element) node, bd);
}
}
}
複製代碼
解析constructor-arg
標籤parseConstructorArgElement
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);
//若是index不爲空 保存到indexedArgumentValues集合中
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));
//將value轉換成RuntimeBeanReference或者TypedStringValue
Object value = parsePropertyValue(ele, bd, null);
ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
//保存type
if (StringUtils.hasLength(typeAttr)) {
valueHolder.setType(typeAttr);
}
//保存name
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);
}
}
//index未配置 保存到普通集合中genericArgumentValues
else {
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();
}
}
}
複製代碼
至此,一個xml配置的bean被徹底存放到了BeanDefinition
中,其實基於掃描註解配置也是同樣的,只不過在作不少清理工做,針對下面配置簡要說明下基於註解的處理:
<context:component-scan base-package="com.yms.market"/>
複製代碼
首先spring讀取到這個node,會查找該node的NameSpaceHandler
,而後調用parse方法解析
而後讀取到屬性base-package
,轉換成對應路徑後查找該路徑下全部的class文件
讀取class文件的註解,查看是否實現了特定註解,若是實現了註解則處裏方式與xml配置的處理相同,不然不處理。
真實的處理過程比較複雜,也是用了不少設計模式,用了不少類來處理,可是我想說的是,不管是用過註解仍是經過xml,最終的處理方式都是同樣的,都是先生成一個BeanDefinition
註冊到IOC中,而後經過getBean()
獲取。
BeanDefinition
建立完成了,還差最後一步,將生成的BeanDefinition
註冊到IOC中,這就必須往回看了。
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
//將bean的屬性都讀取到到BeanDefinitionHolder上
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
//若是bean裏面有自定義標籤 來決定是否再次解析
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 將生成的BeanDefinitionHolder註冊到IOC中
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// 發送註冊事件
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
複製代碼
這段代碼應該還很熟悉,此次看最後一步registerBeanDefinition
public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {
// 獲取beanName
String beanName = definitionHolder.getBeanName();
//註冊BeanDefinition,key爲beanName,value是BeanDefinition
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// 若是配置別名的話獲取別名
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String aliase : aliases) {
//註冊別名
registry.registerAlias(beanName, aliase);
}
}
}
複製代碼
該方法完成了如下事情
BeanDefinition
用beanName做爲key註冊到IOC容器中 再來看具體的註冊過程registry.registerBeanDefinition
,註冊是調用BeanDefinitionRegistry
的registerBeanDefinition
方法,在剛開始的分析說過DefaultListableBeanFactory
實現了BeanDefinitionRegistry
和BeanFactory
,並且實現了具體邏輯,下面的內容就是Spring註冊的過程,爲了看的清晰我省去了不少異常和無用的代碼:
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
//驗證
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
}
}
//這裏保證了線程安全
synchronized (this.beanDefinitionMap) {
BeanDefinition oldBeanDefinition = this.beanDefinitionMap.get(beanName);
if (oldBeanDefinition != null) {
if (!this.allowBeanDefinitionOverriding) {
//不容許覆蓋拋出異常
}
else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
if (this.logger.isWarnEnabled()) {
}
else {
if (this.logger.isInfoEnabled()) {
}
}
}
else {
this.beanDefinitionNames.add(beanName);
this.frozenBeanDefinitionNames = null;
}
//註冊進去嘍
this.beanDefinitionMap.put(beanName, beanDefinition);
}
resetBeanDefinition(beanName);
}
複製代碼
到此爲止,IOC容器的第一步爲bean生成BeanDefinition
並註冊到IOC容器中完成,接下來就是第二步,經過BeanFactory實現依賴注入了。