4 votes

Spring injecté beans null dans la classe imbriquée

Je possède une classe avec 2 classes imbriquées statiques qui effectuent la même opération sur 2 types génériques différents.

J'ai exposé les 2 classes en tant que beans et ajouté @Autowired pour les constructeurs comme je le fais habituellement.

Voici la configuration de base

classe abstraite  Parent implémente MyInterface {
   private final Service service;
   Parent(Service service){ this.service = service; }

   @Override public final void doInterfaceThing(T thing){
     T correctedT = map(thing);
     service.doTheThing(correctedT);
   }

   protected abstract T map(T t);

   @Service
   public static class ImplA extends Parent {
     @Autowired ImplA (Service service){ super(service); }
     A map(A a){ //map a }
   }

   @Service
   public static class ImplB extends Parent {
     @Autowired ImplB (Service service){ super(service); }
     B map(B b){ //map b }
   }

}

**

Et dans une autre classe j'ai

@Service
public class Doer {
   private final List> aImpls;
   @Autowired public Doer(List> aImpls){ this.aImpls = aImpls; }
   public void doImportantThingWithA(A a){
     aImpls.get(0).doInterfaceThing(a);
   }
}

Lorsque j'exécute l'application, tout semble être injecté correctement et lorsque je mets un point d'arrêt dans les constructeurs ImplA et ImplB, j'ai une valeur non nulle pour "service". J'ai également un bean ImplA dans la liste aImpls de Doer.

Cependant, lorsque j'appelle doImportantThingWithA(a), "service" est nul à l'intérieur de ImplA et je me retrouve évidemment bloqué.

Je ne suis pas sûr que cela soit possible car :

  1. Je vois une valeur non nulle dans mes constructeurs pour service qui est un champ final.
  2. Si Spring injecte ImplA et ImplB dans une autre classe, il devrait déjà avoir injecté un Service dans ImplA ou ImplB, ou jeté une exception lors de l'initialisation du bean. Je n'ai rien paramétré pour charger de manière paresseuse et toutes les dépendances de bean sont requises.

La raison des classes imbriquées est que la seule chose qui change entre les 2 implémentations est la fonction map(). J'essaie d'éviter des classes supplémentaires pour une ligne de code variable.

Plus d'informations : Lorsque j'ajoute un point d'arrêt dans Parent.doInterfaceThing(), si j'ajoute une montre sur "service", j'obtiens null comme valeur. Si j'ajoute une méthode getService() et j'appelle ensuite getService() au lieu de faire référence directement à this.service, j'obtiens le bean correct pour service. Je ne connais pas les implications de cela mais quelque chose semble étrange avec le proxying.

**

2voto

Tyler Helmuth Points 73

Il semble que ce qui cause le problème est Parent.doInterfaceThing();

Si je supprime final de la signature de la méthode, le champ "service" est correctement renseigné et le code fonctionne comme prévu.

Je ne comprends pas du tout pourquoi le changement de signature d'une méthode affecte la valeur injectée des champs finals dans ma classe... mais cela fonctionne maintenant.

0voto

daniu Points 8648

Ce que je voulais dire avec mon commentaire "utiliser des mappers" était quelque chose comme ceci :

class MyInterfaceImpl implements MyInterface {
   @Autowired
   private final Service service;

   @Override public final  void doInterfaceThing(T thing, UnaryOperator mapper){
     T correctedT = mapper.apply(thing);
     service.doTheThing(correctedT);
   }

   // nouvelle interface pour permettre l'autowiring malgré l'effacement des types
   public interface MapperA extends UnaryOperator {
     public A map(A toMap);
     default A apply(A a){ map(a); }
   }
   @Component
   static class AMapper implements MapperA {
       public A map(A a) { // ... }
   }

   public interface MapperB extends UnaryOperator {
     public B map(B toMap);
     default B apply(B b){ map(b); }
   }
   @Component
   static class BMapper implements MapperB {
       public B map(B a) { // ... }
   }
}

**Cela ajoute quelques lignes de plus par rapport à l'original, mais pas beaucoup ; cependant, vous avez une meilleure séparation des préoccupations. Je me demande comment fonctionne l'autowiring dans votre code avec les generics, il semble que cela pourrait poser problème.

Votre client ressemblerait à ceci :

@Service
public class Doer {
   private final List aMappers;
   private final MyInterface myInterface;
   @Autowired public Doer(MyInterface if, List mappers){ 
       this.myInterface = if;
       this.aImpls = mappers; }
   public void doImportantThingWithA(A a){
     aMappers.stream().map(m -> m.map(a)).forEach(myInterface::doInterfaceThing);
   }
}**

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X