SpringMVC之淺析上下文初始化(一)

說(shuō)明:本文所用的SpringMVC版本為4.3.4.RELEASE,應(yīng)用服務(wù)器為TomCat8.0.33。下面我們先回顧一下我們?cè)谟肧pringMVC進(jìn)行開(kāi)發(fā)時(shí)在web.xml中進(jìn)行的一些配置:

    <!-- 配置Spring上下文配置文件 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <!-- 配置SpringMVC Servlet -->
    <servlet>
        <servlet-name>spring-miscellaneous</servlet-name>
        <!-- SpringMVC 分發(fā)器 -->
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 更改SpringMVC配置文件的名字 默認(rèn)的是spring-mvc-servlet.xml -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-miscellaneous-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>spring-miscellaneous</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <!-- 配置監(jiān)聽(tīng)器 用來(lái)載入上下文配置文件 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

上面的代碼,基本上是我們用哪個(gè)SpringMVC開(kāi)發(fā)時(shí)進(jìn)行的一些配置。那么這些配置都用什么用呢?下面我們來(lái)一點(diǎn)一點(diǎn)進(jìn)行分析:

ContextLoaderListener

ContextLoaderListener上下文監(jiān)聽(tīng)器。ContextLoaderListener的作用是Web容器啟動(dòng)的時(shí)候,自動(dòng)裝配ApplicationContext的配置信息,即初始化Spring IOC容器。我們看一下這個(gè)類的繼承關(guān)系:

。從圖中我們可以看出ContextLoaderListener實(shí)現(xiàn)了ServletContextListener(Servlet上下文監(jiān)聽(tīng)器)接口。我們簡(jiǎn)單說(shuō)一下ServletContextListener的作用:ServletContextListener中有兩個(gè)方法,一個(gè)是contextInitialized一個(gè)是contextDestroyed。每一個(gè)應(yīng)用都有ServletContext與之相關(guān)聯(lián),ServletContext中存放了一些web應(yīng)用全局性的配置。當(dāng)Servlet容器啟動(dòng)的時(shí)候,會(huì)實(shí)例化和ServletContext相關(guān)的一些類。如ApplicationContext(注意不是Spring中的那個(gè)ApplicationContext)、ApplicationContexFacade、ServletConfig、ServletContextEvent以及ServletContextListener的實(shí)現(xiàn)類等等,并調(diào)用contextInitialized方法進(jìn)行一些上下文初始化相關(guān)的動(dòng)作。當(dāng)Servlet容器關(guān)閉的時(shí)候,會(huì)調(diào)用contextDestroyed銷毀上下文相關(guān)的內(nèi)容。即ServletContextListener是和ServletContext的生命周期相關(guān)聯(lián)的。ContextLoaderListener實(shí)現(xiàn)了ServletContextListener接口,所以當(dāng)容器啟動(dòng)的時(shí)候,會(huì)調(diào)用ContextLoaderListener中的contextInitialized方法,進(jìn)行一系列的初始化的動(dòng)作。
而<context-param>中的內(nèi)容即是ServletContext中的一些配置信息。
下面我們看一下從容器啟動(dòng)到調(diào)用到contextInitialized方法的調(diào)用鏈:



從上圖中我們可以看到在org.apache.catalina.core.StandardContext#listenerStart的方法中調(diào)用了contextInitialized的方法,進(jìn)行上下文的初始化。PS:StandardContex是TomCat體系中很重要的一個(gè)類,可以找時(shí)間說(shuō)一下TomCat的大致體系結(jié)構(gòu),網(wǎng)上也有很多資料。下面我們看一下在ContextLoaderListener中的contextInitialized中發(fā)生了什么。
    @Override
    public void contextInitialized(ServletContextEvent event) {
        initWebApplicationContext(event.getServletContext());
    }

ContextLoaderListener中的內(nèi)容很簡(jiǎn)單,就是調(diào)用了initWebApplicationContext這個(gè)方法,并把ServletContext的實(shí)現(xiàn)類(ApplicationContextFacade)傳進(jìn)去。initWebApplicationContext這個(gè)方法在ContextLoader這個(gè)類中,ContextLoaderListener繼承了ContextLoader這個(gè)類,其實(shí)主要的調(diào)用邏輯都在ContextLoader這個(gè)類中。我們就進(jìn)入到initWebApplicationContext這個(gè)方法中,看看代碼中是怎么寫的(去掉了無(wú)關(guān)的代碼):

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        //如果已經(jīng)初始化過(guò)了,則拋出異常 在ServletContext的生命周期中,只能初始化一次
        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!");
        }
        Log logger = LogFactory.getLog(ContextLoader.class);
        servletContext.log("Initializing Spring root WebApplicationContext");
        long startTime = System.currentTimeMillis();
        try {
            //如果context為空的話,則創(chuàng)建Spring上下文 因?yàn)檫@里有一個(gè)帶WebApplicationContext參數(shù)的構(gòu)造函數(shù)
            if (this.context == null) {
                this.context = createWebApplicationContext(servletContext);
            }
            //如果Spring web上下文為可配置的web上下文
            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
                    //在Spring上下文還未刷新完之前  ApplicationContext是通過(guò)調(diào)用refresh方法來(lái)進(jìn)行bean的加載初始化裝配等一系列動(dòng)作
                    if (cwac.getParent() == null) {
                        // The context instance was injected without an explicit parent ->
                        // determine parent for root web application context, if any.
                        //設(shè)置父類上下文
                        ApplicationContext parent = loadParentContext(servletContext);
                        cwac.setParent(parent);
                    }
                    configureAndRefreshWebApplicationContext(cwac, servletContext);
                }
            }
            //把Spring中的web上下文放到ServletContext中,  在程序中可以通過(guò)ServletContext獲取到Spring中的web上下文
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
            //獲取線程上下文加載器
            ClassLoader ccl = Thread.currentThread().getContextClassLoader();
            //如果線程上下文加載器和當(dāng)前類加載器是同一個(gè)類加載器,則當(dāng)前上下文和Context是一個(gè)東西
            if (ccl == ContextLoader.class.getClassLoader()) {
                currentContext = this.context;
            }
            //如果不是同一個(gè)類加載器加載的,則把剛實(shí)例化的上下文放到currentContextPerThread中 key是線程上下文加載器 value是當(dāng)前實(shí)例化的context
            else if (ccl != null) {
                currentContextPerThread.put(ccl, this.context);
            }
            return this.context;
        }
    }

假設(shè)我們的this.context為null(這里我們只考慮正常的一般場(chǎng)景),我們繼續(xù)到createWebApplicationContext方法中,看一下是怎么創(chuàng)建Spring web上下文的:

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
        //查找WebApplicationContext的實(shí)現(xiàn)類
        Class<?> contextClass = determineContextClass(sc);
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {//獲取到的Class必須是ConfigurableWebApplicationContext的子類
            throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                    "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
        }
        //實(shí)例化WebApplicationContext的實(shí)現(xiàn)類 注意的是這個(gè)實(shí)現(xiàn)類一定是實(shí)現(xiàn)了ConfigurableWebApplicationContext的接口的
        return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    }

上面的代碼很簡(jiǎn)單:查找WebApplicationContext的實(shí)現(xiàn)類,然后通過(guò)反射的方式實(shí)例化。實(shí)例化的過(guò)程不用多說(shuō),我們看一下determineContextClass這個(gè)方法。

protected Class<?> determineContextClass(ServletContext servletContext) {
        //從ServletContext中獲取key為contextClass的配置信息
        String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
        //如果在web.xml中配置了WebApplicationContext的實(shí)現(xiàn)類,則獲取配置的實(shí)現(xiàn)類的類信息
        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 {
            //取默認(rèn)的配置信息
            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);
            }
        }
    }

從上面的代碼中我們可以看出,我們可以配置在web.xml中配置WebApplicationContext的實(shí)現(xiàn)類,如果沒(méi)有在web.xml中進(jìn)行配置的話,則取默認(rèn)的實(shí)現(xiàn)類。下面我們看一下是怎么去默認(rèn)的實(shí)現(xiàn)類的:
在ContextLoader中有這樣的一段代碼:

static {
       //從類路徑下獲取ContextLoader.properties
       //Spring的Resource相關(guān)的類非常好用
       try {
           ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
           //加載ContextLoader.properties
           defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
       }
       catch (IOException ex) {
           throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
       }
   }

靜態(tài)代碼塊,我們知道在類初始化的時(shí)候會(huì)執(zhí)行靜態(tài)代碼塊。所以在ContextLoader實(shí)例化之前已經(jīng)加載過(guò)了ContextLoader.properties配置文件。我們看一下ContextLoader.properties中的內(nèi)容:

org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

我們發(fā)現(xiàn)這里就一個(gè)配置項(xiàng),默認(rèn)配置了一個(gè)WebApplicationContext的實(shí)現(xiàn)類:XmlWebApplicationContext。所以如果我們沒(méi)有在web.xml中進(jìn)行配置的話,這里就會(huì)實(shí)例化XmlWebApplicationContext這個(gè)類。
下面我們回到initWebApplicationContext這個(gè)方法中繼續(xù)分析。因?yàn)槲覀兊腃ontext是剛實(shí)例化的,而active的默認(rèn)值是false,所以會(huì)進(jìn)入到if中,同樣cwac.getParent()==null成立,所以會(huì)執(zhí)行l(wèi)oadParentContext()這個(gè)方法。我們看看這個(gè)方法中做了什么操作:

protected ApplicationContext loadParentContext(ServletContext servletContext) {
        ApplicationContext parentContext = null;
        //從ServletContext中加載配置項(xiàng)為locatorFactorySelector的信息
        String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);
        //從ServletContext中加載配置項(xiàng)為parentContextKey的新
        String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);
        //如果設(shè)置了parentContextKey的信息
        if (parentContextKey != null) {
            // locatorFactorySelector may be null, indicating the default "classpath*:beanRefContext.xml"
            //實(shí)例化一個(gè)單例的BeanFactoryLocator
            BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
            Log logger = LogFactory.getLog(ContextLoader.class);
            if (logger.isDebugEnabled()) {
                logger.debug("Getting parent context definition: using parent context key of '" +
                        parentContextKey + "' with BeanFactoryLocator");
            }
            //獲取父上下文
            this.parentContextRef = locator.useBeanFactory(parentContextKey);
            parentContext = (ApplicationContext) this.parentContextRef.getFactory();
        }
        return parentContext;
    }

loadParentContext主要做的就是從web.xml中加載父應(yīng)用上下文。
下面就到了我們最重要的一個(gè)方法了configureAndRefreshWebApplicationContext了。接下來(lái)我們的分析也就是圍繞這個(gè)類了:

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
        //這里判斷wac有沒(méi)有被初始化 設(shè)置過(guò)
        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
            //如果ServletContext中配置了contextId,則獲取配置的值,并設(shè)置為id的值
            String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
            if (idParam != null) {
                wac.setId(idParam);
            }
            else {
                // Generate default id...
                //生成一個(gè)默認(rèn)的ID值
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                        ObjectUtils.getDisplayString(sc.getContextPath()));
            }
        }
        //把ServletContext設(shè)置到Spring的Web上下文中
        wac.setServletContext(sc);
        //獲取contextConfigLocation的值, 這個(gè)值就是我們的Spring的配置文件
        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);
        //調(diào)用上下文的刷新的方法,這里就開(kāi)始bean的加載初始化實(shí)例化裝配等一系列的動(dòng)作
        wac.refresh();
    }

在上面的代碼中我們分析一下customizeContext這個(gè)方法,對(duì)于wac.refresh();這個(gè)方法我們這里就先不展開(kāi)了。因?yàn)檫@個(gè)方法是SpringIOC容器初始化的入口。內(nèi)容太多了,我們以后在分析Spring的源碼時(shí)再詳細(xì)的說(shuō)明。

   //獲取配置的初始化的類
       List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =
               determineContextInitializerClasses(sc);
       for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {
           Class<?> initializerContextClass =
                   GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
           if (initializerContextClass != null && !initializerContextClass.isInstance(wac)) {
               throw new ApplicationContextException(String.format(
                       "Could not apply context initializer [%s] since its generic parameter [%s] " +
                       "is not assignable from the type of application context used by this " +
                       "context loader: [%s]", initializerClass.getName(), initializerContextClass.getName(),
                       wac.getClass().getName()));
           }
           this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));
       }
       //進(jìn)行排序操作  即上面得到的進(jìn)行初始化的類 需要實(shí)現(xiàn)Ordered的接口或者配置@Order注解
       AnnotationAwareOrderComparator.sort(this.contextInitializers);
       for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
           //調(diào)用初始化方法 進(jìn)行初始化
           initializer.initialize(wac);
       }
   }

下面我們看一下determineContextInitializerClasses這個(gè)查找初始化類的方法

protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>
            determineContextInitializerClasses(ServletContext servletContext) {

        List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes =
                new ArrayList<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>();
        //從ServletContext中獲取初始化參數(shù)為globalInitializerClasses的值
        String globalClassNames = servletContext.getInitParameter(GLOBAL_INITIALIZER_CLASSES_PARAM);
        if (globalClassNames != null) {
            //進(jìn)行字符串的分割
            for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
                classes.add(loadInitializerClass(className));
            }
        }
        //從ServletContext中獲取初始化參數(shù)為contextInitializerClasses的值
        String localClassNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM);
        if (localClassNames != null) {
            //進(jìn)行字符串的分割, ; 空格
            for (String className : StringUtils.tokenizeToStringArray(localClassNames, INIT_PARAM_DELIMITERS)) {
                classes.add(loadInitializerClass(className));
            }
        }

        return classes;
    }

OK,到這里我們關(guān)于Spring Web上下文的分析就先到這里了。這里初始化了一個(gè)父的IOC容器。下篇文章我們接著分析DispatcherServlet的初始化過(guò)程。其時(shí)序圖如下:


資料:線程上下文:
http://blog.csdn.net/zhoudaxia/article/details/35897057

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

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

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