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"); } }
配置文件

<?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>
一般情況下,我們不會像這樣手動去初始化 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 初始化的大致步驟:
- Servlet 容器加載 Servlet 類,把類的 .class 文件中的數據讀到內存中
- Servlet 容器中創建一個 ServletConfig 對象。該對象中包含了 Servlet 的初始化配置信息
- Servlet 容器創建一個 Servlet 對象(我們也可以手動 new,然后手動添加進去)
- 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 的三個層次:
- HttpServletBean 直接繼承自 java 的 HttpServlet,其作用是將 Servlet 中配置的參數設置到相應的 Bean 屬性上
- FrameworkServlet 初始化了 WebApplicationContext
- 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 中只采用一個容器,也就沒有這個問題