The inheritance relationship of Root/Child WebApplicationContext in SpringMVC (the difference between Listener and servlet loading spring files)

Brief description:

Enter the following information in the tomcat startup console:

 

信息: Initializing Spring root WebApplicationContext

2017-10-22 20:20:02,785 INFO - org.springframework.web.context.ContextLoader[272] - Root WebApplicationContext: initialization started 

......

2017-10-22 20:20:03,562 INFO - org.springframework.web.context.ContextLoader[301] - Root WebApplicationContext: initialization completed in 775 ms 

......

信息: Initializing Spring FrameworkServlet 'springMVC'

2017-10-22 20:20:03,866 INFO - org.springframework.web.servlet.FrameworkServlet[444] - FrameworkServlet 'springMVC': initialization started 

......

2017-10-22 20:20:07,844 INFO - org.springframework.web.servlet.FrameworkServlet[463] - FrameworkServlet 'springMVC': initialization completed in 3978 ms 

.......

Where: Root WebApplicationContext is the parent, loaded by the listener, and is the web root

FrameworkServlet is child, has servlet loaded, and root is parent-child relationship.

 

Try the following code:

       WebApplicationContext root = ContextLoader.getCurrentWebApplicationContext();

        ServletContext servletContext = root.getServletContext();

        // true  

        System.out.println(root == servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE));

        // true  

        System.out.println(root == WebApplicationContextUtils.getWebApplicationContext(servletContext));

        WebApplicationContext child = (WebApplicationContext) request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE);

        // false  

        System.out.println("root:" + root.containsLocalBean("myService"));

        // true  

        System.out.println("root:" + root.containsLocalBean("firstController"));

        // false  

        System.out.println("child:" + child.containsLocalBean("myService"));

        // true  

        System.out.println("child:"+ child.containsLocalBean("firstController"));

        // true  

        System.out.println("is parent==" + (child.getParent() == root)); 

======================================================

In the traditional spring mvc program, there will be two WebApplicationContext, one is parent, applicationContext.xmlloaded from it, and the other is child, servlet-context.xmlloaded from it. 

The two are inheritance relationships, and the child WebApplicationContext can getParent()obtain the root WebApplicationContext through functions.

Simply put, the beans in the child WebApplicationContext can be injected into the beans in the root WebApplicationContext, while the beans in the parent WebApplicationContext cannot be injected into the beans in the child WebApplicationContext.

A typical web.xml content is:

    <!-- The definition of the Root Spring Container shared by all Servlets and Filters -->

    <context-param>

        <param-name>contextConfigLocation</param-name>

        <param-value>classpath*:/applicationContext.xml</param-value>

    </context-param>

 

    <!-- Creates the Spring Container shared by all Servlets and Filters -->

    <listener>

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

    </listener>

 

    <!-- Processes application requests -->

    <servlet>

        <servlet-name>appServlet</servlet-name>

        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

        <init-param>

            <param-name>contextConfigLocation</param-name>

            <param-value>/WEB-INF/servlet-context.xml</param-value>

        </init-param>

        <load-on-startup>1</load-on-startup>

        <async-supported>true</async-supported>

    </servlet>

 

    <servlet-mapping>

        <servlet-name>appServlet</servlet-name>

        <url-pattern>/</url-pattern>

    </servlet-mapping>

其中root WebApplicationContext是通过listener初始化的,child WebApplicationContext是通过servlet初始化的。

而在applicationContext.xml里通常只component-scan非Controller的类,如:

<context:component-scan base-package="io.github.test">

     <context:exclude-filter expression="org.springframework.stereotype.Controller" type="annotation" />

     <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" /> </context:component-scan>

servlet-context.xml里通常只component-scan Controller类,如:

<context:component-scan base-package="io.github.test.web" use-default-filters="false"> 

   <context:include-filter expression="org.springframework.stereotype.Controller" type="annotation" /> 

   <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" /> 

</context:component-scan>

如果不这样子分别component-scan的话,可能会出现Bean重复初始化的问题。

上面是Spring官方开始时推荐的做法。

root/child WebApplicationContext继承关系带来的麻烦

root WebApplicationContext里的bean可以在不同的child WebApplicationContext里共享,而不同的child WebApplicationContext里的bean区不干扰,这个本来是个很好的设计。

但是实际上有会不少的问题: 
* 不少开发者不知道Spring mvc里分有两个WebApplicationContext,导致各种重复构造bean,各种bean无法注入的问题。 
* 有一些bean,比如全局的aop处理的类,如果先root WebApplicationContext里初始化了,那么child WebApplicationContext里的初始化的bean就没有处理到。如果在child WebApplicationContext里初始化,在root WebApplicationContext里的类就没有办法注入了。 
* 区分哪些bean放在root/child很麻烦,不小心容易搞错,而且费心思。

一劳永逸的解决办法:bean都由root WebApplicationContext加载

在一次配置metrics-spring时,对配置@EnableMetrics配置在哪个WebApplicationContext里,感到很蛋疼。最终决定试下把所有的bean,包括Controller都移到root WebApplicationContext,即applicationContext.xml里加载,而servlet-context.xml里基本是空的。结果发现程序运行完全没问题。

后面在网上搜索了下,发现有一些相关的讨论:

http://forum.spring.io/forum/spring-projects/container/89149-servlet-context-vs-application-context

spring boot里的做法

在spring boot里默认情况下不需要component-scan的配置,于是猜测在Spring boot里是不是只有一个WebApplicationContext?

后面测试下了,发现在spring boot里默认情况下的确是只有一个WebApplicationContext:org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext,所以在spring boot里省事了很多。

总结

spring 的ApplicationContext继承机制是一个很好的设计,在很多其它地方都可以看到类似的思路,比如Java的class loader。但是在大部分spring web程序里,实际上只要一个WebApplicationContext就够了。如果分开Root/Child WebApplicationContext会导致混乱,而没什么用。

所以推荐把所有的Service/Controller都移到root WebApplicationContext中初始化。

 

 

来自: http://blog.csdn.net/hengyunabc/article/details/47072637

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326307982&siteId=291194637