天天看點

SpringMVC的父子容器你都了解了嗎?

環境:Spring5.3.23

配置檔案

如果我們不使用基于注解的方式,那麼在Spring Web項目一般我們都會在web.xml檔案中做如下的配置:

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中還會配置一個監聽器,而這個監聽器的作用就是初始化父容器的

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

上面配置的監聽程式是用來初始化父容器。

public class ContextLoader {
   private WebApplicationContext context;
   private static final Properties defaultStrategies;
   public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
   static {
     try {
       private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
       // 從類路徑下查找ContextLoader.properties檔案
       ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
       // 加載配置檔案;預設情況下會從spring-web-xxx.jar包下的org.springframework.web.context包下有該檔案
       // 檔案内容:org.springframework.web.context.WebApplicationContext=\
       // org.springframework.web.context.support.XmlWebApplicationContext
       // 也就是預設情況下執行個體化的是XmlWebApplicationContext容器對象
       defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
     }
   }
   protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
     // 擷取容器ApplicationContext具體的類的Class對象
     Class<?> contextClass = determineContextClass(sc);
     // 執行個體化XmlWebApplicationContext對象,該對象就是基于xml的配置的解析類
     return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
   }
   protected Class<?> determineContextClass(ServletContext servletContext) {
     public static final String CONTEXT_CLASS_PARAM = "contextClass";
     // 擷取在web.xml中配置的context-param參數名稱為contextClass;這裡其實就是配置你要使用哪個ApplicationContext容器對象
     String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
     // 如果配置了使用你配置的ApplicationContext容器的具體對象
     if (contextClassName != null) {
       try {
         return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
       }
     } else {
       // 如果沒有在web.xml配置檔案中配置contextClass參數,則通過下面的方式擷取
       // 這裡擷取的就是上面static代碼段中加載的配置
       contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
       try {
         return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
       }
     }
   }
   protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac,ServletContext sc) {
     wac.setServletContext(sc);
     // 擷取在web.xml中配置的上下文參數(context-param), 名稱為contextConfigLocation的參數值
     String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
     if (configLocationParam != null) {
       // 如果配置了設定配置檔案路徑
       wac.setConfigLocation(configLocationParam);
     }
     // 執行重新整理,也就是解析處理上面配置的spring配置檔案
     // 如果沒有配置上面的contextConfigLocation參數,那麼會讀取預設的applicationContext.xml配置檔案
     // 檢視XmlWebApplicationContext類
     wac.refresh();
   }
 }
 public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
   public void contextInitialized(ServletContextEvent event) {
     initWebApplicationContext(event.getServletContext());
   }
   public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
     try {
       if (this.context == null) {
         // 建立容器,在父類中建立出XmlWebApplicationContext對象
         this.context = createWebApplicationContext(servletContext);
       }
       // mlWebApplicationContext實作了ConfigurableWebApplicationContext接口
       if (this.context instanceof ConfigurableWebApplicationContext) {
         ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
         // 确定此應用程式上下文是否處于活動狀态,即它是否已重新整理至少一次且尚未關閉。
         if (!cwac.isActive()) {
           // ...
           // 重新整理上下文
           configureAndRefreshWebApplicationContext(cwac, servletContext);
         }
       }
       // 将執行個體化的ApplicationContext儲存到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) {
         currentContextPerThread.put(ccl, this.context);
       }
       return this.context;
     }
   }
 }
 public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext {
   public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";
   public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";
   public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";
   
   protected String[] getDefaultConfigLocations() {
     if (getNamespace() != null) {
       return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
     } else {
       return new String[] {DEFAULT_CONFIG_LOCATION};
     }
   }
   protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) {
     loadBeanDefinitions(beanDefinitionReader);
   }
   protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
     // 調用父類AbstractRefreshableWebApplicationContext方法
     String[] configLocations = getConfigLocations();
     if (configLocations != null) {
       for (String configLocation : configLocations) {
         reader.loadBeanDefinitions(configLocation);
       }
     }
   }
 }
 // XmlWebApplicationContext在執行refresh的時候有這麼一步
 public abstract class AbstractApplicationContext extends DefaultResourceLoader
     implements ConfigurableApplicationContext {
   public void refresh() {
     ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
   }
   protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
     refreshBeanFactory();
     return getBeanFactory();
   }
 }
 public abstract class AbstractRefreshableWebApplicationContext extends AbstractRefreshableConfigApplicationContext
     implements ConfigurableWebApplicationContext, ThemeSource {
   public String[] getConfigLocations() {
     return super.getConfigLocations();
   }
 }
 public abstract class AbstractRefreshableConfigApplicationContext extends AbstractRefreshableApplicationContext
     implements BeanNameAware, InitializingBean {
   protected String[] getConfigLocations() {
     // 如果沒有配置,則調用預設的方法調用子類XmlWebApplicationContext重寫的方法
     return (this.configLocations != null ? this.configLocations : getDefaultConfigLocations());
   }
 }
 public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {
   protected final void refreshBeanFactory() throws BeansException {
     // 調用子類XmlWebApplicationContext重寫的方法
     loadBeanDefinitions(beanFactory);
   }
 }           

總結:

  • 從配置檔案中擷取contextClass參數

    如果沒有則讀取spring-web-xxx.jar中org.springframework.web.context包下的ContextLoader.properties檔案

    目的就是容器執行個體化具體ApplicationContext那個子類對象。

  • 重新整理初始化ApplicationContext對象

    這裡具體的類是XmlWebApplicationContext對象。

    1. 首先是設定要讀取的配置檔案

    讀取web.xml中配置的<context-param>參數contextConfigLocation,如果沒有設定該 參數,則使用預設的/WEB-INF/applicationContext.xml。

    2. 調用refresh初始化spring容器

  • 儲存spring容器對象

    執行個體化後會将該容器對象儲存到ServletContext中,以WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE為name存入。

子容器初始化

子容器的初始化就是DispatcherServlet配置的<init-param>參數

public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
   public final void init() throws ServletException {
 
     // 這裡代碼的作用就是用來讀取DispatcherServlet配置的contextConfigLocation參數,然後設定到
     // FrameworkServlet中的contextConfigLocation屬性中
     // 這裡就是通過BeanWrapper來完成此操作
     PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
     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);
       }
     }
     // 到這裡就設定完了配置在DispatcherServlet中的contextConfigLocation參數
     
     // 初始化Servlet Bean
     initServletBean();
   }
 }           

FrameworkServlet

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
   public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
   public static final String DEFAULT_NAMESPACE_SUFFIX = "-servlet";
   // 該值在父類的init方法中已經通過BeanWrapper設定搞定了,當如如果沒有配置會有預設機制的,下面會看到
   private String contextConfigLocation;
   protected final void initServletBean() throws ServletException {
     this.webApplicationContext = initWebApplicationContext();
   }
   protected WebApplicationContext initWebApplicationContext() {
     // 從ServletContext中讀取在上面(初始化父容器)設定的WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
     // 容器對象
     WebApplicationContext rootContext =
         WebApplicationContextUtils.getWebApplicationContext(getServletContext());
     WebApplicationContext wac = null;
     // ...
     if (wac == null) {
       // 查找容器
       // 這裡預設還是傳回null,這裡就是看你在配置DispatcherServlet的時候有沒有配置contextAttribute屬性
       // 如果你設定了該屬性,會從ServletContext中讀取配置的contextAttribute屬性對應的值(該值必須是WebApplicationContext)
       // 如果不是則抛出異常
       wac = findWebApplicationContext();
     }
     if (wac == null) {
       // 到這還沒有容器對象,則建立容器對象,同時這裡的rootContext會作為父容器
       wac = createWebApplicationContext(rootContext);
     }
     return wac;
   }
   protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
     return createWebApplicationContext((ApplicationContext) parent);
   }
   protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
     // 這裡就是擷取要執行個體化的預設ApplictionContext對象,XmlWebApplicationContext
     Class<?> contextClass = getContextClass();
     // 執行個體化
     ConfigurableWebApplicationContext wac =
         (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
     wac.setEnvironment(getEnvironment());
     // 設定父容器
     wac.setParent(parent);
     // 擷取要讀取加載的配置檔案,如果你沒有則就是null
     String configLocation = getContextConfigLocation();
     // 如果你沒有配置,不進入
     if (configLocation != null) {
       wac.setConfigLocation(configLocation);
     }
     // 重新整理上下文
     configureAndRefreshWebApplicationContext(wac);
 
     return wac;
   }
   protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
     wac.setServletContext(getServletContext());
     wac.setServletConfig(getServletConfig());
     // 這裡就非常關鍵了,記住你這裡有了namespace的值
     wac.setNamespace(getNamespace());
     wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
 
     ConfigurableEnvironment env = wac.getEnvironment();
     if (env instanceof ConfigurableWebEnvironment) {
       ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
     }
 
     postProcessWebApplicationContext(wac);
     applyInitializers(wac);
     // 這裡refresh的過程就和上面初始化父容器的流程一樣了,會查找使用的那些xml配置檔案
     wac.refresh();
   }
   public String getNamespace() {
     // 根據你配置的<servlet-name>xxx</servlet-name>名稱拼接
     // 預設就傳回:xxx-servlet(xxx就是你配置的servlet名稱)
     return (this.namespace != null ? this.namespace : getServletName() + DEFAULT_NAMESPACE_SUFFIX);
   }
   public String getContextConfigLocation() {
     return this.contextConfigLocation;
   }
 }
 // 如果你沒有配置contextConfigLocation,那麼就找預設的配置xml檔案
 public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext {
   public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";
   public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";
   public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";
   
   protected String[] getDefaultConfigLocations() {
     // 上面看到了我們的getNamespace設定了值,則進入
     if (getNamespace() != null) {
       // 這裡就拼接成:/WEB-INF/xxx-servlet.xml(也就是,如果你沒有為DispatcherServlet配置contextConfigLocation屬性)
       return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
     } else {
       return new String[] {DEFAULT_CONFIG_LOCATION};
     }
   }
 }           

總結:

  • 讀取Servlet配置參數

    讀取配置DispatcherServlet時配置的參數,通過BeanWrapper設定目前Servlet對應的屬性中。

  • 執行個體化Spring容器對象

    1. 執行個體化Spring容器對象

    2. 設定容器對象的配置檔案

    如果你為Servlet配置了contextConfigLocation,則使用該參數對應的值作為子容器解 析的xml配置檔案

    3. 設定名稱空間namespace

    根據你配置的servlet名稱 + '-servlet'作為名稱空間

  • 重新整理Spring容器

    調用refresh方法;如果你沒有配置contextConfigLocation,則會查找預設的配置檔案,而這個預設配置在XmlWebApplicationContext已經重寫了,會判斷目前的namespace是否為空,不為空則傳回/WEB-INF/xxx-servlet.xml (xxx: 取值根據你配置的servlet-name)。

完畢!!!

SpringBoot對Spring MVC都做了哪些事?(一)

SpringBoot對Spring MVC都做了哪些事?(二)

SpringBoot對Spring MVC都做了哪些事?(三)

SpringBoot對Spring MVC都做了哪些事?(四)

Spring中的@Configuration注解你真的了解嗎?

Spring MVC 異常處理方式

Spring中字段格式化的使用詳解

Spring MVC 異步請求方式

Spring容器這些擴充點你都清楚了嗎?

Spring 自定義Advisor以程式設計的方式實作AOP

SpringBoot WebFlux整合Spring Security進行權限認證

SpringBoot項目中應用Spring Batch批處理架構,處理大資料新方案

Spring Security權限控制系列(七)

SpringMVC的父子容器你都了解了嗎?
SpringMVC的父子容器你都了解了嗎?
SpringMVC的父子容器你都了解了嗎?
SpringMVC的父子容器你都了解了嗎?