SpringIoC dependency injection source code analysis

Insert image description here

How many dependency injection methods are there in Spring?

  • Manual injection
  • automatic injection

Manual injection

When defining a Bean in XML, it is injected manually, because the programmer manually assigns a value to a certain attribute.

//这种是底层是通过set方法进行注入
<bean name="userService" class="com.tacy.service.UserService">
	<property name="orderService" ref="orderService"/>
</bean>
//这种是底层是通过构造方法进行注入。
<bean name="userService" class="com.luban.service.UserService">
	<constructor-arg index="0" ref="orderService"/>
</bean>

The bottom layer of manual injection is divided into two types:

  • set method injection
  • Constructor injection

automatic injection

Automatic injection is divided into two types:

  • XML autowire automatic injection
  • Automatic injection of @Autowired annotation

XML autowire automatic injection

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

  • byType
  • byName
  • constructor
  • default
  • no
    means turn off autowire
<bean id="userService" class="com.tacy.service.UserService" autowire="byType"/>

Indicates that Spring will automatically assign values ​​​​to all properties in userService (there is no need for the @Autowired annotation on this property, but this property needs to have a corresponding set method).

In the process of creating a bean, when filling in attributes, Spring will parse the current class and parse out 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:

  • name: This name is not the name of the method, but the name after processing the method name.
    a. If the method name starts with "get", such as "getXXX", then name=XXX
    b. If the method name starts with "is" If the method name starts with "set", such as "isXXX", then name=XXX
    c. If the method name starts with "set", such as "setXXX", then name=XXX
  • readMethodRef: a reference to the Method object representing the get method
  • readMethodName: indicates the name of the get method
  • writeMethodRef: a reference to the Method object representing the set method
  • writeMethodName: indicates the name of the set method
  • 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 the 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 method's return type is boolean)

The definition of the 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, the process when Spring automatically fills attributes through byName is:

  1. Find the names of the XXX parts corresponding to all set methods
  2. Get the bean based on the name of the XXX part

The process when Spring automatically fills properties through byType is:

  1. Get the parameter type of the only parameter in the set method, and get the bean from the container based on this type
  2. If multiple are found, an error will be reported.

Above, we have analyzed the byType and byName situations of autowire, then we will analyze the constructor. The constructor means injection through the constructor method. In fact, this situation is relatively simple and not as complicated as byType and byName.

If it is a constructor, then you do not need to write the set method. When a bean is injected through the constructor method, spring uses the parameter information of the constructor method to find the bean from the Spring container. After finding the bean, it is passed as a parameter to the constructor method. This instantiates a bean object and completes attribute assignment (the code for attribute assignment must be written by the programmer).

Only one parameterized constructor is considered here.
In fact, constructor injection is equivalent to byType+byName. Ordinary byType searches for beans based on the parameter types in the set method. If more than one is found, an error will be reported. Constructor searches for beans through the parameter types in the constructor. If more than one is found, an error will be reported. Determined based on parameter name.

Automatic injection in XML is quite powerful. Why do we usually use the @Autowired annotation? What about not using the automatic injection method mentioned above?

The @Autowired annotation is equivalent to the replacement of the autowire attribute annotation in XML.

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

Essentially, the @Autowired annotation provides the same functionality as autowire, but with finer-grained control and wider applicability

  • Autowire in XML controls all attributes of the entire bean, while the @Autowired annotation is written directly on a certain attribute, a set method, or a constructor.

  • For another example, if a class has multiple constructors, then if you use XML's autowire=constructor, you cannot control which constructor is used, but you can use the @Autowired annotation to directly specify which constructor you want to use.

  • At the same time, using the @Autowired annotation, you can also control which properties you want to be automatically injected and which properties you don’t want. This is also fine-grained control.

  • But @Autowired cannot distinguish byType and byName. @Autowired first byType, if more than one is found, then byName .

Then the bottom layer of automatic injection of XML is actually:

  1. set method injection
  2. Constructor injection

Automatic injection of @Autowired annotation

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

The @Autowired annotation can be written at:

  1. Attributes: First find the bean based on the attribute type. If multiple are found, then determine one based on the attribute name.
  2. In the construction method: first find the bean based on the method parameter type. If multiple are found, then determine one based on the parameter name.
  3. On the set method: first find the bean based on the method parameter type. If multiple are found, then determine one based on the parameter name.

And this bottom layer is used:

  • Property injection
  • set method injection
  • Constructor injection

Find the injection point

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

  1. Traverse all attribute fields Field of the current class
  2. Check whether any of @Autowired, @Value, and @Inject exists on the field. If it exists, the field is considered an injection point.
  3. If the field is static, no injection is performed
  4. Get the value of the required attribute 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 Method
  7. Determine whether the current Method is a bridge method, and if so, find the original method
  8. Check whether any of @Autowired, @Value, and @Inject exists on the method. If it exists, the method is considered an injection point.
  9. If the method is static, no injection is performed
  10. Get the value of the required attribute 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 parent class will be traversed until there is no parent class.
  13. Finally, the currElements collection is encapsulated into an InjectionMetadata object as the injection point collection object for the current Bean and cached

Why are static fields or methods 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 both prototype beans. Assuming that Spring supports static fields for automatic injection, then call them twice now.

  1. UserService userService1 = context.getBean(“userService”)
  2. UserService userService2 = context.getBean(“userService”)

Ask at this time, what is the orderService value of userService1? Or is it the value it injects itself?

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

bridge 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/tacy/service/UserInterface<Lcom/tacy/service/OrderService;>;
// declaration: com/tacy/service/UserService implements com.tacy.service.UserInterface<com.tacy.service.OrderService>
public class com/tacy/service/UserService implements com/tacy/service/UserInterface {
    
    

  // compiled from: UserService.java

  @Lorg/springframework/stereotype/Component;()

  // access flags 0x2
  private Lcom/tacy/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/tacy/service/UserService; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  public setOrderService(Lcom/tacy/service/OrderService;)V
  @Lorg/springframework/beans/factory/annotation/Autowired;()
   L0
    LINENUMBER 19 L0
    ALOAD 0
    ALOAD 1
    PUTFIELD com/tacy/service/UserService.orderService : Lcom/tacy/service/OrderService;
   L1
    LINENUMBER 20 L1
    RETURN
   L2
    LOCALVARIABLE this Lcom/tacy/service/UserService; L0 L2 0
    LOCALVARIABLE orderService Lcom/tacy/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/tacy/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/tacy/service/OrderService
    INVOKEVIRTUAL com/tacy/service/UserService.setOrderService (Lcom/tacy/service/OrderService;)V
    RETURN
   L1
    LOCALVARIABLE this Lcom/tacy/service/UserService; L0 L1 0
    MAXSTACK = 2
    MAXLOCALS = 2
}

You can see that there are two setOrderService methods in the bytecode of UserSerivce:

  • public setOrderService(Lcom/tacy/service/OrderService;)V
  • public synthetic bridge setOrderService(Ljava/lang/Object;)V
    and all have @Autowired annotations.

Therefore, this situation needs to be handled in Spring. When traversing to the bridge method, the original method must be found.

injection point

Field injection

  1. Traverse all AutowiredFieldElement objects.
  2. Encapsulate the corresponding fields into DependencyDescriptor objects.
  3. Call the resolveDependency() method of BeanFactory, pass in the DependencyDescriptor object, perform a dependency search, 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, then the next time you create the bean, you can directly use the cached result object beanName to go to the BeanFactory to get that bean. Object, no need to search again
  5. Use reflection to assign the result object to the field.

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 into a DependencyDescriptor object
  4. Call the resolveDependency() method of BeanFactory, pass in the DependencyDescriptor object, perform a dependency search, and find the Bean object that matches 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, then the next time you create the bean, you can directly use the cached result object beanName to go to the BeanFactory to get that bean. Object, no need to search again
  6. Use reflection to pass all result objects found to the current method and execute it.

DefaultListableBeanFactory中的resolveDependency()

@Nullable
Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
		@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException;

This method means that when a dependency description (DependencyDescriptor) is passed in, the method will find the corresponding only Bean object from the BeanFactory based on the dependency description.

Let's analyze the specific implementation of the resolveDependency() method in DefaultListableBeanFactory and the specific flow chart.
Insert image description here

findAutowireCandidates() implementation

The corresponding execution flow chart of the underlying process of finding beanName based on type
Insert image description here
is:
Insert image description here

  1. Find the names of all beans of type type in the BeanFactory . Note that it is the name, not the Bean object , because we can judge whether it matches the current type based on the BeanDefinition without generating a Bean object.
  2. Find the object whose key is type in resolvableDependencies and add it to result.
  3. Traverse the beanName found based on type and determine whether the bean corresponding to the current beanName can be automatically injected.
  4. First judge the autowireCandidate attribute in the BeanDefinition corresponding to beanName. If it is false, it means that it cannot be used for automatic injection. If it is true, continue to judge.
  5. Determine whether the current type is generic. If it is generic, all beanNames in the container will be found. If this is the case, then in this step, the real type of the generic must be obtained, and then matched. If If the current beanName matches the real type corresponding to the current generic, then continue to judge.
  6. If the @Qualifier annotation exists on the current DependencyDescriptor, then it is necessary to determine whether the Qualifier is defined on the current beanName and whether it is equal to the Qualifier on the current DependencyDescriptor. If equal, it will match.
  7. After the above verification, the current beanName can become an injectable and added to the result.

About the implementation of generic injection in dependency injection

First of all, in Java reflection, there is a Type interface, which represents the type. The specific classification is:

  • raw types: that is, ordinary Class
  • parameterized types: corresponds to ParameterizedType interface, generic type
  • array types: corresponding to GenericArrayType, generic array
  • type variables: corresponds to the TypeVariable interface, representing type variables, which are defined generics, such as T, K
  • primitive types: basic types, int, boolean
public class TypeTest<T> {
    
    

    private int i;
    private Integer it;
    private int[] iarray;
    private List list;
    private List<String> slist;
    private List<T> tlist;
    private T t;
    private T[] tarray;

    public static void main(String[] args) throws NoSuchFieldException {
    
    

        test(TypeTest.class.getDeclaredField("i"));
        System.out.println("=======");
        test(TypeTest.class.getDeclaredField("it"));
        System.out.println("=======");
        test(TypeTest.class.getDeclaredField("iarray"));
        System.out.println("=======");
        test(TypeTest.class.getDeclaredField("list"));
        System.out.println("=======");
        test(TypeTest.class.getDeclaredField("slist"));
        System.out.println("=======");
        test(TypeTest.class.getDeclaredField("tlist"));
        System.out.println("=======");
        test(TypeTest.class.getDeclaredField("t"));
        System.out.println("=======");
        test(TypeTest.class.getDeclaredField("tarray"));

    }

    public static void test(Field field) {
    
    

        if (field.getType().isPrimitive()) {
    
    
            System.out.print(field.getName() + "是基本数据类型; ");
        } else {
    
    
            System.out.print(field.getName() + "不是基本数据类型; ");
        }

        if (field.getGenericType() instanceof ParameterizedType) {
    
    
            System.out.print(field.getName() + "是泛型类型; ");
        } else {
    
    
            System.out.print(field.getName() + "不是泛型类型; ");
        }

        if (field.getType().isArray()) {
    
    
            System.out.print(field.getName() + "是普通数组; ");
        } else {
    
    
            System.out.print(field.getName() + "不是普通数组; ");
        }

        if (field.getGenericType() instanceof GenericArrayType) {
    
    
            System.out.print(field.getName() + "是泛型数组; ");
        } else {
    
    
            System.out.print(field.getName() + "不是泛型数组; ");
        }

        if (field.getGenericType() instanceof TypeVariable) {
    
    
            System.out.println(field.getName() + "是泛型变量");
        } else {
    
    
            System.out.println(field.getName() + "不是泛型变量");
        }

    }

}

Insert image description here

In Spring, when the injection point is a generic type, it will also be processed, such as

@Component
public class UserService extends BaseService<OrderService, StockService> {
    
    
	public void test() {
    
    
		System.out.println(o);
	}
}

public class BaseService<O, S> {
    
    
	@Autowired
	protected O o;

	@Autowired
	protected S s;
}
  1. Spring scan found that UserService is a Bean
  2. Then take out the injection point, which is the two attributes o and s in BaseService.
  3. Next, you need to inject according to the injection point type, but o and s are both generic, so Spring needs to determine the specific types of o and s.
  4. Because the Bean of UserService is currently being created, specific generic information can be obtained through userService.getClass().getGenericSuperclass().getTypeName(), such as com.tacy.service.BaseService<com.tacy.service.OrderService , com.tacy.service.StockService>
  5. Then get the generic variable of BaseService, the parent class of UserService: for (TypeVariable<? extends Class<?>> typeParameter: userService.getClass().getSuperclass().getTypeParameters()) { System.out.println(typeParameter . getName()); }

  6. From the above two pieces of code, we can know that the specific type corresponding to o is OrderService, and the specific type corresponding to s is StockService.
  7. Then call oField.getGenericType() to know which generic type is used by the current field, and then you can know the specific type.

Use of @Qualifier

Define two annotations:

@Target({
    
    ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier("random")
public @interface Random {
    
    
}
@Target({
    
    ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier("roundRobin")
public @interface RoundRobin {
    
    
}

Define an interface and two implementation classes to represent load balancing

public interface LoadBalance {
    
    
	String select();
}
@Component
@Random
public class RandomStrategy implements LoadBalance {
    
    

	@Override
	public String select() {
    
    
		return null;
	}
}
@Component
@RoundRobin
public class RoundRobinStrategy implements LoadBalance {
    
    

	@Override
	public String select() {
    
    
		return null;
	}
}

use:

@Component
public class UserService  {
    
    

	@Autowired
	@RoundRobin
	private LoadBalance loadBalance;

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

}

@Resource

@Resource annotation underlying workflow diagram:
Insert image description here

Guess you like

Origin blog.csdn.net/beautybug1126/article/details/132374993