一、概述
我将初始化流程分为两部分,第一部分是Spring上下文的初始化,基于ContextLoadListener实现,第二部分是Springmvc上下文的初始化,主要发生在DispatcherServlet,applicationContext与WebApplicationContext二者是父子容器关系。
二、Spring上下文applicationContext初始化流程简析
- 如果是结合web容器tomcat,需要在web.xml中配置
标签。
org.springframework.web.context.ContextLoaderListener
- 在tomcat启动时,会调用ContextLoaderListener类的contextInitialized()方法,createContextLoader()方法返回的是null,这是一个留待子类实现的方法,然后会判断contextLoader是否为null,如果为null,转化为contextLoadListener类型,然后调用initWebApplicationContext()方法,对applicationContext初始化。
public void contextInitialized(ServletContextEvent event) {
this.contextLoader = createContextLoader();
if (this.contextLoader == null) {
this.contextLoader = this;
}
this.contextLoader.initWebApplicationContext(event.getServletContext());
}
调用initWebApplicationContext()方法,该方法的作用是:根据CONTEXT_CLASS_PARAM和CONFIG_LOCATION_PARAM,还有servletContext属性,来构建applicationContext。
由于代码有些长,一部分一部分的分析,首先检查servletContext中是否有属性名为WebApplicationContext.ROOT的属性,如果有,那么说明进行了二次初始化,需要检查你的web.xml中是否定义了两个ContextLoader*。
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
- 如果当前servletContext为null,将context赋值为成员变量,防止servletContext被销毁时也是可以用的。
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
- 进入createWebApplicationContext(servletContext)方法看一看,这个方法的作用是返回一个WebApplicationContext的实现类类型。
Class> contextClass = determineContextClass(sc);
- 进入 determineContextClass(sc)方法。
这个方法首先判断是否自定义了上下文,如果自定义就加载自定义的,如果没有的话,默认加载XmlWebApplicationContext,方法都是通过反射创建对象。
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);
}
}
}
- 退出来,然后判断反射获得到的contextClass是否是ConfigurableWebApplicationContext类型,或者是它的子类。
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
- 最后,无论是什么类型,直接强转为ConfigurableWebApplicationContext类型,同时还要判断是不是借口,如果是接口抛出异常,如果不是接口,通过构造方法构造对象。
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
重新回到ContextLoader类的initWebApplicationContext()方法,这里要判断context是不是ConfigurableWebApplicationContext类型,我们在前面已经将返回的对象通过构造方法构造并且强转为ConfigurableWebApplicationContext类型了。
我十分不理解这里还要进行一次强转?怀疑是它的子类吗?
接下来判断context是否还是活跃的?活跃的意思是已经执行过至少一次refresh()方法,还没有关闭。
loadParentContext(),没看明白,加载applicationContext父上下文,没有父上下文的话返回null,官方解释是EJB相关,不了解EJB,并且官方文档提到,在纯web应用下,这个方法返回null。
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
- 进入configureAndRefreshWebApplicationContext()方法,这个方法用于给context设定id,获取到applicationContext.xml的位置,放入applicationContext中,初始化参数,执行refresh(),refresh是整个spring容器最核心的初始化方法,包括BeanFactory的创建,国际化,样式工具等,具体内容会在后面的spring源码解析中详细阐述。
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
prepareRefresh();
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
prepareBeanFactory(beanFactory);
try {
postProcessBeanFactory(beanFactory);
invokeBeanFactoryPostProcessors(beanFactory);
registerBeanPostProcessors(beanFactory);
initMessageSource();
initApplicationEventMulticaster();
onRefresh();
registerListeners();
finishBeanFactoryInitialization(beanFactory);
finishRefresh();
}
catch (BeansException ex) {
destroyBeans();
cancelRefresh(ex);
throw ex;
}
}
}
三、SpringMVC上下文初始化
- SpringMVC的初始化,是从HttpServletBean的init()方法,这个方法的作用是将配置参数映射到此servlet的bean属性上,然后调用子类FrameWorkServlet的初始化方法initServletBean()。
public final void init() throws ServletException {
......
try {
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
.......
}
......
- FrameWorkServlet的initServletBean()方法最核心的代码如下。
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
-进入initWebApplicationContext()方法中,首先获取根容器,Spring初始会根据servletContext的属性获取。
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
-如果webApplicationContext不为null,那么一定是通过构造方法设置的,前面我们提到的利用反射设置。下面是判断类型,是否活跃,有没有父上下文,没有的话就把rootContext设置为它的父上下文等。
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
- 当webApplicationContext已经存在于servletContext中,通过配置在servletContext中attribute中获取。
if (wac == null) {
wac = findWebApplicationContext();
}
- 如果webApplicationContext还没有创建,那么就创建一个,我们接下来看一看createWebApplicationContext()方法。
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
Class> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using 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);
wac.setConfigLocation(getContextConfigLocation());
configureAndRefreshWebApplicationContext(wac);
return wac;
}
- getContextClass()方法默认使用XmlWebApplicationContext创建,然后在继续类似上面的初始化。
Class> contextClass = getContextClass();
回到FrameWorkServlet,这个onRefresh()方法是进入DispatcherServlet的入口。
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac);
}
- 进入initStrategies()方法,进入DispatcherServlet的初始化。是Dispatcher的九大组件的初始化,大致思路都是通过getBean()获取,如果获取不到就调用默认的方法getDefaultStrategy(),详细的分析会在后面的组件源码解析中。
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
- 接下来判断是否需要将webApplicationContext放入servletContext中。
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
大致流程就是以上所述。
四、小结
大致流程就是通过监听器切入,初始化applicationContext,spring容器,webApplicationContext的初始化分为三层,HttpServletBean、FrameWorkServlet、DispatcherServlet,从servlet取出相关属性进行赋值,在FrameWorkServlet中创建WebapplicationContext,最后再DispatcherServlet完成SpringMVC九大组件的初始化。