“This is the 14th day of my participation in the First Challenge 2022. For details: First Challenge 2022.”
Based on the latest Spring 5.x, the container hierarchy in Spring MVC and the concept of parent-child containers are introduced in detail.
We’ve looked at Spring MVC Learning (1) — an introduction to MVC and an introduction to Spring MVC. Now we’ll look at the concept of container hierarchies and parent-child containers in Spring MVC and explain some common problems.
Spring MVC learning series
Spring MVC Learning (1) — An introduction to MVC and an introduction to Spring MVC
Spring MVC Learning (2) – The container hierarchy in Spring MVC and the concept of parent-child containers
Spring MVC Learning (3) – The core components in Spring MVC and the execution flow of the request
Spring MVC Learning (4) — A detailed introduction and use case of the ViewSolvsolver view parser
Spring MVC Learning (5) – Annotation-based Controller configuration complete solution
Spring MVC Learning (6) – Spring data type conversion mechanism
Annotation-based declarative Validation of data
Spring MVC Learning (8) – HandlerInterceptor processor interceptor mechanism complete solution
Spring MVC Learning (9) – Project unified exception handling mechanism details and use cases
Spring MVC Learning (10) – File upload configuration, path configuration of DispatcherServlet, request and response content encoding
Spring MVC Learning (11) – Introduction to cross-domain and solving cross-domain problems using CORS
1 Introduction to parent and child containers
We learned earlier that a normal Spring application has its own ApplicationContext container, whereas a Spring-based Web application has a different container and will use WebApplicationContext.
The DispatcherServlet requires the WebApplicationContext container (WebApplicationContext, extending ApplicationContext (common ApplicationContext)) for its own configuration, Because WebApplicationContext has a getServletContext method that gets a ServletContext, And Spring’s WebApplicationContext container is also associated with the Web application’s ServletContext, So we can use the static method of RequestContextUtils directly in a Web application to find the WebApplicationContext through the current request.
In fact, for many Web applications, having a single WebApplicationContext is sufficient, as well as having a hierarchical context structure. It might be better for an application where a Root WebApplicationContext is shared between multiple instances of a scheduler service (or other Servlet), Each instance has its own Child (Child) WebApplicationContext configuration.
Root WebApplicationContext usually contains the infrastructure beans in Web applications, such as daOs, database configuration beans, and Service beans that need to be shared across multiple Servlet instances, namely the business layer and persistence layer beans in the three-tier architecture. These beans can be overridden (that is, redeclared) in the child WebApplicationContext of a particular Servlet. Child WebApplicationContext is used to store the beans of the presentation layer in the three-tier architecture, such as Controller, ViewResolver, HandlerMapping and other Spring MVC component beans. Therefore, it is also called Servlet WebApplicationContext.
The parent-child container diagram is as follows:
2 Configure the parent and child containers
Can parent and child containers be configured? Of course you can!
For xmL-based configuration, Root WebApplicationContext is configured by loading contextConfigLocation with ContextLoaderListener. The Servlet WebApplicationContext is initialized with the init-param parameter “contextConfigLocation” of DispatchServlet provided in Spring MVC to load the configuration.
The following cases:
<web-app>
<display-name>Archetype Created Web Application</display-name>
<! ContextConfigLocation initialization parameter, specifying the configuration file of the parent container Root WebApplicationContext -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-config.xml</param-value>
</context-param>
<! Listen for contextConfigLocation and initialize the parent container -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<! Configure spring MVC front-end core controller -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<! Servlet WebApplicationContext --> Set contextConfigLocation initialization parameter, specify the configuration file of the child container and create the child container Servlet WebApplicationContext -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc-config.xml</param-value>
</init-param>
<! If the value is a non-negative integer, it means that the application is created when it is loaded. The smaller the value is, the higher the priority of the Servlet is, and the higher the Servlet is loaded first. If the value is negative, it means that the Servlet is loaded when it is first used.
<load-on-startup>1</load-on-startup>
</servlet>
<! -- Configure the mapping path, / indicates that all requests will be processed -->
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
Copy the code
In the above configuration,ContextLoaderListener
The listener is initialized first, and then the ServletContext is initialized and will be changedcontext-param
And the ContextLoaderListener which is actually aServletContextListener
Listener implementation, which will listen for the creation event of the ServletContext and invoke the correspondingContextInitialized method
In this method, the ServletContext configuration will be retrievedcontextConfigLocation
Parameter (this contains the configuration file path we configured, the default path is/WEB-INF/applicationContext.xml
) and initializeRoot WebApplicationContext
Instance, which is the parent container, is actually of typeXmlWebApplicationContext
.
Then set the parent container through the setAttribute method to the ServletContext, properties of key for “org. Springframework. Web. Context. WebApplicationContext. The ROOT”, The “ROOT” at the end indicates that this is a ROOT WebApplicationContext, and WebApplicationContext also keeps references to ServletContext, So WebApplicationContext and ServletContext are associated.
thenDispatcherServlet
Will be instantiated and set initialization parameters after creationinit()
The callback method, which is in its parent class HttpServletBean, will get its owncontextConfigLocation
Parameter, and according to the specified configuration file ininitServletBean()
Method to createServlet WebApplicationContext, which is a subcontainer
. At the same time, it calls the getAttribute method of the ServletContext to determine whether Root WebApplicationContext exists. If it exists, set it to its parent. This is the concept of parent-child context (parent-child container).
The above explanation is only about the process, we will learn the source code when the more in-depth understanding!
Note: If ContextLoaderListener is configured, be sure toContext-param parameter named contextConfigLocation
, that is, specify the location of the Spring configuration file. If none is configured, the default path to look for is/WEB-INF/applicationContext.xml
If the configuration file cannot be found, an exception will be thrown. If DispatcherServlet is configured, be sure toThe init-param parameter named contextConfigLocation
, that is, specify the location of the Spring MVC configuration file. If there is no configuration, the default path to look for is"/ web-INF /"+ nameSpace+ ".xml" (default nameSpace is servletName+"-servlet")
If the configuration file cannot be found, an exception will be thrown.
If you want to by JavaConfig father and son to configure the container, so we need to inherit AbstractAnnotationConfigDispatcherServletInitializer to configure, and configuration information need in the Java configuration class:
public class MyWebAppInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
/ * * *@returnRoot WebApplicationContext configuration class */
@Override
protectedClass<? >[] getRootConfigClasses() {return newClass<? >[]{RootConfig.class}; }/ * * *@returnServlet WebApplicationContext configuration class */
@Override
protectedClass<? >[] getServletConfigClasses() {return newClass<? >[]{ChildConfig.class}; }/ * * *@returnMapping path */
@Override
protected String[] getServletMappings() {
return new String[]{"/"}; }}Copy the code
If you still want to use the XML file, then can inherit AbstractAnnotationConfigDispatcherServletInitializer:
public class MyXmlWebAppInitializer extends
AbstractDispatcherServletInitializer {
@Override
protected WebApplicationContext createRootApplicationContext(a) {
XmlWebApplicationContext cxt = new XmlWebApplicationContext();
cxt.setConfigLocation("/WEB-INF/spring-config.xml");
return cxt;
}
@Override
protected WebApplicationContext createServletApplicationContext(a) {
XmlWebApplicationContext cxt = new XmlWebApplicationContext();
cxt.setConfigLocation("/WEB-INF/spring-mvc-config.xml");
return cxt;
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"}; }}Copy the code
3 Configure only one container
In a traditional Spring Web project, it is common for different components introduced to have different XML configuration files. The benefit is that these configurations can be separated, and the parent-child container is probably also used to differentiate framework boundaries, and you can actually configure multiple child containers to share a parent container.
Of course, we can configure just one container!
- For XML configuration, if you do not need to configure the parent container, you can directly not configure context-param and listener, and write all the configuration of the business layer and persistence layer in the configuration file of Spring MVC. If you do not need to configure the child container, you can write the configuration of the business layer and persistence layer in the configuration file of Spring MVC. We simply leave the param-value of the contextConfigLocation parameter of the DispatcherServlet blank (this means there is no configuration file, the default configuration file will be used if this property is not added).
- For JavaConfig configuration, if we don’t need to configure the parent container, we can directly make the getRootConfigClasses method return NULL. If we don’t need to configure the child container, we can directly make the getServletConfigClasses method return NULL.
Note that if you configure only the parent container, you can have a lot of problems, which are covered below!
4 replenishment of parent and child containers
If a project is configured with parent and child containers, it is important to note the following points that may cause some puzzling problems:
- In a parent-child configuration, child containers can refer to beans in the parent container, but the parent cannot refer to beans in the child container, and beans defined in each child container are not visible to each other. When looking for a bean, if the bean exists in the child container, it is fetched directly from the child container, otherwise it is tried from the parent container!
- If the parent container configuration has the same bean, for example, the scan packet path overlaps between the two configurations, then the bean will be initialized twice. Note that this is not overwriting, but is initialized twice in each parent container and stored in each container, which can cause a lot of problems!
- This results in a large number of identical beans being generated in both parent-child IOC containers, which wastes memory resources.
- For some configuration beans, such as Mq consumers, loading them twice can cause major problems.
- This can lead to some implicit problems, such as if both the child container and the parent container load the Bean of the Service, but the AOP configuration is only applied in the parent container, then the AOP configuration back to the Service is invalid, because the child container will directly use its own internal Service objects that are not AOP enhanced.
- One way to avoid this is to use annotated configurations such as @Transactional, but the best way to do this is either to load all configurations in the child container, or the classes loaded by the parent container are completely separated through different package paths!
- If configured based on XML:
- The Servlet WebApplicationContext child container is always initializedBecause it is with
DispatcherServlet
Is created by default, whereas DispatcherServlets are typically configured. You can choose not to initialize your configuration in the child container, leaving the param-value parameter empty. - The Root WebApplicationContext parent container is not necessarily initializedBecause it is
ContextLoaderListener
ContextLoaderListener can be created without being configured, meaning that the parent container does not really exist. - If both parent and child containers are configured, the request will arrive in the child container first, and in @reqestMapping, only beans in the Servlet WebApplicationContext container will be processed. The bean of the parent container is not looked up (actually only the container associated with the current DispatcherServlet). So if Controller is configured in the parent container, functions annotated with @requestMapping will not be processed in the parent container and handlers will not be generated. Therefore, when the corresponding request comes, the corresponding Hander cannot be found, and the resources corresponding to @ReqestMapping in the Controller cannot be accessed.
- The Servlet WebApplicationContext child container is always initializedBecause it is with
- If configured based on JavaConfig:
- So by implementation
WebApplicationInitializer
Interface to configure items (such as the JavaConfig configuration in the getting started example), there will be only one container, whose displayName is Root WebApplicationContext, but because it is associated with the DispatcherServlet, So you can actually think of it as a Servlet WebApplicationContext container, so you can parse the @ReqestMapping annotation inside the Controller instance of that container, meaning it works fine. - So by inheritance
AbstractAnnotationConfigDispatcherServletInitializer
Abstract class to configure the project:- The Servlet WebApplicationContext subcontainer must be initialized, even if your getServletConfigClasses method returns null, which simply means that the subcontainer will not load any of your other configurations.
- If the getRootConfigClasses method returns NULL, the parent container will not be initialized, meaning that the project will have only one Servlet WebApplicationContext.
- If both parent containers are configured, the request will reach the child container first. The @reqestMapping resolves only beans in the Servlet WebApplicationContext container and does not look for beans in the parent container. So if Controller is configured in the parent container, functions annotated with @requestMapping will not be processed in the parent container and handlers will not be generated. Therefore, when the corresponding request comes, the corresponding Hander cannot be found, and the resources corresponding to @ReqestMapping in the Controller cannot be accessed.
- So by implementation
- The other thing to note is,
Spring boot
Different initialization sequences and strategies are used, using Spring configurations to boot itself and the embedded Servlet container (Tomcat). Filters and servlets are declared in the Spring configuration and registered in the embedded Servlet container. We don’t need to worry about these problems when we use Spring Boot, and we will introduce them later. - If only “one” container is configured, it is recommended to configure only the Servlet WebApplicationContext, that is, the container associated with DispatcherServlet.
5 Other attributes of the DispatcherServlet
We can customize individual DispatcherServlet instances by configuring the following properties, although we may not use them! We’ll see how these properties are resolved later when we look at the Spring MVC source code.
contextClass | Realized the full path name ConfigurableWebApplicationContext interface of container classes, will the current DispatcherServlet instantiate and associations, the default type for the XmlWebApplicationContext. |
contextConfigLocation | The location of the configuration file passed to the WebApplicationContext instance (of type specified by contextClass). Multiple paths are separated by commas (,) |
namespace | The namespace of WebApplicationContext. The default value is servletName-servlet. |
throwExceptionIfNoHandlerFound | Whether a NoHandlerFoundException is thrown if the corresponding Handler for a request cannot be found. We can use HandlerExceptionResolver to catch this exception and handle it uniformly (for example, using the @ExceptionHandler method). The default value is false, in which case the DispatcherServlet sets the response status code to 404 (NOT_FOUND) without raising the exception. Note that if a default processing Servlet is also configured, unresolved requests are always forwarded to the default Servlet and a 404 error is never raised. |
contextId | The ID of the WebApplicationContext associated with this Servlet |
contextAttribute | The configured property name of the WebApplicationContext instance in the ServletContext that this Servlet is associated with |
6 Container Test
6.1 Project Construction
The project structure
Com. Spring. MVC. Service. HelloService and com. The spring MVC. Dao. HelloDao analog business layer and persistence layer, com. Spring. MVC. Controller. The HelloController simulation presentation layer:
@Repository
public class HelloDao {
public HelloDao(a) {
System.out.println("HelloDao create"); }}@Service
public class HelloService {
public HelloService(a) {
System.out.println("HelloService create");
}
@Resource
private HelloDao helloDao;
}
/ * * *@author lx
*/
@Controller
public class HelloController {
public HelloController(a) {
System.out.println("HelloController create");
}
@Resource
private HelloService helloService;
}
/ * * *@author lx
*/
@Controller
public class HelloController {
public HelloController(a) {
System.out.println("HelloController create");
}
@Resource
private HelloService helloService;
}
Copy the code
Spring-config. XML is the spring configuration file:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.spring.mvc.service,com.spring.mvc.dao"/>
</beans>
Copy the code
Spring-mvc-config. XML is the spring MVC configuration file:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.spring.mvc.controller"/>
</beans>
Copy the code
6.2 Single Container
First we test a single subcontainer. Our web. XML looks like this, and we need contextConfigLocation to load two configuration files:
<! DOCTYPEweb-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc-config.xml,classpath:spring-config.xml</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
Copy the code
HelloController adds a test method:
/** * Single container test */
@RequestMapping(path = "/oneLoad")
public String oneLoad(HttpServletRequest servletRequest) {
// Get the current container
WebApplicationContext webApplicationContext = RequestContextUtils.findWebApplicationContext(servletRequest);
// Get the parent container
ApplicationContext parent = webApplicationContext.getParent();
System.out.println("Get from child container:" + webApplicationContext.getBean(HelloService.class));
if(parent ! =null) {
System.out.println("Get from parent container:" + parent.getBean(HelloService.class));
} else {
System.out.println("No parent container");
}
System.out.println("Auto-injected bean:" + helloService);
return "index.jsp";
}
Copy the code
Start the project with the Tomcat plug-in (covered in the first article)!
First we’ll see the loading information for the class:
It did only load once! Then we try to call interface http://localhost:8081/mvc/oneLoad, the output is as follows:
There is only one container, and configured classes are loaded only once!
6.3 Parent and Child Containers
We set up parent containers that load different configuration files!
<web-app>
<display-name>Archetype Created Web Application</display-name>
<! ContextConfigLocation initialization parameter, specifying the configuration file of the parent container Root WebApplicationContext -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-config.xml</param-value>
</context-param>
<! Listen for contextConfigLocation and initialize the parent container -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<! Servlet WebApplicationContext subcontainer configuration -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc-config.xml</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
Copy the code
The startup logs are as follows:
You can see that Root WebApplicationContext Initialized and dispatcherServlet initialized logs, and it looks like HelloService and HelloDao are loaded in the parent container, The HelloController is loaded in the child container associated with the dispatcherServlet!
Call the oneLoad interface again, and the output is as follows. You can see that there are two containers, and both are the same object!
When we debug, we can actually see that the HelloController class instance is in the child container, while the HelloService and HelloDao instances are in the parent container. All instances of this container can be found in the singletonObjects collection in the beanFactory property of the applicationContext.
6.4 Double Loading
We changed the base-package loading path for both configuration files to com.spring.mvc so that when both containers are created, the same configuration can be loaded.
After the change, start the project again, this time the console startup log is as follows:
Something terrible has happened. It seems that the constructor for these classes has been called twice, which means that the objects have been initialized twice!
Call the oneLoad interface again, and the output is as follows. You can see that there are two containers, and the HelloService in the parent container is not the same object!
In fact, we debug can tell that there are different instances of the same type in both containers, andHere not many instances of children blog will "cover" the parent container instance, just because the instance of access mechanism, when we adopt injection or directly access the dependent instance, will be in the first place to look for in the children, can not find to go back to the parent container in search, thus caused the coverage
. When the container is the instance, the parent container there is no access to the instance of the default (will automatically into a container instances), thus causing the waste of memory and other all sorts of problems (for example, if one of your configuration class have a timer, so when the configuration class is loaded after two instances, predictably consequences).
We must avoid parent and child containers loading the same configuration!
6.5 Mapping Failure
In the first test, we loaded all the configuration into a child container, which worked fine. Now, let’s test try to load all the configuration into the parent container!
In this web.xml configuration, we load all configuration files through the parent container, while the child container loads no files. ContextConfigLocation: contextConfigLocation: contextConfigLocation: contextConfigLocation: contextConfigLocation: contextConfigLocation: contextConfigLocation: contextConfigLocation: contextConfigLocation: contextConfigLocation: contextConfigLocation
<! DOCTYPEweb-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<! ContextConfigLocation initialization parameter, specifying the configuration file of the parent container Root WebApplicationContext -->
<context-param>
<param-name>contextConfigLocation</param-name>
<! Load all configuration files -->
<param-value>classpath:spring-config.xml,classpath:spring-mvc-config.xml</param-value>
</context-param>
<! Listen for contextConfigLocation and initialize the parent container -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<! Servlet WebApplicationContext subcontainer configuration -->
<! --param-value is null, no configuration is loaded -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value/>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
Copy the code
At this point, we start the project and look at the console log output:
Well, there seems to be no problem! Then we try to access interface: http://localhost:8081/mvc/oneLoad, the results are as follows:
A 404 exception was raised because the @requestMapping inside the Controller in the parent container was not resolved (actually because the parent container is not associated with the DispatcherServlet, see JavaConfig association configuration in the getting started example). The handler for the path cannot be created, and the resource path cannot be found, even though the resource actually exists!
Related articles:
- spring.io/
- Spring Framework 5.x learning
- Spring Framework 5.x source code
If you need to communicate, or the article is wrong, please leave a message directly. In addition, I hope to like, collect, pay attention to, I will continue to update a variety of Java learning blog!