with resource injection example and java spring polymorphism spring-environment

java - resource - spring injection



Inyectar el frijol de primavera dinĂ¡micamente. (6)

En una aplicación web de Java-Spring, me gustaría poder inyectar beans de forma dinámica. Por ejemplo tengo una interfaz con 2 implementaciones diferentes:

En mi aplicación estoy usando algún archivo de propiedades para configurar inyecciones:

#Determines the interface type the app uses. Possible values: implA, implB myinterface.type=implA

Mis inyecciones realmente se cargaron de forma condicional en los valores de propiedades en el archivo de propiedades. Por ejemplo, en este caso myinterface.type = implA donde inyecto MyInterface, la implementación que se inyectará será ImplA (lo logré extendiendo la anotación Condicional ).

Me gustaría que durante el tiempo de ejecución: una vez que se cambien las propiedades, sucederá lo siguiente (sin reinicio del servidor):

  1. Se inyectará la implementación correcta. Por ejemplo, cuando se configura myinterface.type=implB ImplB se inyectará donde quiera que se use MyInterface
  2. Spring Environment debe actualizarse con los nuevos valores y reinyectarse también en frijoles.

Pensé en refrescar mi contexto pero eso crea problemas. Pensé que tal vez usar los setters para inyección y reutilizar esos setters una vez que las propiedades se reconfiguran. ¿Existe una práctica de trabajo para tal requisito?

¿Algunas ideas?

ACTUALIZAR

Como algunos sugirieron, puedo usar una fábrica / registro que contenga ambas implementaciones (ImplA e ImplB) y devuelva la correcta consultando la propiedad correspondiente. Si hago eso, todavía tengo el segundo desafío: el medio ambiente. por ejemplo, si mi registro se ve así:

@Service public class MyRegistry { private String configurationValue; private final MyInterface implA; private final MyInterface implB; @Inject public MyRegistry(Environmant env, MyInterface implA, MyInterface ImplB) { this.implA = implA; this.implB = implB; this.configurationValue = env.getProperty("myinterface.type"); } public MyInterface getMyInterface() { switch(configurationValue) { case "implA": return implA; case "implB": return implB; } } }

Una vez que la propiedad ha cambiado debo reinyectar mi entorno. ¿Alguna sugerencia para eso?

Sé que puedo consultar esa env dentro del método en lugar del constructor, pero esto es una reducción del rendimiento y también me gustaría pensar en un ider para reinyectar el entorno (de nuevo, ¿tal vez usar una inyección de ajuste?).


Esto puede ser una pregunta duplicada o al menos muy similar, de todos modos respondí a este tipo de pregunta aquí: constructor de prototipo de autowire parcial de Spring Bean

Más o menos, cuando desea un bean diferente para una dependencia en tiempo de ejecución, necesita utilizar un ámbito de prototipo. Luego puede usar una configuración para devolver diferentes implementaciones del bean prototipo. Necesitará manejar la lógica sobre la cual la implementación se devolverá, (incluso podrían estar devolviendo 2 beans singleton diferentes, no importa). Pero digamos que quiere nuevos beans, y la lógica para devolver la implementación está en un bean llamado SomeBeanWithLogic.isSomeBooleanExpression() , entonces puede hacer una configuración:

@Configuration public class SpringConfiguration { @Bean @Autowired @Scope("prototype") public MyInterface createBean(SomeBeanWithLogic someBeanWithLogic ) { if (someBeanWithLogic .isSomeBooleanExpression()) { return new ImplA(); // I could be a singleton bean } else { return new ImplB(); // I could also be a singleton bean } } }

Nunca debería haber una necesidad de volver a cargar el contexto. Si, por ejemplo, desea que la implementación de un bean cambie en tiempo de ejecución, use lo anterior. Si realmente necesita volver a cargar su aplicación, porque este bean se usó en constructores de frijoles singleton o algo raro, entonces necesita repensar su diseño, y si estos frijoles son realmente frijoles singleton. No debe volver a cargar el contexto para volver a crear beans de singleton para lograr un comportamiento de tiempo de ejecución diferente, que no es necesario.

Editar La primera parte de esta respuesta respondió a la pregunta sobre la inyección dinámica de frijoles. Según lo preguntado, pero creo que la pregunta es más de una: "¿Cómo puedo cambiar la implementación de un bean singleton en tiempo de ejecución?". Esto podría hacerse con un patrón de diseño proxy.

interface MyInterface { public String doStuff(); } @Component public class Bean implements MyInterface { boolean todo = false; // change me as needed // autowire implementations or create instances within this class as needed @Qualifier("implA") @Autowired MyInterface implA; @Qualifier("implB") @Autowired MyInterface implB; public String doStuff() { if (todo) { return implA.doStuff(); } else { return implB.doStuff(); } } }


Mantendría esta tarea lo más simple posible. En lugar de cargar condicionalmente una implementación de la interfaz MyInterface al inicio y luego MyInterface un evento que active la carga dinámica de otra implementación de la misma interfaz, abordaría este problema de una manera diferente, que es mucho más simple de implementar y mantener.

En primer lugar, acabo de cargar todas las implementaciones posibles:

@Component public class MyInterfaceImplementationsHolder { @Autowired private Map<String, MyInterface> implementations; public MyInterface get(String impl) { return this.implementations.get(impl); } }

Este bean es solo un soporte para todas las implementaciones de la interfaz MyInterface . Aquí no hay nada mágico, solo el comportamiento común de la alimentación automática de Spring

Ahora, donde sea que necesite inyectar una implementación específica de MyInterface , puede hacerlo con la ayuda de una interfaz:

public interface MyInterfaceReloader { void changeImplementation(MyInterface impl); }

Luego, para cada clase que deba ser notificada de un cambio en la implementación, simplemente haga que implemente la interfaz MyInterfaceReloader . Por ejemplo:

@Component public class SomeBean implements MyInterfaceReloader { // Do not autowire private MyInterface myInterface; @Override public void changeImplementation(MyInterface impl) { this.myInterface = impl; } }

Finalmente, necesita un bean que realmente cambie la implementación en cada bean que tenga MyInterface como un atributo:

@Component public class MyInterfaceImplementationUpdater { @Autowired private Map<String, MyInterfaceReloader> reloaders; @Autowired private MyInterfaceImplementationsHolder holder; public void updateImplementations(String implBeanName) { this.reloaders.forEach((k, v) -> v.changeImplementation(this.holder.get(implBeanName))); } }

Esto simplemente crea automáticamente todos los beans que implementan la interfaz MyInterfaceReloader y actualiza cada uno de ellos con la nueva implementación, que se recupera del titular y se pasa como un argumento. Una vez más, las normas comunes de primavera de autowiring.

Cuando quiera que se cambie la implementación, simplemente debe invocar el método updateImplementations con el nombre del bean de la nueva implementación, que es el nombre simple de la clase del camello inferior, es decir, myImplA o myImplB para las clases MyImplA y MyImplB .

También debe invocar este método al inicio, para que se establezca una implementación inicial en cada bean que implementa la interfaz MyInterfaceReloader .



Resolví un problema similar utilizando org.apache.commons.configuration.PropertiesConfiguration y org.springframework.beans.factory.config.ServiceLocatorFactoryBean:

Deje que VehicleRepairService sea una interfaz:

public interface VehicleRepairService { void repair(); }

y CarRepairService y TruckRepairService dos clases que lo implementan:

public class CarRepairService implements VehicleRepairService { @Override public void repair() { System.out.println("repair a car"); } } public class TruckRepairService implements VehicleRepairService { @Override public void repair() { System.out.println("repair a truck"); } }

Creo una interfaz para una fábrica de servicios:

public interface VehicleRepairServiceFactory { VehicleRepairService getRepairService(String serviceType); }

Vamos a usar Config como clase de configuración:

@Configuration() @ComponentScan(basePackages = "config.test") public class Config { @Bean public PropertiesConfiguration configuration(){ try { PropertiesConfiguration configuration = new PropertiesConfiguration("example.properties"); configuration .setReloadingStrategy(new FileChangedReloadingStrategy()); return configuration; } catch (ConfigurationException e) { throw new IllegalStateException(e); } } @Bean public ServiceLocatorFactoryBean serviceLocatorFactoryBean() { ServiceLocatorFactoryBean serviceLocatorFactoryBean = new ServiceLocatorFactoryBean(); serviceLocatorFactoryBean .setServiceLocatorInterface(VehicleRepairServiceFactory.class); return serviceLocatorFactoryBean; } @Bean public CarRepairService carRepairService() { return new CarRepairService(); } @Bean public TruckRepairService truckRepairService() { return new TruckRepairService(); } @Bean public SomeService someService(){ return new SomeService(); } }

Al utilizar FileChangedReloadingStrategy, su configuración se volverá a cargar cuando cambie el archivo de propiedades.

service=truckRepairService #service=carRepairService

Con la configuración y la fábrica a su servicio, le permite obtener el servicio apropiado de la fábrica utilizando el valor actual de la propiedad.

@Service public class SomeService { @Autowired private VehicleRepairServiceFactory factory; @Autowired private PropertiesConfiguration configuration; public void doSomething() { String service = configuration.getString("service"); VehicleRepairService vehicleRepairService = factory.getRepairService(service); vehicleRepairService.repair(); } }

Espero eso ayude.


Si lo entiendo correctamente, el objetivo no es reemplazar las instancias de objetos inyectados, sino usar diferentes implementaciones durante la llamada del método de interfaz, depende de alguna condición en el tiempo de ejecución.

Si es así, puede intentar ver el mecanismo Sring TargetSource en combinación con ProxyFactoryBean . El punto es que los objetos proxy se inyectarán a los beans que usan su interfaz, y todas las llamadas de método de interfaz se enviarán al destino TargetSource .

Llamemos a esto "Proxy Polimórfico".

Echa un vistazo al siguiente ejemplo:

ConditionalTargetSource.java

@Component public class ConditionalTargetSource implements TargetSource { @Autowired private MyRegistry registry; @Override public Class<?> getTargetClass() { return MyInterface.class; } @Override public boolean isStatic() { return false; } @Override public Object getTarget() throws Exception { return registry.getMyInterface(); } @Override public void releaseTarget(Object target) throws Exception { //Do some staff here if you want to release something related to interface instances that was created with MyRegistry. } }

applicationContext.xml

<bean id="myInterfaceFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces" value="MyInterface"/> <property name="targetSource" ref="conditionalTargetSource"/> </bean> <bean name="conditionalTargetSource" class="ConditionalTargetSource"/>

SomeService.java

@Service public class SomeService { @Autowired private MyInterface myInterfaceBean; public void foo(){ //Here we have `myInterfaceBean` proxy that will do `conditionalTargetSource.getTarget().bar()` myInterfaceBean.bar(); } }

Además, si desea que las implementaciones de MyInterface sean Spring beans y el contexto Spring no pueda contener ambas instancias al mismo tiempo, puede intentar usar ServiceLocatorFactoryBean con alcance de beans de prototype objetivo y anotaciones Conditional en las clases de implementación de destino. Este enfoque se puede utilizar en lugar de MyRegistry .

PS Probablemente, la operación de actualización del Contexto de la aplicación también puede hacer lo que quiera, pero puede causar otros problemas, como los gastos generales de rendimiento.


Tenga en cuenta que, si es interesante saberlo, FileChangedReloadingStrategy hace que su proyecto sea altamente dependiente de las condiciones de implementación: el WAR / EAR debe explotarse por contenedor y debe tener acceso directo al sistema de archivos, condiciones que no siempre se cumplen en todas las situaciones y ambientes.