Für meine Anwendung habe ich eine Scale -Schnittstelle und mehrere Klassen, die diese Schnittstelle implementieren, z. B. NormalizedScale, LogScale usw. In einem meiner Dienste muss ich viele Skalen erstellen und Ich möchte Spring verwenden, um zu definieren, welche Implementierung der Skala erstellt werden soll. Wie würde ich so etwas implementieren?

-

Ich dachte daran, eine Factory ScaleFactory wie im Abstract Factory Pattern zu erstellen, die ich ScaleFactory.getScale() nennen könnte, um einen Maßstab für die Implementierung zu erhalten, die ich in Spring XML konfiguriert habe:

class ScaleFactory {
    Class<? extends Scale> scaleImplClass;
    public static Scale getScale() {
        return scaleImplClass.newInstance();
    }
}


Scale myScale = ScaleFactory.getScale();

Wie kann ich mit diesem Ansatz konfigurieren, welche Implementierung die ScaleFactory aus Spring XML verwenden soll?

-

Eine Alternative wäre, das ScaleFactory zu einem @Service zu machen und dann die ScaleFactory automatisch in meinen Dienst zu verdrahten:

@Autowired
ScaleFactory scaleFactory;

...

Scale myScale = scaleFactory.getScale();

Dann kann ich eine automatisch verdrahtete Eigenschaft in der ScaleFactory verwenden, um scaleImplClass zu definieren. Aber das scheint seltsam, weil meine Fabrik auch ein Service ist und ich eine Instanz dieser Fabrik habe.

-

Ein anderer Ansatz wäre, die Eigenschaft Class scaleImplementationClass in meinem Dienst anstelle der ScaleFacotry zu haben und die ScaleFactory wie folgt zu verwenden:

@Value("${scaleImplementationClass}")
Class scaleImplementationClass

...

Scale myScale = ScaleFactory.getScale(scaleImplementationClass);

Aber dann ist die Fabrik ziemlich sinnlos, weil ich genauso gut scaleImplementationClass.newInstance() ausführen könnte.

5
Benedikt Köppel 25 Dez. 2015 im 22:51

3 Antworten

Beste Antwort

Es gibt verschiedene federähnliche Möglichkeiten, wie Sie damit umgehen können. Der Ansatz, den ich persönlich gewählt habe, sieht ungefähr so aus:

public interface ScaleFactory {

    public Scale newInstance();
    public String type();

}

public class FirstScaleFactory implements ScaleFactory {

    public Scale newInstance() {
        return new FirstScale();
    }

    public String type() {
        return "first";
    }    

}

public class SecondScaleFactory implements ScaleFactory {

    public Scale newInstance() {
        return new SecondScale();
    }

    public String type() {
        return "second";
    }    

}

public class ScaleManager {

    private final Map<String, ScaleFactory> factories;

    @Autowired
    public ScaleManager(List<ScaleFactory> factories) {
        this.factories = factories.stream()
            .collect(Collectors.toMap(f -> f.type(), Function::identity));
    }

    public Scale newInstance(String type) {
        return Optional.ofNullable(factories.get(type))
            .map(factory -> factory.newInstance())
            .orElseThrow(IllegalArgumentException::new);
    }

}

Bei diesem Ansatz ist Ihr ScaleManager eine Standard-Spring-Bean, die mit jeder Klasse verbunden werden kann, die eine Skalierungsinstanz benötigt. Bei der Initialisierung werden alle ScaleFactories, die im Spring-Kontext definiert sind, abgerufen und automatisch als List<ScaleFactory> verdrahtet, der dann in ein Map konvertiert wird (wobei das {{X4}) } Typ ist der Schlüssel). Dies vermeidet, dass Sie sich um Klassennamen von Scale kümmern müssen, und gibt Ihnen die Möglichkeit, diese später zu ändern (solange Sie den Schlüssel type konsistent halten) ``

Ihre ScaleFactory Implementierungen können dann alles tun, was sie brauchen. Wenn Sie beispielsweise einen Typ von Scale haben, von dem Sie wissen, dass er unveränderlich ist, kann die Factory jedes Mal dieselbe Instanz zurückgeben. Alternativ kann jeder Aufruf eine separate Instanz zurückgeben - die Instanziierung von Scale hängt von der implementierungsabhängigen Factory ab.

5
Colin M 25 Dez. 2015 im 23:31

Sie können einfach "Qualifiers" verwenden, die im Grunde genommen auf eine bestimmte "benannte" Bean verweisen. Standardmäßig sind die Bean-Namen der Name Ihrer Klassen, wobei der erste Buchstabe in Kleinbuchstaben geschrieben ist (MyClass -> myClass). Wenn Sie Ihre eigenen Namen definieren möchten, haben Sie folgende Möglichkeiten:

@Service("customizedBeanName")

Sie würden am Ende so etwas tun:

@Autowired
@Qualifier("logScale")
private Scale logScale;

@Autowired
@Qualifier("anotherScale")
private Scale anotherScale;
0
Louis F. 25 Dez. 2015 im 20:02

Für Spring 5.x gibt es eine einfachere und sauberere Möglichkeit, dies zu tun. Ich habe mich für @ConditionalOnProperty Anmerkung, Sie können jedoch eine beliebige @ Bedingung * Ihrer Wahl auswählen.

Hier ist die Sache, die ich extrem vereinfacht habe:

public interface MyService {}

@Service
@ConditionalOnProperty(prefix = "myService", name = "Impl", havingValue = "Some")
public class SomeService implements MyService {}

@Service
@ConditionalOnProperty(prefix = "myService", name = "Impl", havingValue = "Foo")
public class FooService implements MyService {}

@Service
public class SimpleService {

  @Autowired
  SimpleService(MyService service) {
    // service instance will depend on configuration
  }
}

Ich verwende Springboot und habe mich daher für application.properties entschieden, um Werte über Umgebungsvariablen wie diese festzulegen:

myService.Impl=${MY_SERVICE_IMPL}

Dann habe ich eine volldynamische Injektion basierend auf Umgebungsvariablen, die beispielsweise an einen Docker-Container übergeben werden können.

0
Sebastian 31 Dez. 2019 im 18:15