Spring - Principle behind the implementation of @Autowired

foreword

When using spring development, there are two main ways to configure, one is xml, and the other is java config.

Spring technology itself is also constantly developing and changing. Judging from the current popularity of springboot, the application of java config is becoming more and more extensive. In the process of using java config, we will inevitably have a variety of When dealing with annotations, the most used annotation should be the @Autowired annotation. The function of this annotation is to inject a defined bean for us.

So, what other usage methods are there other than the commonly used property injection methods that this annotation removes? How is it implemented at the code level? This is the question that this article focuses on.

1. @Autowired annotation usage

Before analyzing the implementation principle of this annotation, let's review the usage of the @Autowired annotation.

Apply the @Autowired annotation to the constructor as shown in the following example

public class MovieRecommender {
 
    private final CustomerPreferenceDao customerPreferenceDao;
 
    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }
 
    // ...
}

Apply @Autowired annotation to setter method

public class SimpleMovieLister {
 
    private MovieFinder movieFinder;
 
    @Autowired
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
 
    // ...
}

Apply @Autowired annotation to method with arbitrary name and multiple parameters

public class MovieRecommender {
 
    private MovieCatalog movieCatalog;
 
    private CustomerPreferenceDao customerPreferenceDao;
 
    @Autowired
    public void prepare(MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }
 
    // ...
}

You can also apply @Autowired to fields, or mix it with constructors as shown in the following example

public class MovieRecommender {
 
    private final CustomerPreferenceDao customerPreferenceDao;
 
    @Autowired
    private MovieCatalog movieCatalog;
 
    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }
 
    // ...
}

Applying directly to the field is the one we use the most, but using constructor injection from the code level is better. In addition, there are the following less common ways

Add the @Autowired annotation to a field or method that requires an array of that type, and spring will search the ApplicationContext for all beans that match the specified type, as shown in the following example:

public class MovieRecommender {
 
    @Autowired
    private MovieCatalog[] movieCatalogs;
 
    // ...
}

Arrays are OK, we can draw inferences from one case immediately, can containers be OK too, the answer is yes, the following are examples of set and map:

public class MovieRecommender {
 
    private Set<MovieCatalog> movieCatalogs;
 
    @Autowired
    public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }
 
    // ...
}
public class MovieRecommender {
 
    private Map<String, MovieCatalog> movieCatalogs;
 
    @Autowired
    public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }
 
    // ...
}

The above are the main ways of using the @Autowired annotation. If you use spring often, you should not be unfamiliar with the commonly used ones.

2. What is the function of the @Autowired annotation?

We use the @Autowired annotation a lot. Now, what I want to ask is, what is its role?

First of all, from the perspective of its scope, in fact, this annotation is an annotation that belongs to the container configuration of spring, and the annotations that belong to the same container configuration include: @Required, @Primary, @Qualifier and so on. So the @Autowired annotation is an annotation for container configuration.

Secondly, we can look at it literally, the @autowired annotation comes from the English word autowire, which means automatic assembly. What does autowiring mean? The original meaning of the word refers to some industrial use of machines to replace the population, to automatically complete some assembly tasks that need to be completed, or to complete other tasks. In the spring world, autowiring refers to using the bean in the Spring container to automatically assemble the class we need the bean together.

Therefore, the author's personal definition of the function of this annotation is: the bean in the Spring container is automatically assembled and used together with the class we need this bean.

Next, let's take a look at what's going on behind this annotation.

3. How the @Autowired annotation is implemented

In fact, to answer this question, you must first understand how Java supports such a feature as annotations.

The core technology of Java's annotation implementation is reflection, let's understand how it works through some examples and implementing an annotation ourselves.

For example the annotation @Override

The @Override annotation is defined as follows:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

The @Override annotation uses the annotations officially provided by java, and there is no implementation logic in its definition. Note that almost all annotations are like this, annotations can only be seen as metadata, it does not contain any business logic.  An annotation is more like a label, a declaration that the place where it is annotated will have some specific logic.

Then, the question comes one after another, the annotation itself does not contain any logic, so how is the function of the annotation realized? The answer must be that the annotation is implemented somewhere else. Taking the @Override annotation as an example, its function is to rewrite a method, and its implementer is the JVM, the java virtual machine, and the java virtual machine implements this function at the bytecode level.

But for the developer, the implementation of the virtual machine is something out of control and cannot be used for custom annotations. Therefore, if we want to define a unique annotation by ourselves, we need to write an implementation logic for the annotation, in other words, we need to implement the function of annotating specific logic by ourselves.

Implement an annotation yourself

Before writing annotations ourselves, we have some basic knowledge to master, that is, the function of writing annotations requires java support first. Java supports this function in jdk5, and java.lang.annotationprovides four annotations in the package, which are only used for Used when writing annotations, they are:

picture

Let's start to implement an annotation by ourselves, the annotation only supports  primitivesstringand  enumerationsthese three types. All attributes of an annotation are defined as methods, and default values ​​can also be provided. Let's implement the simplest annotation first.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SimpleAnnotation {
    String value();
}

 Only one character pass is defined in the above comment, its target comment object is a method, and the retention policy is during runtime. Below we define a method to use this annotation:

public class UseAnnotation {
 
    @SimpleAnnotation("testStringValue")
    public void testMethod(){
        //do something here
    }
 
}

We used this annotation here and assigned the string to: testStringValue, here, define an annotation and use it, and we are all done.

Simply can't believe it. However, if you think about it carefully, although we wrote a comment and used it, it did not have any effect. It didn't have any effect on our approach. Yes, it is indeed the case now, because of the point we mentioned earlier, we have not implemented its logic for this annotation, and now we will implement the logic for this annotation.

What should I do? Let's think about it for ourselves. First of all, I want to implement functions for the methods or fields marked with this annotation. We must know which methods and fields use this annotation. Therefore, it is easy to think that reflection should be used here.

Secondly, using reflection, after we use reflection to get such a target, we have to implement a logic for him. This logic is logic outside the logic of these methods themselves, which reminds us of knowledge such as agent and aop. Make an enhancement to these methods. In fact, the logic of realizing the main loan is probably this way of thinking. Summarize the general steps as follows:

  • Use reflection mechanism to obtain the Class object of a class

  • Through this class object, you can get each of his methods, or fields, etc.

  • Method, Field and other classes provide methods similar to getAnnotation to get all annotations of a field

  • After getting the annotation, we can judge whether the annotation is the annotation we want to implement, and if so, implement the annotation logic

Now let's implement this logic, the code is as follows:

private static void annotationLogic() {

     Class useAnnotationClass = UseAnnotation.class;
     for(Method method : useAnnotationClass.getMethods()) {
         SimpleAnnotation simpleAnnotation = (SimpleAnnotation)method.getAnnotation(SimpleAnnotation.class);
         if(simpleAnnotation != null) {
             System.out.println(" Method Name : " + method.getName());
             System.out.println(" value : " + simpleAnnotation.value());
             System.out.println(" --------------------------- ");
         }
     }
 }

The logic we implement here is to print a few words. From the above implementation logic, we cannot find that with the help of java reflection, we can directly get all the methods in a class, and then get the annotations on the methods. Of course, we can also get the annotations on the fields. With the help of reflection we can get almost anything that belongs to a class.

A simple annotation and we are done. Now let's go back and see how the @Autowired annotation is implemented.

4. @Autowired annotation implements logical analysis

Knowing the above knowledge, it is not difficult for us to think that the above annotations are simple, but the biggest difference between @Autowired and him should only be the implementation logic of annotations. Other steps such as using reflection to obtain annotations should be consistent. Let's take a look at how the @Autowired annotation is defined in the source code of spring, as follows:

package org.springframework.beans.factory.annotation;
 
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    boolean required() default true;
}

Reading the code, we can see that the Autowired annotation can be applied to the five types of constructors, ordinary methods, parameters, fields, and annotations, and its retention policy is at runtime. Below, let's not talk about the logical implementation of this annotation directly by spring.

In the Spring source code, the Autowired annotation is located in org.springframework.beans.factory.annotationthe package, and the contents of the package are as follows:

picture

 After analysis, it is not difficult to find that the implementation logic of Spring's autowire annotation is located in the class: AutowiredAnnotationBeanPostProcessorwhich has been marked in red above. The core processing code is as follows:

private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
  LinkedList<InjectionMetadata.InjectedElement> elements = new LinkedList<>();
  Class<?> targetClass = clazz;//需要处理的目标类
       
  do {
   final LinkedList<InjectionMetadata.InjectedElement> currElements = new LinkedList<>();
 
            /*通过反射获取该类所有的字段,并遍历每一个字段,并通过方法findAutowiredAnnotation遍历每一个字段的所用注解,并如果用autowired修饰了,则返回auotowired相关属性*/  
 
   ReflectionUtils.doWithLocalFields(targetClass, field -> {
    AnnotationAttributes ann = findAutowiredAnnotation(field);
    if (ann != null) {//校验autowired注解是否用在了static方法上
     if (Modifier.isStatic(field.getModifiers())) {
      if (logger.isWarnEnabled()) {
       logger.warn("Autowired annotation is not supported on static fields: " + field);
      }
      return;
     }//判断是否指定了required
     boolean required = determineRequiredStatus(ann);
     currElements.add(new AutowiredFieldElement(field, required));
    }
   });
            //和上面一样的逻辑,但是是通过反射处理类的method
   ReflectionUtils.doWithLocalMethods(targetClass, method -> {
    Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
    if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
     return;
    }
    AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod);
    if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
     if (Modifier.isStatic(method.getModifiers())) {
      if (logger.isWarnEnabled()) {
       logger.warn("Autowired annotation is not supported on static methods: " + method);
      }
      return;
     }
     if (method.getParameterCount() == 0) {
      if (logger.isWarnEnabled()) {
       logger.warn("Autowired annotation should only be used on methods with parameters: " +
         method);
      }
     }
     boolean required = determineRequiredStatus(ann);
     PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
                   currElements.add(new AutowiredMethodElement(method, required, pd));
    }
   });
    //用@Autowired修饰的注解可能不止一个,因此都加在currElements这个容器里面,一起处理  
   elements.addAll(0, currElements);
   targetClass = targetClass.getSuperclass();
  }
  while (targetClass != null && targetClass != Object.class);
 
  return new InjectionMetadata(clazz, elements);
 }

The blogger added comments to the source code, and combined with the comments, you can understand what it does. Finally, this method returns an InjectionMetadata collection that contains all the autowire annotations. This class consists of two parts:

public InjectionMetadata(Class<?> targetClass, Collection<InjectedElement> elements) {
  this.targetClass = targetClass;
  this.injectedElements = elements;
 }

One is the target class we process, and the other is the set of elements obtained by the above method.

With the target class and all the elements that need to be injected, we can implement the dependency injection logic of autowired. The method is as follows:

@Override
public PropertyValues postProcessPropertyValues(
  PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {

 InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
 try {
  metadata.inject(bean, beanName, pvs);
 }
 catch (BeanCreationException ex) {
  throw ex;
 }
 catch (Throwable ex) {
  throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
 }
 return pvs;
}

The method it calls is the inject method defined in InjectionMetadata, as follows

public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
  Collection<InjectedElement> checkedElements = this.checkedElements;
  Collection<InjectedElement> elementsToIterate =
    (checkedElements != null ? checkedElements : this.injectedElements);
  if (!elementsToIterate.isEmpty()) {
   for (InjectedElement element : elementsToIterate) {
    if (logger.isTraceEnabled()) {
     logger.trace("Processing injected element of bean '" + beanName + "': " + element);
    }
    element.inject(target, beanName, pvs);
   }
  }
 }

The logic is to traverse and then call the inject method. The implementation logic of the inject method is as follows:

/**
 * Either this or {@link #getResourceToInject} needs to be overridden.
 */
protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs)
  throws Throwable {

 if (this.isField) {
  Field field = (Field) this.member;
  ReflectionUtils.makeAccessible(field);
  field.set(target, getResourceToInject(target, requestingBeanName));
 }
 else {
  if (checkPropertySkipping(pvs)) {
   return;
  }
  try {
   Method method = (Method) this.member;
   ReflectionUtils.makeAccessible(method);
   method.invoke(target, getResourceToInject(target, requestingBeanName));
  }
  catch (InvocationTargetException ex) {
   throw ex.getTargetException();
  }
 }
}

In the code here, we can also see that inject also uses reflection technology and is still divided into fields and methods for processing. In the code, methods such as makeAccessible, which can be called brute force cracking, are also called, but reflection technology is designed for frameworks and other purposes, which is understandable.

For fields, it is essentially to set the value of the field, that is, to instantiate and assign objects, such as the following code:

@Autowired
ObjectTest objectTest;

Then what is implemented here is equivalent to assigning a value to this objectTest reference.

For a method, the essence is to call the method, so method.invoke is called here.

The parameter of the getResourceToInject method is the name of the bean to be injected. The function of this method is to get it according to the name of the bean.

The above is the whole analysis of the implementation logic of the @Autowire annotation. If you look at the source code again, it will be more clear. The following is a diagram of how the spring container implements the process of @AutoWired automatic injection:

picture

 To sum up in one sentence: the bean injected using @Autowired is an ordinary member variable in terms of code structure for the target class. @Autowired and spring work together to assign a value to this member variable through reflection, that is, assign it to it. is the desired class instance.

5. Problems

5.1. What is the validity period of annotations?

The first major difference between the various annotations is whether they are used at compile time and then discarded (like @Override), or placed in the compiled class file and available at runtime (like Spring's @Component ). This is determined by the annotation's "@Retention" policy. If you are writing your own annotation, you need to decide whether the annotation is useful at runtime (perhaps for auto-configuration) or only at compile time (for inspection or code generation).

When compiling code with annotations, the compiler sees the annotation just like it sees other modifiers on the source element, such as access modifiers (public/private) or .. When an annotation is encountered, it runs an annotation processor, like a plugin class, that expresses interest in a particular annotation. Annotation processors typically use the reflection API to inspect elements being compiled, and can simply perform inspections on them, modify them, or generate new code to compile.

@Override is an example; it uses the reflection API to ensure that a match for the method signature can be found in one of the superclasses, and if it can't, using @Override will result in a compile error.

5.2. How is the relationship between the injected bean and the bean that uses it maintained?

No matter how it is injected, the injected bean is equivalent to a common object application in the class, which is instantiated by spring to find a matching bean in the container and inject it into the class. The relationship between them is an ordinary relationship in which an object holds a reference to another object. It's just that these objects are all beans in spring.

5.3. Why can't the injected bean be defined as static?

From a design perspective, using static fields encourages the use of static methods. Static methods are evil. The main purpose of dependency injection is to let the container create objects and wire them up for you. Also, it makes testing easier.

Once you start using static methods, you no longer need to create an instance of the object, and testing becomes more difficult. Also, you can't create multiple instances of a given class, each injecting a different dependency (as the field is shared implicitly and global state is created).

Static variables are not properties of Object, but properties of Class. Spring's autowire is done on objects, which makes for a clean design. In spring, we can also define bean objects as singletons, so that the same purpose as static definitions can be achieved functionally.

But from a purely technical level, we can do this:

Using @Autowired with setter methods, you can then have the setter modify the value of a static field. But this practice is highly not recommended.

Guess you like

Origin blog.csdn.net/qq_34272760/article/details/121239541