Java Spring 在运行时选择 bean 实现
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/34350865/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring choose bean implementation at runtime
提问by Tobia
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 配置。
采纳答案by Stanislav
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 windowsand linux, then annotate your service implementations with the @Profileannotation, like @Profile("linux")or @Profile("windows"), and provide one of this profiles for your application.
或者,您可以使用配置文件windows和linux,然后使用@Profile注释(如@Profile("linux")或 )注释您的服务实现@Profile("windows"),并为您的应用程序提供此配置文件之一。
回答by nobeh
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 @Conditionalin your Configurationclass
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. Use @Autowiredas usual
3.@Autowired照常使用
@Service
public class SomeOtherServiceUsingMyService {
@Autowired
private MyService impl;
// ...
}
回答by JamesENL
Autowire all your implementations into a factory with @Qualifierannotations, then return the service class you need from the factory.
将您的所有实现自动装配到带有@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.
现在,您只需将所需的类类型传递给工厂,它就会为您提供所需的实例。非常有帮助,特别是如果您想让服务通用。
回答by grep
Let's create beautiful config.
让我们创建漂亮的配置。
Imagine that we have Animalinterface and we have Dogand Catimplementation. We want to write write:
想象一下,我们有Animal接口,并且有Dog和Cat实现。我们想写写:
@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 @Qualifierand 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();
}
}
Noteour bean name is AnimalBean. why do we need this bean?because when we inject Animal interface we will write just @Qualifier("AnimalBean")
注意我们的 bean 名称是AnimalBean。为什么我们需要这个bean?因为当我们注入 Animal 接口时,我们只会写@Qualifier("AnimalBean")
Also we crated custom annotationto 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;


