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 转载请注明出处