Spring在运行时选择bean实现

时间:2015-12-18 07:56:31

标签: java spring 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配置。

4 个答案:

答案 0 :(得分:52)

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。在Configuration班级

中使用@Conditional
@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;

    // ... 
}

答案 1 :(得分:14)

让我们创造漂亮的配置。

想象一下,我们有动物界面,我们有 Dog Cat 实现。我们想写写:

@Autowired
Animal animal;

但我们应该返回哪个实施?

enter image description here

那么什么是解决方案?有很多方法可以解决问题。我将一起编写如何使用 @Qualifier 和自定义条件。

首先,让我们创建自定义注释:

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

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

}

注意我们的bean名称是 AnimalBean 为什么我们需要这个bean?因为当我们注入Animal接口时我们只会编写 @Qualifier(“AnimalBean”)

我们还创建了自定义注释,将值传递给自定义条件

现在我们的条件看起来像这样(假设“狗”名称来自配置文件或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;

答案 2 :(得分:3)

将所有实施自动装配到带有123 4 5 678 ... 注释的工厂中,然后从工厂返回所需的服务类。

@Qualifier

我的Windows服务:

public class MyService {
    private void doStuff();
}

我的Mac服务:

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

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

我的工厂:

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

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

如果你想变得非常棘手,你可以使用枚举来存储你的实现类类型,然后使用枚举值来选择你想要返回的实现。

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

现在您可以将所需的类类型传递给工厂,它将为您提供所需的实例。非常有用,特别是如果你想使服务通用。

答案 3 :(得分:1)

MyService.java:

public interface MyService {
  String message();
}

MyServiceConfig.java:

@Configuration
public class MyServiceConfig {

  @Value("${service-type}")
  MyServiceTypes myServiceType;

  @Bean
  public MyService getMyService() {
    if (myServiceType == MyServiceTypes.One) {
      return new MyServiceImp1();
    } else {
      return new MyServiceImp2();
    }
  }
}

application.properties:

service-type=one

MyServiceTypes.java

public enum MyServiceTypes {
  One,
  Two
}

在任何Bean / Component / Service / etc中使用。喜欢:

    @Autowired
    MyService myService;
    ...
    String message = myService.message()