spring's Way to God Article 24: Detailed Parent-Child Containers

Posted by Dawg on Sat, 30 Oct 2021 19:45:22 +0200

Again, he was taken to the pit by the interviewer.

Interviewer: Has springmvc been used?

Me: Yes, often?

Interviewer: Why do I need parent-child containers in springmvc?

Me: Hmm.. I didn't understand what you said.

Interviewer: The controller layer is loaded by one spring container, the other service and dao layers are loaded by another spring container, and the web.xml has this configuration, which makes up the parent-child container relationship.

Me: Oh, that's what it used to be. I think, I see everyone uses it, so I use it too.

Interviewer: Have you considered why?

Me: I see everybody uses it online, so I do. I don't know exactly why, but it's easy to use.

Interviewer: Is it OK to use only one container, and all configurations are loaded into one spring container?

Me: Should not!

Interviewer: Are you sure not?

Me: Let me think for a moment... I feel like it's okay, and it works as well.

Interviewer: So we're back to the question at the beginning. Why use parent-child containers?

Me: Am I calling you brother? Don't play with me like that. You're stunned?

Interviewer: Well, go back and try it. Tell me again next time, turn right when you leave the house.

Me: I turned green and went away with a dirty face.

After returning, I had a good study and was going to give the interviewer some color next time.

Main issues

  1. What is a parent-child container?

  2. Why do I need a parent-child container?

  3. How do parent and child containers work?

Let's talk about it.

Let's start with a case

There are two modules in the system: module1 and module2, which are developed independently. module2 will use some classes in module1. module1 will package itself as jar for module2 to use. Let's take a look at the code of these two modules.

Module 1

In the module 1 package, there are three classes

Service1

package com.javacode2018.lesson002.demo17.module1;

import org.springframework.stereotype.Component;

@Component
public class Service1 {
    public String m1() {
        return "I am module1 In Servce1 In m1 Method";
    }
}

Service2

package com.javacode2018.lesson002.demo17.module1;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Service2 {

    @Autowired
    private com.javacode2018.lesson002.demo17.module1.Service1 service1; //@1

    public String m1() //@2
        return this.service1.m1();
    }

}

Both of the above classes, labeled with the @Compontent annotation, are registered with the container by spring.

Service1 is required in @1:Service2, annotated with @Autowired and injected through the spring container

There is an m1 method in @2:Service 2 that calls the service's m1 method internally.

Come up with a spring configuration class: Module1Config

package com.javacode2018.lesson002.demo17.module1;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class Module1Config {
}

Using the @CompontentScan annotation above, all classes in the package in which the current class resides are automatically scanned, and classes labeled with the @Compontent annotation are registered with the spring container, where Service1 and Service2 are registered with the spring container.

Look at Module 2 again

In the module 2 package, there are also three classes, which are a little similar to those in module 1.

Service1

A Service1 is also defined in Module 2, and an m2 method is provided internally as follows:

package com.javacode2018.lesson002.demo17.module2;

import org.springframework.stereotype.Component;

@Component
public class Service1 {
    public String m2() {
        return "I am module2 In Servce1 In m2 Method";
    }
}

Service3

package com.javacode2018.lesson002.demo17.module2;

import com.javacode2018.lesson002.demo17.module1.Service2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Service3 {
    //Using Service1 in Module 2
    @Autowired
    private com.javacode2018.lesson002.demo17.module2.Service1 service1; //@1
    //Using Service2 in Module 1
    @Autowired
    private com.javacode2018.lesson002.demo17.module1.Service2 service2; //@2

    public String m1() {
        return this.service2.m1();
    }

    public String m2() {
        return this.service1.m2();
    }

}

@1: Use Service1 in module 2

@2: Use Service2 in module 1

Think about a question first

Are there any problems with using spring for these classes above? What's wrong?

The problem is simple, and most people can see that errors occur because both modules have Service1, and when registered with the spring container, the bean names conflict, causing registration to fail.

Come to a test class and see the results

package com.javacode2018.lesson002.demo17;

import com.javacode2018.lesson001.demo21.Config;
import com.javacode2018.lesson002.demo17.module1.Module1Config;
import com.javacode2018.lesson002.demo17.module2.Module2Config;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class ParentFactoryTest {

    @Test
    public void test1() {
        //Define Containers
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        //Register bean s
        context.register(Module1Config.class, Module2Config.class); //@1
        //Start Container
        context.refresh();
    }
}

@1: Register Module1Config, Module2Config with the container, and the annotations above these two classes are automatically resolved within spring, that is, @CompontentScan annotation, then a package scan is performed to register the class labeled @Compontent with the spring container.

Run test1 output

Here are some of the outputs:

Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'service1' for bean class [com.javacode2018.lesson002.demo17.module2.Service1conflicts with existingnon-compatible bean definition of same name and class [com.javacode2018.lesson002.demo17.module1.Service1]

The name of service1 bean conflicts.

So how do we solve it?

Modify Service1 in module1? This estimate does not work. module1 was provided to us by jar, and we cannot modify the source code.

module2 is our own development, and we can adjust what's inside, so we can modify Service1 in module2, we can modify the class name, or we can modify the name of this bean, which can solve the problem at this time.

But have you ever thought about the question: if we have many classes in our module that have this problem, it will be more painful to refactor one by one at this time, and after the code refactoring, it will also involve retest issues, which is also a huge workload. These are risks.

The parent and child containers in spring are a good solution to this problem.

What is a parent-child container

When you create a spring container, you can specify a parent container for the current container.

Ways of BeanFactory

//Create parent container parentFactory
DefaultListableBeanFactory parentFactory = new DefaultListableBeanFactory();
//Create a child container
DefaultListableBeanFactory childFactory = new DefaultListableBeanFactory();
//Call setParentBeanFactory to specify the parent container
childFactory.setParentBeanFactory(parentFactory);

How ApplicationContext works

//Create parent container
AnnotationConfigApplicationContext parentContext = new AnnotationConfigApplicationContext();
//Start parent container
parentContext.refresh();

//Create subcontainers
AnnotationConfigApplicationContext childContext = new AnnotationConfigApplicationContext();
//Set parent container for child container
childContext.setParent(parentContext);
//Promoter Container
childContext.refresh();

The above code is relatively simple and can be understood by everyone.

We need to understand the characteristics of parent and child containers, which are critical, as follows.

Parent-Child Container Features

  1. Parent and child containers are isolated from each other and can have bean s with the same name inside them

  2. Child containers can access beans in parent containers, while parent containers cannot access beans in child containers

  3. When a subcontainer's getBean method is called to get a bean, it will start looking up the container above along the current container until the corresponding bean is found

  4. Beans in a child container can be injected into the parent container by any injection, whereas bean s in the parent container cannot be injected into the child container because of point 2

Use parent-child containers to solve starting problems

critical code

@Test
public void test2() {
    //Create parent container
    AnnotationConfigApplicationContext parentContext = new AnnotationConfigApplicationContext();
    //Register Module1Config configuration class with parent container
    parentContext.register(Module1Config.class);
    //Start parent container
    parentContext.refresh();

    //Create subcontainers
    AnnotationConfigApplicationContext childContext = new AnnotationConfigApplicationContext();
    //Register Module2Config configuration class with subcontainers
    childContext.register(Module2Config.class);
    //Set parent container for child container
    childContext.setParent(parentContext);
    //Promoter Container
    childContext.refresh();

    //Getting Service3 from a subcontainer
    Service3 service3 = childContext.getBean(Service3.class);
    System.out.println(service3.m1());
    System.out.println(service3.m2());
}

Run Output

I am module1 In Servce1 In m1 Method
I am module2 In Servce1 In m2 Method

It's normal this time.

Notes on the use of parent-child containers

Some of the methods we often use when working with containers are usually in the following two interfaces

org.springframework.beans.factory.BeanFactory
org.springframework.beans.factory.ListableBeanFactory

There are many methods in these two interfaces, which are not listed here. You can go over the source code. What you need to pay attention to here is when using parent and child containers.

The BeanFactory interface is the top-level interface of the spring container. The methods in this interface support container nested structure lookup, such as the getBean method we commonly use, which is defined in this interface. When a getBean method is called, it will look up along the current container until a qualifying bean is found.

The ListableBeanFactory interface does not support container nested structure lookups, such as the following

String[] getBeanNamesForType(@Nullable Class<?> type)

Gets all the bean names of the specified type, and calls to this method return only the qualified beans in the current container, not the beans in its parent container, recursively.

Take a look at the case code and feel:

@Test
public void test3() {
    //Create parent container parentFactory
    DefaultListableBeanFactory parentFactory = new DefaultListableBeanFactory();
    //Register a bean [userName-> "Pedestrian Armor Java"] with the parent container parentFactory
    parentFactory.registerBeanDefinition("userName",
            BeanDefinitionBuilder.
                    genericBeanDefinition(String.class).
                    addConstructorArgValue("Pedestrian A Java").
                    getBeanDefinition());

    //Create a child container
    DefaultListableBeanFactory childFactory = new DefaultListableBeanFactory();
    //Call setParentBeanFactory to specify the parent container
    childFactory.setParentBeanFactory(parentFactory);
    //Register a bean [address-> "Shanghai"] with the child container parentFactory
    childFactory.registerBeanDefinition("address",
            BeanDefinitionBuilder.
                    genericBeanDefinition(String.class).
                    addConstructorArgValue("Shanghai").
                    getBeanDefinition());

    System.out.println("Obtain bean[userName]: " + childFactory.getBean("userName"));//@1

    System.out.println(Arrays.asList(childFactory.getBeanNamesForType(String.class))); //@2
}

There are two containers defined above

Parent container: parentFactory, which defines a String type bean:userName->Pedestrian Java internally

Subcontainer: childFactory, which also defines a String type bean:address->Shanghai

@1: Call the child container's getBean method to get a bean named userName, which is defined in the parent container, while the getBean method is defined in the BeanFactory interface and supports container-level lookup, so getBean can find userName as a bean

@2: Call the subcontainer's getBeanNamesForType method to get the bean names of all String types, while the getBeanNamesForType method is defined in the ListableBeanFactory interface, where the method does not support hierarchical lookups and only looks in the current container, so it only returns the address of the subcontainer

Let's run it and see what happens:

Obtain bean[userName]: Pedestrian A Java
[address]

The results are consistent with the analysis.

Then the question arises: Is there a way to solve the problem that the ListableBeanFactory interface does not support hierarchical lookups?

There is a tool class in spring that solves this problem, as follows:

org.springframework.beans.factory.BeanFactoryUtils

This class provides many static methods, there are many methods to support hierarchical lookup, source code you can take a closer look, the name contains Ancestors are all supporting hierarchical lookup.

Add the following code to the test2 method:

//Hierarchical lookup of all bean names that match the type
String[] beanNamesForTypeIncludingAncestors = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(childFactory, String.class);
System.out.println(Arrays.asList(beanNamesForTypeIncludingAncestors));

Map<String, String> beansOfTypeIncludingAncestors = BeanFactoryUtils.beansOfTypeIncludingAncestors(childFactory, String.class);
System.out.println(Arrays.asList(beansOfTypeIncludingAncestors));

Run Output

[address, userName]
[{address=Shanghai, userName=Pedestrian A Java}]

The lookup process is to find all the bean s that meet the criteria hierarchically.

Look back at the springmvc parent-child container problem

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

Using only one container works fine.

Question 2: Why do we need parent-child containers in springmvc?

Usually when we use springmvc, we use 3 layers structure, controller layer, service layer, dao layer. The parent container contains the dao and service layers, while the child container contains only the controller layer. These two containers make up the parent-child container relationship, and the controller layer typically injects bean s into the service layer.

Using parent-child containers prevents some people from injecting controller s'bean s into the service layer, resulting in confusing dependency levels.

Parent containers and child containers have different requirements, such as transaction support in parent containers, which injects some extended components that support transactions, which are not needed by controller s in child containers. It is not concerned about these. springmvc-related bean s need to be injected into child containers, which are also unused and not concerned with something in parent containers. Separating these things from each other will help you avoid unnecessary errors and allow your parent and child containers to load faster.

summary

  1. This article needs to understand the use of parent-child containers and the characteristics of parent-child containers: child containers can access beans in parent containers, parent containers cannot access beans in child containers

  2. BeanFactory interface supports hierarchical lookup

  3. ListableBeanFactory interface does not support hierarchical lookup

  4. The BeanFactoryUtils tool class provides some very useful methods, such as those that support bean hierarchical lookup, and so on.

Case Source

https://gitee.com/javacode2018/spring-series

All the case code for Pedestrian java will be put on this one in the future. watch for everyone to keep an eye on the dynamics.

Source: https://mp.weixin.qq.com/s?__ Biz=MzA5MTkxMDQ4MQ==&mid=2648934382&idx=1&sn=7d37aef61cd18ec295f268c902dfb84f&chksm=88621fd0bf1596c6f60c966eb325c6fe0e200666ee0bcdd1ff85691795209e444f2&token=749715143&lang=zh_ CN&scene=21#wechat_ Redirect