Tear spring and spring MVC parent-child containers by hand

1, Background

Spring and spring MVC have a parent-child container relationship, and it is precisely because of this that the problem of package scanning often occurs. Here we analyze and understand the parent-child container relationship between spring and spring MVC, and give the official recommended method of package scanning in spring and spring MVC configuration files.

2, Concept understanding and knowledge foreshadowing

In the core concept of spring's overall framework, container is the core idea, which is used to manage the whole life cycle of beans. In a project, there is not necessarily only one container. Spring can include multiple containers, and the containers have upper and lower level relationships. At present, the most common scenario is to introduce spring and spring MVC into a project, so it is actually two containers. Spring is the parent container, Spring MVC is its child container, and the beans registered in the spring parent container are visible to the spring MVC container, while the beans registered in the spring MVC container are invisible to the spring parent container, that is, the child container can see the registered beans in the parent container, otherwise it can't.

We can use the following unified annotation configuration to batch register beans, instead of using xml separately for each 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 package that use the @ Component annotation, and automatically register them into the container. At the same time, we also scan the @ Controller, @ Service and @ repository annotations, because they inherit from @ component.

We often see the following configuration in the project. In fact, with the above configuration, this can be omitted, because the above configuration will open the following configuration by default. The following configurations declare @ Required, @ Autowired, @ PostConstruct, @ PersistenceContext, @ Resource, @ PreDestroy and other annotations by default.

<context:annotation-config/>

In addition, there is another spring MVC related configuration as follows. It is verified that spring MVC must be configured because it declares @ RequestMapping, @ RequestBody, @ ResponseBody, etc. Moreover, the configuration loads many parameter binding methods by default, such as json conversion parser and so on.

<mvc:annotation-driven />

The above sentence configures spring 3 Versions before 1 are equivalent to the following configuration methods

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

spring3. Versions after 1 are equivalent to the following configuration methods

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

3, Specific scenario analysis

Let's take a detailed look at the reasons for the container conflict between Spring and Spring MVC?

We have two containers, Spring and Spring MVC. Their configuration files are ApplicationContext XML and ApplicationContext MVC xml.

1. In ApplicationContext XML, which is responsible for scanning and registering all beans that need to be registered.

2. In ApplicationContext MVC XML, which is responsible for the use of spring MVC related annotations.

3. Starting the project, we found that spring MVC could not jump. We set the log printing level of log to DEBUG. We found that the requests in spring MVC container did not seem to be mapped to the specific controller.

4. In ApplicationContext MVC XML. After restarting, the verification is successful and the springMVC jump is valid.

Next, let's check the specific reasons, look at the source code, and start looking down from the dispatcher servlet of Spring MVC. We find that when Spring MVC initializes, we will look for all beans in the Spring MVC container that use the @ Controller annotation to determine whether it is a handler. The two-step configuration of 1 and 2 makes the Bean with @ Controller annotation not registered in the current Spring MVC container, but all beans with @ Controller annotation are registered in the parent container of Spring. Therefore, Spring MVC cannot find the processor and cannot jump. The 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());
}

In the method isHandler, it will judge whether the annotation of the current bean is a controller. The source code is as follows:

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

In step 4 configuration, all beans with @ Controller annotation are also registered in the spring MVC container, so spring MVC can find the processor for processing and jump normally.

We have found the reason why we can't jump correctly, so what's the solution?

We notice that in the initHandlerMethods() method, the Switch detectHandlerMethodsInAncestorContexts mainly controls which beans in the container are obtained and whether the parent container is included. It is not included by default. Therefore, the solution is to configure the detecthandlermethodsinancestercontexts property of HandlerMapping in the configuration file of spring MVC to be true (here we need to see which HandlerMapping is used according to the specific project), and let it detect the bean of the parent container. As follows:

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

However, many configurations will be included in the actual project. According to the official recommendation, we divide different types of beans registered in different containers according to different business modules: the Spring parent container is responsible for the registration of all other non @ Controller annotated beans, while the Spring MVC is only responsible for the registration of @ Controller annotated beans, so that they have their own responsibilities and clear boundaries. The configuration method is as follows

1. In ApplicationContext Configuration in XML:

<!-- Spring Non registered in container@controller Annotated 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. Configuration in XML

<!-- SpringMVC Only registered with@controller Annotated 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

In this way, after we understand the parent-child container relationship between spring and spring MVC and the principle of scanning registration, we can assign different types of beans to different containers for management according to official suggestions. If the Bean cannot be found or the spring MVC cannot jump and the transaction configuration fails again, we can quickly locate and solve the problem. In fact, it is also recommended that you should follow a good behavior habit in writing programs and building frameworks, and be clear about classification management and conditioning. In this way, it will be beneficial whether you check errors or later maintenance and expansion.

Thank you for seeing here. I'm Maidong, a programmer and a java developer. I've been working in the industry for six years. I share Java related technical articles or industry information every day

Welcome to pay attention to and forward articles, and welfare gifts in the later stage!

Tags: Java Mybatis Spring Spring Boot locate

Posted by piyushsharmajec on Tue, 24 May 2022 03:28:59 +0300