it-swarm.com.de

Wie füge ich einen Service dynamisch mit einer Laufzeitvariablen "Qualifier" ein?

Ich kann keine einfache Möglichkeit finden, eine Komponente/einen Dienst mit einem vorgegebenen Laufzeitwert zu injizieren.

Ich habe angefangen, das Dokument von @ Spring zu lesen: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-autowired-annotation-qualifiers but Ich kann dort nicht finden, wie die an die @ Qualifier-Annotation übergebenen Werte variiert werden können.

Nehmen wir an, ich habe eine Modellentität mit einer solchen Schnittstelle:

public interface Case {

    String getCountryCode();
    void setCountryCode(String countryCode);

}

In meinem Client-Code würde ich Folgendes tun:

@Inject
DoService does;

(...)

Case myCase = new CaseImpl(); // ...or whatever
myCase.setCountryCode("uk");

does.whateverWith(myCase);

... mit meinem Service als:

@Service
public class DoService {

    @Inject
    // FIXME what kind of #[email protected]& symbol can I use here?
    // Seems like SpEL is sadly invalid here :(
    @Qualifier("${caze.countryCode}")
    private CaseService caseService;

    public void whateverWith(Case caze) {
        caseService.modify(caze);
    }

}

Ich erwarte, dass der caseService der UKCaseService ist (siehe zugehörigen Code unten).

public interface CaseService {

    void modify(Case caze);

}

@Service
@Qualifier("uk")
public class UKCaseService implements CaseService {

}

@Service
@Qualifier("us")
public class USCaseService implements CaseService {

}

Wie "repariere" ich all dies auf die einfachste/eleganteste/effizienteste Art und Weise, indem ich eines oder alle der Spring-Features verwende, also im Wesentlichen KEINE .Eigenschaften, KEIN XML, nur Anmerkungen. Ich vermute jedoch bereits, dass etwas in meinem DoService nicht stimmt, da Spring den "Fall" vor dem Injizieren des caseService kennen müsste ... aber wie kann dies erreicht werden, ohne dass der Client-Code etwas über den caseService weiß ?! Ich kann das nicht herausfinden ...

Ich habe bereits mehrere Ausgaben hier auf SO gelesen, aber meistens haben sie entweder nicht die gleichen Anforderungen und/oder Einstellungen wie ich oder die veröffentlichten Antworten sind mir nicht zufriedenstellend genug (sie scheinen im Wesentlichen zu sein) Problemumgehungen oder (alte) Verwendung von (alten) Spring-Funktionen.

Wie wird Spring automatisch mit Namen verbunden, wenn mehr als eine passende Bean gefunden wird? => bezieht sich nur auf komponentenähnliche Klassen

Dynamische Definition, welche Bohne im Frühjahr automatisch verkabelt werden soll (mithilfe von Qualifikationsmerkmalen => Wirklich interessant, aber die am besten ausgearbeitete Antwort (4 Stimmen) ist ... fast 3 1/2 Jahre alt ?! (Juli 2013)

Frühjahr 3 - Dynamisches Autowiring zur Laufzeit basierend auf einem anderen Objektattribut => ziemlich ähnliches Problem hier, aber die Antwort sieht wirklich wie ein Workaround aus, eher wie ein reales Entwurfsmuster (wie Factory)? und ich mag es nicht, den gesamten Code in den ServiceImpl zu implementieren, wie es getan ist ...

Spring @Autowiring, wie man eine Objektfactory verwendet, um die Implementierung auszuwählen? => Die zweite Antwort scheint interessant zu sein, aber ihr Autor wird nicht erweitert, obwohl ich (ein bisschen) über Java Config & Zeug, ich bin mir nicht sicher, wovon er spricht ...

Wie man verschiedene Dienste zur Laufzeit basierend auf einer Eigenschaft mit Spring ohne XML einfügt => interessante Diskussion, insb. die antwort, aber der benutzer hat eigenschaften gesetzt, die ich nicht habe.

Lesen Sie auch Folgendes: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/expressions.html#expressions-bean-references => Ich kann nicht Hier finden Sie erweiterte Beispiele zur Verwendung von "@" in Ausdrücken. Weiß jemand davon?

Bearbeiten: Gefunden andere verwandte-ähnliche Probleme, niemand bekam eine richtige Antwort: Wie man @Autowired verwendet, um die Implementierung wie ein Factory-Muster dynamisch einzufügenSpring Qualifier und Eigenschafts-PlatzhalterFrühling: Verwenden von @Qualifier mit EigenschaftsplatzhalterWie wird im Frühling eine bedingte automatische Verkabelung durchgeführt?Dynamische Injektion im FrühlingSpEL in @ Qualifier beziehen sich auf das selbe BeanWie benutzt man SpEL, um das Ergebnis eines Methodenaufrufs im Frühling zu injizieren?

Factory Pattern könnte eine Lösung sein? Verwendung von @Autowired, um die Implementierung wie ein Factory-Pattern dynamisch einzufügen

18
maxxyme

Sie können Ihre Bean dynamisch mit einem BeanFactory aus dem Kontext mit dem Namen abrufen:

@Service
public class Doer {

  @Autowired BeanFactory beans;

  public void doSomething(Case case){
    CaseService service = beans.getBean(case.getCountryCode(), CaseService.class)
    service.doSomething(case);
  }
}

Eine Randnotiz. Etwas wie den Ländercode als Bohnennamen zu verwenden, sieht etwas seltsam aus. Fügen Sie mindestens ein Präfix hinzu oder ziehen Sie ein anderes Entwurfsmuster in Betracht.

Wenn Sie immer noch Bohne pro Land haben möchten, würde ich einen anderen Ansatz vorschlagen. Führen Sie einen Registrierungsdienst ein, um einen erforderlichen Dienst nach Ländercode zu erhalten:

@Service
public class CaseServices {

  private final Map<String, CaseService> servicesByCountryCode = new HashMap<>();

  @Autowired
  public CaseServices(List<CaseService> services){
    for (CaseService service: services){
      register(service.getCountryCode(), service);
    }
  }

  public void register(String countryCode, CaseService service) {
    this.servicesByCountryCode.put(countryCode, service);
  }

  public CaseService getCaseService(String countryCode){
    return this.servicesByCountryCode.get(countryCode);
  }
}

Anwendungsbeispiel:

@Service
public class DoService {

  @Autowired CaseServices caseServices;

  public void doSomethingWith(Case case){
    CaseService service = caseServices.getCaseService(case.getCountryCode());
    service.modify(case);
  }
}

In diesem Fall müssen Sie Ihrer CaseService Schnittstelle die String getCountryCode() Methode hinzufügen.

public interface CaseService {
    void modify(Case case);
    String getCountryCode();
}

Alternativ können Sie die Methode CaseService.supports(Case case) hinzufügen, um den Dienst auszuwählen. Wenn Sie die Schnittstelle nicht erweitern können, können Sie die Methode CaseServices.register(String, CaseService) von einem Initialisierer oder einer Klasse @Configuration Aufrufen.

UPDATE: Ich habe vergessen zu erwähnen, dass Spring bereits eine Nice Plugin - Abstraktion zur Wiederverwendung von Boilerplate-Code zur Erstellung von PluginRegistry bietet. so was.

Beispiel:

public interface CaseService extends Plugin<String>{
    void doSomething(Case case);
}

@Service
@Priority(0)
public class SwissCaseService implements CaseService {

  void doSomething(Case case){
    // Do something with the Swiss case
  }

  boolean supports(String countryCode){
    return countryCode.equals("CH");
  }
}

@Service
@Priority(Ordered.LOWEST_PRECEDENCE)
public class DefaultCaseService implements CaseService {

  void doSomething(Case case){
    // Do something with the case by-default
  }

  boolean supports(String countryCode){
    return true;
  }
}

@Service
public class CaseServices {

  private final PluginRegistry<CaseService<?>, String> registry;

  @Autowired
  public Cases(List<CaseService> services){
    this.registry = OrderAwarePluginRegistry.create(services);
  }

  public CaseService getCaseService(String countryCode){
    return registry.getPluginFor(countryCode);
  }
}
17
aux

Laut dieser SO Antwort mit @Qualifier wird Ihnen nicht viel helfen: Holen Sie sich Bean von ApplicationContext von Qualifier

Als alternative Strategie:

  • wenn Sie Spring Boot sind, können Sie @ConditonalOnProperty oder ein anderes Conditional.

  • ein Suchdienst, als @aux schlägt vor

  • benennen Sie Ihre Bohnen einfach einheitlich und suchen Sie sie zur Laufzeit nach Namen.

Beachten Sie, dass sich Ihr Anwendungsfall anscheinend auch um das Szenario dreht, in dem Beans beim Start der Anwendung erstellt werden, die ausgewählte Bean jedoch aufgelöst werden muss , nachdem der applicationContext vorliegt beendete das Injizieren der Bohnen.

4
ninj