Spring official website reading (dependency injection and method injection)

In the last article, we learned the sections 1.2 and 1.3 on the official website, mainly involving containers and some knowledge of Spring instantiation objects. In this article, we will continue to learn about the Spring official website, mainly for section 1.4, which mainly involves the dependency injection of Spring. Although there is only one section, it involves a lot of things. Don't say much, start the text.

Article directory

Dependency injection:

According to the official website, dependency injection is mainly divided into two ways

  1. Constructor Inject

  2. Setter method injection

    Official website:

We test the above two methods respectively. XML is used on the official website, and annotation is used here:

The test code is as follows. We inject LuBanService into the Service

public class Main02 {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext ac = new 
            // The config class mainly completes the scanning of the class
            AnnotationConfigApplicationContext(Config.class);
		Service service = (Service) ac.getBean("service");
		service.test();
	}
}

@Component
public class LuBanService {
	LuBanService(){
		System.out.println("luBan create ");
	}
}
Test setter method injection
@Component
public class Service {

	private LuBanService luBanService;

	public Service() {
		System.out.println("service create");
	}

	public void test(){
		System.out.println(luBanService);
	}
	// Using autowired to specify the set method to complete the injection
	@Autowired
	public void setLuBanService(LuBanService luBanService) {
		System.out.println("injection luBanService by setter");
		this.luBanService = luBanService;
	}
}

The output is as follows:

luBan create 
service create
//Inject luBanService by setter  // It is verified that it is injected through setter
com.dmz.official.service.LuBanService@5a01ccaa
Test constructor injection
@Component
public class Service {

	private LuBanService luBanService;
    
    public Service() {
		System.out.println("service create by no args constructor");
	}
	
    // Specify to use this constructor through Autowired, otherwise no parameter will be used by default
	@Autowired
	public Service(LuBanService luBanService) {
		System.out.println("injection luBanService by constructor with arg");
		this.luBanService = luBanService;
		System.out.println("service create by constructor with arg");
	}

	public void test(){
		System.out.println(luBanService);
	}
}

The output is as follows:

luBan create 
//Inject luBanService by constructor // It is verified that it is injected through constructor
service create by constructor
com.dmz.official.service.LuBanService@1b40d5f0
Doubt:

In the above verification, you may have the following questions:

  1. @What's the difference between adding Autowired directly to a field and adding it to a set method? Why do we need to add it to the setter method when we validate?

    • First of all, let's make it clear that @ Autowired annotation can be added to the field directly, and the injection can be completed without providing a setter method. In the above example, Spring will obtain the luBanService field in the Service through reflection, and then complete the injection through the reflection package method, file.set (Service, luBanService)
    • When we add @ Autowired to the setter method, we can look at the call stack of the method through the breakpoint, as follows:

In this way, the final injection is done through Method.invoke(object,args). The method object here is our setter method

  1. @Why is Autowired added to the constructor to specify the use of this constructor?

    • Let's test it first. What if we don't add this annotation? I annotate the @ Autowired annotation in the previous article, and then run the discovery
    luBan create 
    service create by no args constructor  // You can see that empty parameter construction is performed
    null
    

    Don't rush to the conclusion. Let's do another test. Do you want to add @ Autowired annotation to both functions?

    Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'service': Invalid autowire-marked constructor: public com.dmz.official.service.Service(com.dmz.official.service.LuBanService). Found constructor with 'required' Autowired annotation already: public com.dmz.official.service.Service()
    

    It is found that a direct error is reported, which means that a constructor marked by the @ Autowired annotation has been found, and the required attribute in the annotation is true. Later, I tested to change the required attribute in one of the annotations to false, and found that the same error was reported. Finally, I changed the attributes in both of the annotations to false before the test passed. And the test results are the same as the above, all of which are nonparametric constructions executed.

    To make this clear involves two knowledge

    • The injection model in Spring. The next article will focus on this
    • Spring's inference about constructors. I'm going to write a special article in the source phase. Now let's remember:

    Under the default injection model, if Spring finds two qualified constructors at the same time, Spring will instantiate them with the default nonparametric construction. If there is no nonparametric construction at this time, an error of java.lang.NoSuchMethodException will be reported. What is a qualified constructor? The parameter Spring in the constructor can be found and managed by Spring.

    Here we need to remember: 1. Default injection model; 2. Qualified constructor

  2. What if we add attribute injection and construct injection at the same time?

    Before the test, we can boldly guess that Spring can complete the property injection in the constructor, but this is what the instantiation object phase does, so when the property injection is really carried out later, it will certainly be covered. Now let's test our conclusion

    @Component
    public class Service {
    	private LuBanService luBanService;	
    	public Service(LuBanService luBanService) {
    		System.out.println("injection luBanService by constructor with arg");
    		this.luBanService = luBanService;
    		System.out.println("service create by constructor with arg");
    	}
    	public void test(){
    		System.out.println(luBanService);
    	}
    	@Autowired
    	public void setLuBanService(LuBanService luBanService) {
    		System.out.println("injection luBanService by setter");
    		this.luBanService = null;
    	}
    }
    

    Operation result:

    injection luBanService by constructor with arg  // An injection was made during instantiation
    service create by constructor with arg   // Instantiation complete
    //Inject luBanService by setter    // When injecting attributes, the attributes injected at the time of instantiation are overwritten
    null
    
Difference:

According to the official website above, we can draw the following conclusions:

  1. Constructor injection and setter method injection can be mixed
    1. For some mandatory dependencies, we'd better use constructor injection. For some optional dependencies, we can use setter method injection
  2. The Spring team recommends using constructors to complete the injection. But for some constructors with too long parameters, Spring is not recommended
Method injection:

We don't follow the official website order completely. First, let's look at this section. The corresponding positions on the official website are as follows:

Why method injection is needed:

First of all, let's think about a question: why do we need method injection when we have dependency injection? In other words, what problem does method injection solve?

Let's look at the following scenario:

@Component
public class MyService {

	@Autowired
	private LuBanService luBanService;

	public void test(int a){
		luBanService.addAndPrint(a);
	}

}

@Component
// Prototype object
@Scope("prototype")
public class LuBanService {
	int i;

	LuBanService() {
		System.out.println("luBan create ");
	}
	// Every time i+a attribute of the current object is printed
	public void addAndPrint(int a) {
		i+=a;
		System.out.println(i);
	}
}

public class Main02 {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
		MyService service = (MyService) ac.getBean("myService");
		service.test(1);
		service.test(2);
		service.test(3);
	}
}

In the above code, we have two beans. MyService is a single Bean and LuBanService is a prototype Bean. Our intention may be to get different lubanservices every time. The expected results should be printed out:

1,2,3

Actual output:

1
3
6

This result shows that the LuBanService we call each time is the same object. Of course, this is also well understood, because we have completed the injection of LuBanService in the dependency injection stage, and then we will not inject again when we call the test method, so we always use the same object.

We can say that in this case, the prototype object loses the meaning of the prototype, because the same object is used every time. So how to solve this problem? As long as I go to retrieve the Bean every time I use it, then we can solve it through method injection at this time.

By injecting context (applicationContext object)

It can be divided into the following two ways:

  • Implement the org.springframework.context.ApplicationContextAware interface
@Component
public class MyService implements ApplicationContextAware {

	private ApplicationContext applicationContext;

	public void test(int a) {
		LuBanService luBanService = ((LuBanService) applicationContext.getBean("luBanService"));
		luBanService.addAndPrint(a);
	}

	@Override
	public void setApplicationContext(@Nullable ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}
}
  • Direct injection context
@Component
public class MyService{
	@Autowired
	private ApplicationContext applicationContext;

	public void test(int a) {
		LuBanService luBanService = ((LuBanService) applicationContext.getBean("luBanService"));
		luBanService.addAndPrint(a);
	}
}
Through @ LookUp (also divided into annotation and XML, only annotation is shown here)
@Component
public class MyService{
	public void test(int a) {
		LuBanService luBanService = lookUp();
		luBanService.addAndPrint(a);
	}
	// 
	@Lookup
	public LuBanService lookUp(){
		return null;
	}
}
Replace method of method injection

There is another way of method injection, that is, through the form of replace method, no corresponding annotation is found, so here we also test it with XML:

<?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 id="myService" class="com.dmz.official.service.MyService">
		<replaced-method replacer="replacer" name="test"/>
	</bean>

	<bean id="replacer" class="com.dmz.official.service.MyReplacer"/>
</beans>
public class MyReplacer implements MethodReplacer {
    @Override
   public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
        System.out.println("Replace"+obj+"Method in, method name:"+method.getName());
        System.out.println("Execute logic in new method");
        return null;
    }
}

public class MyService{
    public void test(int a) {
        System.out.println(a);
    }
}

public class Main {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext cc =
            new ClassPathXmlApplicationContext("application.xml");
        MyService myService = ((MyService) cc.getBean("myService"));
        myService.test(1);
    }
}

Execution result:

Replace the method in com.dmz.official.service.MyService$$EnhancerBySpringCGLIB$c14242@63e31ee, method name: test
 Execute logic in new method

One thing to note here:

When I test the method injection of replace method, influenced by dynamic agent, I always want to execute the method we are replacing. The code is as follows:

public class MyReplacer implements MethodReplacer {

	@Override
	public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
//		System.out.println("replace method in" + obj + ", method name:" + method.getName());
//		System.out.println("execute the logic in the new method");
		method.invoke(obj,args);
		return null;
	}
}

However, this code is unable to execute, which will report stack memory overflow. Because obj is our proxy object, method.invoke(obj,args) will enter the dead cycle of method call when executing. In the end, I didn't find a proper way to implement the replaced method. At present, it seems that this may also be Spring's design, so our scenario of using replace method should be to completely replace the execution logic of a method, rather than use AOP to complete some logic before and after the execution of a method.

Summary of dependency injection and method injection:
  • First of all, we need to be clear about what are Dependencies? Take a look at a passage on the official website:

It can be said that the dependency of an object is its own attribute, and the dependency injection in Spring is attribute injection.

  • We know that an object consists of two parts: Property + behavior (method). It can be said that Spring controls the whole bean through property injection + method injection.
  • Both attribute injection and method injection are the means Spring provides for us to handle the cooperative relationship between beans
  • There are two methods of property injection: constructor and Setter method.
  • Method injection (LookUp Method and Replace Method) depends on dynamic agent
  • Method injection complements attribute injection to some extent, because in the case of attribute injection, the prototype object may lose the meaning of the prototype, as shown in: Why method injection is needed

The picture is as follows:

120 original articles published, 57 praised, 40000 visitors+
Private letter follow

Tags: Spring Attribute xml Java

Posted on Mon, 09 Mar 2020 03:54:31 -0400 by tbare