Tengo una interfaz y una implementación múltiple. Estoy conectando automáticamente la interfaz en clases para su uso. Necesito elegir una implementación diferente en tiempo de ejecución.
public class Util { public void getClient(); }Implementaciones
public class UtilOne implements Util { public void getClient() {...} } public class UtilTwo implements Util { public void getClient() {...} } @Configuration public class AppConfig { @Autowired @Bean @Primary public Util utilOne() { return new UtilOne(); } @Autowired @Bean public Util utilTwo() { return new UtilTwo(); } } @Component public class DemoService { @Autowired private Util util; } Por alguna razón, si no podemos obtener un cliente en UtilOne, quiero cambiar a UtilTwo sin reiniciar la aplicación. Quiero cambiar el objeto Util en DemoService al objeto UtilTwo. La propiedad active.util vendrá de la base de datos y podemos actualizarla desde la interfaz de usuario.
No funciona de esta manera: si tiene una cierta implementación de Util conectada a, por ejemplo, la clase SampleClass (que es un singleton), realmente no puede cambiar la implementación de Util a algo diferente sin reiniciar el contexto de la aplicación.
Entonces, en lugar de ir por este camino, sugiero una alternativa. Usted dice que bajo ciertas condiciones que se evalúan en tiempo de ejecución, desea cambiar las implementaciones. ¿Qué tipo de condición es? ¿Es posible extraer esta lógica de decisión de condición?
Si es así, puede autoconectar un DynamicUtil especial que contendrá la referencia a todas las utilidades y llamará a la utilidad requerida dependiendo de la condición:
// represents all possible business 'runtime' outcomes enum ConditionOutcome { A, B, C } interface ConditionEvaluator { ConditionOutcome evaluate(); // when called in runtime will evaluate a condition that currently exists in the system } interface Util { void foo(); ConditionOutcome relevantOfOutcome(); } class Utill1Impl implements Util { public void foo() {...} public ConditionOutcome relevantOfOutcome() {return ConditionOutcome.A;} } class Utill2Impl implements Util { public void foo() {...} public ConditionOutcome relevantOfOutcome() {return ConditionOutcome.B;} } class Utill3Impl implements Util { public void foo() {...} public ConditionOutcome relevantOfOutcome() {return ConditionOutcome.C;} } class DynamicUtil { private final Map<ConditionOutcome, Util> possibleImpls; private final ConditionEvaluator evaluator; public class DynamicUtil(List<Util> allImplementations, ConditionEvaluator evaluator) { // create a map by calling the 'relevantOfOutcome' per util impl in a loop this.evaluator = evaluator; } public void foo() { ConditionOutcome key = evaluator.evaluate(); // pick the relevant implementation based on evaluated key possibleImpls.get(key).foo(); } } Ahora, con un diseño de este tipo, puede agregar dinámicamente nuevos resultados posibles (junto con utilidades que deberían implementarlos). Sin embargo, sus clases en el sistema tendrán que autoconectar DynamicUtil , por lo que efectivamente introducirá un nivel adicional de indirección pero ganará flexibilidad.
class SampleClass { // a business class that will need to work with util capable of being changed during the runtime @Autowired private DynamicUtil util; ... }Puede intentar acercarse con la delegación de proxy . Tenga un bean Util principal que simplemente envuelva la implementación real y permita cambiar su delegado interno en tiempo de ejecución. Además, puede crear algo como una clase de administrador/ayudante que contenga referencias a todos los beans de implementación reales para simplificar el cambio entre ellos.
@Component @Primary public class DelegatingUtil implements Util { private Util delegate; public void setDelegate(Util delegate){ this.delegate = delegate; } public Util getDelegate(){ return delegate; } public void getClient() { return delegate.getClient(); } }Y donde se aplica la lógica de conmutación:
// Use @Named or @Qualifier or any other way to obtain references to actual implementations private Util defaultImpl; private Util fallbackImpl; @Autowired private DelegatingUtil switcher; public void switchToFallback(){ this.switcher.setDelegate(this.fallbackImpl); }Tenga en cuenta que este es solo un ejemplo esquemático, debe tener cuidado con los detalles como el orden de creación de beans, la inyección con calificadores (tal vez condicionales), la inicialización, etc.
Aquí hay un enfoque simple basado en su situación. La idea principal es leer la propiedad active.util de DB by PropertyService y envolver sus Utils en RouteUtil :
@Component public class RouteUtil { @Autowired private PropertyService propertyService; @Qualifier("one") @Autowired private Util utilOne; @Qualifier("two") @Autowired private Util utilTwo; public void getClient() { if ("one".equals(propertyService.read("active.util"))) { utilOne.getClient(); } else { utilTwo.getClient(); } } }y en DemoService:
@Service public class DemoService { @Autowired private RouteUtil util; // RouteUtil.getClient() ... } Puede cambiar active.util para seleccionar qué Util se usará en tiempo de ejecución sin reiniciar la aplicación.