Spring MVC源碼——Root WebApplicationContext

Spring MVC源碼——Root WebApplicationContext

  打算開始讀一些框架的源碼,先拿 Spring MVC 練練手,歡迎點擊這裏訪問個人源碼註釋, SpringMVC官方文檔一開始就給出了這樣的兩段示例:html

WebApplicationInitializer示例:

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletCxt) {

        // Load Spring web application configuration
        AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
        ac.register(AppConfig.class);
        ac.refresh();

        // Create and register the DispatcherServlet
        DispatcherServlet servlet = new DispatcherServlet(ac);
        ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/app/*");
    }
}

web.xml 示例:

<web-app>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/app-context.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>app</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>

</web-app>

  咱們按照 web.xml 中的實例來看一下 Spring MVC 初始化過程.git

上下文層次結構

  Spring MVC 的上下文有以下這樣的層級:github

  

  圖中的 Servlet WebApplicationContext 是與 DispatcherServlet 綁定的上下文, 其中還有 controllers、ViewResolver、HandlerMapping 等組件.web

Root WebApplicationContext 不是必須的上下文, 在須要時,能夠用來在多個 DispatcherServlet 間共享一些 bean.spring

Root WebApplicationContext 初始化和銷燬

  ContextLoaderListener

  web.xml 中配置的 ContextLoaderListener 用於啓動和終止 Spring 的 root WebApplicationContext.spring-mvc

  

   ContextLoaderListener

 

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

    public ContextLoaderListener() {
    }

    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }
    
    /**
     * Initialize the root web application context.
     * Servlet 上下文初始化,調用父類的方法初始化 WebApplicationContext
     */
    @Override
    public void contextInitialized(ServletContextEvent event) {
        initWebApplicationContext(event.getServletContext());
    }


    /**
     * Close the root web application context.
     * Servlet 上下文被銷燬,調用父類的方法銷燬 WebApplicationContext
     */
    @Override
    public void contextDestroyed(ServletContextEvent event) {
        closeWebApplicationContext(event.getServletContext());
        // 銷燬 ServletContext 上實現了 DisposableBean 的屬性並移除他們
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}

ContextLoaderListener 直接調用了父類 ContextLoader 的方法來初始化和銷燬上下文.mvc

ContextLoaderListener 在建立上下文時,會嘗試讀取 contextClass context-param 來指定上下文的類型,被指定的類須要實現 ConfigurableWebApplicationContext 接口. 若是沒有獲取到,默認會使用 WebApplicationContext.app

初始化上下文時,會嘗試讀取 contextConfigLocation context-param, 做爲 xml 文件的路徑.框架

  初始化上下文

initWebApplicationContext() 方法以下:webapp

public WebApplicationContext initWebApplicationContext(ServletContext 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!");
    }

    servletContext.log("Initializing Spring root WebApplicationContext");
    Log logger = LogFactory.getLog(ContextLoader.class);
    if (logger.isInfoEnabled()) {
        logger.info("Root WebApplicationContext: initialization started");
    }
    // 初始化開始時間
    long startTime = System.currentTimeMillis();

    try {
        // Store context in local instance variable, to guarantee that
        // it is available on ServletContext shutdown.
        // 保存上下文到本地實例變量中,保證上細紋能在 ServletContext 關閉時訪問到
        if (this.context == null) {
            // 建立上下文
            this.context = createWebApplicationContext(servletContext);
        }
        if (this.context instanceof ConfigurableWebApplicationContext) {
            // 若是上下文實現了 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);
            }
        }
        // 將上下文綁定到 servletContext 的屬性上
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

        // 獲取當前線程的上下文類加載器
        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        if (ccl == ContextLoader.class.getClassLoader()) {
            currentContext = this.context;
        }
        else if (ccl != null) {
            // 若是有線程上下文類加載器,並且不是 ContextLoader 自己的類加載器,放入到 currentContextPerThread 中。這是一個 static 的域
            currentContextPerThread.put(ccl, this.context);
        }

        if (logger.isInfoEnabled()) {
            long elapsedTime = System.currentTimeMillis() - startTime;
            logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
        }

        return this.context;
    }
    catch (RuntimeException | Error ex) {
        // 發生異常, 把異常綁定到上下文對應的屬性上,以後不會再進行初始化
        logger.error("Context initialization failed", ex);
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
        throw ex;
    }
}

initWebApplicationContext() 方法調用了 createWebApplicationContext() 方法來建立上下文;調用了 configureAndRefreshWebApplicationContext() 來對實現了 ConfigurableWebApplicationContext 接口的上下文作初始化.

createWebApplicationContext() 會調用 determineContextClass() 來獲取上下文類型的 Class 對象.

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
    // 獲取上下文類型
    Class<?> contextClass = determineContextClass(sc);
    // 檢查是否實現了 ConfigurableWebApplicationContext
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
    }
    // 實例化
    return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

protected Class<?> determineContextClass(ServletContext servletContext) {
    // 獲取 serveltContext 的 'contextClass' 初始化參數。
    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);
        }
    }
}

configureAndRefreshWebApplicationContext() 方法.

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
        // 獲取 ServletContext 的 'contextId' 初始化參數。
        String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
        if (idParam != null) {
            wac.setId(idParam);
        }
        else {
            // Generate default id...
            // 生成默認 id
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                    ObjectUtils.getDisplayString(sc.getContextPath()));
        }
    }
    // 設置 servletContext 屬性
    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
    // 初始化屬性源, 確保 servlet 屬性源到位並可以在任何 refresh 以前的後期處理和初始化中使用
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
    }

    // 在設置了配置文件以後上下文刷新以前,自定義上下文
    customizeContext(sc, wac);
    wac.refresh();
}

protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
    // 根據 ServletContext 的 'contextInitializerClasses' 和 'globalInitializerClasses' 初始化參數 加載 ApplicationContextInitializer 的 class
    List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =
            determineContextInitializerClasses(sc);

    for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {
        // 獲取範型參數類型
        Class<?> initializerContextClass =
                GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
        // 檢查 Initializer 是否適用於當前上下文對象
        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()));
        }
        // 建立 Initializer 實例,並添加到 contextInitializers
        this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));
    }
    // 根據 org.springframework.core.Ordered 和 org.springframework.core.annotation.Order 排序,若是沒有實現或註解,會被排到最後
    AnnotationAwareOrderComparator.sort(this.contextInitializers);
    // 執行每一個 initializer 的 initialize() 方法
    for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
        initializer.initialize(wac);
    }
}

  銷燬上下文

closeWebApplicationContext() 方法以下:

public void closeWebApplicationContext(ServletContext servletContext) {
    servletContext.log("Closing Spring root WebApplicationContext");
    try {
        // 若是 context 是 ConfigurableWebApplicationContext 調用 close() 方法
        if (this.context instanceof ConfigurableWebApplicationContext) {
            ((ConfigurableWebApplicationContext) this.context).close();
        }
    }
    finally {
        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        if (ccl == ContextLoader.class.getClassLoader()) {
            currentContext = null;
        }
        else if (ccl != null) {
            currentContextPerThread.remove(ccl);
        }
        // 移除 servletContext 中的 context 屬性
        servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
    }
}

Servlet 3.0+ 中初始化

Servlet 3.0+ 中能夠經過 ServletContext 的 addlistener() 方法來添加監聽器.所以能夠先把 Spring 容器先建立好,再傳給 ContextLoaderListener 的構造器.這裏就不本身寫例子了,選了單元測試中的 ContextLoaderTests.testContextLoaderListenerWithDefaultContext() 方法:

public void testContextLoaderListenerWithDefaultContext() {
    MockServletContext sc = new MockServletContext("");
    sc.addInitParameter(ContextLoader.CONFIG_LOCATION_PARAM,
            "/org/springframework/web/context/WEB-INF/applicationContext.xml " +
            "/org/springframework/web/context/WEB-INF/context-addition.xml");
    ServletContextListener listener = new ContextLoaderListener();
    ServletContextEvent event = new ServletContextEvent(sc);
    listener.contextInitialized(event);
    String contextAttr = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
    WebApplicationContext context = (WebApplicationContext) sc.getAttribute(contextAttr);
    assertTrue("Correct WebApplicationContext exposed in ServletContext", context instanceof XmlWebApplicationContext);
    assertTrue(WebApplicationContextUtils.getRequiredWebApplicationContext(sc) instanceof XmlWebApplicationContext);
    LifecycleBean lb = (LifecycleBean) context.getBean("lifecycle");
    assertTrue("Has father", context.containsBean("father"));
    assertTrue("Has rod", context.containsBean("rod"));
    assertTrue("Has kerry", context.containsBean("kerry"));
    assertTrue("Not destroyed", !lb.isDestroyed());
    assertFalse(context.containsBean("beans1.bean1"));
    assertFalse(context.containsBean("beans1.bean2"));
    listener.contextDestroyed(event);
    assertTrue("Destroyed", lb.isDestroyed());
    assertNull(sc.getAttribute(contextAttr));
    assertNull(WebApplicationContextUtils.getWebApplicationContext(sc));
}

 

 轉自:https://www.cnblogs.com/FJH1994/p/10798028.html

相關文章
相關標籤/搜索