Spring 在运行时选择 bean 实现

我使用的是带注释的 SpringBeans,并且需要在运行时选择不同的实现。

@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 配置。

73243 次浏览

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

@Configuration
public class AppConfig {


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

或者,您可以使用配置文件 windowslinux,然后使用 @Profile注释(如 @Profile("linux")@Profile("windows"))注释您的服务实现,并为您的应用程序提供其中一个配置文件。

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;


// ...
}

使用 @Qualifier注释将所有实现自动连接到一个工厂,然后从工厂返回所需的服务类。

public class MyService {
private void doStuff();
}

我的视窗服务:

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


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

我的 Mac 服务:

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

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

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

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

让我们创建美丽的配置。

假设我们有 动物接口,我们有 凯特实现,我们想写:

@Autowired
Animal animal;

但是我们应该返回哪个实现呢?

enter image description here

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

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

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

配置:

@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 名称是 动物豆.我们为什么需要这个魔豆?,因为当我们注入 Animal 接口时,我们将只写 < strong >@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;

我的服务 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();
}
}
}

应用性能:

service-type=one

我的服务类型

public enum MyServiceTypes {
One,
Two
}

在任何 Bean/组件/服务等中使用,如:

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


只要使 @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;
}
}

奇迹发生了。 条件就在它所属的位置: 在实现类中。

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


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

然后你可以像往常一样使用 Dependency Injection,例如通过 Lombok@RequiredArgsConstructor@Autowired

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

把这个放在你的申请表里:

implementation: FOO

方法注释的实现 FooCondition 将被实例化。没有幻影实例化。

我只是在这个问题上多说两句。注意,一个人不必像其他答案显示的那样实现那么多的 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 {
// ...
}

把这个放在你的申请表里:

property.my.service: foo