Spring - Principio detrás de la implementación de @Autowired

prefacio

Cuando se usa Spring Development, hay dos formas principales de configurar, una es xml y la otra es Java config.

La tecnología Spring en sí misma también está en constante desarrollo y cambio. A juzgar por la popularidad actual de Springboot, la aplicación de la configuración de Java se está volviendo cada vez más extensa. En el proceso de usar la configuración de Java, inevitablemente tendremos una variedad de Cuando se trata de anotaciones, la anotación más utilizada debe ser la anotación @Autowired. La función de esta anotación es inyectar un bean definido para nosotros.

Entonces, ¿qué otros métodos de uso hay además de los métodos de inyección de propiedad de uso común que elimina esta anotación? ¿Cómo se implementa a nivel de código? Esta es la pregunta en la que se centra este artículo.

1. Uso de anotaciones @Autowired

Antes de analizar el principio de implementación de esta anotación, revisemos el uso de la anotación @Autowired.

Aplique la anotación @Autowired al constructor como se muestra en el siguiente ejemplo

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

Aplicar la anotación @Autowired al método setter

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

Aplique la anotación @Autowired al método con un nombre arbitrario y múltiples parámetros

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

También puede aplicar @Autowired a los campos o combinarlo con constructores como se muestra en el siguiente ejemplo

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

La aplicación directa al campo es la que más usamos, pero es mejor usar la inyección del constructor desde el nivel del código. Además, existen las siguientes formas menos comunes

Agregue la anotación @Autowired a un campo o método que requiera una matriz de ese tipo, y Spring buscará en ApplicationContext todos los beans que coincidan con el tipo especificado, como se muestra en el siguiente ejemplo:

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

Las matrices están bien, podemos sacar inferencias de un caso de inmediato, los contenedores también pueden estar bien, la respuesta es sí, los siguientes son ejemplos de conjunto y mapa:

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;
    }
 
    // ...
}

Las anteriores son las principales formas de usar la anotación @Autowired. Si usa Spring con frecuencia, no debería estar familiarizado con los que se usan comúnmente.

2. ¿Cuál es la función de la anotación @Autowired?

Usamos mucho la anotación @Autowired.Ahora, lo que quiero preguntar es, ¿cuál es su función?

En primer lugar, desde la perspectiva de su alcance, de hecho, esta anotación es una anotación que pertenece a la configuración del contenedor de Spring, y las anotaciones que pertenecen a la misma configuración del contenedor incluyen: @Required, @Primary, @Qualifier, etc. en. Entonces, la anotación @Autowired es una anotación para la configuración del contenedor.

En segundo lugar, podemos verlo literalmente, la anotación @autowired proviene de la palabra inglesa autowire, que significa ensamblaje automático. ¿Qué significa autocableado? El significado original de la palabra se refiere a algún uso industrial de las máquinas para reemplazar a la población, para completar automáticamente algunas tareas de ensamblaje que deben completarse o para completar otras tareas. En el mundo de Spring, el cableado automático se refiere al uso del bean en el contenedor de Spring para ensamblar automáticamente la clase que necesitamos para unir el bean.

Por lo tanto, la definición personal del autor de la función de esta anotación es: el bean en el contenedor Spring se ensambla automáticamente y se usa junto con la clase que necesitamos este bean.

A continuación, echemos un vistazo a lo que sucede detrás de esta anotación.

3. Cómo se implementa la anotación @Autowired

De hecho, para responder a esta pregunta, primero debe comprender cómo Java admite una función como las anotaciones.

La tecnología central de la implementación de anotaciones de Java es la reflexión, comprendamos cómo funciona a través de algunos ejemplos e implementando una anotación nosotros mismos.

Por ejemplo, la anotación @Override

La anotación @Override se define de la siguiente manera:

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

La anotación @Override usa las anotaciones proporcionadas oficialmente por java y no hay una lógica de implementación en su definición. Tenga en cuenta que casi todas las anotaciones son así, las anotaciones solo se pueden ver como metadatos, no contienen ninguna lógica comercial.  Una anotación es más como una etiqueta, una declaración de que el lugar donde se anota tendrá una lógica específica.

Entonces, la pregunta viene una tras otra, la anotación en sí misma no contiene ninguna lógica, entonces, ¿cómo se realiza la función de la anotación? La respuesta debe ser que la anotación se implementa en otro lugar. Tomando como ejemplo la anotación @Override, su función es reescribir un método, y su implementador es la JVM, la máquina virtual java, y la máquina virtual java implementa esta función a nivel de bytecode.

Pero para el desarrollador, la implementación de la máquina virtual es algo fuera de control y no se puede usar para anotaciones personalizadas. Por lo tanto, si queremos definir una anotación única por nosotros mismos, debemos escribir una lógica de implementación para la anotación, en otras palabras, debemos implementar la función de anotar una lógica específica por nosotros mismos.

Implemente una anotación usted mismo

Antes de escribir anotaciones nosotros mismos, debemos dominar algunos conocimientos básicos, es decir, la función de escribir anotaciones requiere primero el soporte de Java.Java admite esta función en jdk5 y java.lang.annotationproporciona cuatro anotaciones en el paquete, que solo se usan para usar al escribir anotaciones. , ellos son:

imagen

Comencemos a implementar una anotación por nosotros mismos, la anotación solo admite  primitivesstringenumerationsestos tres tipos. Todos los atributos de una anotación se definen como métodos y también se pueden proporcionar valores predeterminados. Implementemos primero la anotación más simple.

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();
}

 Solo se define un pase de carácter en el comentario anterior, su objeto de comentario de destino es un método y la política de retención es durante el tiempo de ejecución. A continuación definimos un método para usar esta anotación:

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

Usamos esta anotación aquí y asignamos la cadena a: testStringValue, aquí, defina una anotación y úsela, y ya hemos terminado.

Simplemente no puedo creerlo. Sin embargo, si lo piensa detenidamente, aunque escribimos un comentario y lo usamos, no tuvo ningún efecto. No tuvo ningún efecto en nuestro enfoque. Sí, de hecho es el caso ahora, debido al punto que mencionamos anteriormente, no hemos implementado su lógica para esta anotación, y ahora implementaremos la lógica para esta anotación.

¿Qué tengo que hacer? Pensemos en ello por nosotros mismos. En primer lugar, quiero implementar funciones para los métodos o campos marcados con esta anotación. Debemos saber qué métodos y campos usan esta anotación. Por lo tanto, es fácil pensar que aquí se debe usar la reflexión.

En segundo lugar, al usar la reflexión, después de usar la reflexión para obtener ese objetivo, tenemos que implementar una lógica para él. Esta lógica es lógica fuera de la lógica de estos métodos mismos, lo que nos recuerda el conocimiento como agente y aop. Hacer una mejora a estos métodos. De hecho, la lógica de realizar el préstamo principal es probablemente esta forma de pensar. Resumir los pasos generales de la siguiente manera:

  • Use el mecanismo de reflexión para obtener el objeto Class de una clase

  • A través de este objeto de clase, puede obtener cada uno de sus métodos, campos, etc.

  • Method, Field y otras clases proporcionan métodos similares a getAnnotation para obtener todas las anotaciones de un campo.

  • Después de obtener la anotación, podemos juzgar si la anotación es la anotación que queremos implementar y, de ser así, implementar la lógica de la anotación.

Ahora implementemos esta lógica, el código es el siguiente:

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(" --------------------------- ");
         }
     }
 }

La lógica que implementamos aquí es imprimir algunas palabras. A partir de la lógica de implementación anterior, no podemos encontrar que con la ayuda de la reflexión de Java, podemos obtener directamente todos los métodos en una clase y luego obtener las anotaciones en los métodos. Por supuesto, también podemos obtener las anotaciones en los campos. Con la ayuda de la reflexión podemos obtener casi cualquier cosa que pertenezca a una clase.

Una simple anotación y listo. Ahora regresemos y veamos cómo se implementa la anotación @Autowired.

4. La anotación @Autowired implementa el análisis lógico

Conociendo el conocimiento anterior, no es difícil para nosotros pensar que las anotaciones anteriores son simples, pero la mayor diferencia entre @Autowired y él debería ser solo la lógica de implementación de las anotaciones. Otros pasos, como usar la reflexión para obtener anotaciones, deben ser consistentes. . Echemos un vistazo a cómo se define la anotación @Autowired en el código fuente de Spring, de la siguiente manera:

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;
}

Al leer el código, podemos ver que la anotación Autowired se puede aplicar a los cinco tipos de constructores, métodos ordinarios, parámetros, campos y anotaciones, y su política de retención es en tiempo de ejecución. A continuación, no hablemos de la implementación lógica de esta anotación directamente por primavera.

En el código fuente de Spring, la anotación Autowired se encuentra en org.springframework.beans.factory.annotationel paquete y el contenido del paquete es el siguiente:

imagen

 Después del análisis, no es difícil encontrar que la lógica de implementación de la anotación automática de Spring se encuentra en la clase: AutowiredAnnotationBeanPostProcessorque se ha marcado en rojo arriba. El código de procesamiento central es el siguiente:

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);
 }

El blogger agregó comentarios al código fuente y, combinados con los comentarios, puede comprender lo que hace.Finalmente, este método devuelve una colección de InjectionMetadata que contiene todas las anotaciones de autowire. Esta clase consta de dos partes:

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

Uno es la clase objetivo que procesamos y el otro es el conjunto de elementos obtenidos por el método anterior.

Con la clase objetivo y todos los elementos que necesitan ser inyectados, podemos implementar la lógica de inyección de dependencia de autowired, el método es el siguiente:

@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;
}

El método al que llama es el método de inyección definido en InjectionMetadata, de la siguiente manera

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);
   }
  }
 }

La lógica es atravesar y luego llamar al método de inyección.La lógica de implementación del método de inyección es la siguiente:

/**
 * 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();
  }
 }
}

En el código aquí, también podemos ver que inyectar también usa tecnología de reflexión y todavía se divide en campos y métodos para el procesamiento. En el código, también se llaman métodos como makeAccessible, que se puede llamar descifrado de fuerza bruta, pero la tecnología de reflexión está diseñada para marcos y otros propósitos, lo cual es comprensible.

Para los campos, es esencialmente establecer el valor del campo, es decir, instanciar y asignar objetos, como el siguiente código:

@Autowired
ObjectTest objectTest;

Entonces, lo que se implementa aquí es equivalente a asignar un valor a esta referencia objectTest.

Para un método, la esencia es llamar al método, por lo que aquí se llama a method.invoke.

El parámetro del método getResourceToInject es el nombre del bean a inyectar, la función de este método es obtenerlo según el nombre del bean.

Lo anterior es el análisis completo de la lógica de implementación de la anotación @Autowire. Si vuelve a mirar el código fuente, será más claro. El siguiente es un diagrama de cómo el contenedor de resorte implementa el proceso de inyección automática @AutoWired:

imagen

 Para resumir en una oración: el bean inyectado usando @Autowired es una variable miembro ordinaria en términos de estructura de código para la clase de destino. @Autowired y Spring trabajan juntos para asignar un valor a esta variable miembro a través de la reflexión, es decir, asignarlo es la instancia de clase deseada.

5. Problemas

5.1 ¿Cuál es el período de validez de las anotaciones?

La primera gran diferencia entre las diversas anotaciones es si se usan en tiempo de compilación y luego se descartan (como @Override), o si se colocan en el archivo de clase compilado y están disponibles en tiempo de ejecución (como @Component de Spring). Esto está determinado por la política "@Retention" de la anotación. Si está escribiendo su propia anotación, debe decidir si la anotación es útil en tiempo de ejecución (quizás para la configuración automática) o solo en tiempo de compilación (para inspección o generación de código).

Al compilar código con anotaciones, el compilador ve la anotación tal como ve otros modificadores en el elemento fuente, como modificadores de acceso (público/privado) o .. Cuando se encuentra una anotación, ejecuta un procesador de anotaciones, como una clase de complemento, que expresa interés en una anotación en particular. Los procesadores de anotaciones suelen utilizar la API de reflexión para inspeccionar los elementos que se compilan y pueden simplemente realizar inspecciones en ellos, modificarlos o generar código nuevo para compilar.

@Override es un ejemplo; utiliza la API de reflexión para garantizar que se pueda encontrar una coincidencia para la firma del método en una de las superclases y, si no es así, el uso de @Override generará un error de compilación.

5.2 ¿Cómo se mantiene la relación entre el grano inyectado y el grano que lo utiliza?

No importa cómo se inyecte, el bean inyectado es equivalente a una aplicación de objeto común en la clase, que Spring crea una instancia para encontrar un bean coincidente en el contenedor e inyectarlo en la clase. La relación entre ellos es una relación ordinaria en la que un objeto tiene una referencia a otro objeto. Es solo que estos objetos son todos frijoles en primavera.

5.3 ¿Por qué el bean inyectado no se puede definir como estático?

Desde una perspectiva de diseño, el uso de campos estáticos fomenta el uso de métodos estáticos. Los métodos estáticos son malos. El objetivo principal de la inyección de dependencia es permitir que el contenedor cree objetos y los conecte por usted. Además, facilita las pruebas.

Una vez que comience a usar métodos estáticos, ya no necesitará crear una instancia del objeto y las pruebas se volverán más difíciles. Además, no puede crear varias instancias de una clase dada, cada una inyectando una dependencia diferente (ya que el campo se comparte implícitamente y se crea un estado global).

Las variables estáticas no son propiedades de Objeto, sino propiedades de Clase. El cableado automático de Spring se realiza en objetos, lo que contribuye a un diseño limpio. En primavera, también podemos definir objetos de frijol como singletons, de modo que se pueda lograr funcionalmente el mismo propósito que las definiciones estáticas.

Pero desde un nivel puramente técnico, podemos hacer esto:

Usando @Autowired con métodos setter, puede hacer que el setter modifique el valor de un campo estático. Pero esta práctica no es muy recomendable.

Supongo que te gusta

Origin blog.csdn.net/qq_34272760/article/details/121239541
Recomendado
Clasificación