• 03SpringMVC容器初始化


    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的九大组件
  • 相关阅读:
    (四)安装硬件驱动程序
    (三)操作系统安装与更新
    (二)主板BIOS设置与硬盘分区、调整
    通过ip远程控制电脑
    (一)计算机基本组成部分
    计算机与操作系统
    单位换算
    TestDirector(TD)—测试管理工具
    QualityCenter(QC)—测试管理工具
    teamviewer破解版
  • 原文地址:https://www.cnblogs.com/lusaisai/p/15983111.html
Copyright © 2020-2023  润新知