@Do you know the principle behind the implementation of Autowired

  1. home page
  2. special column
  3. java
  4. Article details
0

@Do you know the principle behind the implementation of Autowired

Official account IT brother Published 16 minutes ago

preface

When developing with spring, there are two main ways to configure, one is xml, the other is java config.

spring technology itself is also constantly developing and changing. From the current popularity of springboot, the application of Java config is more and more widely. In the process of using java config, we will inevitably deal with all kinds of annotations. Among them, the annotation we use most should be @ Autowired annotation. The function of this annotation is to inject a defined bean for us.

So, in addition to our common attribute injection methods, what other ways do we use this annotation? How is it implemented at the code level? This is the problem that this article focuses on.

@Autowired annotation usage

Before analyzing the implementation principle of this annotation, we might as well review the usage of @ 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 the @ Autowired annotation to the setter method

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

Apply the @ Autowired annotation to a method with any 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 a field or mix it with a constructor, 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;
    }
 
    // ...
}

Direct application to fields is the most commonly used method, but it is better to use constructor injection from the code level. In addition, there are several less common ways

If you add the @ Autowired annotation to a field or method that requires an array of this type, spring will search ApplicationContext for all bean s 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 instance immediately. Is the container OK? 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 is the main usage of @ Autowired annotation. If you often use spring, you should not be unfamiliar with several of them.

@What is the function of Autowired annotation

@We often use Autowired annotation. Now, what I want to ask is, what is its function?

First of all, from the perspective of its scope, in fact, this annotation is an annotation belonging to the container configuration of spring. The annotations belonging to the same container configuration include: @ Required,@Primary, @Qualifier, etc. Therefore, the @ Autowired annotation is an annotation for container configuration.

Secondly, from the literal meaning, the @ autowired annotation comes from the English word autowire, which means automatic assembly. What does automatic assembly mean? The original meaning of this word means that some industrial machines replace the population and automatically complete some assembly tasks or other tasks that need to be completed. In the spring world, automatic assembly refers to automatically assembling the bean in the spring container with the class we need this bean.

Therefore, the author's personal definition of this annotation is to automatically assemble and use the beans in the Spring container together with the classes we need this bean.

Next, let's take a look at what is done behind this annotation.

@How is the Autowired annotation implemented

In fact, to answer this question, we must first understand how java supports annotation.

The core technology of java annotation implementation is reflection. Let's understand its working principle through some examples and implementing an annotation ourselves.

For example, annotation @ Override

@The Override annotation is defined as follows:

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

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

Then, the problem comes one after another. The annotation itself does not contain any logic, so how to realize the function of annotation? 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 and java virtual machine, which implements this function at the bytecode level.

However, for developers, the implementation of virtual machines is beyond their control and cannot be used for custom annotations. Therefore, if we want to define a unique annotation, we need to write an implementation logic for the annotation. In other words, we need to implement the function of annotating a specific logic.

Implement an annotation yourself

We need to master some basic knowledge before writing annotations. That is, we need java to support the function of writing annotations. Java supports this function in jdk5, and provides four annotations in the java.lang.annotation package, which are only used when writing annotations. They are:

Now let's start implementing an annotation ourselves. Annotations only support   primitives,   string and   enumerations these three types. All attributes of the annotation are defined as methods, or default values can 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 transfer is defined in the above annotation. Its target annotation object is a method, and the retention policy is during operation. Let's define a method to use this annotation:

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

We use this annotation here and assign the string to: testStringValue. Here, define an annotation and use it, and we have completed it all.

Simply can't believe it. However, if you think about it carefully, although we wrote an annotation and used it, it didn't produce any effect. It doesn't have any effect on our methods. Yes, it is. The reason is that we have not implemented the logic for this annotation. Now let's implement the logic for this annotation.

What should I do? We might as well think about it ourselves. First of all, I want to implement the function for the method or field marked with this annotation. We must know which methods and fields use this annotation. Therefore, it is easy for us to think that reflection should be used here.

Secondly, using reflection, after we use reflection to achieve such a goal, we have to implement a logic for him. This logic is the logic outside the logic of these methods, which reminds us of knowledge such as agent and aop. We are equivalent to strengthening these methods. In fact, the logic of realizing the main borrowing is probably this idea. The general steps are as follows:

  • Get the Class object of a Class by using the reflection mechanism
  • Through this class object, you can get every method, method, Field, etc
  • Method, Field and other classes provide methods similar to getAnnotation to obtain all annotations of this Field
  • After getting the annotation, we can judge whether the annotation is the annotation we want to implement. If so, we can 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 sentences. From the above implementation logic, we can't find that with the help of java reflection, we can directly get all the methods in a class, and then get the annotation on the method. Of course, we can also get the annotation on the field. With the help of reflection, we can get almost anything belonging to a class.

We're done with a simple annotation. Now let's go back and see how the @ Autowired annotation is implemented.

@Logical analysis of Autowired annotation

Knowing the above knowledge, it is not difficult for us to think that although the above annotation is simple, the biggest difference between @ Autowired and him should only lie in the implementation logic of annotation, and other steps such as obtaining annotation by reflection should be consistent. Let's take a look at the definition of @ Autowired annotation in spring source code, 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;
}

After reading the code, we can see that Autowired annotation can be applied to five types: construction method, general method, parameter, field and annotation. Its retention policy is at run time. Now, let's not talk more, let's take a direct look at spring's logical implementation of this annotation

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

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

private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
  LinkedList<InjectionMetadata.InjectedElement> elements = new LinkedList<>();
  Class<?> targetClass = clazz;//Target class to be processed
       
  do {
   final LinkedList<InjectionMetadata.InjectedElement> currElements = new LinkedList<>();
 
            /*Get all the fields of this class through reflection, and traverse each field, and traverse the annotation used for each field through the method findautowired annotation. If it is decorated with autowired, return the auotowired related attribute*/  
 
   ReflectionUtils.doWithLocalFields(targetClass, field -> {
    AnnotationAttributes ann = findAutowiredAnnotation(field);
    if (ann != null) {//Verify whether the autowired annotation is used on the static method
     if (Modifier.isStatic(field.getModifiers())) {
      if (logger.isWarnEnabled()) {
       logger.warn("Autowired annotation is not supported on static fields: " + field);
      }
      return;
     }//Determine whether required is specified
     boolean required = determineRequiredStatus(ann);
     currElements.add(new AutowiredFieldElement(field, required));
    }
   });
            //The same logic as above, but the method of processing the class through reflection
   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));
    }
   });
    //There may be more than one annotation decorated with @ Autowired, so they are all added to the currElements container and processed together   
   elements.addAll(0, currElements);
   targetClass = targetClass.getSuperclass();
  }
  while (targetClass != null && targetClass != Object.class);
 
  return new InjectionMetadata(clazz, elements);
 }

The blogger adds comments to the source code, and you can understand what it does in combination with comments. Finally, this method returns an InjectionMetadata collection containing all the annotations decorated with autowire. This class consists of two parts:

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

The first is the target class we process, and the second is the collection of all elements obtained by the above method.

With the target class and all the elements to be injected, we can implement the dependency injection logic of autowired. The implementation methods are 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);
   }
  }
 }

Its logic is traversing and then invoking 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. makeAccessible is also called in the code, which can be called brute force cracking, but the reflection technology is designed for the purpose of framework and so on.

For a field, it is essentially to set the value of the field, that is, instantiate and assign values to the object. For example, the following code:

@Autowired
ObjectTest objectTest;

Then the implementation here is equivalent to assigning a value to the objecTest reference.

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

The parameter of 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 all the analysis of the implementation logic of @ Autowire annotation. If you look at it again in combination with the source code, it will be clearer. The following is a diagram of how the spring container implements @ AutoWired automatic injection:

To sum up, the bean injected with @ Autowired is an ordinary member variable for the target class in terms of code structure. @ Autowired works with spring to assign a value to this member variable through reflection, that is, assign it to the desired class instance.

problem

What is the validity period of an annotation?

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

When compiling code with comments, the compiler sees the comments just like other modifiers on the source element, such as the access modifier (public/private) or. When an annotation is encountered, it runs an annotation processor, like a plug-in class, indicating interest in a particular annotation. Annotation processors typically use reflection API s to check the elements being compiled, and can simply check them, modify them, or generate new code to compile.

@Override is an example; It uses the reflection API to ensure that a method signature match can be found in one of the superclasses. If not, using @ override will lead to compilation errors.

How is the relationship between the injected bean and the bean using it maintained?

No matter how to inject, the injected bean is equivalent to a common object application in the class. This is its instantiation. Spring instantiates the bean in the container and injects it into the class. The relationship between them is that one object holds a reference to another object. These objects are just beans in spring.

Why can't injected bean s be defined as static?

From a design perspective, using static fields encourages the use of static methods. The static method is evil. The main purpose of dependency injection is to let the container create and connect objects for you. Moreover, it makes testing easier.

Once you start using static methods, you no longer need to create instances of objects, and testing becomes more difficult. Similarly, you cannot create multiple instances of a given class, each injecting a different dependency (because the field is implicitly shared and creates a global state).

Static variables are not Object attributes, but Class attributes. Spring's autowire is completed on the Object, which makes the design very clean.   In spring, we can also define bean objects as singletons, so that we can achieve the same purpose as static definition in function.

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

Use @ Autowired with the setter method, and then let the setter modify the value of the static field. However, this practice is not recommended.

Read 23 updated 14 minutes ago
Like collection
882 reputation
1.1k fans
Focus on the author
Submit comments
You know what?

Register login
882 reputation
1.1k fans
Focus on the author
Article catalog
follow
Billboard

Tags: Java Spring

Posted on Thu, 28 Oct 2021 20:45:50 -0400 by jawaidpk