usar sirve settitle que para codigo java spring spring-bean

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;