Spring动态加载Bean实现及过程源码分析

前阵子因为项目需要实现由Spring管理数据源动态创建。所以上网查也有一些实现,但不很系统,而且写法不是很优雅。自己看了一下源码,也写了一个实现,分享给大家。
先说简单一下Spring加载Bean的过程中的几个重要步骤
1)编写Spring的启动类SpringListener,SpringListener调用ContextLoader.initWebApplicationContext()
public class SpringListener extends ContextLoaderListener {
private ContextLoader contextLoader;
public void contextInitialized(ServletContextEvent servletContextEvent) {
this.contextLoader = createContextLoader();
//创建Spring Context
WebApplicationContext context = this.contextLoader.initWebApplicationContext(servletContextEvent.getServletContext());
SearchContext.setSpringContext((SpringContext)context);
}
}
2)Spring 根共享环境配置获取,加入Context创建,ContextLoader.initWebApplicationContext()方法调用ContextLoader.createWebApplicationContext()方法
3)创建WebApplicationContext,并设置参数,ContextLoader.createWebApplicationContext()方法调用ConfigurableWebApplicationContext.refresh()
protected WebApplicationContext createWebApplicationContext(
ServletContext servletContext, ApplicationContext parent) throws BeansException {
Class contextClass = determineContextClass(servletContext);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setParent(parent);
wac.setServletContext(servletContext);
wac.setConfigLocation(servletContext.getInitParameter(CONFIG_LOCATION_PARAM));
customizeContext(servletContext, wac);
//Bean加载方法
wac.refresh();
return wac;
}
4)中间调用过程
AbstractApplicationContext.refresh()调用AbstractApplicationContext.obtainFreshBeanFactory()
AbstractRefreshableApplicationContext.obtainFreshBeanFactory()调用AbstractRefreshableApplicationContext.refreshBeanFactory()
5)记载Spring Bean,AbstractRefreshableApplicationContext.refreshBeanFactory()调用XmlWebApplicationContext.loadBeanDefinitions()
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws 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.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);
//使用beanDefinitionReader进行Bean加载
loadBeanDefinitions(beanDefinitionReader);
}
       从上面的第5步可以看见,Spring 是通过XmlBeanDefinitionReader进行Bean加载的,换句话说我们只要获得XmlBeanDefinitionReader就可以进行Bean加载。如何获取XmlBeanDefinitionReader呢?网上很多的实现是自己重新new一个XmlBeanDefinitionReader,但是觉得这种写法不是很优雅。本人的解决思路如下:
1)继承XmlWebApplicationContext,复写initBeanDefinitionReader(),获取XmlBeanDefinitionReader
public class SpringContext extends XmlWebApplicationContext{
private XmlBeanDefinitionReader beanReader;
@Override
protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {
super.initBeanDefinitionReader(beanDefinitionReader);
this.beanReader = beanDefinitionReader;
}
public XmlBeanDefinitionReader getBeanReader() {
return beanReader;
}
}
2)web.xml配置name为contextClass,value为SpringContext的context-param
如何Spring Centext默认创建的XmlBeanDefinitionReader换为自定义的SpringContext,可以看Spring Bean加载的第3步中ContextLoader.determineContextClass(),方法会到web.xml中查找context-param为contextClass的参数。如果没有就默认创建XmlWebApplicationContext,有就创建contextClass的值所对应的类。
<context-param>
<param-name>contextClass</param-name>
<param-value>cn.wxdl.extension.spring.SpringContext</param-value>
</context-param>
3)使用XmlBeanDefinitionReader加载Bean,Bean我是使用Freemark进行封装的
XmlBeanDefinitionReader beanDefinitionReader = context.getBeanReader();
beanDefinitionReader.setResourceLoader(context);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(context));
beanDefinitionReader.loadBeanDefinitions(new ByteArrayResource(writer.getBuffer().toString().getBytes()));
       在最后一步用的是ByteArrayResource,原来觉得loadBeanDefinitions的参数为AbstractResource,因此觉得她的子类都可以,正好可以用Freemark流封装成InputStreamResource传参,但是最后发现报错,跟踪到里面发现InputStreamResource.isOpen始终返回为true,验证不通过,最后改为了ByteArrayResource。
附录
文中使用类的全路径
org.springframework.core.io.AbstractResource
org.springframework.core.io.ByteArrayResource
org.springframework.core.io.InputStreamResource
org.springframework.web.context.ContextLoader
org.springframework.web.context.support.XmlWebApplicationContext
org.springframework.context.support.AbstractApplicationContext
org.springframework.context.support.AbstractRefreshableApplicationContext