Servlet项目启动执行顺序

由前面的回顾内容可知,Servlet项目启动执行顺序如下

  • ServletContainerInitializer.onStartup(Set<Class<?>> c, ServletContext ctx)
  • ServletContextListener.contextInitialized(ServletContextEvent sce)
  • Filter-------init(FilterConfig filterConfig)
  • HttpServlet-------------init()
  • Filter-------doFilter 过滤前
  • HttpServlet------------doget/dopost
  • Filter-------doFilter 过滤后

SpringMVC的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/applicationContext.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>classpath:springmvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

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

applicationContext.xml

<?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
">

    <!-- 扫描基本包并开启spring注解 过滤controller层是因为Spring MVC管理Controller,Spring 管理Controller之外的Bean -->
    <context:component-scan base-package="com.yoocar" >
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
    </context:component-scan>


</beans>

springmvc.xml

<?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:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-4.2.xsd"


>

    <!-- 使用基于注解的控制器,spring会自动扫描base-package下的子包和类,如果扫描到会把这些类注册为bean-->
    <context:component-scan base-package="com.yoocar.controller"/>
    <!-- mvc 请求映射处理器与适配器 @RequestMapping  -->
    <mvc:annotation-driven />

    <!--配置视图解析器 默认的视图解析器- -->
    <bean id="defaultViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
        <!--<property name="contentType" value="text/html" />-->
        <property name="prefix" value="/WEB-INF/" />
        <property name="suffix" value=".jsp" />
    </bean>

    <!-- json 支持 -->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
    </bean>
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="messageConverters">
            <list>
                <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
                <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
            </list>
        </property>
    </bean>

    <!-- 文件上传配置 -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize">
            <value>104857600</value>
        </property>
        <property name="maxInMemorySize">
            <value>4096</value>
        </property>
    </bean>

    <!-- 拦截器配置 -->
    <mvc:interceptors>
        <!-- 多个拦截器,顺序执行 -->
        <mvc:interceptor>
            <!-- 需要拦截的url -->
            <mvc:mapping path="/*/*"/>
            <!-- 不拦截该请求 -->
            <mvc:exclude-mapping path="/user/login*"/>
            <mvc:exclude-mapping path="/video/**"/>
            <!-- 拦截器实现类 -->
            <bean class="com.yoocar.config.VisitInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>

</beans>

SpringServletContainerInitializer

SpringMVC实现了Servlet的spi机制.,如下

image

源码如下

@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) {
      //传入所有实现了WebApplicationInitializer的子类
         for (Class<?> waiClass : webAppInitializerClasses) {
            // Be defensive: Some servlet containers provide us with invalid classes,
            // no matter what @HandlesTypes says...
            //判断是否是接口或者抽象类,都不是,进如下逻辑
            if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
                  WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
               try {
               	  //利用反射,实例化实现了WebApplicationInitializer的子类,并加入到initializers中去
                  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");
      AnnotationAwareOrderComparator.sort(initializers);
      for (WebApplicationInitializer initializer : initializers) {
      	//调用所有实现了WebApplicationInitializer的子类的onStartup方法
         initializer.onStartup(servletContext);
      }
   }

}
  • 调用onStartUp()前会先找到@HandlesTypes(WebApplicationInitializer.class) 所有实现了WebApplicationInitializer的类,

  • 传入到OnStartup的webAppInitializerClasses参数中,并传入Servlet上下文对象。

  • 找到所有WebApplicationInitializer的实现类后, 不是接口、不是抽象则通过反射进行实例化(所以,你会发现内部实现类都是抽象的,你想让其起作用我们必须添加一个自定义实现类,在下文提供我的自定义实现类)

  • 调用所有上一步实例化后的对象的onStartup方法

注意,如果使用xml,且没有自定义配置,这里SpringServletContainerInitializer并没有特殊的处理,以下的知识点用于注解版补充,可以先略过

实现了了WebApplicationInitializer接口的类结构图

image

AbstractDispatcherServletInitializer.onStartup

public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {

   /**
    * The default servlet name. Can be customized by overriding {@link #getServletName}.
    */
   public static final String DEFAULT_SERVLET_NAME = "dispatcher";


   @Override
   public void onStartup(ServletContext servletContext) throws ServletException {
      super.onStartup(servletContext);
      //注册DispatcherServlet
      registerDispatcherServlet(servletContext);
   }


   protected void registerDispatcherServlet(ServletContext servletContext) {
	  //获取DispatcherServlet的名字
      String servletName = getServletName();
      Assert.hasLength(servletName, "getServletName() must not return null or empty");

	  //创建WebApplicationContext对象,创建子容器
      WebApplicationContext servletAppContext = createServletApplicationContext();
      Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
      
	  //这里new了一个DispatcherServlet
      FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
      Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
      dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

	  //添加dispatcherServlet到servletContext
      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.");
      }
	  //设置启动时加载
      registration.setLoadOnStartup(1);
      //调用抽象方法设置映射路径:getServletMappings()(此抽象方法在我们自定义的子类中实现提供我们自定义的映射路径 )
      registration.addMapping(getServletMappings());
      registration.setAsyncSupported(isAsyncSupported());

      Filter[] filters = getServletFilters();
      if (!ObjectUtils.isEmpty(filters)) {
         for (Filter filter : filters) {
            registerServletFilter(servletContext, filter);
         }
      }

      customizeRegistration(registration);
   }


   protected String getServletName() {
      return DEFAULT_SERVLET_NAME;
   }


   protected abstract WebApplicationContext createServletApplicationContext();


   protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
      return new DispatcherServlet(servletAppContext);
   }


   @Nullable
   protected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() {
      return null;
   }


   protected abstract String[] getServletMappings();


   @Nullable
   protected Filter[] getServletFilters() {
      return null;
   }


   protected FilterRegistration.Dynamic registerServletFilter(ServletContext servletContext, Filter filter) {
      String filterName = Conventions.getVariableName(filter);
      Dynamic registration = servletContext.addFilter(filterName, filter);

      if (registration == null) {
         int counter = 0;
         while (registration == null) {
            if (counter == 100) {
               throw new IllegalStateException("Failed to register filter with name '" + filterName + "'. " +
                     "Check if there is another filter registered under the same name.");
            }
            registration = servletContext.addFilter(filterName + "#" + counter, filter);
            counter++;
         }
      }

      registration.setAsyncSupported(isAsyncSupported());
      registration.addMappingForServletNames(getDispatcherTypes(), false, getServletName());
      return registration;
   }

   private EnumSet<DispatcherType> getDispatcherTypes() {
      return (isAsyncSupported() ?
            EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ASYNC) :
            EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE));
   }


   protected boolean isAsyncSupported() {
      return true;
   }


   protected void customizeRegistration(ServletRegistration.Dynamic registration) {
   }

}

createServletApplicationContext

@Override
protected WebApplicationContext createServletApplicationContext() {
	//创建子容器
   AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
   //获取子容器的配置类
   Class<?>[] configClasses = getServletConfigClasses();
   if (!ObjectUtils.isEmpty(configClasses)) {
   	  //把配置类注册到子容器中
      context.register(configClasses);
   }
   return context;
}

ContextLoaderListener

ContextLoaderListener通过实现ServletContextListener接口,将创建springIOC容器作为父容器融入web容器当中。

  • 父容器:springIOC容器,扫描包的时候排除controller层
  • 子容器:springweb,只扫描controller层

子容器可以使用父容器的bean

父容器不可以使用子容器的bean

ContextLoaderListener.contextInitialized

直接看ContextLoaderListener.contextInitialized源码

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

 
   public ContextLoaderListener() {
   }

   public ContextLoaderListener(WebApplicationContext context) {
      super(context);
   }


   /**
    * 初始化根容器,关键方法
    * Initialize the root web application context.
    */
   @Override
   public void contextInitialized(ServletContextEvent event) {
      initWebApplicationContext(event.getServletContext());
   }


   /**
    * Close the root web application context.
    */
   @Override
   public void contextDestroyed(ServletContextEvent event) {
      closeWebApplicationContext(event.getServletContext());
      ContextCleanupListener.cleanupAttributes(event.getServletContext());
   }

}

ContextLoader.initWebApplicationContext

//创建和初始化spring主容器对应的WebApplicationContext对象实例
//并调用refresh方法完成从contextConfigLocation指定的配置中,加载BeanDefinitions和创建bean实例
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    //判断是否已经有Root  WebApplicationContext,已经有则抛出异常
    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 {
        if (this.context == null) {
            //创建上下文对象,即XmlWebApplicationContext(静态方法中从ContextLoader.properties文件中读取)  并赋值给全局变量context
            this.context = createWebApplicationContext(servletContext);
        }
        //XmlWebApplicationContext是ConfigurableWebApplicationContext类型的
        if (this.context instanceof ConfigurableWebApplicationContext) {
        	//这里的cwac就是XmlWebApplicationContext
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    // 设置父容器(如果有),一般来说没有,可忽略
                    ApplicationContext parent = loadParentContext(servletContext);
                    cwac.setParent(parent);
                }
                //核心方法,完成配置加载,BeanDefinition定义和bean对象创建
                configureAndRefreshWebApplicationContext(cwac, servletContext);
            }
        }
        //ioc容器上下文设置到servlet上下文servletContext,将ROOT容器存入到Servlet域中供子容器使用
        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);
        }

        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;
    }
}

ContextLoader.createWebApplicationContext

这里就是实例化了一个root 容器,即spring ioc容器,知识实例化,还没有refresh

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
	//核心代码
   Class<?> contextClass = determineContextClass(sc);
   if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
      throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
            "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
   }
   //利用反射实例化contextClass
   return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
ContextLoader.determineContextClass

获取ServletContextclass对象,如果没有指定,xml方式默认为XmlWebApplicationContext

protected Class<?> determineContextClass(ServletContext servletContext) {
	//从配置文件中获取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 {
   	  //如果没有指定,默认为XmlWebApplicationContext
      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);
      }
   }
}
defaultStrategies.getProperty

defaultStrategies的内容在static代码块中加载

private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";

private static final Properties defaultStrategies;

static {
   // Load default strategy implementations from properties file.
   // This is currently strictly internal and not meant to be customized
   // by application developers.
   try {
      ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
      defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
   }
   catch (IOException ex) {
      throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
   }
}

上面的DEFAULT_STRATEGIES_PATH如下

private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";

ContextLoader.properties文件位于ContextLoader类的统计目录,我们看下ContextLoader.properties文件的内容

# Default WebApplicationContext implementation class for ContextLoader.
# Used as fallback when no explicit context implementation has been specified as context-param.
# Not meant to be customized by application developers.

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

xml方式默认为XmlWebApplicationContext,类结构如下

image

instantiateClass

根据class对象利用反射实例化

public static <T> T instantiateClass(Class<T> clazz) throws BeanInstantiationException {
   Assert.notNull(clazz, "Class must not be null");
   if (clazz.isInterface()) {
      throw new BeanInstantiationException(clazz, "Specified class is an interface");
   }
   try {
   	  //根据class对象利用反射实例化
      return instantiateClass(clazz.getDeclaredConstructor());
   }
   catch (NoSuchMethodException ex) {
      Constructor<T> ctor = findPrimaryConstructor(clazz);
      if (ctor != null) {
         return instantiateClass(ctor);
      }
      throw new BeanInstantiationException(clazz, "No default constructor found", ex);
   }
   catch (LinkageError err) {
      throw new BeanInstantiationException(clazz, "Unresolvable class definition", err);
   }
}
instantiateClass
public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
   Assert.notNull(ctor, "Constructor must not be null");
   try {
      ReflectionUtils.makeAccessible(ctor);
      if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
         return KotlinDelegate.instantiateClass(ctor, args);
      }
      else {
         Class<?>[] parameterTypes = ctor.getParameterTypes();
         Assert.isTrue(args.length <= parameterTypes.length, "Can't specify more arguments than constructor parameters");
         Object[] argsWithDefaultValues = new Object[args.length];
         for (int i = 0 ; i < args.length; i++) {
            if (args[i] == null) {
               Class<?> parameterType = parameterTypes[i];
               argsWithDefaultValues[i] = (parameterType.isPrimitive() ? DEFAULT_TYPE_VALUES.get(parameterType) : null);
            }
            else {
               argsWithDefaultValues[i] = args[i];
            }
         }
         //利用反射实例化
         return ctor.newInstance(argsWithDefaultValues);
      }
   }
   catch (InstantiationException ex) {
      throw new BeanInstantiationException(ctor, "Is it an abstract class?", ex);
   }
   catch (IllegalAccessException ex) {
      throw new BeanInstantiationException(ctor, "Is the constructor accessible?", ex);
   }
   catch (IllegalArgumentException ex) {
      throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", ex);
   }
   catch (InvocationTargetException ex) {
      throw new BeanInstantiationException(ctor, "Constructor threw exception", ex.getTargetException());
   }
}

loadParentContext(servletContext);

@Nullable
protected ApplicationContext loadParentContext(ServletContext servletContext) {
   return null;
}

默认为空,留着扩展

configureAndRefreshWebApplicationContext

这个方法就是根据root容器的配置文件去初始化spring ioc容器并实例化root容器的配置文件的bean

注意,这里springmvc容器的bean还没有实例化

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {

	//配置root容器的contextId
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
        if (idParam != null) {
            wac.setId(idParam);
        }
        else {
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                      ObjectUtils.getDisplayString(sc.getContextPath()));
        }
    }

    wac.setServletContext(sc);
    //CONFIG_LOCATION_PARAM=contextConfigLocation
    //获取web.xml中的contextConfigLocation配置,即springIOC配置文件的路径
    String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
    if (configLocationParam != null) {
        wac.setConfigLocation(configLocationParam);
    }
	//获取Environment环境对象的信息,若没有,则创建Environment环境对象并返回
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
    }
	// 使用ApplicationContextInitializer对ApplicationContext进行初始化
    customizeContext(sc, wac);
    //spring的核心方法
    wac.refresh();
}
getEnvironment
@Override
public ConfigurableEnvironment getEnvironment() {
   if (this.environment == null) {
      this.environment = createEnvironment();
   }
   return this.environment;
}
customizeContext(sc, wac);

这里initializerClasses默认size=0,可忽略

protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
   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));
   }

   AnnotationAwareOrderComparator.sort(this.contextInitializers);
   for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
      initializer.initialize(wac);
   }
}

servletContext.setAttribute

将实例化的root容器存到servletContext备用

String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

currentContextPerThread.put();

private static final Map<ClassLoader, WebApplicationContext> currentContextPerThread =
			new ConcurrentHashMap<>(1);

currentContextPerThread.put(ccl, this.context);

总结

ContextLoaderListener监听器的作用就是启动Web容器时,自动装配root容器的的配置信息。

如果web.xml中加入了ContextLoaderListener则初始化SpringIOC容器,即root ApplicationContex.

ContextLoaderListener监听器实现了ServletContextListener这个接口,在web.xml配置了这个监听器时,启动容器时,就会执行它实现的contextInitialized()方法

该方法初始化了WebApplicationContext实例(XmlWebApplicationContext),IOC容器的一系列流程,扫描包并实例化了除controller层之外的bean

并将该IOC容器放入到ServletContext中,以便后面使用。

DispachterServlet

springmvc最重要的类,DispachterServlet本质HttpServlet

类结构如下

image

web容器:web容器使用ServletContext来维护每一个web应用,ContextLoaderListener将spring容器,即WebApplicationContext,作为ServletContext的一个attribute,key为,保存在ServletContext中,从而web容器和spring项目可以通过ServletContext来交互。

从FrameworkServlet中获取WebApplicationContext,然后从WebApplicationContext中获取DispatcherServlet的相关功能子组件bean,然后在自身维护一个引用。实现doService方法并使用这些功能子组件来完成请求的处理和生成响应。

我们知道,一个HttpServlet会先调用init(),那么我们来看下DispatcherServlet的init()

DispatcherServlet本身没有实现init()方法,主要逻辑在其父类HttpServletBean中实现,因此我们先看HttpServletBean.init()方法

HttpServletBean.init()

@Override
public final void init() throws ServletException {

   //读取springmvc配置文件的init-param标签,该标签配置了springmvc容器的配置文件路径
   // Set bean properties from init parameters.
   PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
   if (!pvs.isEmpty()) {
      try {
      	 //将DispatcherServlet包装成BeanWrapper对象,并将上面的springmvc容器的配置文件路径读取到DispatcherServlet里去
         BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
         ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
         bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
         initBeanWrapper(bw);
         //将上面的springmvc容器的配置文件路径读取到DispatcherServlet里去
         bw.setPropertyValues(pvs, true);
      }
      catch (BeansException ex) {
         if (logger.isErrorEnabled()) {
            logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
         }
         throw ex;
      }
   }
	//关键方法
   // Let subclasses do whatever initialization they like.
   initServletBean();
}

FrameworkServlet.initServletBean

@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 {
   	  //关键方法,创建子容器并返回
      this.webApplicationContext = initWebApplicationContext();
      //空方法,扩展用
      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);
   }

   if (logger.isInfoEnabled()) {
      logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
   }
}

FrameworkServlet.initWebApplicationContext

protected WebApplicationContext initWebApplicationContext() {
	//从ServletContext中拿到springIOC父容器
   WebApplicationContext rootContext =
         WebApplicationContextUtils.getWebApplicationContext(getServletContext());
         
   //这里就是方法返回值,返回的是子容器
   WebApplicationContext wac = null;
	//这里的webApplicationContext就是子容器,第一次为空
   if (this.webApplicationContext != null) {
      // A context instance was injected at construction time -> use it
      wac = this.webApplicationContext;
      if (wac instanceof ConfigurableWebApplicationContext) {
         ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
         //判断是否激活
         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 -> set
               // the root application context (if any; may be null) as the parent
               //设置父容器,就是IOC容器
               cwac.setParent(rootContext);
            }
            //执行refresh方法
            configureAndRefreshWebApplicationContext(cwac);
         }
      }
   }
   if (wac == null) {
      // No context instance was injected at construction time -> see if one
      // has been registered in the servlet context. If one exists, it is assumed
      // that the parent context (if any) has already been set and that the
      // user has performed any initialization such as setting the context id
      //获取子容器,第一次获取不到
      wac = findWebApplicationContext();
   }
   
   //如果子容器为空
   if (wac == null) {
      // No context instance is defined for this servlet -> create a local one
      //使用父容器读取springmvc配置文件,并作为子容器返回
      wac = createWebApplicationContext(rootContext);
   }

   if (!this.refreshEventReceived) {
      // Either the context is not a ConfigurableApplicationContext with refresh
      // support or the context injected at construction time had already been
      // refreshed -> trigger initial onRefresh manually here.
      synchronized (this.onRefreshMonitor) {
      	 //这里是由DispatcherServlet实现了
         onRefresh(wac);
      }
   }

   if (this.publishContext) {
      // Publish the context as a servlet context attribute.
      String attrName = getServletContextAttributeName();
      //将子容器和org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcher存到ServletContext备用
      getServletContext().setAttribute(attrName, wac);
   }

   return wac;
}
findWebApplicationContext
@Nullable
protected WebApplicationContext findWebApplicationContext() {
   //这里attrName返回null
   String attrName = getContextAttribute();
   if (attrName == null) {
      return null;
   }
   //获取springIOC容器
   WebApplicationContext wac =
         WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
   if (wac == null) {
      throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
   }
   return wac;
}
createWebApplicationContext

在这里实例化并初始化springmvc容器作为web容器

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
	//获取父容器的Class对象
   Class<?> contextClass = getContextClass();
   if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
      throw new ApplicationContextException(
            "Fatal initialization error in servlet with name '" + getServletName() +
            "': custom WebApplicationContext class [" + contextClass.getName() +
            "] is not of type ConfigurableWebApplicationContext");
   }
   //根据root容器的Class对象实例化web容器,这里web容器只是实例化,还没有refresh
   ConfigurableWebApplicationContext wac =
         (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
	
   wac.setEnvironment(getEnvironment());
   //将root容器作为刚刚实例化的web容器的父容器
   wac.setParent(parent);
   //获取子容器的配置文件路径
   String configLocation = getContextConfigLocation();
   if (configLocation != null) {
   	  //设置springmvc配置文件到父容器
      wac.setConfigLocation(configLocation);
   }
   //关键代码,就是根据web容器的配置文件去初始化容器和bean
   configureAndRefreshWebApplicationContext(wac);

   return wac;
}
configureAndRefreshWebApplicationContext

和父容器的逻辑大体一致

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
   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
      if (this.contextId != null) {
         wac.setId(this.contextId);
      }
      else {
         // Generate default id...
         wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
               ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
      }
   }

   wac.setServletContext(getServletContext());
   wac.setServletConfig(getServletConfig());
   wac.setNamespace(getNamespace());
   wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

   // 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(getServletContext(), getServletConfig());
   }

   postProcessWebApplicationContext(wac);
   applyInitializers(wac);
   //执行refresh方法
   wac.refresh();
}

到这里加载springmvc的配置文件的子容器已经产生,并返回了,然后执行子容器的.refresh(),实例化了子容器的bean

进入wac.refresh();其中有个finishRefresh()方法

protected void finishRefresh() {
   // Clear context-level resource caches (such as ASM metadata from scanning).
   clearResourceCaches();

   // Initialize lifecycle processor for this context.
   initLifecycleProcessor();

   // Propagate refresh to lifecycle processor first.
   getLifecycleProcessor().onRefresh();

   // Publish the final event. 这里在web容器启动时会发布事件
   publishEvent(new ContextRefreshedEvent(this));

   // Participate in LiveBeansView MBean, if active.
   LiveBeansView.registerApplicationContext(this);
}

而DispatcherServlet的父类FrameworkServlet里有个ContextRefreshListener,会监听此事件

private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

   @Override
   public void onApplicationEvent(ContextRefreshedEvent event) {
      FrameworkServlet.this.onApplicationEvent(event);
   }
}

具体实现如下

public void onApplicationEvent(ContextRefreshedEvent event) {
   this.refreshEventReceived = true;
   synchronized (this.onRefreshMonitor) {
      onRefresh(event.getApplicationContext());
   }
}

而该onRefresh方法由DispatcherServlet实现,源码如下

DispatcherServlet.onRefresh

@Override
protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
}

//初始化九大核心组件
protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);//初始化我们web上下文对象的 用于文件上传下载的解析器对象
    initLocaleResolver(context);//初始化我们web上下文对象用于处理国际化资源的
    initThemeResolver(context);//主题解析器对象初始化
    initHandlerMappings(context);//url请求映射,初始化我们的HandlerMapping
    initHandlerAdapters(context);//初始化真正调用controloler方法的类,实例化我们的HandlerAdapters
    initHandlerExceptionResolvers(context);//异常解析,实例化我们处理器异常解析器对象
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);//视图解析,给DispatcherSerlvet的ViewResolvers处理器
    initFlashMapManager(context);
}

至此,springmvc的root容器和web容器都已启动完成

总结

1.Tomcat在启动时首先调用 实现了ServletContextListener接口的ContextLoaderListener

  • 在调用ContextLoaderListener过程中会实例化父容器,并根据配置查找root容器的配置
  • 将root容器以及root配置文件的bean初始化,最后将root容器设置到ServletContext,以备后续调用

2.Tomcat在启动时调用实现了HttpServlet接口的DispatcherServlet对象的init方法

  • 在init方法的执行过程中会根据root容器的class对象利用反射实例化一个新的容器作为web容器
  • 将root容器设置为web容器的父容器
  • 根据配置查找web容器的配置
  • 将web容器以及web配置文件的bean初始化
  • 在初始化的过程中发布事件,在监听事件过程中调用DispatcherServlet的onRefresh方法初始化springmvc的九大组件
posted on 2022-03-08 23:29  路仁甲  阅读(130)  评论(0编辑  收藏  举报