SpringMVCContextLoaderListener和DispatcherServlet
Posted 蒙恬括
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringMVCContextLoaderListener和DispatcherServlet相关的知识,希望对你有一定的参考价值。
在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配置:
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
首先会创建父容器: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); } } }
在方法 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的两个实例。
在HttpServletBean中有一个init()的方法,就是初始化的入口,其实servlet规范中是一个init(ServletConfig config)的初始化方法,但是这个方法在GenericServlet中被重写,
所以在子类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配置:
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
首先会创建父容器: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); } } }
在方法 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的两个实例。
在HttpServletBean中有一个init()的方法,就是初始化的入口,其实servlet规范中是一个init(ServletConfig config)的初始化方法,但是这个方法在GenericServlet中被重写,
所以在子类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 很重要的类。
实现了两个aware接口,可以使用spring容器和servlet容器。
它里面有很多@Bean的方法。
但是默认是不生效的,要让他生效,需要自己继承这个类,并加上@Configuration注解才会生效。可以自定义组件。
4:@EnableWebMvc注解
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented @Import({DelegatingWebMvcConfiguration.class}) public @interface EnableWebMvc { }
就引入了一个类 DelegatingWebMvcConfiguration,这个类继承了上面那个类,重写了一些方法并且加上了@Configuration注解
里面有个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的自动注入的配置类上面有一个配置:
看到那个OnMissingBean的注解就能明白。
------------恢复内容结束------------
以上是关于SpringMVCContextLoaderListener和DispatcherServlet的主要内容,如果未能解决你的问题,请参考以下文章