spring擴展之自定義XmlWebApplicationContext和DefaultBeanDefinitionDocumentReader
斷點加載配置文件的流程
- 首先我們在AbstractApplicationContext文件的refresh()方法加上斷點
- 進入obtainFreshBeanFactory()方法
- 進入refreshBeanFactory()方法,創建了默認的DefaultListableBeanFactory
- 進入loadBeanDefinitions(beanFactory)方法,根據上面創建發factory去加載配置文件到beandefinition
- 可以看到initBeanDefinitionReader()方法在XmlWebApplicationContext類裏面第空實現。也就是說如果我們自定義一個類集成XmlWebApplicationContext後,再實現XmlWebApplicationContext的initBeanDefinitionReader()方法,然後loadBeanDefinitions()方法就會調用我們自定義的initBeanDefinitionReader()方法。這個是本文第一個需要擴展的地方。
- 進入loadBeanDefinitions(beanDefinitionReader)方法
- 進入reader.loadBeanDefinitions(configLocation)方法
- 進入loadBeanDefinitions(location, null)方法
- 進入loadBeanDefinitions(Resource… resources)方法
- 進入doLoadBeanDefinitions(InputSource inputSource, Resource resource)方法
- 進入registerBeanDefinitions(Document doc, Resource resource)方法
- 進入
- this.documentReaderClass的默認初始化是private Class<?> documentReaderClass = DefaultBeanDefinitionDocumentReader.class;
- 進入documentReader.registerBeanDefinitions(doc, createReaderContext(resource))方法
- 進入doRegisterBeanDefinitions(root)方法
- 可以看到,在doRegisterBeanDefinitions方法裏面處理Element節點的信息的時候會有一個preProcessXml()方法和一個postProcessXml()方法,這2個方法都是空實現。
- Element節點就是加載xml配置文件後的信息存放的地方,例如一個配置爲
<context:component-scan base-package="com.tqy.document.reader.extention.service"/>
的配置文件,加載都Element後,使用
((DeferredElementNSImpl)element).getNodeName()得到的就是"context:component-scan"
例如配置爲
<bean class="com.tqy.document.reader.extention.test.Test" id="test"/>
使用
((DeferredElementNSImpl)element).getAttributeNode("class")得到的就是"com.tqy.document.reader.extention.test.Test"
- 由於doRegisterBeanDefinitions(Element root)方法是DefaultBeanDefinitionDocumentReader類的方法,而前面又提到的第二個擴展到設置的就是這個類,所以如果我們能在設置這個類的時候改變DefaultBeanDefinitionDocumentReader爲我們自己的,就可以成功覆蓋執行preProcessXml()和postProcessXml()方法了
自定義配置文件和類文件實現修改配置
- XmlWebApplicationContext類的加載時機:在FrameworkServlet類初始化的時候,默認就定義了
public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
而我們在web.xml裏面配置的DispatcherServlet默認就是繼承的FrameworkServlet
所以,我們在web.xml裏面配置的時候就可以加上一個默認的參數
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!--配置dispatcher.xml作爲mvc的配置文件-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:dispatcher-servlet.xml</param-value>
</init-param>
<init-param>
<param-name>contextClass</param-name>
<param-value>com.tqy.document.reader.extention.context.MyXmlWebApplicationContext</param-value> //使用自定義的MyXmlWebApplicationContext
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
此時,就使用了我們自己定義的MyXmlWebApplicationContext替換了原始的XmlWebApplicationContext
- MyXmlWebApplicationContext只是提供了initBeanDefinitionReader( XmlBeanDefinitionReader beanDefinitionReader )方法覆蓋調用的機會,這個方法裏面可以做一些我們想做的事。比如,改變DefaultBeanDefinitionDocumentReader.class爲我們自己的。代碼如下
package com.tqy.document.reader.extention.context;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.web.context.support.XmlWebApplicationContext;
/**
* @author tengqingya
* @create 2019-04-10 19:24
*/
public class MyXmlWebApplicationContext extends XmlWebApplicationContext {
private static final Logger LOGGER = Logger.getLogger( MyXmlWebApplicationContext.class );
@Override
protected void initBeanDefinitionReader( XmlBeanDefinitionReader beanDefinitionReader ) {
LOGGER.info("initBeanDefinitionReader......................");
beanDefinitionReader.setDocumentReaderClass(MyDefaultBeanDefinitionDocumentReader.class);
}
}
- MyDefaultBeanDefinitionDocumentReader類中複寫了preProcessXml()和postProcessXml()方法,這樣,當調用MyDefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(Element root)方法時,裏面調用的preProcessXml()和postProcessXml()方法就是我們自定義的方法了。就可以達到修改Element的目的。而Element是實例化bean的基礎配置信息,所以就可以做到動態修改配置文件的信息,爲所欲爲。
- MyDefaultBeanDefinitionDocumentReader類如下
package com.tqy.document.reader.extention.context;
import com.sun.org.apache.xerces.internal.dom.DeferredElementNSImpl;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* @author tengqingya
* @create 2019-04-10 19:31
*/
public class MyDefaultBeanDefinitionDocumentReader extends DefaultBeanDefinitionDocumentReader {
private static final Logger LOGGER = Logger.getLogger(MyDefaultBeanDefinitionDocumentReader.class);
@Override
protected void preProcessXml( Element root ) {
LOGGER.info("preProcessXml...");
NodeList nl = root.getChildNodes();
for( int i = 0; i < nl.getLength(); i++ ) {
Node node = nl.item(i);
if( node instanceof Element && node instanceof DeferredElementNSImpl ) {
DeferredElementNSImpl deferredElementNS = (DeferredElementNSImpl)node;
String nodeName = deferredElementNS.getNodeName();
if( "context:component-scan".equals(nodeName) ) {
NamedNodeMap attributes = deferredElementNS.getAttributes();
LOGGER.info(attributes.getNamedItem("base-package"));
}
NamedNodeMap attributes = deferredElementNS.getAttributes();
LOGGER.info(attributes.getNamedItem("class"));
if( "bean".equals(nodeName) && attributes.getNamedItem("class").toString().contains("extention") ) {
Attr aClass = deferredElementNS.getAttributeNode("class");
Attr id = deferredElementNS.getAttributeNode("id");
aClass.setValue("com.tqy.document.reader.extention.test.Test2");
id.setValue("test2");
}
//base-package="com.tqy.document.reader.extention.controller"
//id="viewResolver"
}
}
}
@Override
protected void postProcessXml( Element root ) {
LOGGER.info("postProcessXml...");
String bean = root.getAttribute("bean");
NodeList context = root.getElementsByTagName("context");
String nodeName = root.getNodeName();
LOGGER.info(bean);
LOGGER.info(context);
LOGGER.info(nodeName);
}
}
其中關鍵代碼就是
Attr aClass = deferredElementNS.getAttributeNode("class");
Attr id = deferredElementNS.getAttributeNode("id");
aClass.setValue("com.tqy.document.reader.extention.test.Test2");
id.setValue("test2");
含義是:
找到節點的class屬性"com.tqy.document.reader.extention.test.Test"替換成新的"com.tqy.document.reader.extention.test.Test2"
找到節點的id屬性"test"替換成"test2"
也就相當於把配置
<bean class="com.tqy.document.reader.extention.test.Test" id="test"/>
替換成了
<bean class="com.tqy.document.reader.extention.test2.Test" id="test2"/>
所以我們在其他地方使用bean的時候,就可以直接使用test2,但是使用不了test。
controller文件
package com.tqy.document.reader.extention.controller;
import com.tqy.document.reader.extention.test.Test2;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
/**
* @author tengqingya
* @create 2019-04-10 13:05
*/
@Controller
public class TestController {
/**
* 此處注入的test2沒有在配置文件中配置
* 配置文件中配置的是test
* 在preProcessXml中修改test的配置爲test2
*/
@Autowired
private Test2 test2;
private static final Logger LOGGER = Logger.getLogger( TestController.class );
@RequestMapping("/test")
@ResponseBody
public String test( HttpServletRequest httpRequest ){
LOGGER.info(httpRequest.getRequestURI());
return test2.test("hello ");
}
}
效果展示
- 直接注入test2報錯
- 但是運行卻正常
- 直接配置test,不報錯
- 但是運行報錯
總結
通過2個擴展點的配置,實現了運行過程中的修改配置文件內容的需求。
可以實現如請求http接口根據環境動態加載配置文件的需求。或者····自行腦補的需求···
附:github源碼
版權所有:tengqingya 轉載請註明出處