spring读取xml生成beanDefinion时的扩展点(虽然基本上用不到,但既然看到了还是记录一下(┬_┬))
通常在我们的web项目中spring的上下文实现类是XmlWebApplicationContext类。
比如org.springframework.web.context.ContextLoader#determineContextClass
protected Class<?> determineContextClass(ServletContext servletContext) {
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
if (contextClassName != null) {
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load custom context class [" + contextClassName + "]", ex);
}
}
else {
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load default context class [" + contextClassName + "]", ex);
}
}
}
在web中配置的ContextLoaderListener启动之后会调用上面的方法确定使用什么Context实现类,如果没有额外配置的话会使用默认的XmlWebApplicationContext。
org.springframework.context.support.AbstractRefreshableApplicationContext#refreshBeanFactory
@Override
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
//此处会加载BeanDefinitions
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
在AbstractRefreshableApplicationContext的refreshBeanFactory方法中会加载所有的BeanDefinitions。
然后我们看到XmlWebApplicationContext实现了AbstractRefreshableApplicationContext的loadBeanDefinitions方法。
org.springframework.web.context.support.XmlWebApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory)
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
在上面的方法中XmlWebApplicationContext创建了一个XmlBeanDefinitionReader类用于读取xml中的数据。
spring加载beanDefinion都是通过AbstractBeanDefinitionReader抽象类来加载的,AbstractBeanDefinitionReader继承了BeanDefinitionReader接口,而具体是什么介质并加载是由子类来实现BeanDefinitionReader接口的loadBeanDefinitions方法来做的。
AbstractBeanDefinitionReader的子类实现有三个分别是PropertiesBeanDefinitionReader、GroovyBeanDefinitionReader、XmlBeanDefinitionReader。
我们看到
由于XmlBeanDefinitionReader继承于AbstractBeanDefinitionReader,所以实现了BeanDefinitionReader接口的org.springframework.beans.factory.support.BeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.Resource)方法。并且在该方法中最终会走到registerBeanDefinitions方法。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
documentReader.setEnvironment(this.getEnvironment());
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
如上方法会调用createBeanDefinitionDocumentReader()方法获取BeanDefinitionDocumentReader对象,并调用该对象的registerBeanDefinitions()方法。我们的扩展点也就是在这个方法中扩展的。
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);//此处调用下面的方法
}
protected void doRegisterBeanDefinitions(Element root) {
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);
if (!this.environment.acceptsProfiles(specifiedProfiles)) {
return;
}
}
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(this.readerContext, root, parent);
preProcessXml(root);//在解析xml并注册BeanDefinition之前执行
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);//在解析xml并注册BeanDefinition之后执行
this.delegate = parent;
}
spring有默认的BeanDefinitionDocumentReader接口实现类:DefaultBeanDefinitionDocumentReader。
我们看到DefaultBeanDefinitionDocumentReader类有两个没有任何实现的方法。
/**
* Allow the XML to be extensible by processing any custom element types first,
* before we start to process the bean definitions. This method is a natural
* extension point for any other custom pre-processing of the XML.
* <p>The default implementation is empty. Subclasses can override this method to
* convert custom elements into standard Spring bean definitions, for example.
* Implementors have access to the parser's bean definition reader and the
* underlying XML resource, through the corresponding accessors.
* @see #getReaderContext()
*/
protected void preProcessXml(Element root) {
}
/**
* Allow the XML to be extensible by processing any custom element types last,
* after we finished processing the bean definitions. This method is a natural
* extension point for any other custom post-processing of the XML.
* <p>The default implementation is empty. Subclasses can override this method to
* convert custom elements into standard Spring bean definitions, for example.
* Implementors have access to the parser's bean definition reader and the
* underlying XML resource, through the corresponding accessors.
* @see #getReaderContext()
*/
protected void postProcessXml(Element root) {
}
preProcessXml()方法在解析xml注册beanDefinion()之前执行,postProcessXml()在之后执行。所以,我们要想在这两个步骤中做自己的处理就必须继承DefaultBeanDefinitionDocumentReader并实现preProcessXml()方法和postProcessXml()方法。
我们看到在org.springframework.beans.factory.xml.XmlBeanDefinitionReader#registerBeanDefinitions方法中会调用它的createBeanDefinitionDocumentReader()方法创建一个BeanDefinitionDocumentReader对象并返回。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();//这儿
documentReader.setEnvironment(this.getEnvironment());
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));
}
createBeanDefinitionDocumentReader()方法中是通过自身的documentReaderClass属性来确定了创建对象类型。所以,我们可以通过XmlBeanDefinitionReader类的setDocumentReaderClass(Class documentReaderClass)方法来改变这个属性为我们自己定义的继承了DefaultBeanDefinitionDocumentReader并实现了preProcessXml()方法和postProcessXml()方法的类的类型。
public void setDocumentReaderClass(Class<?> documentReaderClass) {
if (documentReaderClass == null || !BeanDefinitionDocumentReader.class.isAssignableFrom(documentReaderClass)) {
throw new IllegalArgumentException(
"documentReaderClass must be an implementation of the BeanDefinitionDocumentReader interface");
}
this.documentReaderClass = documentReaderClass;
}
我们知道在初始化容器并调用org.springframework.web.context.support.XmlWebApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory)这个方法的时候会创建一个XmlBeanDefinitionReader对象。
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
在上面的方法中提供了org.springframework.web.context.support.XmlWebApplicationContext#initBeanDefinitionReader方法来初始化XmlBeanDefinitionReader对象,所以我们可以继承XmlWebApplicationContext类来实现initBeanDefinitionReader()方法,并在该方法中调用传进去的XmlBeanDefinitionReader对象的setDocumentReaderClass()方法来设置我们自定义的继承BeanDefinitionDocumentReader并实现preProcessXml()方法和postProcessXml()方法的类类型。
最后一步就是在web.xml中显示配置contextClass变量为继承了XmlWebApplicationContext的自定义容器类类型。
比如:
<context-param>
<param-name>contextClass</param-name>
<param-value>com.explore.develop.MyXmlWebApplicationContext</param-value>
</context-param>
当然springMVC的容器是另外一个容器,只是在初始化的时候会把ContextLoaderListener加载的容器指定为父容器。如果想要springMVC容器在加载beanDefinion的时候也执行扩展点,也需要指定它的contextClass变量。如下:
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/spring/spring-mvc.xml</param-value>
</init-param>
<init-param>
<param-name>contextClass</param-name>
<param-value>com.explore.develop.MyXmlWebApplicationContext</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
总结
实现扩展spring读取xml生成beanDefinion时的扩展点的步骤为:
1. 继承DefaultBeanDefinitionDocumentReader并实现preProcessXml()方法和postProcessXml()。
2. 继承XmlWebApplicationContext类并重写initBeanDefinitionReader()方法。
3. 在重写了XmlWebApplicationContext的initBeanDefinitionReader()方法中,调用XmlBeanDefinitionReader对象的setDocumentReaderClas()方法设置我们在第1步继承DefaultBeanDefinitionDocumentReader的子类类型。
4. web.xml中配置contextClass变量类型为第2步自定义的XmlWebApplicationContext类的子类