Spring IOC 父子容器(注解方式啟動)


SpringBoot 啟動默認沒有父子容器,只有一個容器

 

一、調試環境

依賴使用 Maven 管理,只用導入 spring-context 即可,這里的版本為 5.2.7

通常使用 spring 有兩種配置方式:注解和配置文件

public static void main(String[] args) {
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
    // ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");

    Person bean = applicationContext.getBean(Person.class);
    System.out.println(bean);

    // 打印 IOC 容器中所有 bean 的名稱
    String[] namesForType = applicationContext.getBeanDefinitionNames();
    for (String name : namesForType) {
        System.out.println(name);
    }
}

兩種配置方式都可以啟動 IOC 容器

注解

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration // 告訴 Spring 這是一個配置類
@ComponentScan // 默認掃描當前包(com.test.config)及其子包(com.test.config.*)
public class MainConfig {

    // 給容器中注冊一個 Bean,類型為返回值的類型,id 默認是用方法名作為 id
    @Bean("person123")
    public Person person() {
        return new Person("lisi", 20, "nice");
    }
}
View Code

配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 包掃描、只要標注了@Controller、@Service、@Repository,@Component,禁用默認規則才可自定義掃描規則 -->
    <context:component-scan base-package="com.test" use-default-filters="false"/>

    <bean id="person" class="com.test.bean.Person" scope="prototype">
        <property name="age" value="29"></property>
        <property name="name" value="zhangsan"></property>
    </bean>
</beans>
View Code

一般情況下,我們不會像這樣手動去初始化 IOC 容器,而是在 Web 環境下使用,它會自動初始化 IOC 容器,附上調試環境:https://www.cnblogs.com/jhxxb/p/10512553.html

 

二、開始源碼

Tomcat 啟動后會自動加載”配置類“:https://www.cnblogs.com/jhxxb/p/13596565.html

 

第一個被執行的 Spring 程序類應該就是 SpringServletContainerInitializer  類了

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    @Override
    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
        List<WebApplicationInitializer> initializers = new LinkedList<>();

        if (webAppInitializerClasses != null) {
            for (Class<?> waiClass : webAppInitializerClasses) {
                // 只處理實體類,接口和抽象類一概不管
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        initializers.add((WebApplicationInitializer) ReflectionUtils.accessibleConstructor(waiClass).newInstance());
                    } catch (Throwable ex) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
                    }
                }
            }
        }

        if (initializers.isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
            return;
        }

        servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
        // 排序后,循環調用 onStartup 方法進行初始化
        AnnotationAwareOrderComparator.sort(initializers);
        for (WebApplicationInitializer initializer : initializers) {
            initializer.onStartup(servletContext);
        }
    }
}

可以看到這里只有我們寫的一個配置類,抽象類和接口都被過濾掉了

 

往下走,onStartup 的實現在 AbstractDispatcherServletInitializer 類

public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        super.onStartup(servletContext);
        // 注冊 DispatcherServlet,讓它去初始化 Spring MVC 的子容器
        registerDispatcherServlet(servletContext);
    }


public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
    // 父類方法,super.onStartup(servletContext)
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        // 注冊 ContextLoaderListener 監聽器,去初始化 Spring 父容器
        registerContextLoaderListener(servletContext);
    }

這里可以看出分兩步,先初始化 Spring IOC,再初始化 Spring MVC IOC

 

注冊 ContextLoaderListener,為了后續初始化 Spring IOC

public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
    protected void registerContextLoaderListener(ServletContext servletContext) {
        WebApplicationContext rootAppContext = createRootApplicationContext();
        if (rootAppContext != null) {
            // 創建 listener,並且把已經創建好的容器放進去
            ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
            // 放入監聽器需要的一些上下文,此處沒有。一般都為 null 即可。若有需要(自己定制),子類復寫此方法即可
            listener.setContextInitializers(getRootApplicationContextInitializers());
            // 把監聽器加入進來  這樣該監聽器就能監聽 ServletContext 了,並且執行 contextInitialized 方法
            servletContext.addListener(listener);
        }

createRootApplicationContext 創建了一個 AnnotationConfigWebApplicationContext,並把配置文件注冊了進去

public abstract class AbstractAnnotationConfigDispatcherServletInitializer extends AbstractDispatcherServletInitializer {
    @Override
    @Nullable // 返回值允許為 null,這里不分析為 null 的執行流程
    protected WebApplicationContext createRootApplicationContext() {
        Class<?>[] configClasses = getRootConfigClasses();
        if (!ObjectUtils.isEmpty(configClasses)) {
            AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
            // 配置文件可以有多個,會以累加的形式添加進去
            context.register(configClasses);
            return context;
        } else {
            return null;
        }
    }

 

注冊 DispatcherServlet,為了后續初始化 Spring MVC IOC

public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
    protected void registerDispatcherServlet(ServletContext servletContext) {
        // Servlet 名稱,一般用系統默認的即可,否則自己復寫此方法也成
        String servletName = getServletName();
        Assert.hasLength(servletName, "getServletName() must not return null or empty");

        // 創建 web 的子容器。創建的代碼和上面差不多,也是使用調用者提供的配置文件,創建 AnnotationConfigWebApplicationContext,注:此處不可能為 null
        WebApplicationContext servletAppContext = createServletApplicationContext();
        Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");

        // 創建 DispatcherServlet,並且把子容器傳進去了。其實就是 new 一個出來,最后加到容器里,就能夠執行一些 init 初始化方法了
        FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
        Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");

        // 同樣的 getServletApplicationContextInitializers(),一般也為 null 即可
        dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

        // 注冊 servlet 到 web 容器里,這樣就可以接收 HTTP 請求了
        ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
        if (registration == null) {
            throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. Check if there is another servlet registered under the same name.");
        }

        // 1 表示立馬執行
        registration.setLoadOnStartup(1);
        // 調用者必須實現
        registration.addMapping(getServletMappings());
        // 默認就是開啟了支持異步的
        registration.setAsyncSupported(isAsyncSupported());

        // 處理自定義的 Filter 進來,一般我們 Filter 不這么加進來,而是自己 @WebFilter,或者借助 Spring。注:這里添加進來的 Filter 都僅僅只攔截過濾上面注冊的 dispatchServlet
        Filter[] filters = getServletFilters();
        if (!ObjectUtils.isEmpty(filters)) {
            for (Filter filter : filters) {
                registerServletFilter(servletContext, filter);
            }
        }

        // 調用者若對 dispatcherServlet 有自己更個性化的參數設置,復寫此方法即可
        customizeRegistration(registration);
    }

 

初始化 Spring IOC

繼續執行,就來到了 ContextLoaderListener 的初始化方法,附帶會初始化 Spring 容器(注:到了此處,就和 web.xml 方式啟動一模一樣了)

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent event) {
        initWebApplicationContext(event.getServletContext());
    }

和 web.xml 不一樣的是,使用注解驅動啟動的此時,ContextLoaderListener 對象已經持有 WebApplicationContext 的引用(但是還沒有放進 ServletContext 里面去)

往下走,進入父類的 initWebApplicationContext 方法

public class ContextLoader {
    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        // 雖然注解驅動傳進來的監聽器對象持有 WebApplicationContext 的引用,但是並沒有放進 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 {
            // 這句特別重要,兼容了 web.xml 的方式以及注解驅動的方式。這里是注解驅動的方式,所以此處不會為 null。用 web.xml 的方式的時候,會去詳細看 createWebApplicationContext(servletContext) 方法
            if (this.context == null) {
                this.context = createWebApplicationContext(servletContext);
            }
            // 從上圖可以看出:XmlWebApplicationContext(xml驅動) 和 AnnotationConfigWebApplicationContext(注解驅動) 是復合的,都會進來
            if (this.context instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
                // 一般剛創建的 context 不會處於激活狀態,所以會進來完善一些更多的容器信息。比如刷新容器、設置父容器等
                if (!cwac.isActive()) {
                    if (cwac.getParent() == null) {
                        // Spring 5.0 開始,默認實現只是返回 null。絕大多數情況下,Spring 容器不用再給設置父容器。這里主要是想多個 Web 應用可以有一個共同的父容器,例如 EJB、EAR
                        ApplicationContext parent = loadParentContext(servletContext);
                        cwac.setParent(parent);
                    }
                    // 讀取相應的配置並且刷新 context 對象,這一步極其重要了,因為刷新容器做了太多的事,屬於容器的最最最核心邏輯(這里不做分析)
                    configureAndRefreshWebApplicationContext(cwac, servletContext);
                }
            }
            // 放進 ServletContext 上下文,避免再次被初始化,也讓我們能更加方便的獲取到容器
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

            // 此處把容器和當前線程綁定,public static WebApplicationContext getCurrentWebApplicationContext() 這樣就可以更加方便的得到容器,類為:ContextLoader
            ClassLoader ccl = Thread.currentThread().getContextClassLoader();
            if (ccl == ContextLoader.class.getClassLoader()) {
                currentContext = this.context;
            } else if (ccl != null) {
                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;
        }
    }


    // 最重要的一個方法
    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
        // 一般此處為真,給 ApplicationContext 設置一個 id
        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
            // 獲取 servletContext 中的 contextId 屬性 contextId,可在 web.xml 里配置,一般也不用配置,采用 else 里的默認值即可
            String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
            if (idParam != null) {
                // 存在則設為指定的 id 名
                wac.setId(idParam);
            } else {
                // 生成默認 id,一般為 org.springframework.web.context.WebApplicationContext:${contextPath}
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath()));
            }
        }

        // 讓 Spring 容器關聯上 servlet 上下文
        wac.setServletContext(sc);
        // 讀取 contextConfigLocation 屬性(在 web.xml 配置,但是注解驅動里沒有,因此為 null)
        String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
        if (configLocationParam != null) {
            // 設置指定的 spring 文件所在地,支持 classpath 前綴並多文件,以 ,; 為分隔符
            wac.setConfigLocation(configLocationParam);
        }
// 這里有一個注意的地方,ConfigurableEnvironment 生成的地方 // wac.setConfigLocation(configLocationParam); 時根據 configLocationParam 設置配置參數路徑時就會初始化 StandardServletEnvironment(ConfigurableEnvironment 的子類) // StandardServletEnvironment 符合條件,因此會執行 initPropertySources 方法 ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(sc, null); } // 檢查 web.xml 是否有一些其余初始化類的配置,極大多數情況都不需要,所以粗暴理解為沒什么用 customizeContext(sc, wac); // 容器的核心方法,也是最難的一個方法 // 這里先理解為就是初始化容器,比如加載 bean、攔截器、各種處理器的操作就夠了(也是最耗時的一步操作) wac.refresh(); }

該方法完成之后,看控制台日志:

Root WebApplicationContext initialized in 435212 ms

就說明 Spring 根容器初始化完成了

 

初始化 Spring MVC IOC

繼續執行,就來到了 DispatcherServlet 的初始化方法,附帶會初始化 Spring 子容器(Web 容器),首先要知道 Servlet 初始化的大致步驟:

  1. Servlet 容器加載 Servlet 類,把類的 .class 文件中的數據讀到內存中
  2. Servlet 容器中創建一個 ServletConfig 對象。該對象中包含了 Servlet 的初始化配置信息
  3. Servlet 容器創建一個 Servlet 對象(我們也可以手動 new,然后手動添加進去)
  4. Servlet 容器調用 Servlet 對象的 init() 方法進行初始化

由於設置了 registration.setLoadOnStartup(1),在容器啟動完成后就會調用 servlet 的 init()

DispatcherServlet 繼承 FrameworkServlet 繼承 HttpServletBean 繼承 HttpServlet,在 HttpServletBean 實現了 init()

先看 HttpServletBean 的 init 方法

public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
    @Override
    public final void init() throws ServletException {
        // 把 Servlet 的初始化參數封裝進來...
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);

        // 這里我們並沒有給此 Servlet 初始化的一些參數,所以此處為空,為 false
        // 若進來了,可以看到里面會做一些處理:將這個 DispatcherServlet 轉換成一個 BeanWrapper 對象,從而能夠以 Spring 的方式來對初始化參數的值進行注入。這些屬性如 contextConfigLocation、namespace 等等。
        // 同時注冊一個屬性編輯器,一旦在屬性注入的時候遇到 Resource 類型的屬性就會使用 ResourceEditor 去解析。再留一個 initBeanWrapper(bw) 方法給子類覆蓋,讓子類處真正執行 BeanWrapper 的屬性注入工作。
        // 但是 HttpServletBean 的子類 FrameworkServlet 和 DispatcherServlet 都沒有覆蓋其 initBeanWrapper(bw) 方法,所以創建的 BeanWrapper 對象沒有任何作用。

        // 備注:此部分把當前 Servlet 封裝成一個 BeanWrapper,再把它交給 Spring 容器管理,這部分非常重要,使用 SpringBoot 啟動的時候,會看出來這部分代碼的重要性
        if (!pvs.isEmpty()) {
            try {
                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) {
                if (logger.isErrorEnabled()) {
                    logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
                }
                throw ex;
            }
        }
        // Spring 把這個 init 方法給 final 掉了,然后開了這個口子,子類可以根據自己的需要,在初始化的時候復寫這個方法,而不再是 init 方法
        initServletBean();
    }

 繼續 initServletBean() 方法,它是由 FrameworkServlet 實現的

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    @Override
    protected final void initServletBean() throws ServletException {
        getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
        if (logger.isInfoEnabled()) {
            logger.info("Initializing Servlet '" + getServletName() + "'");
        }
        long startTime = System.currentTimeMillis();

        try {
            // 重點,開始初始化 Spring 子容器
            this.webApplicationContext = initWebApplicationContext();
            // 留一個口,給子類去復寫初始化所需要的操作,一般都為空實現即可,除非自己要復寫 DispatcherServlet
            initFrameworkServlet();
        } catch (ServletException | RuntimeException ex) {
            logger.error("Context initialization failed", ex);
            throw ex;
        }

        if (logger.isDebugEnabled()) {
            String value = this.enableLoggingRequestDetails ? "shown which may lead to unsafe logging of potentially sensitive data" : "masked to prevent unsafe logging of potentially sensitive data";
            logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails + "': request parameters and headers will be " + value);
        }

        // 當看到這句日志,dispatcherServlet 就已經初始化完成,Web 子容器也就初始化完成
        if (logger.isInfoEnabled()) {
            logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
        }
    }

繼續 initWebApplicationContext 方法:創建一個 Web 子容器,並且和上面 Spring 已經創建好了的父容器關聯上

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    protected WebApplicationContext initWebApplicationContext() {
        // 從 ServletContext 中把上面已經創建好的根容器拿到手
        WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;

        // 上面注冊 DispatcherServlet(createDispatcherServlet(servletAppContext)方法)的時候,已經傳入了根據配置文件創建好了的子容器,因此,此處肯定是不為 null 的,會進去,和上面一樣,完成子容器的初始化、刷新工作,這里就不再解釋了
        if (this.webApplicationContext != null) {
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                if (!cwac.isActive()) {
                    if (cwac.getParent() == null) {
                        // 把根容器,設置為自己的父容器
                        cwac.setParent(rootContext);
                    }
                    // 根據綁定的配置,初始化、刷新容器
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }

        // 若是 web.xml 方式,會走這里,進而走 findWebApplicationContext()
        if (wac == null) {
            wac = findWebApplicationContext();
        }
        if (wac == null) {
            wac = createWebApplicationContext(rootContext);
        }

        // 此處注意:下面有解釋,refreshEventReceived 屬性保證了 onRefresh 方法不會被重復執行
        if (!this.refreshEventReceived) {
            synchronized (this.onRefreshMonitor) {
                onRefresh(wac);
            }
        }

        // 是否需要把我們的容器發布出去,作為 ServletContext 的一個屬性值?默認值為 true,一般情況下就讓為 true 即可
        if (this.publishContext) {
            // 這個 attr 的 key 的默認值,就是 FrameworkServlet.SERVLET_CONTEXT_PREFIX,保證了全局唯一性
            // 這么一來,我們的根容器、web 子容器其實就都放進 ServletContext 上下文里了,拿取都非常的方便。只是我們一般拿這個容器的情況較少,一般都是拿根容器,比如那個工具類就是獲取根容器的
            String attrName = getServletContextAttributeName();
            getServletContext().setAttribute(attrName, wac);
        }

        return wac;
    }

FrameworkServlet 策略式的實現了監聽方法,監聽應用的刷新事件。

當我們刷新應用的時候(比如上面執行 refresh() 方法,這里就會執行,並且打上標記說已經執行過了),然而 onRefresh() 是一個模版方法,具體實現交給子類,這樣子 DispatcherServlet 就可以做初始化 Web 組件的一些事情

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    public void onApplicationEvent(ContextRefreshedEvent event) {
        this.refreshEventReceived = true;
        synchronized (this.onRefreshMonitor) {
            onRefresh(event.getApplicationContext());
        }
    }

    private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) {
            FrameworkServlet.this.onApplicationEvent(event);
        }
    }

這就是為何會抽象出 FrameworkServlet 的原因,因為它設計的初衷不僅僅只想支持到 Servlet

所以此處就不得不說一下,子類自己實現的 onRefresh() 方法:

public class DispatcherServlet extends FrameworkServlet {
    @Override
    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }

    // 初始化 Spring MVC 的 9 大組件(至此,才算全部初始化完成了)
    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }

該方法完成之后,看控制台日志:

INFO (FrameworkServlet.java:547)- Completed initialization in 2015760 ms

就說明整個 Spring 父子容器全部初始化、啟動完成了

 

三、其他

SpringMVC 中的 Servlet 的三個層次:

  1. HttpServletBean 直接繼承自 java 的 HttpServlet,其作用是將 Servlet 中配置的參數設置到相應的 Bean 屬性上
  2. FrameworkServlet 初始化了 WebApplicationContext
  3. DispatcherServlet 初始化了自身的 9 個組件(重點)

在任何地方獲取 Spring 容器(根容器):

@Autowired
private HttpServletRequest request;

@Override
public Object hello() {
    ApplicationContext ctx1 = WebApplicationContextUtils.getWebApplicationContext(request.getSession().getServletContext());
    WebApplicationContext ctx2 = ContextLoader.getCurrentWebApplicationContext();
    System.out.println(ctx1 == ctx2); // true

    return "service hello";
}

用了 ContextLoader 的靜態方法

public static WebApplicationContext getCurrentWebApplicationContext() {
    ClassLoader ccl = Thread.currentThread().getContextClassLoader();
    if (ccl != null) {
        WebApplicationContext ccpt = currentContextPerThread.get(ccl);
        if (ccpt != null) {
            return ccpt;
        }
    }
    return currentContext;
}

關於 DispatcherServlet:

DispatcherServlet 創建自己的 WebApplicationContext 並管理這個 WebApplicationContext 里面的 handlers/controllers/view-resolvers

FrameworkServlet 實現(Spring 4.0 之后才實現的)了 ApplicationContextAware 接口的 setApplicationContext() 方法,可知 DispatcherServlet 的 applicationContext 來自 FrameworkServlet

setApplicationContext 方法只用於嵌入式的 Servlet 環境。war 環境這里是不會執行的,war 環境下 WebApplicationContext 不會以 Bean 的形式存在於 Spring 容器

在 DispatcherServlet 的 doService 方法里都有這樣的一段代碼,方便我們非常方便獲取到一些參數,比如 web 子容器等等

public class DispatcherServlet extends FrameworkServlet {
    @Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // Make framework objects available to handlers and view objects.
        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

關於父子容器:

優點:能讓 web 環境和普通的 Spring 環境達到隔離的效果。web 容器專注於管理 web 相關 Bean,其余 bean 的交給父容器。 

缺點:父子容器的設計提高了 Spring 初始化、管理 Bean 的復雜度(雖然對使用者一般無感),但萬一要用到相關功能時,若不理解原理會有莫名其妙的一些問題,提高了復雜性

理論上可以有任意多個容器(只是我們一般其它的 Bean 都只放進主容器統一管理,但 Spring 是提供了這樣的功能的),比如:

  • 主容器:applicationContext.xml(主文件,包括 JDBC 配置,hibernate.cfg.xml,與所有的 Service 與 DAO 基類)
  • web 子容器:application-servlet.xml(管理 Spring MVC 9 大組件以及相關的Bean)
  • cache 子容器:applicationContext-cache.xml(cache 策略配置,管理和緩存相關的Bean)
  • JMX 子容器:applicationContext-jmx.xml(JMX 相關的 Bean)

值得注意的是,SpringMVC 在調用 HandlerMapper 進行 url 到 controller 函數方法映射解析的時候,HandlerMapper 會在 SpringMVC 容器中尋找 controller,也就是在子容器中尋找,不會去父容器 spring 容器中尋找的。

所以如果用父容器來管理 controller 的話,子容器不去管理,在訪問頁面的時候會出現 404 錯誤。

Spring Boot 中只采用一個容器,也就沒有這個問題

 


https://blog.csdn.net/f641385712/article/details/87883205

https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-servlet-context-hierarchy


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM