Do you understand the parent-child container in Spring?

In the SSM project, the Spring container is the parent container, and SpringMVC is the child container. The child container can access the beans of the parent container, but the parent container cannot access the beans of the child container.

One step closer, some friends may also know that it seems that it is possible to simply use a SpringMVC container without a parent-child container, and the project can also run.

So now the question comes: Since a single SpringMVC container can make the project run, why do we use parent-child containers? What are the advantages of parent-child containers?

1. Parent-child container

First of all, in fact, the parent-child design is very common. After using the parent-child container, if you go to the parent container to find the bean, then simply look for the bean in the parent container; Search in the child container, return if found, continue to search in the parent container if not found, until it is found (if you have searched all the parent containers and still have nothing, then you can only throw an exception).

2. Why do we need parent-child containers

2.1 Presentation of the problem

Why do you need parent-child containers? Can't we just use a container honestly?

Since there are parent-child containers in the Spring container, this thing must have its usage scenarios.

Suppose I have a multi-module project, which includes a merchant module and a customer module. Both the merchant module and the customer module have role management RoleService. The project structure is as follows:

├── admin
│   ├── pom.xml
│   └── src
│       ├── main
│       │   ├── java
│       │   └── resources
├── consumer
│   ├── pom.xml
│   └── src
│       ├── main
│       │   ├── java
│       │   │   └── org
│       │   │       └── admin4j
│       │   │           └── consumer
│       │   │               └── RoleService.java
│       │   └── resources
│       │       └── consumer_beans.xml
├── merchant
│   ├── pom.xml
│   └── src
│       ├── main
│       │   ├── java
│       │   │   └── org
│       │   │       └── admin4j
│       │   │           └── merchant
│       │   │               └── RoleService.java
│       │   └── resources
│       │       └── merchant_beans.xml
└── pom.xml

Now there is a RoleService class in both consumer and merchant, and then register this class with the Spring container in their respective configuration files.

org.admin4j.consumer.RoleService:

public class RoleService {
    public String hello() {
        return "hello consumer";
    }
}

org.admin4j.merchant.RoleService:

public class RoleService {
    public String hello() {
        return "hello merchant";
    }
}

consumer_beans.xml is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="org.admin4j.consumer.RoleService" id="roleService"/>
</beans>

merchant_beans.xml is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="org.admin4j.merchant.RoleService" id="roleService"/>
</beans>

Note that these two beans have the same name.

Now, in the admin module, both consumer and merchant are dependent, and these two configuration files are loaded at the same time, so can two beans with the same name from different modules be registered to the Spring container at the same time?

code show as below:

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext();
ctx.setConfigLocations("consumer_beans.xml", "merchant_beans.xml");
ctx.refresh();
org.admin4j.merchant.RoleService rs1 = ctx.getBean(org.admin4j.merchant.RoleService.class);
org.admin4j.consumer.RoleService rs2 = ctx.getBean(org.admin4j.consumer.RoleService.class);

After this execution, the following questions will be thrown:

picture

Friends can see that this org.admin4j.consumer.RoleServiceservice cannot be found, but the other RoleService is actually found, because by default, the bean with the same name defined later overrides the previous one, so if there is a bean, it cannot be found.

If bean coverage is not allowed, the following configuration can be performed:

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext();
ctx.setConfigLocations("consumer_beans.xml", "merchant_beans.xml");
ctx.setAllowBeanDefinitionOverriding(false);
ctx.refresh();

At this time, an error is reported as soon as it starts:

picture

The meaning is also relatively clear, the definition of Bean conflicts, so the definition fails.

So is there any way to solve the above problem elegantly? The answer is the parent-child container!

2.2 Parent-child container

For the above problem, we can configure the consumer and merchant as a parent-child relationship or a brother relationship, which can solve this problem very well.

2.2.1 Brotherhood

First look at the sibling relationship, the code is as follows:

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext();
ClassPathXmlApplicationContext child1 = new ClassPathXmlApplicationContext("consumer_beans.xml");
ClassPathXmlApplicationContext child2 = new ClassPathXmlApplicationContext("merchant_beans.xml");
child1.setParent(ctx);
child2.setParent(ctx);
ctx.setAllowBeanDefinitionOverriding(false);
ctx.refresh();
org.admin4j.consumer.RoleService rs1 = child1.getBean(org.admin4j.consumer.RoleService.class);
org.admin4j.merchant.RoleService rs2 = child2.getBean(org.admin4j.merchant.RoleService.class);
System.out.println("rs1.hello() = " + rs1.hello());
System.out.println("rs2.hello() = " + rs2.hello());

Friends, take a look, this kind of container is created for consumer and merchant respectively. This kind of container relationship is brother container. The two brothers have a common parent which is ctx. Now you can get your own Bean in each container.

It should be noted that in the above structure, the child container can get the bean of the parent, but cannot get the bean of the brother container, that is, if the consumer references the bean in the merchant, then there is a problem with the above configuration.

2.2.2 Parent-child relationship

Now assume that the consumer is used as the parent container, and the merchant is used as the child container, then the configuration is as follows:

ClassPathXmlApplicationContext parent = new ClassPathXmlApplicationContext("consumer_beans.xml");
ClassPathXmlApplicationContext child = new ClassPathXmlApplicationContext("merchant_beans.xml");
child.setParent(parent);
child.refresh();
org.admin4j.consumer.RoleService rs1 = parent.getBean(org.admin4j.consumer.RoleService.class);
org.admin4j.merchant.RoleService rs2 = child.getBean(org.admin4j.merchant.RoleService.class);
org.admin4j.consumer.RoleService rs3 = child.getBean(org.admin4j.consumer.RoleService.class);
System.out.println("rs1.hello() = " + rs1.hello());
System.out.println("rs2.hello() = " + rs2.hello());
System.out.println("rs3.hello() = " + rs3.hello());

First create two containers, namely parent and child, and then set the parent for the child container, remember to refresh the child container after the setting is complete.

Now we can obtain the original Bean in the parent container from the parent container, or obtain the original Bean of the child container or the Bean of the parent from the child container.

This is the parent-child container.

The parent container and the child container are essentially two different containers isolated from each other, so beans with the same name are allowed to exist. When the child container calls the getBean method to obtain a Bean, if the current container does not find it, it will go to the parent container to search, and keep searching until it is found.

The core is BeanFactory. BeanFactory has a subclass HierarchicalBeanFactory. The name is BeanFactory with hierarchical relationship:

public interface HierarchicalBeanFactory extends BeanFactory {

 /**
  * Return the parent bean factory, or {@code null} if there is none.
  */
 @Nullable
 BeanFactory getParentBeanFactory();

 /**
  * Return whether the local bean factory contains a bean of the given name,
  * ignoring beans defined in ancestor contexts.
  * <p>This is an alternative to {@code containsBean}, ignoring a bean
  * of the given name from an ancestor bean factory.
  * @param name the name of the bean to query
  * @return whether a bean with the given name is defined in the local factory
  * @see BeanFactory#containsBean
  */
 boolean containsLocalBean(String name);

}

Parent-child relationship can be configured as long as it is a subclass of HierarchicalBeanFactory. The parent-child relationship diagram is as follows:

picture

2.3 Special circumstances

It should be noted that not all methods of obtaining beans support parent-child relationship search, and some methods can only be searched in the current container, and will not search in the parent container:

ClassPathXmlApplicationContext parent = new ClassPathXmlApplicationContext("consumer_beans.xml");
ClassPathXmlApplicationContext child = new ClassPathXmlApplicationContext("merchant_beans.xml");
child.setParent(parent);
child.refresh();
String[] names1 = child.getBeanNamesForType(org.admin4j.merchant.RoleService.class);
String[] names2 = child.getBeanNamesForType(org.admin4j.consumer.RoleService.class);
System.out.println("names1 = " + Arrays.toString(names1));
System.out.println("names2 = " + Arrays.toString(names2));

As above, when looking up the bean name according to the type, we use the getBeanNamesForType method, which is provided by the ListableBeanFactory interface, and this interface has no inheritance relationship with the HierarchicalBeanFactory interface, so the getBeanNamesForType method does not support searching for beans in the parent container , which only looks for beans in the current container.

but! If you really have a need and want to be able to find the Bean name according to the type, and also be able to automatically go to the parent container to find it, then you can use the tools provided by Spring, as follows:

ClassPathXmlApplicationContext parent = new ClassPathXmlApplicationContext("consumer_beans.xml");
ClassPathXmlApplicationContext child = new ClassPathXmlApplicationContext();
child.setParent(parent);
child.refresh();
String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(child, org.admin4j.consumer.RoleService.class);
for (String name : names) {
    System.out.println("name = " + name);
}

However, this search cannot find the name of the Bean with the same name in the parent-child container.

2.4 Spring 和 SpringMVC

The above content is understood, and the relationship between Spring and SpringMVC is easy to understand. Spring is the parent container, and SpringMVC is the child container.

In SpringMVC, when the DispatcherServlet is initialized, a SpringMVC container is created and the parent is set for the SpringMVC container. The relevant code is as follows:

FrameworkServlet#initWebApplicationContext:

protected WebApplicationContext initWebApplicationContext() {
 WebApplicationContext rootContext =
   WebApplicationContextUtils.getWebApplicationContext(getServletContext());
 WebApplicationContext wac = null;
 if (this.webApplicationContext != null) {
  // A context instance was injected at construction time -> use it
  wac = this.webApplicationContext;
  if (wac instanceof ConfigurableWebApplicationContext cwac && !cwac.isActive()) {
   // The context has not yet been refreshed -> provide services such as
   // setting the parent context, setting the application context id, etc
   if (cwac.getParent() == null) {
    // The context instance was injected without an explicit parent -> set
    // the root application context (if any; may be null) as the parent
    cwac.setParent(rootContext);
   }
   configureAndRefreshWebApplicationContext(cwac);
  }
 }
 if (wac == null) {
  // No context instance was injected at construction time -> see if one
  // has been registered in the servlet context. If one exists, it is assumed
  // that the parent context (if any) has already been set and that the
  // user has performed any initialization such as setting the context id
  wac = findWebApplicationContext();
 }
 if (wac == null) {
  // No context instance is defined for this servlet -> create a local one
  wac = createWebApplicationContext(rootContext);
 }
 return wac;
}

The rootContext here is the parent container, and wac is the child container. No matter which way you get the child container, you will try to set a parent container for it.

If we do not configure the Spring container separately in a Web project, but directly configure the SpringMVC container, and then scan all the beans into the SpringMVC container, there is no problem in doing so, and the project can run normally. However, in general projects, we still separate these two containers, which has the following advantages:

  1. Convenient management, SpringMVC mainly deals with beans related to the control layer, such as Controller, view parser, parameter processor, etc., while the Spring layer mainly controls beans related to the business layer, such as Service, Mapper, data source, transaction, authority, etc. Related Beans.
  2. For novices, configuring the two containers separately can better understand the relationship between the Controller, Service, and Dao layers, and can also avoid writing ridiculous codes that inject Controllers into the Service layer.

3. Summary

Ok, now everyone should understand the parent-child container in the Spring container, right? You can set a parent container for non-ListableBeanFactory containers. The parent container cannot access the beans of the child container, but the child container can access the beans of the parent container.

3.1 Features of parent-child containers

  • The parent container and the child container are isolated from each other, and beans with the same name can exist inside them

  • The child container can access the beans in the parent container, but the parent container cannot access the beans in the child container

  • When calling the getBean method of the sub-container to obtain the bean, it will start to search the container above the current container until the corresponding bean is found

  • The bean in the parent container can be injected into the child container through any injection method, but the bean in the child container cannot be injected into the parent container because of the second point

  • The BeanFactory interface supports hierarchical search, but the ListableBeanFactory interface does not support hierarchical search

  • The BeanFactoryUtils tool class provides some very practical methods, such as methods that support bean hierarchy search, etc.

3.2 Look back at the problem of springmvc parent-child container

Question 1: Is it possible to use only one container in springmvc?

It works fine with just one container.

Question 2: So why do you need to use parent-child containers in springmvc?

Usually when we use springmvc, we adopt a 3-layer structure, controller layer, service layer, and dao layer; the parent container will contain the dao layer and the service layer, while the child container contains only the controller layer; these two containers form the parent-child container relationship, the controller layer usually injects beans in the service layer.

The use of parent-child containers can prevent some people from injecting beans in the controller layer at the service layer, resulting in confusion in the entire dependency hierarchy.

The needs of the parent container and the child container are also different. For example, the parent container needs to have transaction support, and some extension components that support transactions will be injected, but the controller in the child container does not use these at all, and does not care about these. In the child container Need to inject springmvc-related beans, and these beans will not be used in the parent container, and they don’t care about some things. Separate these things that don’t care about each other, which can effectively avoid some unnecessary mistakes, and the parent-child Containers will also load a little faster.

Guess you like

Origin blog.csdn.net/agonie201218/article/details/131681028