Spring - Principe derrière la mise en œuvre de @Autowired

avant-propos

Lors de l'utilisation du développement de printemps, il existe deux façons principales de configurer, l'une est xml et l'autre est java config.

La technologie Spring elle-même est également en constante évolution et évolution. À en juger par la popularité actuelle de springboot, l'application de java config devient de plus en plus étendue. Dans le processus d'utilisation de java config, nous aurons inévitablement une variété d'annotations, l'annotation la plus utilisée devrait être l'annotation @Autowired. La fonction de cette annotation est de nous injecter un bean défini.

Alors, quelles autres méthodes d'utilisation existe-t-il autres que les méthodes d'injection de propriétés couramment utilisées que cette annotation supprime ? Comment est-il implémenté au niveau du code ? C'est la question sur laquelle cet article se concentre.

1. Utilisation des annotations @Autowired

Avant d'analyser le principe d'implémentation de cette annotation, passons en revue l'utilisation de l'annotation @Autowired.

Appliquez l'annotation @Autowired au constructeur comme indiqué dans l'exemple suivant

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

Appliquer l'annotation @Autowired à la méthode setter

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

Appliquer l'annotation @Autowired à la méthode avec un nom arbitraire et plusieurs paramètres

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

Vous pouvez également appliquer @Autowired aux champs ou le mélanger avec des constructeurs comme indiqué dans l'exemple suivant

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

L'application directe sur le terrain est celle que nous utilisons le plus, mais utiliser l'injection de constructeur à partir du niveau du code est préférable. De plus, il existe les moyens moins courants suivants

Ajoutez l'annotation @Autowired à un champ ou à une méthode qui nécessite un tableau de ce type, et spring recherchera dans ApplicationContext tous les beans correspondant au type spécifié, comme illustré dans l'exemple suivant :

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

Les tableaux sont OK, nous pouvons tirer des conclusions d'un cas immédiatement, les conteneurs peuvent-ils être OK aussi, la réponse est oui, voici des exemples d'ensemble et de carte :

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

Ce qui précède sont les principales façons d'utiliser l'annotation @Autowired. Si vous utilisez souvent le ressort, vous ne devriez pas être familier avec ceux couramment utilisés.

2. Quelle est la fonction de l'annotation @Autowired ?

Nous utilisons beaucoup l'annotation @Autowired. Maintenant, ce que je veux demander, c'est quel est son rôle ?

Tout d'abord, du point de vue de sa portée, en fait, cette annotation est une annotation qui appartient à la configuration de conteneur de spring, et les annotations qui appartiennent à la même configuration de conteneur incluent : @Required, @Primary, @Qualifier, etc. au. Ainsi, l'annotation @Autowired est une annotation pour la configuration du conteneur.

Deuxièmement, on peut le regarder littéralement, l'annotation @autowired vient du mot anglais autowire, qui signifie assemblage automatique. Que signifie le câblage automatique ? Le sens original du mot fait référence à une certaine utilisation industrielle de machines pour remplacer la population, pour effectuer automatiquement certaines tâches d'assemblage qui doivent être effectuées ou pour effectuer d'autres tâches. Dans le monde du printemps, le câblage automatique fait référence à l'utilisation du bean dans le conteneur Spring pour assembler automatiquement la classe dont nous avons besoin.

Par conséquent, la définition personnelle de l'auteur de la fonction de cette annotation est la suivante : le bean dans le conteneur Spring est automatiquement assemblé et utilisé avec la classe dont nous avons besoin pour ce bean.

Voyons ensuite ce qui se cache derrière cette annotation.

3. Comment l'annotation @Autowired est implémentée

En fait, pour répondre à cette question, vous devez d'abord comprendre comment Java prend en charge une fonctionnalité telle que les annotations.

La technologie de base de l'implémentation des annotations de Java est la réflexion, comprenons comment cela fonctionne à travers quelques exemples et implémentons nous-mêmes une annotation.

Par exemple l'annotation @Override

L'annotation @Override est définie comme suit :

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

L'annotation @Override utilise les annotations officiellement fournies par Java, et il n'y a pas de logique d'implémentation dans sa définition. Notez que presque toutes les annotations sont comme ça, les annotations ne peuvent être vues que comme des métadonnées, elles ne contiennent aucune logique métier.  Une annotation ressemble plus à une étiquette, une déclaration selon laquelle l'endroit où elle est annotée aura une logique spécifique.

Ensuite, la question vient l'une après l'autre, l'annotation elle-même ne contient aucune logique, alors comment la fonction de l'annotation est-elle réalisée ? La réponse doit être que l'annotation est implémentée ailleurs. En prenant l'annotation @Override comme exemple, sa fonction est de réécrire une méthode, et son implémenteur est la JVM, la machine virtuelle java, et la machine virtuelle java implémente cette fonction au niveau du bytecode.

Mais pour le développeur, la mise en œuvre de la machine virtuelle est hors de contrôle et ne peut pas être utilisée pour des annotations personnalisées. Par conséquent, si nous voulons définir nous-mêmes une annotation unique, nous devons écrire une logique d'implémentation pour l'annotation, en d'autres termes, nous devons implémenter nous-mêmes la fonction d'annotation d'une logique spécifique.

Implémentez vous-même une annotation

Avant d'écrire nous-mêmes des annotations, nous avons quelques connaissances de base à maîtriser, c'est-à-dire que la fonction d'écriture d'annotations nécessite d'abord la prise en charge de Java. Java prend en charge cette fonction dans jdk5 et java.lang.annotationfournit quatre annotations dans le package, qui ne sont utilisées que pour Utilisé lors de l'écriture d'annotations , elles sont:

image

Commençons à implémenter une annotation par nous-mêmes, l'annotation ne prend en charge que  primitivesstringet  enumerationsces trois types. Tous les attributs d'une annotation sont définis comme des méthodes et des valeurs par défaut peuvent également être fournies. Commençons par implémenter l'annotation la plus 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();
}

 Une seule passe de caractères est définie dans le commentaire ci-dessus, son objet de commentaire cible est une méthode et la stratégie de rétention est pendant l'exécution. Ci-dessous, nous définissons une méthode pour utiliser cette annotation :

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

Nous avons utilisé cette annotation ici et attribué la chaîne à : testStringValue, ici, définissez une annotation et utilisez-la, et nous avons terminé.

Je ne peux tout simplement pas y croire. Cependant, si vous y réfléchissez bien, bien que nous ayons écrit un commentaire et que nous l'ayons utilisé, cela n'a eu aucun effet. Cela n'a eu aucun effet sur notre approche. Oui, c'est bien le cas maintenant, à cause du point que nous avons mentionné précédemment, nous n'avons pas implémenté sa logique pour cette annotation, et maintenant nous allons implémenter la logique pour cette annotation.

Que dois-je faire? Réfléchissons-y par nous-mêmes. Tout d'abord, je souhaite implémenter des fonctions pour les méthodes ou les champs marqués de cette annotation. Nous devons savoir quelles méthodes et quels champs utilisent cette annotation. Par conséquent, il est facile de penser que la réflexion doit être utilisée ici.

Deuxièmement, en utilisant la réflexion, après avoir utilisé la réflexion pour obtenir une telle cible, nous devons lui implémenter une logique.Cette logique est une logique extérieure à la logique de ces méthodes elles-mêmes, qui nous rappelle des connaissances telles que agent et aop. à ces méthodes. En fait, la logique de réalisation du prêt principal est probablement cette façon de penser. Résumez les étapes générales comme suit :

  • Utiliser le mécanisme de réflexion pour obtenir l'objet Class d'une classe

  • Grâce à cet objet de classe, vous pouvez obtenir chacune de ses méthodes, ou champs, etc.

  • Method, Field et d'autres classes fournissent des méthodes similaires à getAnnotation pour obtenir toutes les annotations d'un champ

  • Après avoir obtenu l'annotation, nous pouvons juger si l'annotation est l'annotation que nous voulons implémenter, et si c'est le cas, implémenter la logique d'annotation

Maintenant implémentons cette logique, le code est le suivant :

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 logique que nous implémentons ici est d'imprimer quelques mots. À partir de la logique d'implémentation ci-dessus, nous ne pouvons pas trouver qu'avec l'aide de la réflexion Java, nous pouvons obtenir directement toutes les méthodes d'une classe, puis obtenir les annotations sur les méthodes.Bien sûr, nous pouvons également obtenir les annotations sur les champs. Avec l'aide de la réflexion, nous pouvons obtenir presque tout ce qui appartient à une classe.

Une simple annotation et le tour est joué. Revenons maintenant en arrière et voyons comment l'annotation @Autowired est implémentée.

4. L'annotation @Autowired implémente l'analyse logique

Connaissant les connaissances ci-dessus, il ne nous est pas difficile de penser que les annotations ci-dessus sont simples, mais la plus grande différence entre @Autowired et lui ne devrait être que la logique de mise en œuvre des annotations. D'autres étapes telles que l'utilisation de la réflexion pour obtenir des annotations doivent être cohérentes . Voyons comment l'annotation @Autowired est définie dans le code source de spring, comme suit :

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

En lisant le code, nous pouvons voir que l'annotation Autowired peut être appliquée aux cinq types de constructeurs, méthodes ordinaires, paramètres, champs et annotations, et sa politique de rétention est à l'exécution. Ci-dessous, ne parlons pas de l'implémentation logique de cette annotation directement par spring.

Dans le code source Spring, l'annotation Autowired se trouve dans org.springframework.beans.factory.annotationle package, et le contenu du package est le suivant :

image

 Après analyse, il n'est pas difficile de constater que la logique d'implémentation de l'annotation autowire de Spring se situe dans la classe : AutowiredAnnotationBeanPostProcessorqui a été marquée en rouge ci-dessus. Le code de traitement de base est le suivant :

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

Le blogueur a ajouté des commentaires au code source, et combiné avec les commentaires, vous pouvez comprendre ce qu'il fait.Enfin, cette méthode renvoie une collection InjectionMetadata qui contient toutes les annotations autowire. Cette classe se compose de deux parties :

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

L'un est la classe cible que nous traitons, et l'autre est l'ensemble des éléments obtenus par la méthode ci-dessus.

Avec la classe cible et tous les éléments à injecter, on peut implémenter la logique d'injection de dépendances d'autowired, la méthode est la suivante :

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

La méthode qu'il appelle est la méthode inject définie dans InjectionMetadata, comme suit

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 logique consiste à parcourir puis à appeler la méthode inject. La logique d'implémentation de la méthode inject est la suivante :

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

Dans le code ici, nous pouvons également voir que inject utilise également la technologie de réflexion et est toujours divisé en champs et méthodes de traitement. Dans le code, des méthodes telles que makeAccessible, qui peuvent être appelées fissuration par force brute, sont également appelées, mais la technologie de réflexion est conçue pour les frameworks et à d'autres fins, ce qui est compréhensible.

Pour les champs, il s'agit essentiellement de définir la valeur du champ, c'est-à-dire d'instancier et d'affecter des objets, comme le code suivant :

@Autowired
ObjectTest objectTest;

Alors ce qui est implémenté ici revient à attribuer une valeur à cette référence objectTest.

Pour une méthode, l'essence est d'appeler la méthode, donc method.invoke est appelé ici.

Le paramètre de la méthode getResourceToInject est le nom du bean à injecter, la fonction de cette méthode est de l'obtenir en fonction du nom du bean.

Ce qui précède est l'analyse complète de la logique de mise en œuvre de l'annotation @Autowire. Si vous regardez à nouveau le code source, ce sera plus clair. Voici un schéma de la façon dont le conteneur à ressort implémente le processus d'injection automatique @AutoWired :

image

 Pour résumer en une phrase : le bean injecté à l'aide de @Autowired est une variable membre ordinaire en termes de structure de code pour la classe cible. @Autowired et Spring travaillent ensemble pour attribuer une valeur à cette variable membre par réflexion, c'est-à-dire l'attribuer est l'instance de classe souhaitée.

5. Problèmes

5.1. Quelle est la durée de validité des annotations ?

La première différence majeure entre les différentes annotations est de savoir si elles sont utilisées au moment de la compilation puis supprimées (comme @Override), ou placées dans le fichier de classe compilé et disponibles au moment de l'exécution (comme Spring's @Component ). Ceci est déterminé par la politique "@Retention" de l'annotation. Si vous écrivez votre propre annotation, vous devez décider si l'annotation est utile au moment de l'exécution (peut-être pour la configuration automatique) ou uniquement au moment de la compilation (pour l'inspection ou la génération de code).

Lors de la compilation de code avec des annotations, le compilateur voit l'annotation de la même manière qu'il voit les autres modificateurs sur l'élément source, tels que les modificateurs d'accès (public/privé) ou .. Lorsqu'une annotation est rencontrée, il exécute un processeur d'annotation, comme une classe de plug-in, qui exprime son intérêt pour une annotation particulière. Les processeurs d'annotation utilisent généralement l'API de réflexion pour inspecter les éléments en cours de compilation et peuvent simplement effectuer des inspections sur eux, les modifier ou générer un nouveau code à compiler.

@Override est un exemple ; il utilise l'API de réflexion pour s'assurer qu'une correspondance pour la signature de la méthode peut être trouvée dans l'une des superclasses, et si ce n'est pas le cas, l'utilisation de @Override entraînera une erreur de compilation.

5.2. Comment la relation entre la fève injectée et la fève qui l'utilise est-elle maintenue ?

Quelle que soit la manière dont il est injecté, le bean injecté équivaut à une application d'objet commun dans la classe, qui est instanciée par spring pour trouver un bean correspondant dans le conteneur et l'injecter dans la classe. La relation entre eux est une relation ordinaire dans laquelle un objet contient une référence à un autre objet. C'est juste que ces objets sont tous des haricots au printemps.

5.3. Pourquoi le bean injecté ne peut-il pas être défini comme statique ?

Du point de vue de la conception, l'utilisation de champs statiques encourage l'utilisation de méthodes statiques. Les méthodes statiques sont mauvaises. Le but principal de l'injection de dépendances est de laisser le conteneur créer des objets et de les connecter pour vous. De plus, cela facilite les tests.

Une fois que vous commencez à utiliser des méthodes statiques, vous n'avez plus besoin de créer une instance de l'objet et les tests deviennent plus difficiles. De plus, vous ne pouvez pas créer plusieurs instances d'une classe donnée, chacune injectant une dépendance différente (car le champ est partagé implicitement et l'état global est créé).

Les variables statiques ne sont pas des propriétés d'Objet, mais des propriétés de Classe. L'autowire de Spring se fait sur des objets, ce qui donne un design épuré. Au printemps, nous pouvons également définir les objets bean comme des singletons, de sorte que le même objectif que les définitions statiques puisse être atteint de manière fonctionnelle.

Mais d'un niveau purement technique, nous pouvons faire ceci :

En utilisant @Autowired avec des méthodes setter, vous pouvez ensuite demander au setter de modifier la valeur d'un champ statique. Mais cette pratique est fortement déconseillée.

Je suppose que tu aimes

Origine blog.csdn.net/qq_34272760/article/details/121239541
conseillé
Classement