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配置。
您可以将bean注入移动到配置中,如下所示:
@Configuration public class AppConfig { @Bean public MyService getMyService() { if(windows) return new MyServiceWin(); else return new MyServiceLnx(); } }
或者,您可以使用配置文件windows
和linux
,然后使用@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; // ... }
让我们创造漂亮的配置。
想象一下,我们有Animal界面,我们有Dog和Cat实现。 我们想写写:
@Autowired Animal animal;
但我们应该返回哪个实施?
那么解决方案是什么? 有很多方法可以解决问题。 我将一起编写如何使用@Qualifier和Custom Conditions。
首先,让我们创建自定义注释:
@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名称是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; } }
最后注射:
@Qualifier("AnimalBean") @Autowired Animal animal;
使用@Qualifier
注释将所有实现自动@Qualifier
到工厂中,然后从工厂返回所需的服务类。
public class MyService { private void doStuff(); }
我的Windows服务:
@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, ServiceStore> mapOfClassTypes = new HashMap, 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 ServiceStore getOrdinalFromValue(Class> clazz) { return mapOfClassTypes.get(clazz); } }
然后您的工厂可以进入应用程序上下文并将实例拉入其自己的地图。 添加新服务类时,只需在枚举中添加另一个条目,这就是您所要做的。
public class ServiceFactory implements ApplicationContextAware { private final Map myServices = new Hashmap(); 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)); } }
现在,您可以将所需的类类型传递到工厂,它将为您提供所需的实例。 非常有用,特别是如果你想使服务通用。