SpringMVC源碼閱讀筆記----初始化

一、概述

我將初始化流程分為兩部分,第一部分是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九大組件的初始化。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容