Source code analysis of dependency injection in Spring

Underlying schematic flow chart of dependency injection: https://www.processon.com/view/link/5f899fa5f346fb06e1d8f570

1, How many dependency injection methods are there in Spring?

First, there are two types:

1. Manual injection
2. Automatic injection

1. Manual injection

When defining beans in XML, it is manual injection, because programmers manually specify the value of an attribute.

<bean name="userService" class="com.jihu.service.UserService">
	<property name="orderService" ref="orderService"/>
</bean>

The above bottom layer is injected through the set method.

<bean name="userService" class="com.jihu.service.UserService">
	<constructor-arg index="0" ref="orderService"/>
</bean>

The above bottom layer is injected through the construction method.

Therefore, the bottom layer of manual injection is divided into two types:

1. set method injection

2. Construction method injection

2. Automatic injection

Automatic injection is divided into two types:

1. autowire automatic injection of XML
2. Automatic injection of @ Autowired annotation

2.1 autowire automatic injection of XML

In XML, we can specify the automatic injection mode of a Bean when defining it:

  • byType
  • byName
  • constructor
  • default
  • no

For example:

<bean id="userService" class="com.jihu.service.UserService" autowire="byType"/>

This means that Spring will automatically assign values to all attributes in userService (there is no need to have @ Autowired annotation on this attribute, but there is a corresponding set method on this attribute).

In the process of creating a Bean, when filling in properties, spring will parse the current class and all the methods of the current class. Spring will parse each method to get the corresponding PropertyDescriptor object. There are several properties in the PropertyDescriptor:

1,name
This name is not the name of the method, but the name after processing the method name.

  • If the method name starts with "get", such as "getXXX", then name=XXX
  • If the method name starts with "is", such as "isXXX", then name=XXX
  • If the method name starts with "set", such as "setXXX", then name=XXX

2,readMethodRef
Represents a reference to the Method object of the get Method.

3,readMethodName
Represents the name of the get method.

4,writeMethodRef
Represents a reference to the Method object of the set Method.

5,writeMethodName
Represents the name of the set method.

6,propertyTypeRef
If there is a get method, it corresponds to the type of the return value. If it is a set method, it corresponds to the type of the only parameter in the set method.

The definition of get method is: the number of method parameters is 0, and the method name starts with "get" or the method name starts with "is", and the return type of the method is boolean.

The definition of set method is: the number of method parameters is 1, and (the method name starts with "set" and the method return type is void).

Therefore, when Spring automatically populates the attribute by byName, the process is:
1. Find the name of XXX part corresponding to all set methods
2. Get the bean according to the name of the XXX part

When Spring automatically populates attributes through byType, the process is as follows:
1. Get the parameter type of the unique parameter in the set method, and get the bean in the container according to the type
2. If more than one is found, an error will be reported

The byType and byName of autowire are analyzed above. Next, the constructor is analyzed. The constructor indicates that it is injected through the construction method. In fact, this situation is relatively simple, not as complex as byType and byName

If it is a constructor, you can not write the set method. When a bean is injected through the construction method, spring uses the parameter information of the construction method to find the bean from the spring container. After finding the bean, it is passed to the construction method as a parameter, so as to instantiate a bean object and complete the attribute assignment (the code for attribute assignment must be written by the programmer).

We will not consider the case that a class has multiple construction methods, but we will talk about inference construction methods separately later. We will only consider only one parametric construction method.

In fact, construction method injection is equivalent to byType+byName. Ordinary byType is to find beans according to the parameter types in the set method, and errors will be reported if multiple are found, while constructor is to find beans through the parameter types in the construction method. If multiple are found, they will be determined according to the parameter names.

The other two:

1. no means to turn off autowire
2. Default indicates the default value. We have been demonstrating the autowire of a bean. You can also directly set the autowire in the < beans > tag. If it is set, the autowire set in the tag will be used if it is default.

It can be found that automatic injection in XML is very powerful, so the problem is, why do we usually use @ Autowired annotation instead of the automatic injection method mentioned above?

@The autowire annotation is equivalent to the substitution of the annotation method of the autowire attribute in XML, which is mentioned on the official website.

Essentially, the @Autowired annotation provides the same capabilities as described in Autowiring Collaborators but with more fine-grained control and wider applicability

In essence, @ Autowired annotation provides the same functions as autowire, but has finer grained control and wider applicability.

Note: finer grained control.

autowire in XML controls all attributes of the whole bean, while @ Autowired annotation is written directly on an attribute, a set method and a construction method.

For another example, if a class has multiple construction methods, if you use autowire=constructor in XML, you can't control which construction method to use, but you can directly specify which construction method you want to use with @ Autowired annotation.

At the same time, with @ Autowired annotation, you can also control which attributes want to be automatically injected and which attributes don't. this is also fine-grained control.

However, @ Autowired cannot distinguish byType from byName, @ Autowired is byType first, and byName if more than one is found.

So the bottom layer of XML automatic injection is actually:
1. set method injection
2. Construction method injection

2.2 automatic injection of @ Autowired annotation

The @ Autowired annotation mentioned above is a combination of byType and byName.

@The Autowired annotation can be written in:

  • On attributes: first find the Bean according to the attribute type. If multiple beans are found, then determine one according to the attribute name
  • On the construction method: first find the Bean according to the method parameter type. If multiple beans are found, then determine one according to the parameter name
  • On the set method: first find the Bean according to the method parameter type. If multiple beans are found, then determine one according to the parameter name

And the bottom is:
1. Attribute injection
2. set method injection
3. Construction method injection

2, Find injection point

In the process of creating a Bean, Spring will use the postProcessMergedBeanDefinition() of AutowiredAnnotationBeanPostProcessor to find the injection point and cache it. The process of finding the injection point is as follows:

1. Traverse all property fields of the current class
2. Check whether any of @ Autowired, @ Value, @ Inject exists in the field. If it exists, it is considered that the field is an injection point
3. If the field is static, no injection is performed
4. Gets the value of the required property in @ Autowired
5. Construct the field information into an AutowiredFieldElement object and add it to the currElements collection as an injection point object
6. Traverse all methods of the current class
7. Judge whether the current Method is a bridging Method (it can be seen at the bytecode level, which is related to generics). If it is, find the original Method
8. Check whether any of @ Autowired, @ Value, @ Inject exists on the method. If it exists, the method is considered as an injection point
9. If the method is static, no injection is performed
10. Gets the value of the required property in @ Autowired
11. Construct the method information into an AutowiredMethodElement object and add it to the currElements collection as an injection point object
12. After traversing the fields and methods of the current class, the of the parent class will be traversed until there is no parent class
13. Finally, encapsulate the currElements collection into an InjectionMetadata object as the injection point collection object of the current Bean, and cache it

1. Why is static's field or method not supported

@Component
@Scope("prototype")
public class OrderService {
}
@Component
@Scope("prototype")
public class UserService  {

	@Autowired
	private static OrderService orderService;

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

Looking at the above code, UserService and OrderService are prototype beans. Assuming that Spring supports automatic injection of static fields, now call them twice
:

UserService userService1 = context.getBean("userService")
UserService userService2 = context.getBean("userService")

What is the orderService value of userService1 at this time? Or does it inject its own value? ​

The answer is No. once userService2 is created, the value of the static orderService field is modified, resulting in a bug.

2. Bridging method

public interface UserInterface<T> {
	void setOrderService(T t);
}
@Component
public class UserService implements UserInterface<OrderService> {

	private OrderService orderService;

	@Override
	@Autowired
	public void setOrderService(OrderService orderService) {
		this.orderService = orderService;
	}

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

}

The bytecode corresponding to UserService is:

/ class version 52.0 (52)
// access flags 0x21
// signature Ljava/lang/Object;Lcom/zhouyu/service/UserInterface<Lcom/zhouyu/service/OrderService;>;
// declaration: com/zhouyu/service/UserService implements com.zhouyu.service.UserInterface<com.zhouyu.service.OrderService>
public class com/jihu/service/UserService implements com/jihu/service/UserInterface {

  // compiled from: UserService.java

  @Lorg/springframework/stereotype/Component;()

  // access flags 0x2
  private Lcom/jihu/service/OrderService; orderService

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 12 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Lcom/jihu/service/UserService; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  public setOrderService(Lcom/jihu/service/OrderService;)V
  @Lorg/springframework/beans/factory/annotation/Autowired;()
   L0
    LINENUMBER 19 L0
    ALOAD 0
    ALOAD 1
    PUTFIELD com/jihu/service/UserService.orderService : Lcom/jihu/service/OrderService;
   L1
    LINENUMBER 20 L1
    RETURN
   L2
    LOCALVARIABLE this Lcom/jihu/service/UserService; L0 L2 0
    LOCALVARIABLE orderService Lcom/jihu/service/OrderService; L0 L2 1
    MAXSTACK = 2
    MAXLOCALS = 2

  // access flags 0x1
  public test()V
   L0
    LINENUMBER 23 L0
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "test123"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L1
    LINENUMBER 24 L1
    RETURN
   L2
    LOCALVARIABLE this Lcom/jihu/service/UserService; L0 L2 0
    MAXSTACK = 2
    MAXLOCALS = 1

  // access flags 0x1041
  public synthetic bridge setOrderService(Ljava/lang/Object;)V
  @Lorg/springframework/beans/factory/annotation/Autowired;()
   L0
    LINENUMBER 11 L0
    ALOAD 0
    ALOAD 1
    CHECKCAST com/jihu/service/OrderService
    INVOKEVIRTUAL com/jihu/service/UserService.setOrderService (Lcom/jihu/service/OrderService;)V
    RETURN
   L1
    LOCALVARIABLE this Lcom/jihu/service/UserService; L0 L1 0
    MAXSTACK = 2
    MAXLOCALS = 2
}

You can see that there are two setOrderService methods in the bytecode of userservice:
1,public setOrderService(Lcom/jihu/service/OrderService;)V
2,public synthetic bridge setOrderService(Ljava/lang/Object;)V

And there are @ Autowired annotations. ​

Therefore, this situation needs to be handled in Spring. When traversing the bridge method, you have to find the original method (you don't need to use the bridge method as an injection point).

3, Injection point

In the postProcessProperties() method of AutowiredAnnotationBeanPostProcessor, Spring will traverse the found injection points (all injection points were found before and cached, but now it is really traversing the injection) for injection in turn. ​

1. Field injection

1. Traverse all AutowiredFieldElement objects
2. Encapsulate the corresponding field as a DependencyDescriptor object
3. Call the resolveDependency() method of BeanFactory, pass in the DependencyDescriptor object, find the dependency, and find the Bean object matching the current field
4. Encapsulate the DependencyDescriptor object and the found result object beanName into a ShortcutDependencyDescriptor object as a cache. For example, if the current bean is a prototype bean, the next time you create the bean, you can directly take the cached result object beanName to the bean object in BeanFactory without searching again
5. Assign a result object to a field using reflection

2. Set method injection

1. Traverse all AutowiredMethodElement objects
2. Traverse the parameters of the corresponding method and encapsulate each parameter into a MethodParameter object
3. Encapsulate the MethodParameter object as a DependencyDescriptor object
4. Call the resolveDependency() method of BeanFactory, pass in the DependencyDescriptor object, find the dependency, and find the Bean object matching the current method parameters.
5. Encapsulate the DependencyDescriptor object and the found result object beanName into a ShortcutDependencyDescriptor object as a cache. For example, if the current bean is a prototype bean, the next time you create the bean, you can directly take the cached result object beanName to the bean object in BeanFactory without searching again
6. Use reflection to pass all the result objects found to the current method and execute.

Keywords: Java Spring mvc

Added by Danno13 on Sat, 20 Nov 2021 08:04:44 +0200