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.
Ответы
Ответ 1
Вы можете переместить 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")
, и предоставить один из этих профилей для вашего приложения.
Ответ 2
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. Используйте @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. Используйте @Autowired
как обычно
@Service
public class SomeOtherServiceUsingMyService {
@Autowired
private MyService impl;
// ...
}
Ответ 3
Создайте красивую конфигурацию.
Представьте, что у нас есть Animal интерфейс, и у нас есть реализация Dog и Cat. Мы хотим написать write:
@Autowired
Animal animal;
но какую реализацию мы должны вернуть?
![введите описание изображения здесь]()
Итак, что такое решение? Существует много способов решения проблемы. Я напишу, как использовать @Qualifier и пользовательские условия вместе.
Итак, сначала создайте нашу аннотацию:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
public @interface AnimalType {
String value() default "";
}
и config: ( Примечание, что вам может потребоваться написать applicationContext.getBean( "Собака", Cat.class) или @Autowire Dog > вместо new Dog(), если последний вводит компоненты, иначе вам не удастся внедрить какие-либо компоненты в Dog bean):
@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. зачем нам этот bean?, потому что когда мы вводим интерфейс 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;
Ответ 4
Autowire все ваши реализации в factory с аннотациями @Qualifier
, затем верните класс службы, который вам нужен, из factory.
public class MyService {
private void doStuff();
}
Моя служба Windows:
@Service("myWindowsService")
public class MyWindowsService implements MyService {
@Override
private void doStuff() {
//Windows specific stuff happens here.
}
}
Сервис My Mac:
@Service("myMacService")
public class MyMacService implements MyService {
@Override
private void doStuff() {
//Mac specific stuff happens here
}
}
Мой 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;
}
}
}
Если вы хотите получить очень сложно, вы можете использовать перечисление для хранения типов классов реализации, а затем использовать значение перечисления, чтобы выбрать, какую реализацию вы хотите вернуть.
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);
}
}
Затем ваш factory может использовать контекст приложения и вытаскивать экземпляры в свою собственную карту. Когда вы добавляете новый класс сервиса, просто добавьте еще одну запись в перечисление и все, что вам нужно сделать.
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));
}
}
Теперь вы можете просто передать тип класса, который вы хотите, в factory, и он предоставит вам требуемый экземпляр. Очень полезно, особенно если вы хотите сделать общие сервисы.