Spring在运行时选择bean实现

Tob*_*bia 40 java spring spring-bean

我正在使用带有注释的Spring Beans,我需要在运行时选择不同的实现.

@Service
public class MyService {
   public void test(){...}
}
Run Code Online (Sandbox Code Playgroud)

例如对于我需要的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();
   }
}
Run Code Online (Sandbox Code Playgroud)

请考虑我只使用注释而不是XML配置.

nob*_*beh 64

1.实施自定义 Condition

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

同样的Windows.

2. @ConditionalConfiguration课堂上使用

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

   @Bean
   @Conditional(WindowsCondition.class)
   public MyService getMyWindowsService() {
      return new WindowsService();
   }
}
Run Code Online (Sandbox Code Playgroud)

3. @Autowired照常使用

@Service
public class SomeOtherServiceUsingMyService {

    @Autowired    
    private MyService impl;

    // ... 
}
Run Code Online (Sandbox Code Playgroud)


gre*_*rep 23

让我们创建漂亮的配置.

想象一下,我们有Animal界面,我们有DogCat实现.我们想写写:

@Autowired
Animal animal;
Run Code Online (Sandbox Code Playgroud)

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

在此输入图像描述

那么解决方案是什么?有很多方法可以解决问题.我将一起编写如何使用 @Qualifier和Custom Conditions.

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

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

和配置:

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

}
Run Code Online (Sandbox Code Playgroud)

注意我们的bean名称是AnimalBean.为什么我们需要这个豆?因为当我们注入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;
    }
}
Run Code Online (Sandbox Code Playgroud)

最后注射:

@Qualifier("AnimalBean")
@Autowired
Animal animal;
Run Code Online (Sandbox Code Playgroud)

  • 我认为 Qualifier 不应该与 Autowired 混合使用 (2认同)

Sta*_*lav 16

您可以将bean注入移动到配置中,如下所示:

@Configuration
public class AppConfig {

    @Bean
    public MyService getMyService() {
        if(windows) return new MyServiceWin();
        else return new MyServiceLnx();
    }
}
Run Code Online (Sandbox Code Playgroud)

另外,您也可以使用配置文件windowslinux,然后与注释您的服务实现@Profile批注,如@Profile("linux")@Profile("windows"),并提供此配置文件用于您的应用程序之一.


Jam*_*ENL 12

将您的所有实现自动装配到带有@Qualifier注释的工厂中,然后从工厂返回您需要的服务类。

public class MyService {
    private void doStuff();
}
Run Code Online (Sandbox Code Playgroud)

我的 Windows 服务:

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

    @Override
    private void doStuff() {
        //Windows specific stuff happens here.
    }
}
Run Code Online (Sandbox Code Playgroud)

我的 Mac 服务:

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

    @Override
    private void doStuff() {
        //Mac specific stuff happens here
    }
}
Run Code Online (Sandbox Code Playgroud)

我的工厂:

@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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你的工厂可以进入应用程序上下文并将实例拉入它自己的地图。当您添加一个新的服务类时,只需向枚举添加另一个条目,这就是您要做的全部工作。

 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));
      }
 }
Run Code Online (Sandbox Code Playgroud)

现在,您只需将所需的类类型传递给工厂,它就会为您提供所需的实例。非常有帮助,特别是如果您想让服务通用。

  • 委托模式的良好应用 - 理论上。在实践中,它引入了一些扩展 MyService 的开销——添加的每个方法也应该由 MyFactory 实现(顺便说一句,它不是工厂而是代理)。没关系,虽然 MyService 只有一两个方法,但随着 MyService 的增长变得乏味。 (2认同)

lil*_*nux 9

只需将@Service带注释的类设置为有条件即可: 仅此而已。不需要其他显式@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;
    }
}
Run Code Online (Sandbox Code Playgroud)

神奇的事情发生了。条件就在它所属的地方:在实现类中。

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

@Conditional(BarCondition.class)
@Service
class MyServiceBarImpl implements MyService {
    // ...
}
Run Code Online (Sandbox Code Playgroud)

然后您可以Dependency Injection照常使用,例如Lombokvia@RequiredArgsConstructor@Autowired

@Service
@RequiredArgsConstructor
public class MyApp {
    private final MyService myService;
    // ...
}
Run Code Online (Sandbox Code Playgroud)

将其放入您的 application.yml 中:

implementation: FOO
Run Code Online (Sandbox Code Playgroud)

只有用 注释的实现 FooCondition 才会被实例化。没有幻像实例化。


Fel*_*ipe 9

只是在这个问题上添加我的 2 美分。请注意,不必像其他答案所示那样实现那么多的 java 类。人们可以简单地使用@ConditionalOnProperty。例子:

@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 {
    // ...
}
Run Code Online (Sandbox Code Playgroud)

将其放入您的 application.yml 中:

property.my.service: foo
Run Code Online (Sandbox Code Playgroud)