一锋谐、概述
1. 為什么需要理解XML配置解析?
我是一個(gè)剛交了一年社保的一年工作經(jīng)驗(yàn)的小老弟,從大學(xué)剛接觸軟件開(kāi)發(fā)到畢業(yè)正式入職所接觸到JavaSE或JavaEE項(xiàng)目中侥加,基本都會(huì)使用Spring作為項(xiàng)目的對(duì)象管理容器褒傅。尤其在大學(xué)期間弃锐,WEB項(xiàng)目使用Spring的時(shí)候基本都是通過(guò)配置applicationContext.xml全局配置文件,然后使用web.xml配置ContextLoaderListener加載這個(gè)全局配置文件去初始化容器殿托。
早期版本的Spring霹菊,只能通過(guò)加載XML去啟動(dòng)配置文件,演變到現(xiàn)在也可以通過(guò)掃描類和注解去啟動(dòng)Spring容器支竹,但Spring的本質(zhì)和核心是不會(huì)變的旋廷。通過(guò)學(xué)習(xí)XML配置解析可以起到窺一斑而知全貌的效果,當(dāng)你在研究掃描類和注解的時(shí)候會(huì)突然發(fā)現(xiàn)礼搁,大致的流程基本是很相似的柳洋。
2. BeanDefinition的注冊(cè)有必要了解嗎?
當(dāng)你成功啟動(dòng)了一個(gè)Spring容器叹坦,有一個(gè)Bean配置的是懶加載熊镣,那么此時(shí)這個(gè)Bean在容器中是否存在?或者是以什么方式存在?
答案就是在一個(gè)需要被實(shí)例化的類還沒(méi)有創(chuàng)建的時(shí)候绪囱,它在Spring中就是一個(gè)BeanDefinition测蹲,也就是這個(gè)Bean的定義,BeanDefinition包含了這個(gè)對(duì)象實(shí)例化所需要用到的所有信息鬼吵。(詳情可見(jiàn)之前的文章有描述過(guò))
由此可見(jiàn)BeanDefinition注冊(cè)是Spring容器的啟動(dòng)中非常重要的一環(huán)扣甲,BeanDefinition的注冊(cè)不難,簡(jiǎn)單來(lái)講就是Spring中有一個(gè)裝BeanDefiniton的Map齿椅,當(dāng)你掃描解析完XML的Bean標(biāo)簽之后就會(huì)注冊(cè)到這個(gè)Map中琉挖。
二、ClassPathXmlApplicationContext解析XML
1. ClassPathXmlApplicationContext的解析XML入口方法
// 方法在AbstractApplicationContext的709行左右
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
/**
* 主要內(nèi)容:配置信息到beanDefinition的轉(zhuǎn)換過(guò)程
* 模版設(shè)計(jì)模式
* Spring中運(yùn)用的最多的設(shè)計(jì)模式
* obtainFreshBeanFactory方法是一個(gè)模版方法
* refreshBeanFactory方法是一個(gè)鉤子方法涣脚,需要子類去實(shí)現(xiàn)
*/
refreshBeanFactory();
return getBeanFactory();
}
這個(gè)方法在容器的refresh()流程中被調(diào)用示辈,ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); 實(shí)際調(diào)用的是ClassPathXmlApplicationContext的父類AbstractApplicationContext的方法。(模版方法模式)
obtainFreshBeanFactory()方法也是一個(gè)模版方法遣蚀,其中定義了一個(gè)鉤子方法refreshBeanFactory()矾麻,refreshBeanFactory()方法在AbstractApplicationContext中是一個(gè)抽象方法,需要子類去實(shí)現(xiàn)芭梯。
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
1.1 XML解析前創(chuàng)建工廠
經(jīng)過(guò)查詢?nèi)萜黝惖睦^承結(jié)構(gòu)险耀,發(fā)現(xiàn)最終調(diào)用的是AbstractRefreshableApplicationContext的refreshBeanFactory()方法。該方法中不重要的掐頭去尾已經(jīng)省略了玖喘,主要有記個(gè)關(guān)鍵點(diǎn)和概念需要理解一下甩牺,就是BeanFactory是一個(gè)實(shí)例工廠,主要是用來(lái)管理Bean的累奈,容器Context包含了BeanFactory柴灯。
protected final void refreshBeanFactory() throws BeansException {
// ...
try {
/**
* 創(chuàng)建beanFactory
* beanFactory:實(shí)例工廠,無(wú)論什么實(shí)例只要被spring管理费尽,都在這個(gè)工廠里面,主要是bean的相關(guān)操作赠群。
* Context和beanFactory的關(guān)系:從屬關(guān)系。 不重要理解即可
*/
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
/*
* 容器重要屬性設(shè)置:
* 1.是否允許同名bean
* 2.是否允許循環(huán)依賴
*/
customizeBeanFactory(beanFactory);
/**
* 解析XML旱幼,注冊(cè)beanDefinition 重要:* * * * *
*/
loadBeanDefinitions(beanFactory);
// ...
}
// ...
}
其中我們創(chuàng)建的工廠就是Spring默認(rèn)的工廠查描,DefaultListableBeanFactory。
protected DefaultListableBeanFactory createBeanFactory() {
return new DefaultListableBeanFactory(getInternalParentBeanFactory());
}
1.2 創(chuàng)建工廠后設(shè)置一些參數(shù)
在上述方法流程中會(huì)執(zhí)行customizeBeanFactory()柏卤,這個(gè)方法會(huì)設(shè)置一些工廠的屬性冬三,是否允許同名bean存在和是否允許循環(huán)依賴。(看代碼注釋即可)
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
/*
* 是否允許同名的bean存在
* 默認(rèn)是不允許的
*/
if (this.allowBeanDefinitionOverriding != null) {
beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
/*
* 是否允許循環(huán)依賴
* 默認(rèn)是允許的
*/
if (this.allowCircularReferences != null) {
beanFactory.setAllowCircularReferences(this.allowCircularReferences);
}
}
1.3 執(zhí)行加載XML注冊(cè)BeanDefinition的方法缘缚。
同樣是在1.1的方法流程中勾笆,執(zhí)行了loadBeanDefinitions(beanFactory);方法并傳入當(dāng)前創(chuàng)建的BeanFactory。
loadBeanDefinition(DefaultListableBeanFactory beanFactory)這個(gè)方法在AbstractRefreshableApplicationContext是一個(gè)抽象方法桥滨,也需要子類去實(shí)現(xiàn)它窝爪。經(jīng)過(guò)查詢其繼承結(jié)構(gòu)發(fā)現(xiàn)是AbstractXmlApplicationContext實(shí)現(xiàn)的弛车。
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
throws BeansException, IOException;
在AbstractXmlApplicationContext抽象類提佣,loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法主要是采用委托設(shè)計(jì)模式去實(shí)現(xiàn)的油狂。 也就是說(shuō)容器將XML的解析委托給了XmlBeanDefinitionReader類的實(shí)例淳玩,自身并沒(méi)有詳細(xì)的解析流程莺掠,將自身作為參數(shù)傳入構(gòu)造器,使得XmlBeanDefinitionReader類的實(shí)例可以再解析XML的過(guò)程中在工廠中注冊(cè)BeanDefinition肃拜。
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
/**
* 創(chuàng)建XML解析器笤闯,這里涉及到了委托設(shè)計(jì)模式
* 委托設(shè)計(jì)模式:【專人專事】
* 主類要持有委托類的引用响逢,在實(shí)現(xiàn)服務(wù)時(shí)把具體實(shí)現(xiàn)的邏輯委托給委托類去實(shí)現(xiàn)望蜡。
* 創(chuàng)建XML解析器
* Create a new XmlBeanDefinitionReader for the given BeanFactory.
*/
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(this.getEnvironment());
/**
* 使得XmlBeanDefinitionReader持有當(dāng)前Spring容器的對(duì)象唤崭,這個(gè)很重要需要記住。
* 把當(dāng)前的Spring容器當(dāng)作ResourceLoader注入到XmlBeanDefinitionReader
* ClasspathXmlApplicationContext 類的繼承鏈最頂端是一個(gè) ResourceLoader
*/
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
initBeanDefinitionReader(beanDefinitionReader);
/**
* 執(zhí)行解析脖律,解析XML為BeanDefinition過(guò)程很重要
*/
loadBeanDefinitions(beanDefinitionReader);
}
// XmlBeanDefinitionReader 重載的 loadBeanDefinitions 方法谢肾,拿URL,直接委托状您。
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
// ...
//沒(méi)有多余的代碼簡(jiǎn)單的條件直接委托給其他對(duì)象去執(zhí)行,就是專人專事兜挨。
String[] configLocations = getConfigLocations();
if (configLocations != null) {
// 走這個(gè)方法 重要 : * * * * *
reader.loadBeanDefinitions(configLocations);
}
}
2. 解析XML從AbstractBeanDefinitionReader開(kāi)始
2.1 解析URL的心路歷程
緊接上文執(zhí)行reader.loadBeanDefinitions(configLocations);膏孟,實(shí)際調(diào)用的是AbstractBeanDefinitionReader類的loadBeanDefinitions(String... locations) 方法。loadBeanDefinitions(String... locations)方法又會(huì)循環(huán)遍歷這個(gè)locations去挨個(gè)解析location拌汇。
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int count = 0;
/**
* 循環(huán)遍歷解析各個(gè)路徑下的xml文件
*/
for (String location : locations) {
count += loadBeanDefinitions(location);
}
return count;
}
loadBeanDefinitions(String location)方法會(huì)調(diào)用另一個(gè)重載的方法柒桑,loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources)方法,只不過(guò)actualResources參數(shù)傳的是null噪舀。
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
/**
* 根據(jù)地址解析XML文件
*/
return loadBeanDefinitions(location, null);
}
2.2 從解析URL到解析Resource的心路歷程
下面代碼先執(zhí)行了 getResourceLoader() 魁淳,這個(gè)ResourceLoader就是容器本身,在創(chuàng)建XmlBeanDefinitionReader時(shí)候持有的容器對(duì)象与倡。因?yàn)槿萜鞅旧眄攲訉?shí)現(xiàn)了ResourcePatternResolver接口界逛,所以可以通過(guò)getResources方法得到資源數(shù)組(Resource[])。
然后再次調(diào)用重載的遍歷解析資源的loadBeanDefinitions(Resource... resources)方法纺座。
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
/**
* resourceLoader在loadBeanDefinitions前被注入到reader對(duì)象中
* 這個(gè)resourceLoader其實(shí)就是容器本身對(duì)象
*/
ResourceLoader resourceLoader = getResourceLoader();
// .......
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
/**
* Spring.xml對(duì)象封裝成一個(gè)Resource對(duì)象息拜,Resource對(duì)象中有文件對(duì)象,文件流净响。
*/
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
/**
* 解析這些Resource對(duì)象
*/
int count = loadBeanDefinitions(resources);
}
// .......
}
轉(zhuǎn)來(lái)轉(zhuǎn)去少欺,還沒(méi)有結(jié)束,批量解析資源的loadBeanDefinitions遍歷調(diào)用解析單個(gè)資源的loadBeanDefinitions方法馋贤。
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int count = 0;
for (Resource resource : resources) {
/**
* AbstractBeanDefinitionReader父類的方法
* 模版方法
*/
count += loadBeanDefinitions(resource);
}
return count;
}
2.3 從解析Resource到解析InputStream的心路歷程
上面遍歷調(diào)用解析Resource的是赞别,調(diào)用的是BeanDefinitionReader接口的這個(gè)方法
int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
實(shí)際執(zhí)行的是XmlBeanDefinitionReader實(shí)現(xiàn)類的方法,然后又會(huì)將這個(gè)resource編碼后再次調(diào)用重載的方法配乓。
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException
{
/**
* 實(shí)現(xiàn)類實(shí)現(xiàn)的鉤子方法
* resource -> Encoded 將流變成有編碼的流
*/
return loadBeanDefinitions(new EncodedResource(resource));
}
我們來(lái)查看下加載有編碼的流的方法是什么樣子的仿滔。就是獲取編碼流中的InputStream然后繼續(xù)去解析這個(gè)流惠毁。
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
//......
try {
/**
* 拿到文件流
*/
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
/*
* InputSource是XML文檔解析的類 , JDK的類堤撵。專門(mén)做XML文檔解析的一個(gè)類
*/
InputSource inputSource = new InputSource(inputStream);
//......
/**
* 加載Bean的定義信息
*/
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
//......
}
2.4 Java 的 SAX 解析XML
承接2.3繼續(xù)到了doLoadBeanDefinitions(InputSource inputSource, Resource resource)看見(jiàn)了曙光仁讨,終于是將inputSource和resource解析成了Document對(duì)象。提到Document對(duì)象我們都不會(huì)陌生实昨,就是XML解析出的文檔信息洞豁,里面包含了Node標(biāo)簽,也就是我們配置的Bean荒给。
然后就是注冊(cè)BeanDefinition的方法丈挟,在了解BeanDefinition注冊(cè)之前,需要詳細(xì)的了解一下doLoadDocument(InputSource inputSource, Resource resource) 這個(gè)方法的具體實(shí)現(xiàn)志电。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
//...........
/**
* xml文件流對(duì)象解析為document對(duì)象
*/
Document doc = doLoadDocument(inputSource, resource);
/**
* 將document對(duì)象解析為BeanDefinition曙咽,并返回解析的數(shù)量
*/
int count = registerBeanDefinitions(doc, resource);
//...........
}
這個(gè)方法的實(shí)現(xiàn)很長(zhǎng)一串,我們點(diǎn)進(jìn)loadDocument( ... ... )這個(gè)方法挑辆。
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
/**
* 實(shí)際解析XML InputSource的方法例朱,解析成一個(gè)Document對(duì)象
*/
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
可以看著這是一個(gè)SAX解析的常規(guī)流程,創(chuàng)建工廠鱼蝉,創(chuàng)建builder洒嗤,解析輸入流,然后返回Document魁亦。
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
/**
* XML解析封裝document對(duì)象常規(guī)流程 SAX解析常用
* 1.創(chuàng)建工廠
* 2.創(chuàng)建builder
* 3.解析輸入流
*/
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isTraceEnabled()) {
logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
/**
* 解析成Document對(duì)象并返回
*/
return builder.parse(inputSource);
}
到此為止將一個(gè)XML解析成Document的內(nèi)容就結(jié)束了渔隶,當(dāng)然Document還需要進(jìn)一步解析成一個(gè)個(gè)Element并提取每個(gè)元素的信息如果是Bean的話就組冊(cè)BeanDefinition。
3. 解析Document洁奈,注冊(cè)BeanDefinition
3.1 開(kāi)始解析Document
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
//...........
/**
* xml文件流對(duì)象解析為document對(duì)象
*/
Document doc = doLoadDocument(inputSource, resource);
/**
* 將document對(duì)象解析為BeanDefinition间唉,并返回解析的數(shù)量
*/
int count = registerBeanDefinitions(doc, resource);
//...........
}
回到doLoadBeanDefinitions這個(gè)方法,執(zhí)行完將Resource轉(zhuǎn)換為Document后利术,著重看解析document并注冊(cè)BeanDefinition的內(nèi)容呈野。下面我們看registerBeanDefinitions(doc, resource)方法具體實(shí)現(xiàn)。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
/**
* 委托設(shè)計(jì)模式:
* 把解析的工作委托給BeanDefinitionDocumentReader對(duì)象
* 已把xml -> document 印叁,繼續(xù)委托給DocumentReader解析document
*/
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
/**
* 解析Document對(duì)象并注冊(cè)BeanDefiniton
* 方法:registerBeanDefinitions(args1,args2)
* 參數(shù):Document doc, XmlReaderContext readerContext
*/
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
可以看到我們解析Document對(duì)象依舊是吧這個(gè)解析的過(guò)程委托給了BeanDefinitionDocumentReader類的實(shí)例际跪。
上述執(zhí)行流程中關(guān)注下 createReaderContext(resource) ,這個(gè)方法是返回XmlReaderContext類的實(shí)例喉钢,并且在構(gòu)造實(shí)例的過(guò)程中姆打,持有了當(dāng)前的XmlBeanDefinitionReader的對(duì)象。之前我們提到過(guò)XmlBeanDefinitionReader持有了當(dāng)前容器的引用肠虽,所以在createReaderContext(resource)方法的返回值XmlReaderContext實(shí)例中也間接持有了當(dāng)前容器的引用幔戏,它可以去注冊(cè)BeanDefinition。
繼續(xù)看documentReader.registerBeanDefinitions這個(gè)調(diào)用方法的具體實(shí)現(xiàn)税课。
public interface BeanDefinitionDocumentReader {
void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
throws BeanDefinitionStoreException;
}
來(lái)到了BeanDefinitionDocumentReader接口闲延,最終調(diào)用的是下面的DefaultBeanDefinitionDocumentReader實(shí)現(xiàn)類的方法痊剖。可以看到方法將readerContext的實(shí)例持有到自身的對(duì)象之中垒玲。并開(kāi)始解析根結(jié)點(diǎn)陆馁。
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
/**
* 把Document根結(jié)點(diǎn) (root) 傳進(jìn)去
*/
doRegisterBeanDefinitions(doc.getDocumentElement());
}
3.2 具體Element標(biāo)簽的解析
來(lái)到根節(jié)點(diǎn)Element的具體解析實(shí)現(xiàn),其實(shí)可以忽略大部分的內(nèi)容合愈,我們只需要看parseBeanDefinitions(root, this.delegate)這個(gè)主要的解析標(biāo)簽方法就好了叮贩,因?yàn)楂@取delegate,這個(gè)delegate是用來(lái)解析自定義標(biāo)簽的佛析,自定義標(biāo)簽解析內(nèi)容很多益老,所以需要總結(jié)一篇詳細(xì)的文章來(lái)描述自定義標(biāo)簽的解析,這里就暫時(shí)忽略了寸莫。
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
/**
* 主要是獲取delegate捺萌,用來(lái)委托給第三方解析起解析自定義標(biāo)簽
*/
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
// ...... 省略多行代碼
}
/**
* 預(yù)處理模版方法 暫時(shí)無(wú)具體實(shí)現(xiàn)
*/
preProcessXml(root);
/**
* 主要看這個(gè)方法,標(biāo)簽的具體解析過(guò)程
*/
parseBeanDefinitions(root, this.delegate);
/**
* 后處理模版方法 暫時(shí)無(wú)具體實(shí)現(xiàn)
*/
postProcessXml(root);
this.delegate = parent;
}
這里他首先會(huì)獲取根結(jié)點(diǎn)中的所有子結(jié)點(diǎn)膘茎,也就是我們配置的Bean桃纯,Import等傳統(tǒng)標(biāo)簽,當(dāng)然也有context-componentsacn等自定義的標(biāo)簽披坏。這里我們主要看默認(rèn)的傳統(tǒng)標(biāo)簽解析态坦。自定義標(biāo)簽解析需要詳細(xì)的總結(jié)一篇文章來(lái)描述。
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
/**
* 獲取根節(jié)點(diǎn)中所有的子節(jié)點(diǎn)
*/
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)) {
/**
* 默認(rèn)標(biāo)簽解析
*/
parseDefaultElement(ele, delegate);
}
else {
/**
* 自定義標(biāo)簽解析刮萌,委托給delegate解析
*/
delegate.parseCustomElement(ele);
}
}
}
}else {delegate.parseCustomElement(root);}
}
默認(rèn)標(biāo)簽中我們常用的也就是<bean /> <import />標(biāo)簽驮配,最重要的是<bean />標(biāo)簽娘扩,這個(gè)是我們最最常用的着茸。我給標(biāo)了重要程度五顆星。進(jìn)入processBeanDefinition(ele, delegate);這個(gè)方法詳細(xì)的看下琐旁。
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
/**
* import標(biāo)簽的解析涮阔,重要程度:*
*/
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
/**
* alias標(biāo)簽的解析 別名標(biāo)簽,重要程度:*
*/
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
/**
* bean標(biāo)簽的解析灰殴,重要程度:* * * * *
*/
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
/**
* recurse 敬特,不重要 外層的beans
*/
doRegisterBeanDefinitions(ele);
}
}
到這里我們看其實(shí)我之前看源碼的注釋描述的還是很多的,可見(jiàn)這里面涉及到了很多知識(shí)點(diǎn)牺陶,但是不重要伟阔,我們只關(guān)注 Element 具體是如何解析的,并且這個(gè)解析好的BeanDefinitionHolder注冊(cè)到了容器的哪個(gè)地方掰伸,有沒(méi)有什么業(yè)務(wù)規(guī)則皱炉。
3.2.1 大致描述processBeanDefinition方法的實(shí)現(xiàn):
- 將element解析成BeanDefinitionHolder
- 裝飾這個(gè)BeanDefinitionHolder如果需要的話
- 注冊(cè)這個(gè)BeanDefinitionHolder
接下來(lái)會(huì)把這三點(diǎn)拆分成3個(gè)小的點(diǎn)來(lái)描述。
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
/**
* 方法:parseBeanDefinitionElement
* 解析document封裝成beanDefinition
*
* 重要程度:* * * * *
*/
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
/**
* 沒(méi)吊用狮鸭,但需要學(xué)習(xí)設(shè)計(jì)思想合搅。
*
* 裝飾者設(shè)計(jì)模式多搀,加上SPI(service provider interface)的設(shè)計(jì)思想SPI(Mybatis,Spring灾部,Dubbo的SPI擴(kuò)展)
* SPI用來(lái)解耦康铭,擴(kuò)展的設(shè)計(jì)思想,在不改變?cè)写a的前提下進(jìn)行開(kāi)發(fā)赌髓,實(shí)現(xiàn)熱插拔从藤。和策略模式有點(diǎn)像
* SPI簡(jiǎn)單來(lái)講就是加載配置文件通過(guò)配置文件類中的類路徑信息,再不修改核心代碼的前提下擴(kuò)展功能春弥。
*
* 內(nèi)容:
* 1.namespace uri和解析類建立映射關(guān)系
* 2.解析類實(shí)現(xiàn)統(tǒng)一接口完成多態(tài)
* 3.beandefinition的不斷包裝/裝飾
*
* 不使用構(gòu)造器和屬性注入呛哟,在bean標(biāo)簽中使用前綴屬性 p: c:進(jìn)行注入 ,在xmlns中加入schema/p schema/c 約束
*
* 重要程度:*
*/
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
/**
* 邏輯很簡(jiǎn)單匿沛,構(gòu)建 別名 -> beanname -> beandefinition 的映射扫责。
*
* 完成document到BeanDefinition對(duì)象的轉(zhuǎn)換,對(duì)BeandDefinition對(duì)象進(jìn)行緩存注冊(cè)
* 重要程度: * * *
*/
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));
}
}
3.3 <bean /> 標(biāo)簽元素解析成BeanDefinitionHolder的過(guò)程
先進(jìn)入 delegate.parseBeanDefinitionElement(ele);的方法看一下逃呼,依然是重載外面包個(gè)殼鳖孤。
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
return parseBeanDefinitionElement(ele, null);
}
再次進(jìn)入parseBeanDefinitionElement(ele, null);這個(gè)方法。
3.3.1 parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) 外層提取信息具體實(shí)現(xiàn)
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
// 解析過(guò)程不復(fù)雜抡笼,但是解析項(xiàng)比較多苏揣。BeanDifinition的屬性比較多。
/**
* 參數(shù)提韧埔觥:提取標(biāo)簽的 ID 和 name
*/
String id = ele.getAttribute(ID_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
/**
* 標(biāo)簽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.isTraceEnabled()) {
logger.trace("No XML 'id' specified - using '" + beanName +
"' as bean name and " + aliases + " as aliases");
}
}
/**
* 檢查beanname的唯一性
*/
if (containingBean == null) {
checkNameUniqueness(beanName, aliases, ele);
}
/**
* 解析這個(gè)元素 ele 剩余的全部的屬性
* 返回 名為beanName的 AbstractBeanDefinition 對(duì)象
* 詳細(xì)的解析過(guò)程:* * * * *
*/
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
// ............. 省略了一大堆代碼
String[] aliasesArray = StringUtils.toStringArray(aliases);
/**
* BeanDefinition再次進(jìn)行一個(gè)包裝 -> BeanDefinitionHolder
*
* BeanDefinitionHolder是BeanDefinition平匈,名稱,別名的組裝
*/
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
return null;
}
代碼概述:
- 先提取了標(biāo)簽的id和name藏古,如果name的配置了多個(gè)別名還要拆一下增炭,如果id是空的直接拿第一個(gè)別名用一下。
- 因?yàn)楫?dāng)前參數(shù)containingBean是空的拧晕,所以要檢測(cè)一下Bean的唯一性隙姿,也就是beanName和別名的唯一性。方法里面內(nèi)容不多很好理解厂捞,自己點(diǎn)進(jìn)去看下就好了输玷。
- parseBeanDefinitionElement方法是,具體的提取信息創(chuàng)建BeanDefinition靡馁,提取屬性設(shè)置屬性欲鹏。(這個(gè)方法最重要)
- 包裝成BeanDefinitionHolder返回,BeanDefinitionHolder和BeanDefinition區(qū)別就是BeanDefinitionHolder包裝了除了類定義外的beanName和別名數(shù)組臭墨。
3.3.2 parseBeanDefinitionElement(Element ele, String beanName, @Nullable BeanDefinition containingBean) 屬性設(shè)置具體實(shí)現(xiàn)
進(jìn)入 parseBeanDefinitionElement(ele, beanName, containingBean); 方法查看具體實(shí)現(xiàn)赔嚎。
@Nullable
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, @Nullable BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName));
/*
* 獲取它的class屬性,如果有裙犹。
*/
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
/*
* 獲取它的parent屬性尽狠,如果有衔憨。
*/
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
try {
/**
* 創(chuàng)建一個(gè)GenericBeanDefinition對(duì)象并設(shè)置父對(duì)象id
*/
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
/**
* 重要: * * * * *
* 解析bean標(biāo)簽的屬性把屬性設(shè)置到對(duì)象中
*/
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
/*
* Bean標(biāo)簽的Meta子標(biāo)簽的解析,沒(méi)什么用
*/
parseMetaElements(ele, bd);
/**
* lookup-method demo05
* 替代某方法的返回值袄膏。 使用代理實(shí)現(xiàn)践图。
* 重要程度:* *
*/
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
/**
* replace-method demo06
* arg-type子標(biāo)簽區(qū)分重載參數(shù)類型
* 在不改變?cè)写a的基礎(chǔ)上進(jìn)行增強(qiáng),可以用AOP替代.
* 重要程度:* *
*/
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
/**
* 解析Bean中的constructor-arg標(biāo)簽
* 重要程度:* *
*/
parseConstructorArgElements(ele, bd);
/**
* 解析Bean中的property, 屬性注入可以用@Value替代
* 重要程度:* *
*/
parsePropertyElements(ele, bd);
/**
* @Qualifier指定注入哪個(gè)bean
* 重要程度:* *
*/
parseQualifierElements(ele, bd);
// .............省略
/**
* 整個(gè)bean標(biāo)簽就解析完了
*/
return bd;
}
// .............省略
}
具體實(shí)現(xiàn)內(nèi)容概述:
- 獲取標(biāo)簽中的class屬性
- 獲取標(biāo)簽中的parent屬性
- 基于class和parent先創(chuàng)建一個(gè)BeanDefinition對(duì)象
- parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);這個(gè)方法里面會(huì)對(duì)BeanDefinition設(shè)置很多屬性沉馆,如scop屬性码党、abstract屬性、lazy屬性斥黑、autowireMode屬性揖盘、depends-on屬性、autowire-candidate屬性锌奴、init-method屬性兽狭、destory-method屬性、factory-bean和factory-method屬性等鹿蜀。如果對(duì)于屬性含義不了解看下我之前等 《快速理解Spring加載流程》的那個(gè)文章箕慧,里面有對(duì)BeanDefinition的屬性的一些描述。
- <meta />子標(biāo)簽解析茴恰,解析后的鍵值對(duì)會(huì)存儲(chǔ)到BeanDefinition的attributes這個(gè)Map中颠焦。如果需要用到<meta />這個(gè)值,需要先獲取BeanDefinition往枣。
- lookup-method這個(gè)屬性的作用是伐庭,如果創(chuàng)建當(dāng)前這個(gè)類的Bean,會(huì)給這個(gè)類的某一個(gè)方法塞一個(gè)返回值分冈。如下案例就是調(diào)用ShowSixClass類的getPeople方法圾另,我返回woman。(就是這么個(gè)作用丈秩,實(shí)際信息存儲(chǔ)在BeanDefinition的MethodOverrides屬性中)
<bean id="people" class="com.jd.nlp.dev.muzi.spring5.exercise.demo05.ShowSixClass" >
<!--簡(jiǎn)單理解就是 給某個(gè)方法塞返回值 體現(xiàn)出一種多態(tài)的方式-->
<lookup-method name="getPeople" bean="woman" />
</bean>
- replace-method這個(gè)屬性就是增強(qiáng)某個(gè)方法盯捌,這個(gè)可以使用AOP去代替淳衙,但是還是弄個(gè)案例看一下蘑秽。這個(gè)方法需要進(jìn)行業(yè)務(wù)功能增強(qiáng),但是又不希望在原來(lái)基礎(chǔ)上修改箫攀,可以用 replaced-method標(biāo)簽肠牲。下述案例就是originClass這個(gè)Bean在調(diào)用method(String str)方法的時(shí)候會(huì)調(diào)用replaceClass的reimplement方法。(實(shí)際信息存儲(chǔ)在BeanDefinition的MethodOverrides屬性中)
//-----------配置--------------
<bean id="replaceClass" class="com.jd.nlp.dev.muzi.spring5.exercise.demo06.ReplaceClass" />
<bean id="originClass" class="com.jd.nlp.dev.muzi.spring5.exercise.demo06.OriginClass">
<replaced-method name="method" replacer="replaceClass">
<!-- 使用arg-type來(lái)區(qū)分重載的方法 -->
<arg-type match="java.lang.String" />
</replaced-method>
</bean>
// ------------代碼---------------
public class OriginClass {
public void method(String param) {
System.out.println();
System.out.println("I am origin method! param = " + param);
System.out.println();
}
public void method(List param) {
System.out.println();
System.out.println("I am origin method! param = " + param);
System.out.println();
}
}
public class ReplaceClass implements MethodReplacer {
@Override
public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
System.out.println("I am replace method -> reimplement -> begin");
System.out.println("obj:"+ obj.toString());
System.out.println("method:" + method.getName());
System.out.println("args:" + args);
System.out.println("I am replace method -> reimplement -> end");
return null;
}
}
- constructor-arg標(biāo)簽的內(nèi)容最終會(huì)存儲(chǔ)在 BeanDefinition的 ConstructorArgumentValues 屬性中靴跛,用作實(shí)例化有參構(gòu)造函數(shù)參數(shù)獲取缀雳。
- 解析Bean中的property,在當(dāng)前支持注解掃描的版本下梢睛,可以用@Value替代 肥印。
- @Qualifier指定注入哪個(gè)bean
以上就是parseBeanDefinitionElement(ele, beanName, containingBean)方法的具體實(shí)現(xiàn)內(nèi)容识椰,其實(shí)梳理下來(lái)就很簡(jiǎn)單,無(wú)非就是提取<bean />標(biāo)簽中配置的參數(shù)嘛深碱,然后都包裝到BeanDefinition對(duì)應(yīng)的屬性當(dāng)中腹鹉,最終把這個(gè)BeanDefinition返回。
至此敷硅,一個(gè)Bean標(biāo)簽的基本信息就被解析完了功咒。返回BeanDefinition并被包裝成一個(gè)Holder對(duì)象,然后我們回到之前解析的主流程中绞蹦。
3.4 BeanDefinitionHolder是否需要被裝飾力奋?
再次看一下解析一個(gè)傳統(tǒng)<bean /> 標(biāo)簽主流程的代碼。
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
/**
* 方法:parseBeanDefinitionElement
* 解析document封裝成beanDefinition
* 重要程度:* * * * *
*/
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
/**
* 沒(méi)吊用幽七,但需要學(xué)習(xí)設(shè)計(jì)思想景殷。
* 裝飾者設(shè)計(jì)模式,加上SPI(service provider interface)的設(shè)計(jì)思想SPI(Mybatis澡屡,Spring滨彻,Dubbo的SPI擴(kuò)展)
* SPI用來(lái)解耦,擴(kuò)展的設(shè)計(jì)思想挪蹭,在不改變?cè)写a的前提下進(jìn)行開(kāi)發(fā)亭饵,實(shí)現(xiàn)熱插拔。和策略模式有點(diǎn)像
* SPI簡(jiǎn)單來(lái)講就是加載配置文件通過(guò)配置文件類中的類路徑信息梁厉,再不修改核心代碼的前提下擴(kuò)展功能辜羊。
* 內(nèi)容:
* 1.namespace uri和解析類建立映射關(guān)系
* 2.解析類實(shí)現(xiàn)統(tǒng)一接口完成多態(tài)
* 3.beandefinition的不斷包裝/裝飾
* 不使用構(gòu)造器和屬性注入,在bean標(biāo)簽中使用前綴屬性 p: c:進(jìn)行注入 词顾,在xmlns中加入schema/p schema/c 約束
* 重要程度:*
*/
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
// ................................................
}
}
delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);裝飾這個(gè)BeanDefinitionHolder如果需要的話八秃?
3.4.1 什么是裝飾?
在我理解肉盹,裝飾就是不斷的對(duì)一個(gè)對(duì)象進(jìn)行包裝昔驱,填充這個(gè)對(duì)象的可變的屬性列表值,使得我們可以動(dòng)態(tài)的去達(dá)到自己想要表達(dá)的內(nèi)容上忍。
有的裝飾是通過(guò)繼承不斷的去修改最初始的內(nèi)容骤肛,不斷的擴(kuò)充自己所擁有的東西。而在Spring的裝飾中窍蓝,其實(shí)就是通過(guò) SPI(service provider interface)服務(wù)發(fā)現(xiàn)思想腋颠,去判斷我這個(gè)Bean是否需要被裝飾。
3.4.2 什么是SPI吓笙?
SPI是service provider interface的縮寫(xiě)淑玫,是一種服務(wù)發(fā)現(xiàn)機(jī)制。
Spring的SPI設(shè)計(jì)首先要理解什么是 " namespaceURI ",如下面代碼所示 xmlns 后面配置的 "http://www.springframework.org/schema/beans" 就是 " namespaceURI "絮蒿。xsi:schemaLocation里面配置的是你的自定義標(biāo)簽的具體Schema(自定義標(biāo)簽解析相關(guān)內(nèi)容暫且不提)尊搬。
<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="decoratorBean" class="com.jd.nlp.dev.muzi.spring5.exercise.demo07.DecoratorBean"
p:username="jack" p:password="123"
c:age="12" c:sex="1"
/>
</beans>
我們看上面配置文件中bean標(biāo)簽中有兩種很奇怪的屬性 “p:” 和 “c:” ,其中 “p:” 配置和Property功能類似土涝,“c:” 配置和construct-args子標(biāo)簽屬性功能類似毁嗦,但是在Spring中這種屬性是如何解析的呢?
想要知道如何解析“p:”和“c:”回铛,首先要打開(kāi)spring-beans的這個(gè)jar包狗准,找到META-INF目錄,找到spring.handlers文件打開(kāi)茵肃。這里面定義的就是“p:”和“c:”的解析類與namespaceURI的關(guān)系腔长。
文件:META-INF/spring.handlers
http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
回到之前調(diào)用裝飾方法的入口,看一下bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);具體的方法是怎么實(shí)現(xiàn)的验残?
依然是包裝了一下捞附,然后調(diào)用到實(shí)際的實(shí)現(xiàn)方法中,首先他是獲取標(biāo)簽Element元素的全部Node去遍歷您没,查看是否需要裝飾鸟召。主要的方法是 decorateIfRequired 方法。
public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder) {
return decorateBeanDefinitionIfRequired(ele, definitionHolder, null);
}
public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
Element ele, BeanDefinitionHolder definitionHolder, @Nullable BeanDefinition containingBd) {
BeanDefinitionHolder finalDefinition = definitionHolder;
/**
* 通過(guò)元素的屬性來(lái)裝飾
*/
NamedNodeMap attributes = ele.getAttributes();
// 循環(huán)
for (int i = 0; i < attributes.getLength(); i++) {
Node node = attributes.item(i);
// 裝飾如果需要
finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
}
/**
* 通過(guò)子標(biāo)簽裝飾
*/
NodeList children = ele.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
}
}
return finalDefinition;
}
這個(gè)方法我們可以看到氨鹏,它是在看當(dāng)前Node是否能得到NamespaceURI欧募,得到了NamespaceURI就會(huì)通過(guò)NamespaceURI獲取NamespaceHandler。然后調(diào)用handler.decorate去裝飾這個(gè)beanDefinition仆抵。
public BeanDefinitionHolder decorateIfRequired(
Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {
// node 就是 p:xxxx="xxxx" demo07
// 根據(jù)node獲取node的命名空間跟继,形如:http://www.springframework.org/schema/p
String namespaceUri = getNamespaceURI(node);
if (namespaceUri != null && !isDefaultNamespace(namespaceUri)) {
/**
* SPI服務(wù)發(fā)現(xiàn)思想,通過(guò)URI獲取spring.handlers配置的處理類
*
* resolve(namespaceUri)方法中有詳細(xì)的解析過(guò)程
*/
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler != null) {
/**
* 實(shí)際的解析類镣丑,調(diào)用裝飾方法舔糖。 可以理解為 handler 是一個(gè)裝飾者, beanDefinition是被裝飾者莺匠。
*/
BeanDefinitionHolder decorated =
handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
// ................
}
// ................
}
return originalDef;
}
看下resolve具體實(shí)現(xiàn)金吗,是如何獲取到NamespaceHandler的?
- getHandlerMappings獲取到所有jar包中的spring.handlers文件中的namespaceURI和其對(duì)應(yīng)的處理類的類路徑趣竣,放入一個(gè)Map中摇庙。
- 反射這個(gè)類,并創(chuàng)建這個(gè)累的實(shí)例期贫,得到namespaceHandler跟匆。
- 調(diào)用namespaceHandler的init方法异袄。
- 把實(shí)例好的對(duì)象和namespaceURI映射上
- 返回這個(gè)namespaceHandler對(duì)象通砍。
這樣上文通過(guò)resolve獲得的namespaceHandler對(duì)象,就是spring.handlers配置的當(dāng)前namespaceURI對(duì)應(yīng)的解析類的實(shí)例。這樣Spring就可以通過(guò)namespaceHandler調(diào)用decorate方法進(jìn)行裝飾了封孙。
public NamespaceHandler resolve(String namespaceUri) {
/**
* 加載"META-INF/spring.handlers"文件迹冤,建立URI和處理類的映射關(guān)系
*
* uri和類的映射關(guān)系,通過(guò)uri唯一找到一個(gè)類
*
* 方法:getHandlerMappings
* 重要程度:* * *
*/
Map<String, Object> handlerMappings = getHandlerMappings();
// 根據(jù)URI就可以找到唯一的處理類(字符串)
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
}
else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler) handlerOrClassName;
}
else {
// 處理類(字符串)反射
String className = (String) handlerOrClassName;
try {
/**
* 反射這個(gè)類
*/
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
// ......................
}
/**
* 基于類對(duì)象來(lái)實(shí)例化
* 備注:所有處理類必須繼承NamespaceHandler虎忌,實(shí)現(xiàn)多態(tài)泡徙。
* 例如:
* SimpleConstructorNamespaceHandler implements NamespaceHandler
*
* 所有spring.handlers這些命名解析類都有一個(gè)特點(diǎn)是必須實(shí)現(xiàn)NamespaceHandler接口,來(lái)實(shí)現(xiàn)多態(tài)
*/
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
// 調(diào)用處理類初始化方法
namespaceHandler.init();
// 替換映射關(guān)系key對(duì)應(yīng)的值
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
// ......................
}
}
簡(jiǎn)單看下"p:"屬性對(duì)應(yīng)的namespaceURI對(duì)應(yīng)的解析類把膜蠢,這是p的spring.handlers的配置堪藐。對(duì)應(yīng)的解析類是SimplePropertyNamespaceHandler。
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
這是p的解析類的代碼挑围,SimplePropertyNamespaceHandler類的 init 方法沒(méi)有具體的內(nèi)容礁竞,但是decorate有裝飾的詳細(xì)邏輯,就不解讀了杉辙。就是往BeanDefinition對(duì)應(yīng)的屬性塞值模捂。和之前那波設(shè)置屬性的操作差不多。"p:" 對(duì)應(yīng)之前的邏輯的就是"property"子標(biāo)簽屬性設(shè)置蜘矢。
public class SimplePropertyNamespaceHandler implements NamespaceHandler {
private static final String REF_SUFFIX = "-ref";
@Override
public void init() {
}
/**
* p:實(shí)際對(duì)應(yīng)的裝飾方法
*/
@Override
public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
/**
* 被裝飾對(duì)象 definition
* 主要內(nèi)容是 解析 p:xxxx="xxxx" 內(nèi)容狂男,封裝到屬性 MutablePropertyValues 列表元素中去
*/
if (node instanceof Attr) {
Attr attr = (Attr) node;
String propertyName = parserContext.getDelegate().getLocalName(attr);
String propertyValue = attr.getValue();
MutablePropertyValues pvs = definition.getBeanDefinition().getPropertyValues();
if (pvs.contains(propertyName)) {
parserContext.getReaderContext().error("Property '" + propertyName + "' is already defined using " +
"both <property> and inline syntax. Only one approach may be used per property.", attr);
}
if (propertyName.endsWith(REF_SUFFIX)) {
propertyName = propertyName.substring(0, propertyName.length() - REF_SUFFIX.length());
pvs.add(Conventions.attributeNameToPropertyName(propertyName), new RuntimeBeanReference(propertyValue));
}
else {
/**
* 把屬性內(nèi)容加入到definition的MutablePropertyValues列表中
* 這樣一種反復(fù)的對(duì)definition進(jìn)行裝飾/包裝,體現(xiàn)了裝飾者設(shè)計(jì)模式的感覺(jué)品腹。
*
* 具體誰(shuí)是裝飾者已經(jīng)不重要了岖食,對(duì)beanDefinition已經(jīng)進(jìn)行反復(fù)的修改了。
*/
pvs.add(Conventions.attributeNameToPropertyName(propertyName), propertyValue);
}
}
return definition;
}
}
3.5 BeanDefinition注冊(cè)的位置在哪里舞吭?
回歸到之前解析<bean >標(biāo)簽的主流程中县耽, BeanDefinitionReaderUtils.registerBeanDefinition這個(gè)方法就是在注冊(cè)BeanDefinition。
getReaderContext() 獲取到的就是之前從第一次委托對(duì)象持有的容器本身的引用镣典,一直被間接的持有著這個(gè)對(duì)象兔毙。容器本身是含有BeanFactory和注冊(cè)器的,所以可以獲取到BeanDefinitionRegistry兄春。
BeanDefinitionRegistry其實(shí)就是DefaultListableBeanFactory澎剥,BeanDefinitionRegistry是個(gè)接口。
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
//.........................
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
//....................
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
// ................................................
}
}
3.5.1 看下BeanDefinitionReaderUtils.registerBeanDefinition具體實(shí)現(xiàn):(注冊(cè)BeanDefinition至特定容器)
- BeanName和BeanDefinition注冊(cè)構(gòu)建映射關(guān)系
- Alias和BeanName注冊(cè)構(gòu)建映射關(guān)系
可以理解赶舆,我們可以通過(guò)BeanName快速的找到BeanDefinition哑姚,當(dāng)然也可以通過(guò)別名,找到BeanName間接的找到BeanDefinition芜茵。
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
/**
* BeanName和BeanDefinition注冊(cè)構(gòu)建映射關(guān)系
* 重要:* * *
*/
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
/**
* Alias和BeanName注冊(cè)構(gòu)建映射關(guān)系
*/
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
3.5.2 看下registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());具體實(shí)現(xiàn):(DefaultListableBeanFactory類的實(shí)現(xiàn)方法)
- 代碼有點(diǎn)長(zhǎng)該省略的都省略了
- 裝beanDefinition的容器是 beanDefinitionMap
- 裝beanName的集合是 beanDefinitionNames
以上2和3需要牢記叙量,玩Spring源碼debug的時(shí)候需要去這里找內(nèi)容。
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
/**
* 重要代碼最下方
*/
// .......................省略一萬(wàn)行
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if (existingDefinition != null) {
// .......................省略一萬(wàn)行
}
else {
// .......................省略一萬(wàn)行
else {
/**
* 把BeanDefinition緩存到Map中
*/
this.beanDefinitionMap.put(beanName, beanDefinition);
/**
* 把 beanname 放到 BeanDefinitionNames 這個(gè)List中九串,在Bean實(shí)例化時(shí)需要使用到該List绞佩。
* 該List包含所有的beanDefinition的名稱
*/
this.beanDefinitionNames.add(beanName);
this.manualSingletonNames.remove(beanName);
}
this.frozenBeanDefinitionNames = null;
}
// .......................
}
3.5.3 看下registry.registerAlias(beanName, alias);方法的具體實(shí)現(xiàn):
GenericApplicationContext.class
public void registerAlias(String beanName, String alias) {
/**
* 構(gòu)建別名和 beanName的映射關(guān)系
*/
this.beanFactory.registerAlias(beanName, alias);
}
SimpleAliasRegistry.class
public void registerAlias(String name, String alias) {
// ...................
/**
* 映射關(guān)系在最下面
*/
synchronized (this.aliasMap) {
// ................... 省略一萬(wàn)行
else {
String registeredName = this.aliasMap.get(alias);
// ................... 省略一萬(wàn)行
/**
* 裝的是別名和beanName【id的名稱】的映射關(guān)系
* 別名取對(duì)象:
* 別名 - beanName 有映射關(guān)系
* beanName - beanDefinition 有映射關(guān)系
*
* 總體來(lái)說(shuō)如果通過(guò)別名找beanDefinition需要二級(jí)映射
*/
this.aliasMap.put(alias, name);
// ...................
}
}
}
可以看到 alias 和 beanName的映射關(guān)系是在 aliasMap 中存儲(chǔ)的寺鸥。最外層是循環(huán)別名數(shù)組,以當(dāng)前item 別名為key品山,以beanName為value一對(duì)一對(duì)存的胆建。所以通過(guò)任意一個(gè)定義好別名都可以找到對(duì)應(yīng)的beanName。
至此肘交,ClassPathXmlApplicationContext 的 解析XML注冊(cè)BeanDefinition流程就梳理完了笆载。梳理的很難受,模版設(shè)計(jì)模式擴(kuò)展性不錯(cuò)涯呻,就是跳來(lái)跳去的凉驻,抓耳撓腮,看濕了 ... ...
三复罐、總結(jié)
簡(jiǎn)單總結(jié)一下沿侈,雖然微服務(wù)的大環(huán)境下,ClassPathXmlApplicaitionContext容器在我們?nèi)粘5拇a中越來(lái)越少的去使用了市栗,但是萬(wàn)變不離其宗缀拭,Spring不論再怎么演變,初始的結(jié)構(gòu)就是這樣填帽,以后只能是擴(kuò)展和兼容蛛淋,相似的功能還會(huì)復(fù)用之前的代碼。所以吃透一套流程篡腌,待我們分析注解配置啟動(dòng)容器的時(shí)候褐荷,也是小事一樁。
就目前來(lái)看BeanDefinition的這些屬性嘹悼,有一些我們是基本不會(huì)用到的叛甫,就比如lookup-method,init-method(實(shí)現(xiàn)InititalizeBean接口杨伙,@PostConstruct可以替代)其监,factory-bean(實(shí)現(xiàn)Factorybean接口就是將 getObject()方法結(jié)果放入Spring的factoryBeanObjectCache這個(gè)容器里,我們根據(jù)類實(shí)際的beanName獲得的bean其實(shí)是getObject()方法返回的bean限匣,如果要獲取類真正的實(shí)例Bean抖苦,需要在beanName前加個(gè)&符號(hào),這種機(jī)制目前確實(shí)不知道有什么用米死。
總之锌历,源碼內(nèi)容不要過(guò)分的深究,要一遍一遍的讀峦筒,先從整體角度去考慮究西,然后針對(duì)細(xì)節(jié)做深化梳理,我總結(jié)的Spring相關(guān)內(nèi)容也是基于以廣度為先物喷,按照流程的先后在細(xì)致的對(duì)每一個(gè)我想知道的知識(shí)點(diǎn)進(jìn)行深度梳理卤材。