在web.xml中有如下定义:

<!-- 该类作为spring的listener使用,它会在创建时自动查找web.xml配置的applicationContext.xml文件 -->
  <listener>
    <listener-class>
      org.springframework.web.context.ContextLoaderListener
    </listener-class>
  </listener>
ContextLoaderListener 源码分析:主要做父容器的初始化,就是spring容器的初始化
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    public ContextLoaderListener() {
    }

    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }
    // 会先调用Context的初始化方法
    public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext());
    }

    public void contextDestroyed(ServletContextEvent event) {
        this.closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}

        父子容器的概念:

在之前的Springmvc项目中,一般都有两个xml配置:

SpringMVC(四)ContextLoaderListener和DispatcherServlet_初始化

 

1: 其中的spring.xml是提供给spring使用的,作为父容器。 如果两个配置文件都存在的话,需要在这个配置文件中的component-scan 排除controller的扫描。它会创建一个IOC容器,如果下面那个配置文件中和这个配置文件中都扫描了同一个目录,那么在父子容器中都会存在同一个类的不同实例的对象。

2: spring-servlet.xml是提供给springmvc使用的,作为子容器。如果两g个配置文件都存在的话,需要在这个配置文件中只扫描controller。它也会创建一个IOC容器,

即使存在两个也不会有问题,因为在子容器中默认是使用子容器的,getBean在子容器中找不到就会从父容器中找。

加入在spring.xml中配置了扫描service包下面的bean。在spring-servlet.xml中配置扫描controller包下面的类,如果要在service包下面注入controller下面的类,就会失败,因为在父容器中是找不到子容器中的bean的。不存在父容器找子容器。

 回到Listener中,看创建容器的方法:initWebApplicationContext

SpringMVC(四)ContextLoaderListener和DispatcherServlet_xml_02

 

 首先会创建父容器:createWebApplicationContext(servletContext)

  protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
        Class<?> contextClass = this.determineContextClass(sc);
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
        } else {
            return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
        }
    }
protected Class<?> determineContextClass(ServletContext servletContext) {

// 可以在init-param中进行设置使用那个容器
// 如果没有设置,就采用默认的 默认是从配置文件中加载XmlWebApplicationContext String contextClassName = servletContext.getInitParameter("contextClass"); if (contextClassName != null) { try { return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader()); } catch (ClassNotFoundException var4) { throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", var4); } } else { contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName()); try { return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader()); } catch (ClassNotFoundException var5) { throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", var5); } } }

 

SpringMVC(四)ContextLoaderListener和DispatcherServlet_spring_03

 

 SpringMVC(四)ContextLoaderListener和DispatcherServlet_mvc_04

 在方法 configureAndRefreshWebApplicationContext中,会把servlet的容器和spring的容器关联起来。

  protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
        String configLocationParam;
        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
            configLocationParam = sc.getInitParameter("contextId");
            if (configLocationParam != null) {
                wac.setId(configLocationParam);
            } else {
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath()));
            }
        }
        // servlet容器和servlet容器关联
        wac.setServletContext(sc);
        configLocationParam = sc.getInitParameter("contextConfigLocation");
        if (configLocationParam != null) {
            wac.setConfigLocation(configLocationParam);
        }

        ConfigurableEnvironment env = wac.getEnvironment();
        if (env instanceof ConfigurableWebEnvironment) {
            ((ConfigurableWebEnvironment)env).initPropertySources(sc, (ServletConfig)null);
        }

        this.customizeContext(sc, wac);
// spring容器的启动 wac.refresh(); }

 

 


 

 DispatcherServlet  源码分析:

在web.xml中,一般都会配置下DispatchServlet相关的信息。<init-param>  <loadon-starup>等都是tomcat加载的。它里面有个重要的方法init()方法,就是1:初始化子容器的 2:初始化九大组件。

所谓的父子容器其实就是WebApplicationContext的两个实例。

SpringMVC(四)ContextLoaderListener和DispatcherServlet_初始化_05

 

 

 在HttpServletBean中有一个init()的方法,就是初始化的入口,其实servlet规范中是一个init(ServletConfig config)的初始化方法,但是这个方法在GenericServlet中被重写,

 SpringMVC(四)ContextLoaderListener和DispatcherServlet_mvc_06

 

 所以在子类HttpServletBean中有一个init()方法,才是有具体逻辑的:

public final void init() throws ServletException {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Initializing servlet '" + this.getServletName() + "'");
        }

        PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
        if (!pvs.isEmpty()) {
            try {
                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
                bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
                this.initBeanWrapper(bw);
                bw.setPropertyValues(pvs, true);
            } catch (BeansException var4) {
                if (this.logger.isErrorEnabled()) {
                    this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", var4);
                }

                throw var4;
            }
        }
          // 初始化servlet方法,是个模板方法,让子类去实现
        this.initServletBean();
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Servlet '" + this.getServletName() + "' configured successfully");
        }

    }

 

 在FrameworkServlet中有实现这个方法

 protected final void initServletBean() throws ServletException {
        this.getServletContext().log("Initializing Spring FrameworkServlet '" + this.getServletName() + "'");
        if (this.logger.isInfoEnabled()) {
            this.logger.info("FrameworkServlet '" + this.getServletName() + "': initialization started");
        }

        long startTime = System.currentTimeMillis();

        try {
// 初始化子容器 this.webApplicationContext = this.initWebApplicationContext();
// mvc的容器上面那个方法已经初始化完成了,这个方法在mvc中是个空的实现,用于扩展用的。 this.initFrameworkServlet(); } catch (ServletException var5) { this.logger.error("Context initialization failed", var5); throw var5; } catch (RuntimeException var6) { this.logger.error("Context initialization failed", var6); throw var6; } if (this.logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - startTime; this.logger.info("FrameworkServlet '" + this.getServletName() + "': initialization completed in " + elapsedTime + " ms"); } }

 

 protected WebApplicationContext initWebApplicationContext() {
// 获取父容器 WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext()); WebApplicationContext wac = 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); } // 上面父容器初始化过程中也有这个功能 ,就是启动容器 this.configureAndRefreshWebApplicationContext(cwac); } } } if (wac == null) { wac = this.findWebApplicationContext(); } if (wac == null) { wac = this.createWebApplicationContext(rootContext); } if (!this.refreshEventReceived) {
// 这个也是用户扩展,和spring中的onRefresh方法差不多 this.onRefresh(wac); } if (this.publishContext) { String attrName = this.getServletContextAttributeName(); this.getServletContext().setAttribute(attrName, wac); if (this.logger.isDebugEnabled()) { this.logger.debug("Published WebApplicationContext of servlet '" + this.getServletName() + "' as ServletContext attribute with name [" + attrName + "]"); } } return wac; }

 

上面方法执行完,子容器就创建完成了。扩展方法onRefresh  由子类DispatchServlet实现。

 protected void onRefresh(ApplicationContext context) {
// 初始化前端组件 this.initStrategies(context); } protected void initStrategies(ApplicationContext context) { this.initMultipartResolver(context); this.initLocaleResolver(context); this.initThemeResolver(context);
// 现在主要就是这两个功能了 this.initHandlerMappings(context); this.initHandlerAdapters(context);
this.initHandlerExceptionResolvers(context); this.initRequestToViewNameTranslator(context); this.initViewResolvers(context); this.initFlashMapManager(context); }

 


使用JavaConfig的方式配置webmvc



------------恢复内容开始------------

在web.xml中有如下定义:

<!-- 该类作为spring的listener使用,它会在创建时自动查找web.xml配置的applicationContext.xml文件 -->
  <listener>
    <listener-class>
      org.springframework.web.context.ContextLoaderListener
    </listener-class>
  </listener>
ContextLoaderListener 源码分析:主要做父容器的初始化,就是spring容器的初始化
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    public ContextLoaderListener() {
    }

    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }
    // 会先调用Context的初始化方法
    public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext());
    }

    public void contextDestroyed(ServletContextEvent event) {
        this.closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}

        父子容器的概念:

在之前的Springmvc项目中,一般都有两个xml配置:

SpringMVC(四)ContextLoaderListener和DispatcherServlet_初始化

 

1: 其中的spring.xml是提供给spring使用的,作为父容器。 如果两个配置文件都存在的话,需要在这个配置文件中的component-scan 排除controller的扫描。它会创建一个IOC容器,如果下面那个配置文件中和这个配置文件中都扫描了同一个目录,那么在父子容器中都会存在同一个类的不同实例的对象。

2: spring-servlet.xml是提供给springmvc使用的,作为子容器。如果两g个配置文件都存在的话,需要在这个配置文件中只扫描controller。它也会创建一个IOC容器,

即使存在两个也不会有问题,因为在子容器中默认是使用子容器的,getBean在子容器中找不到就会从父容器中找。

加入在spring.xml中配置了扫描service包下面的bean。在spring-servlet.xml中配置扫描controller包下面的类,如果要在service包下面注入controller下面的类,就会失败,因为在父容器中是找不到子容器中的bean的。不存在父容器找子容器。

 回到Listener中,看创建容器的方法:initWebApplicationContext

SpringMVC(四)ContextLoaderListener和DispatcherServlet_xml_02

 

 首先会创建父容器:createWebApplicationContext(servletContext)

  protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
        Class<?> contextClass = this.determineContextClass(sc);
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
        } else {
            return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
        }
    }
protected Class<?> determineContextClass(ServletContext servletContext) {

// 可以在init-param中进行设置使用那个容器
// 如果没有设置,就采用默认的 默认是从配置文件中加载XmlWebApplicationContext String contextClassName = servletContext.getInitParameter("contextClass"); if (contextClassName != null) { try { return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader()); } catch (ClassNotFoundException var4) { throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", var4); } } else { contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName()); try { return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader()); } catch (ClassNotFoundException var5) { throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", var5); } } }

 

SpringMVC(四)ContextLoaderListener和DispatcherServlet_spring_03

 

 SpringMVC(四)ContextLoaderListener和DispatcherServlet_mvc_04

 在方法 configureAndRefreshWebApplicationContext中,会把servlet的容器和spring的容器关联起来。

  protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
        String configLocationParam;
        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
            configLocationParam = sc.getInitParameter("contextId");
            if (configLocationParam != null) {
                wac.setId(configLocationParam);
            } else {
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath()));
            }
        }
        // servlet容器和servlet容器关联
        wac.setServletContext(sc);
        configLocationParam = sc.getInitParameter("contextConfigLocation");
        if (configLocationParam != null) {
            wac.setConfigLocation(configLocationParam);
        }

        ConfigurableEnvironment env = wac.getEnvironment();
        if (env instanceof ConfigurableWebEnvironment) {
            ((ConfigurableWebEnvironment)env).initPropertySources(sc, (ServletConfig)null);
        }

        this.customizeContext(sc, wac);
// spring容器的启动 wac.refresh(); }

 

 


 

 DispatcherServlet  源码分析:

在web.xml中,一般都会配置下DispatchServlet相关的信息。<init-param>  <loadon-starup>等都是tomcat加载的。它里面有个重要的方法init()方法,就是1:初始化子容器的 2:初始化九大组件。

所谓的父子容器其实就是WebApplicationContext的两个实例。

SpringMVC(四)ContextLoaderListener和DispatcherServlet_初始化_05

 

 

 在HttpServletBean中有一个init()的方法,就是初始化的入口,其实servlet规范中是一个init(ServletConfig config)的初始化方法,但是这个方法在GenericServlet中被重写,

 SpringMVC(四)ContextLoaderListener和DispatcherServlet_mvc_06

 

 所以在子类HttpServletBean中有一个init()方法,才是有具体逻辑的:

public final void init() throws ServletException {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Initializing servlet '" + this.getServletName() + "'");
        }

        PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
        if (!pvs.isEmpty()) {
            try {
                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
                bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
                this.initBeanWrapper(bw);
                bw.setPropertyValues(pvs, true);
            } catch (BeansException var4) {
                if (this.logger.isErrorEnabled()) {
                    this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", var4);
                }

                throw var4;
            }
        }
          // 初始化servlet方法,是个模板方法,让子类去实现
        this.initServletBean();
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Servlet '" + this.getServletName() + "' configured successfully");
        }

    }

 

 在FrameworkServlet中有实现这个方法

 protected final void initServletBean() throws ServletException {
        this.getServletContext().log("Initializing Spring FrameworkServlet '" + this.getServletName() + "'");
        if (this.logger.isInfoEnabled()) {
            this.logger.info("FrameworkServlet '" + this.getServletName() + "': initialization started");
        }

        long startTime = System.currentTimeMillis();

        try {
// 初始化子容器 this.webApplicationContext = this.initWebApplicationContext();
// mvc的容器上面那个方法已经初始化完成了,这个方法在mvc中是个空的实现,用于扩展用的。 this.initFrameworkServlet(); } catch (ServletException var5) { this.logger.error("Context initialization failed", var5); throw var5; } catch (RuntimeException var6) { this.logger.error("Context initialization failed", var6); throw var6; } if (this.logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - startTime; this.logger.info("FrameworkServlet '" + this.getServletName() + "': initialization completed in " + elapsedTime + " ms"); } }

 

 protected WebApplicationContext initWebApplicationContext() {
// 获取父容器 WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext()); WebApplicationContext wac = 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); } // 上面父容器初始化过程中也有这个功能 ,就是启动容器 this.configureAndRefreshWebApplicationContext(cwac); } } } if (wac == null) { wac = this.findWebApplicationContext(); } if (wac == null) { wac = this.createWebApplicationContext(rootContext); } if (!this.refreshEventReceived) {
// 这个也是用户扩展,和spring中的onRefresh方法差不多 this.onRefresh(wac); } if (this.publishContext) { String attrName = this.getServletContextAttributeName(); this.getServletContext().setAttribute(attrName, wac); if (this.logger.isDebugEnabled()) { this.logger.debug("Published WebApplicationContext of servlet '" + this.getServletName() + "' as ServletContext attribute with name [" + attrName + "]"); } } return wac; }

 

上面方法执行完,子容器就创建完成了。扩展方法onRefresh  由子类DispatchServlet实现。

 protected void onRefresh(ApplicationContext context) {
// 初始化前端组件 this.initStrategies(context); } protected void initStrategies(ApplicationContext context) { this.initMultipartResolver(context); this.initLocaleResolver(context); this.initThemeResolver(context);
// 现在主要就是这两个功能了 this.initHandlerMappings(context); this.initHandlerAdapters(context);
this.initHandlerExceptionResolvers(context); this.initRequestToViewNameTranslator(context); this.initViewResolvers(context); this.initFlashMapManager(context); }

 


使用JavaConfig的方式配置webmvc
1:实现 WebMvcConfigurer的接口重写里面的方法,比如增加Fastjson的消息转换器。
void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.add(new  FastJsonHttpMessageConverter())
}

 

2:继承 WebMvcConfigurerAdapter 是个类,实现了WebMvcConfigurer接口,都是空的实现,jdk1.8以后接口中允许默认实现了,这个类也就没什么用了。
3:WebMvcConfigurationSupport 很重要的类。

SpringMVC(四)ContextLoaderListener和DispatcherServlet_springmvc_13

 

 实现了两个aware接口,可以使用spring容器和servlet容器。

它里面有很多@Bean的方法。

SpringMVC(四)ContextLoaderListener和DispatcherServlet_springmvc_14

 

 但是默认是不生效的,要让他生效,需要自己继承这个类,并加上@Configuration注解才会生效。可以自定义组件。

4:@EnableWebMvc注解     

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}

 

就引入了一个类 DelegatingWebMvcConfiguration,这个类继承了上面那个类,重写了一些方法并且加上了@Configuration注解

SpringMVC(四)ContextLoaderListener和DispatcherServlet_mvc_15

 

 里面有个webmvc的配置类 WebMvcConfigurerComposite,它实现了 WebMvcConfigurer接口,下面的configurers变成一个bean容器,是一个WebMvcConfigurer接口的聚合。

private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

    public DelegatingWebMvcConfiguration() {
    }

    @Autowired(
        required = false
    )
    public void setConfigurers(List<WebMvcConfigurer> configurers) {
// 这里注入所有webmvc的配置类 都添加到configuresrs中 if (!CollectionUtils.isEmpty(configurers)) { this.configurers.addWebMvcConfigurers(configurers); } }

 

使用方法:
有自定义组件,需要生效的话。
1)@EnableWebMvc + 实现WebMvcConfigurer接口
2)继承 WebMvcConfigurationSupport+@Configuration
但是这里有一个问题,就是在springboot中如果使用了 @EnableWebMvc 或者 继承 WebMvcConfigurationSupport类。springmvc自动注入就会失效。
这是因为webmvc的自动注入的配置类上面有一个配置:

SpringMVC(四)ContextLoaderListener和DispatcherServlet_springmvc_16

 看到那个OnMissingBean的注解就能明白。





















------------恢复内容结束------------