web-dev-qa-db-fra.com

Le printemps peut-il @Autowired Map?

Voici la carte

@Autowired
private Map<String, ISendableConverter> converters;

et ISendableConverter

public interface ISendableConverter {

    ISendableMsg convert(BaseMessage baseMessage);

    String getType();
}

Il existe des classes qui implémentent ISendableConverter

Je veux les injecter dans la variable converters en utilisant l'annotation spring @Autowried.

L'instance de classe comme valeur et le résultat de la méthode getType() comme clé.

comme celui-ci

@Component
public class SendableVoiceMsgConverter implements ISendableConverter {

    @Override
    public ISendableMsg convert(BaseMessage baseMessage) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public String getType() {
        return "VOICE";
    }
}

Est-ce possible? et comment?

27
Joe

Essayez avec quelque chose comme @ Resource - Je n'ai pas testé ce code.

@Resource(name="converters")
private Map<String, ISendableConverter> converters;

ou

@Value("#{converters}")
private Map<String, ISendableConverter> converters;

De Spring Docs

(..) les beans qui sont eux-mêmes définis comme un type de collection ou de carte ne peuvent pas être injectés via @Autowired, car la correspondance de type ne leur est pas correctement applicable. Utilisez @ Resource pour ces beans, en faisant référence à la collection spécifique ou au bean de carte par nom unique.

Cela devrait fonctionner, seulement si vous préparez un bean converters comme ceci:

<util:map id="converters" scope="prototype" map-class="Java.util.HashMap" 
          key-type="Java.lang.String" value-type="...ISendableConverter">
    <entry key="VOICE" value="sendableVoiceMsgConverter" />
</util:map>

C'est aussi une question similaire:

15
MariuszS

vous pouvez faire quelque chose comme ça:

@SuppressWarnings("unused")
private List<OneTypeImageLoader> imageLoaders;
private Map<String, OneTypeImageLoader> imageLoaderMap=new HashMap<>();

@Autowired
public void setImageLoaders(List<OneTypeImageLoader> imageLoaders) {
    this.imageLoaders = imageLoaders;
    imageLoaders.forEach(l-> {
        imageLoaderMap.put(l.getType(), l);
    });
}
10
TimYi

Vous pouvez créer une carte automatiquement initialisée avec les clés de votre choix en utilisant Spring Java:

En classe annoté avec @Configuration annotation:

@Autowired
private List<ISendableConverter> sendableConverters;

@Bean
public Map<String, ISendableConverter> sendableConvertersMap() {
    Map<String, ISendableConverter> sendableConvertersMap = new HashMap<>();
    for (ISendableConverter converter : sendableConverters) {
        sendableConvertersMap.put(converter.getType(), converter);
    }
    return sendableConvertersMap;
}

Que vous injectez cette carte avec:

@Resource()
private Map<String, ISendableConverter> sendableConverters;

Vous pouvez éventuellement ajouter une chaîne de sélection à votre @Resource annotation si vous avez défini plusieurs cartes du même type.

De cette façon, tout ce que vous avez à faire est d'implémenter ISendableConverter par votre bean Spring et il apparaîtra automatiquement dans la carte définie ci-dessus. Vous n'avez pas besoin de créer des éléments de carte à la main pour chaque implémentation.

9
Ondrej Bozek

Essayez quelque chose comme ça, ça marche pour moi

private Map<String, ISendableConverter> converters;

@Autowired
public void setUploadServices(Set<ISendableConverter> converters){
    this.conveters = converters.stream()
        .collect(Collectors.toMap(ISendableConverter::getType, Function.identity()));
}

Le même résultat peut être obtenu en utilisant l'injection de constructeur:

private Map<String, ISendableConverter> converters;

@Autowired
public Foo(Set<ISendableConverter> converters) {
    this.conveters = converters.stream()
        .collect(Collectors.toMap(ISendableConverter::getType, Function.identity()));
}
8
Sergii Bishyr

Vous pouvez facilement câbler automatiquement les beans de la même interface qu'une carte. La clé est le nom du bean. Donc, si vous nommez les composants eux-mêmes (c'est-à-dire avec la valeur finale statique, vous pouvez également l'utiliser dans la méthode), cela devrait déjà faire l'affaire.

0
Manuel Polacek
@Component("VOICE")
public class SendableVoiceMsgConverter implements ISendableConverter {

    @Override
    public ISendableMsg convert(BaseMessage baseMessage) {
        // TODO Auto-generated method stub
        return null;
    }
}

Vous souhaiterez peut-être simplement ajouter le nom du type directement dans l'annotation du composant, qui fera le travail.

0
Rigeborod

Vous pouvez le rendre plus générique et créer quelque chose comme ceci:

    public interface BasicStrategy {
        String getKey();
    }

    public final class StrategyMap<K extends BasicStrategy> extends HashMap<String, K> {

    public StrategyMap(List<K> strategies) {
        super(strategies.stream().collect(Collectors.toMap(K::getKey, Function.identity())));
    }

    @Override
    public K get(Object key) {
        BasicStrategy basicStrategy = super.get(key);
        if (basicStrategy == null) {
            throw new RuntimeException("No strategy found for key: '" + key + "'");
        }
        return (K) basicStrategy;
    }
}

Vous pouvez maintenant utiliser ce StrategyMap partout dans votre code comme ceci:

private StrategyMap<ISendableConverter> converters;

@Autowired
public Foo(List<ISendableConverter> converters) {
    this.conveters = new StrategyMap<>(converters);
}

Cette approche génère la création d'un StrategyMap et aussi la logique pour le cas où la valeur n'est pas trouvée peut être centralisée.

PS: Bien sûr, ISendableConverter doit étendre l'interface BasicStrategy.

0
Borys Rudenko