这些天一直在看spring关于加载bean的源码,wnm看的头大,看了一下XmlBeanFactory 的源码,XmlBeanFactory 现在已经被弃用,现在用的是ClassPathXmlApplicationContext。
接下来我们研究一下spring结合springMVC启动时加载bean的过程。
首先要提的一点是ClassPathXmlApplicationContext在平常开发中是直接调用解析xml文件加载bean的,spring+springMVC加载bean用的是ContextLoaderListener、DispatcherServlet;但是归根结底bean的加载都是调用了AbstractApplicationContext的refresh方法。
目录
1、ClassPathXmlApplicationContext
2、ContextLoaderListener、DispatcherServlet
3、AbstractApplicationContext中的refresh方法
1、ClassPathXmlApplicationContext
ClassPathXmlApplicationContext调用如下
ApplicationContext ac = new ClassPathXmlApplicationContext("mybatis.xml");
EmpDao dao = ac.getBean("empDao",EmpDao.class);
List<Emp> list = dao.findAll();
System.out.println(list);
看一下ClassPathXmlApplicationContext源码,最终会调用refresh()方法,此处的refresh()是继承与AbstractApplicationContext的refresh方法,在后面再详细介绍。
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
2、ContextLoaderListener、DispatcherServlet
1.ContextLoaderListener、DispatcherServlet介绍
ContextLoaderListener、DispatcherServlet这两个配置在web.xml中。如下:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/applicationContext-*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>manager</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- contextConfigLocation不是必须的, 如果不配置contextConfigLocation, springmvc的配置文件默认在:WEB-INF/servlet的name+"-servlet.xml" -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>manager</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
两者都是容器启动时加载bean的配置,区别是
DispatcherServlet一般会加载MVC相关的bean配置管理(如: ViewResolver, Controller, MultipartResolver, ExceptionHandler, etc.)
ContextLoaderListener一般会加载整个Spring容器相关的bean配置管理(如: Log, Service, Dao, PropertiesLoader, DataSource Bean, etc.)
DispatcherServlet默认使用WebApplicationContext作为上下文.
DispatcherServlet也可以配置自己的初始化参数,覆盖默认配置:
参数 | 描述 |
contextClass | 实现WebApplicationContext接口的类,当前的servlet用它来创建上下文。如果这个参数没有指定, 默认使用XmlWebApplicationContext。详细看下面源码 |
contextConfigLocation | 传给上下文实例(由contextClass指定)的字符串,用来指定上下文的位置。这个字符串可以被分成多个字符串(使用逗号作为分隔符) 来支持多个上下文(在多上下文的情况下,如果同一个bean被定义两次,后面一个优先)。 默认为/WEB-INF/[server-name]-servlet.xml |
namespace | WebApplicationContext命名空间。默认值是[server-name]-servlet。 |
DispatcherServlet继承FrameworkServlet,初始化时执行FrameworkServlet类中的initServletBean方法,下面是获取默认contextClass的源码(多余部分删除了)
public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;
public Class<?> getContextClass() {
return this.contextClass;
}
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
Class<?> contextClass = getContextClass();//调用上面的getContextClass方法
得到最终Class:XmlWebApplicationContext.class
if (logger.isTraceEnabled()) {
logger.trace("Servlet '" + getServletName() +
"' will create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", parent context [" + parent + "]");
}
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
configureAndRefreshWebApplicationContext(wac);
return wac;
}
ContextLoaderListener覆盖默认配置:
参数 | 描述 |
contextClass | 实现WebApplicationContext接口的类,当前的servlet用它来创建上下文。如果这个参数没有指定, 默认使用XmlWebApplicationContext。详细看下面源码 |
contextConfigLocation | 默认为/WEB-INF/applicationContext.xml |
在ContextLoader类中 加载contextClass
protected Class<?> determineContextClass(ServletContext servletContext) {
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);//读取web.xml中contextClass的配置
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);
}
}
}
上面的defaultStrategies在静态代码块中赋值,读取了ContextLoader.properties的数据
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
ClassPathResource resource =
new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);//DEFAULT_STRATEGIES_PATH=ContextLoader.properties
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
}
}
ContextLoader.properties内容如下
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
值得注意的是, DispatcherServlet的上下文仅仅是Spring MVC的上下文, 而ContextLoaderListener的上下文则对整个Spring都有效. 一般Spring web项目中同时会使用这两种上下文.
上下文创建完后会放在ServletContext对象中, 其中:
1) ContextLoaderListener加载的上下文放在ServletContext的key为WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE属性中;
2) DispatcherServlet加载的上下文在每次请求时会放一份在request对象的key为WEB_APPLICATION_CONTEXT_ATTRIBUTE属性中.
因而两者的获取方式也不一样, 前者可以通过:
WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext)或
WebApplicationContextUtils.getWebApplicationContext(servletContext)或
WebApplicationContextUtils.getWebApplicationContext(servletContext,attrname)方法来获取对应的applicationContext,
而后者则通过:
RequestContextUtils.getWebApplicationContext(request)或
WebApplicationContextUtils.getWebApplicationContext(servletContext,attrname)方法来获取对应的applicationContext.
(注: 对于ContextLoaderListener加载的上下文, attrname即上面提到的WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
而对于DispatcherServlet中的上下文则为FrameworkServlet.class.getName() + ".CONTEXT." + getServletName())
2.ContextLoaderListener、DispatcherServlet调用refresh()方法的入口
DispatcherServlet是在FrameworkServlet中的configureAndRefreshWebApplicationContext方法调用refresh方法,此refresh是继承AbstractApplicationContext的方法
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
wac.refresh();
}
ContextLoaderListener在ContextLoader类中的configureAndRefreshWebApplicationContext方法中调用refresh方法,此refresh是继承AbstractApplicationContext的方法
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
wac.setServletContext(sc);
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
customizeContext(sc, wac);
wac.refresh();
}
3、AbstractApplicationContext中的refresh方法
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
后面的加载过程参考这边文章,本来想自己写来着后来发现这片文章写写七七八八,就懒得写了,如有错误请帮忙指出,谢谢。
https://blog.csdn.net/kunpeng90/article/details/80189431
上面的ContextLoaderListener、DispatcherServlet部分内容参考了下面这篇文章
https://blog.csdn.net/py_xin/article/details/52052627
如果想详细了解一下加载过程可以看一下《Spring源码深度解析》。