A preliminary study on the parent child container relationship between Spring and SpringMVC

  • 2020-12-10 00:44:25
  • OfStack

1. The background

Recently due to the package scans the problem of the project, in the process of problem solving, accidentally discovered the Spring and SpringMVC vessel is a father and son relationship, and it is because the only package scanning problem, often can appear we are here to analyze and understand the relationships container Spring and SpringMVC and give Spring and SpringMVC configuration file scanning the official recommended packages.

2. Conceptual understanding and knowledge foundation

In Spring overall framework of the core concepts, the container is a core idea, is used to manage the entire life cycle of Bean, and in one project, container, not 1 only one Spring can include multiple containers, and the container has a lower relationship, currently one of the most common scenario is in one project introduced Spring and SpringMVC the two frameworks, it is actually two containers, Spring is the parent container, SpringMVC is its container, And Bean registered in the Spring parent container is visible to the SpringMVC container, while Bean registered in the SpringMVC container is not visible to the Spring parent container, that is, the child container can see the registered Bean in the parent container, and vice versa.

Instead of having to configure each Bean separately using xml, we can use the following annotation configuration from Unified 1 for bulk registration of Bean.


<context:component-scan base-package="com.hafiz.www" />

From the reference manual provided by Spring we know that the function of this configuration is to scan all classes under the configured base-ES37en package that use @Component annotations and register them automatically into the container, as well as scan @Controller, @Service, and @Respository annotations since they are inherited from @Component.

We often see the following configuration in projects, but with the above configuration, this can be omitted because the above configuration will turn on the following configuration by default. The following configuration declares @ES45en, @ES46en, @PostConstruct, @PersistenceContext, @Resource, @ES50en and so on by default.


<context:annotation-config/>

In addition, there is another configuration related to SpringMVC as follows. It has been verified that SpringMVC must configure this configuration because it declares @RequestMapping, @RequestBody, @ES58en, etc. Also, the configuration loads many parameter binding methods by default, such as the json conversion parser.


<mvc:annotation-driven />

The above configuration of spring3.1 is equivalent to the following configuration


<!-- Configure the annotation controller mapper , It is a SpringMVC In is used to Request request URL To map to concrete Controller-->
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
<!-- Configure the annotation controller mapper , It is a SpringMVC Is used to map specific requests to specific methods -->
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>

Versions after spring3.1 are equivalent to the following configuration


<!-- Configure the annotation controller mapper , It is a SpringMVC In is used to Request request URL To map to concrete Controller-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<!-- Configure the annotation controller mapper , It is a SpringMVC Is used to map specific requests to specific methods -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>

3. Specific scene analysis

Now let's discuss in detail the reason for the collision between Spring and SpringMVC.

We have two containers, Spring and SpringMVC, with configuration files of applicationContext.xml and ES81en-ES82en.xml.

1. Configured in ES86en. xml < context: component - scan base - package = "com. hafiz. www" / > , responsible for the scanning and registration of all Bean required to be registered.

2. Configuration in applicationContext-ES102en.ES103en < mvc:annotation-driven / > , responsible for the use of SpringMVC related annotations.

3. To start the project, we found that SpringMVC could not jump, set the log printing level of log to DEBUG for debugging, and found that the request in SpringMVC container did not seem to map to specific controller.

4. Configuration in ES117en-ES118en.ES119en < context: component - scan base - package = "com. hafiz. www" / > After restarting, the springMVC jump is valid after verification.

Now let's look at the specific reasons. Looking at the source code from SpringMVC's DispatcherServlet, we find that when SpringMVC is initialized, it will look for all Bean in the SpringMVC container using the @Controller annotation to determine whether it is an handler. The 1,2 two-step configuration causes the current springMVC container not to register Bean annotated with @Controller, but to register all Bean annotated with @Controller in the parent container, so springMVC cannot find the processor and jump. Core source code is as follows:


protected void initHandlerMethods() {
  if (logger.isDebugEnabled()) {
    logger.debug("Looking for request mappings in application context: " + getApplicationContext());
  }
  String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
       BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
       getApplicationContext().getBeanNamesForType(Object.class));
  for (String beanName : beanNames) {
    if (isHandler(getApplicationContext().getType(beanName))){
      detectHandlerMethods(beanName);
    }
  }
  handlerMethodsInitialized(getHandlerMethods());
}

The method isHandler determines whether the current bean annotation is controller. The source code is as follows:


protected boolean isHandler(Class<?> beanType) {
  return AnnotationUtils.findAnnotation(beanType, Controller.class) != null;
}

In step 4, all Bean annotations with @ES157en are registered in the SpringMVC container, so SpringMVC can find a processor to process and jump normally.

We found the reason why the jump did not occur correctly, so what is its solution?

We note that in the initHandlerMethods() method, detectHandlerMethodsInAncestorContexts, the Switch, basically controls which containers bean gets and whether the parent container is included, which is not by default. So the solution is to configure HandlerMapping's detectHandlerMethodsInAncestorContexts attribute as true in the springMVC configuration file (depending on which HandlerMapping is being used) and let it detect the parent container's bean. As follows:


<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
  <property name="detectHandlerMethodsInAncestorContexts">
    <value>true</value>
  </property>
</bean>

However, in the actual project, there will be a lot of configuration. According to the official recommendation, we will divide different types of Bean: Spring registered in different containers according to different business modules. The parent container is responsible for the registration of all other non-@Controller annotated Bean, while SpringMVC is only responsible for the registration of @ES182en annotated Bean, making them have their own responsibilities and clear boundaries. The configuration is as follows

1. Configuration in ES186en. xml:


<!-- Spring Registered non in the container @controller annotations Bean -->
<context:component-scan base-package="com.hafiz.www">
  <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

2. applicationContext - MVC. xml in configuration


<!-- SpringMVC The container registers only with @controller annotations Bean -->
<context:component-scan base-package="com.hafiz.www" use-default-filters="false">
  <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>

3. Summary

Once we understand the parent-child container relationship between spring and springMVC and the principle of scanning registration, we can manage the different types of Bean assigned to different containers according to the official recommendations. If Bean can't be found or SpringMVC can't jump and the transaction configuration fails, we can quickly locate and solve the problem. Very happy, no ~


Related articles: