简体   繁体   English

Spring 在运行时选择 bean 实现

[英]Spring choose bean implementation at runtime

I'm using Spring Beans with annotations and I need to choose different implementation at runtime.我正在使用带有注释的 Spring Beans,我需要在运行时选择不同的实现。

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

For example for windows's platform I need MyServiceWin extending MyService , for linux platform I need MyServiceLnx extending MyService .例如,对于 Windows 平台,我需要MyServiceWin extending MyService ,对于 linux 平台,我需要MyServiceLnx extending MyService

For now I know only one horrible solution:现在我只知道一个可怕的解决方案:

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

Please consider that I'm using annotation only and not XML config.请考虑我只使用注释而不是 XML 配置。

1. Implement a custom Condition 1. 实现自定义Condition

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

Same for Windows .对于Windows

2. Use @Conditional in your Configuration class 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. Use @Autowired as usual 3. 像往常一样使用@Autowired

@Service
public class SomeOtherServiceUsingMyService {

    @Autowired    
    private MyService impl;

    // ... 
}

You can move the bean injection into the configuration, as:您可以将 bean 注入移动到配置中,如下所示:

@Configuration
public class AppConfig {

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

Alternatively, you may use profiles windows and linux , then annotate your service implementations with the @Profile annotation, like @Profile("linux") or @Profile("windows") , and provide one of this profiles for your application.或者,您可以使用配置文件windowslinux ,然后使用@Profile注释来注释您的服务实现,例如@Profile("linux")@Profile("windows") ,并为您的应用程序提供此配置文件之一。

Let's create beautiful config.让我们创建漂亮的配置。

Imagine that we have Animal interface and we have Dog and Cat implementation.想象一下,我们有Animal接口,并且有DogCat实现。 We want to write write:我们想写写:

@Autowired
Animal animal;

but which implementation should we return?但是我们应该返回哪个实现?

在此处输入图片说明

So what is solution?那么什么是解决方案? There are many ways to solve problem.有很多方法可以解决问题。 I will write how to use @Qualifier and Custom Conditions together.我将写如何一起使用@Qualifier和自定义条件。

So First off all let's create our custom annotation:所以首先让我们创建我们的自定义注释:

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

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

}

Note our bean name is AnimalBean .注意我们的 bean 名称是AnimalBean why do we need this bean?为什么我们需要这个bean? because when we inject Animal interface we will write just @Qualifier("AnimalBean")因为当我们注入 Animal 接口时,我们只会写@Qualifier("AnimalBean")

Also we crated custom annotation to pass the value to our custom Condition .我们还创建了自定义注释以将值传递给我们的自定义 Condition

Now our conditions look like this (imagine that "Dog" name comes from config file or JVM parameter or...)现在我们的条件看起来像这样(假设“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;
    }
}

and finally injection:最后注入:

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

Autowire all your implementations into a factory with @Qualifier annotations, then return the service class you need from the factory.使用@Qualifier注释将所有实现自动@Qualifier到工厂中,然后从工厂返回您需要的服务类。

public class MyService {
    private void doStuff();
}

My Windows Service:我的 Windows 服务:

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

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

My Mac Service:我的 Mac 服务:

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

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

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

If you want to get really tricky you can use an enum to store your implementation class types, and then use the enum value to choose which implementation you want to return.如果你想变得非常棘手,你可以使用枚举来存储你的实现类类型,然后使用枚举值来选择你想要返回的实现。

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

Then your factory can tap into the Application context and pull instances into it's own map.然后你的工厂可以进入应用程序上下文并将实例拉入它自己的地图。 When you add a new service class, just add another entry to the enum, and that's all you have to do.当您添加一个新的服务类时,只需向枚举添加另一个条目,这就是您要做的全部工作。

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

Now you can just pass the class type you want into the factory, and it will provide you back the instance you need.现在,您只需将所需的类类型传递给工厂,它就会为您提供所需的实例。 Very helpful especially if you want to the make the services generic.非常有帮助,特别是如果您想让服务通用。

Just adding my 2 cents to this question.只需将我的 2 美分加到这个问题上。 Note that one doesn't have to implement so many java classes as the other answers are showing.请注意,不必像其他答案所示那样实现那么多 java 类。 One can simply use the @ConditionalOnProperty .可以简单地使用@ConditionalOnProperty Example:例子:

@Service
@ConditionalOnProperty(
  value="property.my.service", 
  havingValue = "foo", 
  matchIfMissing = true)
class MyServiceFooImpl implements MyService {
    // ...
}

@ConditionalOnProperty(
  value="property.my.service", 
  havingValue = "bar")
class MyServiceBarImpl implements MyService {
    // ...
}

Put this in your application.yml:将其放入您的 application.yml 中:

property.my.service: foo

MyService.java:我的服务.java:

public interface MyService {
  String message();
}

MyServiceConfig.java:我的ServiceConfig.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我的服务类型.java

public enum MyServiceTypes {
  One,
  Two
}

Use in any Bean/Component/Service/etc.在任何 Bean/组件/服务/等中使用。 like:喜欢:

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

Simply make the @Service annotated classes conditional: That's all.只需将@Service注释类设置为有条件的:仅此@Service No need for other explicit @Bean methods.不需要其他显式@Bean方法。

public enum Implementation {
    FOO, BAR
}

@Configuration
public class FooCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Implementation implementation = Implementation.valueOf(context.getEnvironment().getProperty("implementation"));
        return Implementation.FOO == implementation;
    }
}

@Configuration
public class BarCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Implementation implementation = Implementation.valueOf(context.getEnvironment().getProperty("implementation"));
        return Implementation.BAR == implementation;
    }
}

Here happens the magic.这里发生了魔法。 The condition is right where it belongs: At the implementating classes.条件就在它所属的地方:在实现类中。

@Conditional(FooCondition.class)
@Service
class MyServiceFooImpl implements MyService {
    // ...
}

@Conditional(BarCondition.class)
@Service
class MyServiceBarImpl implements MyService {
    // ...
}

You can then use Dependency Injection as usual, eg via Lombok 's @RequiredArgsConstructor or @Autowired .然后您可以像往常一样使用Dependency Injection ,例如通过Lombok@RequiredArgsConstructor@Autowired

@Service
@RequiredArgsConstructor
public class MyApp {
    private final MyService myService;
    // ...
}

Put this in your application.yml:把它放在你的 application.yml 中:

implementation: FOO

👍 Only the implementations annotated with the FooCondition will be instantiated. 👍只有使用FooCondition注释的实现才会被实例化。 No phantom instantiations.没有幻像实例化。 👍 👍

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM