web-dev-qa-db-de.com

Die Implementierung von Spring Choose Bean erfolgt zur Laufzeit

Ich verwende Spring Beans mit Anmerkungen und muss zur Laufzeit eine andere Implementierung auswählen.

@Service
public class MyService {
   public void test(){...}
}

Zum Beispiel für Windows-Plattform brauche ich MyServiceWin extending MyService, für die Linux-Plattform brauche ich MyServiceLnx extending MyService.

Im Moment kenne ich nur eine schreckliche Lösung:

@Service
public class MyService {

    private MyService impl;

   @PostInit
   public void init(){
        if(windows) impl=new MyServiceWin();
        else impl=new MyServiceLnx();
   }

   public void test(){
        impl.test();
   }
}

Bitte beachten Sie, dass ich nur Annotation und keine XML-Konfiguration verwende.

37
Tobia

Sie können die Bohneninjektion wie folgt in die Konfiguration verschieben:

@Configuration
public class AppConfig {

    @Bean
    public MyService getMyService() {
        if(windows) return new MyServiceWin();
        else return new MyServiceLnx();
    }
}

Alternativ können Sie die Profile windows und linux verwenden und dann Ihre Service-Implementierungen mit der Annotation @Profile Versehen, z. B. @Profile("linux") oder @Profile("windows"). und geben Sie eines dieser Profile für Ihre Anwendung an.

14
Stanislav

1. Implementieren Sie ein benutzerdefiniertes Condition

public class LinuxCondition implements Condition {
  @Override
  public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    return context.getEnvironment().getProperty("os.name").contains("Linux");  }
}

Gleiches gilt für Windows.

2. Verwenden Sie @Conditional in Ihrer Klasse Configuration

@Configuration
public class MyConfiguration {
   @Bean
   @Conditional(LinuxCondition.class)
   public MyService getMyLinuxService() {
      return new LinuxService();
   }

   @Bean
   @Conditional(WindowsCondition.class)
   public MyService getMyWindowsService() {
      return new WindowsService();
   }
}

3. Verwenden Sie @Autowired wie gewöhnlich

@Service
public class SomeOtherServiceUsingMyService {

    @Autowired    
    private MyService impl;

    // ... 
}
60
nobeh

Lass uns eine schöne Konfiguration erstellen.

Stellen Sie sich vor, wir haben tierische Schnittstelle und wir haben Hund und Cat Implementierung. Wir wollen schreiben schreiben:

@Autowired
Animal animal;

aber welche Implementierung sollten wir zurückgeben?

enter image description here

Was ist also eine Lösung? Es gibt viele Möglichkeiten, ein Problem zu lösen. Ich werde schreiben, wie @ Qualifier und Custom Conditions zusammen verwendet werden.

Als erstes erstellen wir eine benutzerdefinierte Anmerkung:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
public @interface AnimalType {
    String value() default "";
}

und config:

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class AnimalFactoryConfig {

    @Bean(name = "AnimalBean")
    @AnimalType("Dog")
    @Conditional(AnimalCondition.class)
    public Animal getDog() {
        return new Dog();
    }

    @Bean(name = "AnimalBean")
    @AnimalType("Cat")
    @Conditional(AnimalCondition.class)
    public Animal getCat() {
        return new Cat();
    }

}

Beachten Sie , dass unser Bohnenname AnimalBean ist. Warum brauchen wir diese Bean? Denn wenn wir Animal Interface injizieren, schreiben wir nur @ Qualifier ("AnimalBean") )

Außerdem haben wir eine benutzerdefinierte Annotation erstellt , um den Wert an unsere benutzerdefinierte Bedingung zu übergeben .

Jetzt sehen unsere Bedingungen so aus (stellen Sie sich vor, der Name "Dog" stammt aus der Konfigurationsdatei oder dem JVM-Parameter oder ...)

   public class AnimalCondition implements Condition {

    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        if (annotatedTypeMetadata.isAnnotated(AnimalType.class.getCanonicalName())){
           return annotatedTypeMetadata.getAnnotationAttributes(AnimalType.class.getCanonicalName())
                   .entrySet().stream().anyMatch(f -> f.getValue().equals("Dog"));
        }
        return false;
    }
}

und schließlich Injektion:

@Qualifier("AnimalBean")
@Autowired
Animal animal;
21
grep

Autowire alle Ihre Implementierungen in eine Fabrik mit @Qualifier Anmerkungen, und geben Sie dann die Serviceklasse ab Werk zurück, die Sie benötigen.

public class MyService {
    private void doStuff();
}

Mein Windows-Dienst:

@Service("myWindowsService")
public class MyWindowsService implements MyService {

    @Override
    private void doStuff() {
        //Windows specific stuff happens here.
    }
}

Mein Mac Service:

@Service("myMacService")
public class MyMacService implements MyService {

    @Override
    private void doStuff() {
        //Mac specific stuff happens here
    }
}

Meine fabrik:

@Component
public class MyFactory {
    @Autowired
    @Qualifier("myWindowsService")
    private MyService windowsService;

    @Autowired
    @Qualifier("myMacService")
    private MyService macService;

    public MyService getService(String serviceNeeded){
        //This logic is ugly
        if(serviceNeeded == "Windows"){
            return windowsService;
        } else {
            return macService;
        }
    }
}

Wenn Sie wirklich knifflig werden möchten, können Sie Ihre Implementierungsklassentypen mithilfe einer Aufzählung speichern und anschließend anhand des Aufzählungswerts auswählen, welche Implementierung Sie zurückgeben möchten.

public enum ServiceStore {
    MAC("myMacService", MyMacService.class),
    WINDOWS("myWindowsService", MyWindowsService.class);

    private String serviceName;
    private Class<?> clazz;

    private static final Map<Class<?>, ServiceStore> mapOfClassTypes = new HashMap<Class<?>, ServiceStore>();

    static {
        //This little bit of black magic, basically sets up your 
        //static map and allows you to get an enum value based on a classtype
        ServiceStore[] namesArray = ServiceStore.values();
        for(ServiceStore name : namesArray){
            mapOfClassTypes.put(name.getClassType, name);
        }
    }

    private ServiceStore(String serviceName, Class<?> clazz){
        this.serviceName = serviceName;
        this.clazz = clazz;
    }

    public String getServiceBeanName() {
        return serviceName;
    }

    public static <T> ServiceStore getOrdinalFromValue(Class<?> clazz) {
        return mapOfClassTypes.get(clazz);
    }
}

Dann kann Ihre Factory auf den Anwendungskontext tippen und Instanzen in ihre eigene Map ziehen. Wenn Sie eine neue Serviceklasse hinzufügen, fügen Sie der Aufzählung einfach einen weiteren Eintrag hinzu, und das ist alles, was Sie tun müssen.

 public class ServiceFactory implements ApplicationContextAware {

     private final Map<String, MyService> myServices = new Hashmap<String, MyService>();

     public MyService getInstance(Class<?> clazz) {
         return myServices.get(ServiceStore.getOrdinalFromValue(clazz).getServiceName());
     }

      public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
          myServices.putAll(applicationContext.getBeansofType(MyService.class));
      }
 }

Jetzt können Sie den gewünschten Klassentyp einfach an die Factory übergeben und erhalten die Instanz, die Sie benötigen. Sehr hilfreich, insbesondere wenn Sie die Dienste generisch gestalten möchten.

4
JamesENL