一、概述
我將初始化流程分為兩部分,第一部分是Spring上下文的初始化,基于ContextLoadListener實現(xiàn),第二部分是Springmvc上下文的初始化,主要發(fā)生在DispatcherServlet,applicationContext與WebApplicationContext二者是父子容器關(guān)系。
二、Spring上下文applicationContext初始化流程簡析
- 如果是結(jié)合web容器tomcat,需要在web.xml中配置<listener>標(biāo)簽。
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
- 在tomcat啟動時,會調(diào)用ContextLoaderListener類的contextInitialized()方法,createContextLoader()方法返回的是null,這是一個留待子類實現(xiàn)的方法,然后會判斷contextLoader是否為null,如果為null,轉(zhuǎn)化為contextLoadListener類型,然后調(diào)用initWebApplicationContext()方法,對applicationContext初始化。
public void contextInitialized(ServletContextEvent event) {
this.contextLoader = createContextLoader();
if (this.contextLoader == null) {
this.contextLoader = this;
}
this.contextLoader.initWebApplicationContext(event.getServletContext());
}
調(diào)用initWebApplicationContext()方法,該方法的作用是:根據(jù)CONTEXT_CLASS_PARAM和CONFIG_LOCATION_PARAM,還有servletContext屬性,來構(gòu)建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!");
}
- 如果當(dāng)前servletContext為null,將context賦值為成員變量,防止servletContext被銷毀時也是可以用的。
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
- 進入createWebApplicationContext(servletContext)方法看一看,這個方法的作用是返回一個WebApplicationContext的實現(xiàn)類類型。
Class<?> contextClass = determineContextClass(sc);
- 進入 determineContextClass(sc)方法。
這個方法首先判斷是否自定義了上下文,如果自定義就加載自定義的,如果沒有的話,默認(rèn)加載XmlWebApplicationContext,方法都是通過反射創(chuàng)建對象。
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() + "]");
}
- 最后,無論是什么類型,直接強轉(zhuǎn)為ConfigurableWebApplicationContext類型,同時還要判斷是不是借口,如果是接口拋出異常,如果不是接口,通過構(gòu)造方法構(gòu)造對象。
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
重新回到ContextLoader類的initWebApplicationContext()方法,這里要判斷context是不是ConfigurableWebApplicationContext類型,我們在前面已經(jīng)將返回的對象通過構(gòu)造方法構(gòu)造并且強轉(zhuǎn)為ConfigurableWebApplicationContext類型了。
我十分不理解這里還要進行一次強轉(zhuǎn)?懷疑是它的子類嗎?
接下來判斷context是否還是活躍的?活躍的意思是已經(jīng)執(zhí)行過至少一次refresh()方法,還沒有關(guān)閉。
loadParentContext(),沒看明白,加載applicationContext父上下文,沒有父上下文的話返回null,官方解釋是EJB相關(guān),不了解EJB,并且官方文檔提到,在純web應(yīng)用下,這個方法返回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設(shè)定id,獲取到applicationContext.xml的位置,放入applicationContext中,初始化參數(shù),執(zhí)行refresh(),refresh是整個spring容器最核心的初始化方法,包括BeanFactory的創(chuàng)建,國際化,樣式工具等,具體內(nèi)容會在后面的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()方法,這個方法的作用是將配置參數(shù)映射到此servlet的bean屬性上,然后調(diào)用子類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初始會根據(jù)servletContext的屬性獲取。
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
-如果webApplicationContext不為null,那么一定是通過構(gòu)造方法設(shè)置的,前面我們提到的利用反射設(shè)置。下面是判斷類型,是否活躍,有沒有父上下文,沒有的話就把rootContext設(shè)置為它的父上下文等。
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);
}
}
}
- 當(dāng)webApplicationContext已經(jīng)存在于servletContext中,通過配置在servletContext中attribute中獲取。
if (wac == null) {
wac = findWebApplicationContext();
}
- 如果webApplicationContext還沒有創(chuàng)建,那么就創(chuàng)建一個,我們接下來看一看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()方法默認(rèn)使用XmlWebApplicationContext創(chuàng)建,然后在繼續(xù)類似上面的初始化。
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()獲取,如果獲取不到就調(diào)用默認(rèn)的方法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 + "]");
}
}
大致流程就是以上所述。
四、小結(jié)
大致流程就是通過監(jiān)聽器切入,初始化applicationContext,spring容器,webApplicationContext的初始化分為三層,HttpServletBean、FrameWorkServlet、DispatcherServlet,從servlet取出相關(guān)屬性進行賦值,在FrameWorkServlet中創(chuàng)建WebapplicationContext,最后再DispatcherServlet完成SpringMVC九大組件的初始化。