java - sirve - Spring elige implementación de frijol en tiempo de ejecución
settitle java (4)
Estoy usando Spring Beans con anotaciones y debo elegir una implementación diferente en tiempo de ejecución.
@Service
public class MyService {
public void test(){...}
}
Por ejemplo, para la plataforma de Windows necesito MyServiceWin extending MyService
, para la plataforma Linux necesito MyServiceLnx extending MyService
.
Por ahora solo conozco una solución horrible:
@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();
}
}
Tenga en cuenta que solo estoy usando anotación y no configuración XML.
1. Implementar una Condition
personalizada
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").contains("Linux"); }
}
Lo mismo para Windows
.
2. Usa Conditional en tu clase de 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 @Autowired
como de costumbre
@Service
public class SomeOtherServiceUsingMyService {
@Autowired
private MyService impl;
// ...
}
Encienda automáticamente todas sus implementaciones en una fábrica con @Qualifier
anotaciones de @Qualifier
, luego devuelva la clase de servicio que necesita de la fábrica.
public class MyService {
private void doStuff();
}
Mi servicio de Windows:
@Service("myWindowsService")
public class MyWindowsService implements MyService {
@Override
private void doStuff() {
//Windows specific stuff happens here.
}
}
Mi servicio de Mac:
@Service("myMacService")
public class MyMacService implements MyService {
@Override
private void doStuff() {
//Mac specific stuff happens here
}
}
Mi fabrica:
@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;
}
}
}
Si desea ser realmente complicado, puede usar una enumeración para almacenar sus tipos de clase de implementación, y luego usar el valor de enumeración para elegir qué implementación desea devolver.
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);
}
}
Luego, su fábrica puede acceder al contexto de la aplicación y extraer instancias en su propio mapa. Cuando agrega una nueva clase de servicio, simplemente agregue otra entrada a la enumeración, y eso es todo lo que tiene que hacer.
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));
}
}
Ahora puede pasar el tipo de clase que desea a la fábrica y le devolverá la instancia que necesita. Muy útil sobre todo si quieres que los servicios sean genéricos.
Puede mover la inyección de frijol a la configuración, como:
@Configuration
public class AppConfig {
@Bean
public MyService getMyService() {
if(windows) return new MyServiceWin();
else return new MyServiceLnx();
}
}
Alternativamente, puede usar los perfiles de windows
y linux
, luego anotar sus implementaciones de servicio con la anotación @Profile
, como @Profile("linux")
o @Profile("windows")
, y proporcionar uno de estos perfiles para su aplicación.
Vamos a crear una hermosa configuración.
Imagina que tenemos una interfaz Animal y una implementación para perros y gatos . Queremos escribir escribir:
@Autowired
Animal animal;
¿Pero qué implementación debemos devolver?
Entonces, ¿qué es la solución? Hay muchas maneras de resolver el problema. Escribiré cómo usar @Qualifier y Condiciones personalizadas juntos.
Así que en primer lugar vamos a crear nuestra anotación personalizada:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
public @interface AnimalType {
String value() default "";
}
y 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();
}
}
Note que nuestro nombre de frijol es AnimalBean . ¿Por qué necesitamos este frijol? porque cuando inyectamos Animal interface escribiremos solo @Qualifier ("AnimalBean")
También creamos una anotación personalizada para pasar el valor a nuestra Condición personalizada .
Ahora nuestras condiciones se ven así (imagina que el nombre "Perro" proviene del archivo de configuración o del parámetro JVM o ...)
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;
}
}
y finalmente inyección:
@Qualifier("AnimalBean")
@Autowired
Animal animal;