Подтвердить что ты не робот

Spring выберите bean реализацию во время выполнения

Я использую Spring Beans с аннотациями, и мне нужно выбрать другую реализацию во время выполнения.

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

Например, для платформы Windows мне нужно MyServiceWin extending MyService, для платформы linux мне нужно MyServiceLnx extending MyService.

Пока я знаю только одно ужасное решение:

@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();
   }
}

Пожалуйста, учтите, что я использую только аннотацию, а не конфигурацию XML.

4b9b3361

Ответ 1

Вы можете переместить bean инъекцию в конфигурацию, как:

@Configuration
public class AppConfig {

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

В качестве альтернативы вы можете использовать профили windows и linux, а затем аннотировать свои реализации служб с помощью аннотации @Profile, например @Profile("linux") или @Profile("windows"), и предоставить один из этих профилей для вашего приложения.

Ответ 2

1. Внедрение пользовательского Condition

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

То же самое для Windows.

2. Используйте @Conditional в классе 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. Используйте @Autowired как обычно

@Service
public class SomeOtherServiceUsingMyService {

    @Autowired    
    private MyService impl;

    // ... 
}

Ответ 3

Создайте красивую конфигурацию.

Представьте, что у нас есть Animal интерфейс, и у нас есть реализация Dog и Cat. Мы хотим написать write:

@Autowired
Animal animal;

но какую реализацию мы должны вернуть?

введите описание изображения здесь

Итак, что такое решение? Существует много способов решения проблемы. Я напишу, как использовать @Qualifier и пользовательские условия вместе.

Итак, сначала создайте нашу аннотацию:

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

и config: ( Примечание, что вам может потребоваться написать applicationContext.getBean( "Собака", Cat.class) или @Autowire Dog > вместо new Dog(), если последний вводит компоненты, иначе вам не удастся внедрить какие-либо компоненты в Dog bean):

@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();
    }

}

Примечание наше bean имя AnimalBean. зачем нам этот bean?, потому что когда мы вводим интерфейс Animal, мы будем писать только @Qualifier ( "AnimalBean" )

Также мы разбиваем пользовательскую аннотацию, чтобы передать значение нашему настраиваемому условию.

Нет, наши условия выглядят так (представьте, что имя "Dog" происходит из файла конфигурации или JVM-параметра или...)

   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;
    }
}

и, наконец, инъекция:

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

Ответ 4

Autowire все ваши реализации в factory с аннотациями @Qualifier, затем верните класс службы, который вам нужен, из factory.

public class MyService {
    private void doStuff();
}

Моя служба Windows:

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

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

Сервис My Mac:

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

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

Мой factory:

@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;
        }
    }
}

Если вы хотите получить очень сложно, вы можете использовать перечисление для хранения типов классов реализации, а затем использовать значение перечисления, чтобы выбрать, какую реализацию вы хотите вернуть.

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);
    }
}

Затем ваш factory может использовать контекст приложения и вытаскивать экземпляры в свою собственную карту. Когда вы добавляете новый класс сервиса, просто добавьте еще одну запись в перечисление и все, что вам нужно сделать.

 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));
      }
 }

Теперь вы можете просто передать тип класса, который вы хотите, в factory, и он предоставит вам требуемый экземпляр. Очень полезно, особенно если вы хотите сделать общие сервисы.