Tengo la necesidad de crear condicionalmente una de las tres implementaciones posibles de un servicio según el entorno detectado por una aplicación Spring en tiempo de ejecución. Si el Servicio A está disponible, quiero crear una clase de implementación concreta que use el Servicio A como una dependencia. Si el Servicio A no está disponible, quiero crear una implementación usando el Servicio B como una dependencia. Y así.
Las clases que dependen de la implementación conectarán automáticamente la interfaz y no les importará cuál fue el servicio subyacente que se seleccionó para el entorno particular.
Mi primer intento en esto fue implementar múltiples métodos @Bean que devuelven un bean o nulo, dependiendo de si el Servicio está disponible, y luego tener una clase @Configuration separada que @Autowire(required=false) los dos servicios posibles, creando condicionalmente la implementación dependiendo de cuál de los campos @Autowired no era nulo.
El problema aquí es que cuando required=false, a Spring no parece importarle si espera a que se construyan los candidatos; es decir, la clase que intenta elegir la implementación puede construirse antes de que se construya uno o ambos Beans required=false, lo que garantiza que uno o ambos siempre sean nulos, independientemente de si pueden inicializarse correctamente.
Parece que voy contra la corriente en este punto, así que estoy buscando consejos sobre la forma "correcta" de hacer este tipo de cosas, donde un conjunto completo de frijoles podría cambiarse según la disponibilidad. de algún servicio o entorno exterior.
Los perfiles no parecen la respuesta correcta, porque no lo sabré hasta después de que mis beans de servicio intenten inicializar qué implementación quiero elegir; Ciertamente no lo sabré en el momento en que cree el contexto.
@Order tampoco logra el objetivo. Tampoco @Conditional y testing sobre la existencia del bean (porque es posible que aún no se haya construido). El mismo problema con FactoryBean: no sirve de nada verificar la existencia de beans que podrían no haberse construido en el momento en que se le pide a FactoryBean que cree una instancia.
Lo que realmente necesito hacer es crear un Bean basado en la disponibilidad de otros beans, pero solo DESPUÉS de que esos beans hayan tenido al menos la oportunidad de intentar inicializarse.
Spring Profiles es tu amigo. Puede establecer el perfil actual mediante variables ambientales, argumentos de línea de comandos y otros métodos. Puede anotar un componente administrado por Spring para que se cree para un perfil determinado.
Bueno, en este caso resultó ser un error tangencial que influyó en todo el comportamiento incorrecto.
Para dar algunos antecedentes, mi primer enfoque ingenuo (pero factible) se veía así:
@Autowired(required=false) @Qualifier(RedisConfig.HISTORY) private RLocalCachedMap<String, History> redisHistoryMap; @Autowired(required=false) @Qualifier(HazelcastConfig.HISTORY) private IMap<String, History> hazelcastHistoryMap; // RequestHistory is an interface @Bean public RequestHistory requestHistory() { if (redisHistoryMap != null) { return new RedisClusteredHistory(redisHistoryMap); } else if (hazelcastHistoryMap != null) { return new HazelcastClusteredHistory(hazelcastHistoryMap); } else { return new LocalRequestHistory(); // dumb hashmap } } En otras clases de @Configuration , si los beans que obtienen @Autowired aquí no están disponibles (debido a la falta de configuración, excepciones durante la inicialización, etc.), los métodos @Bean que los crean devuelven un valor nulo.
El comportamiento observado fue que este método @Bean se llamaba después de que se llamara al método RLocalCachedMap<> @Bean , pero antes de que Spring intentara crear el IMap<> llamando a su método @Bean . Había pensado incorrectamente que esto tenía algo que ver con required=false pero, de hecho, no tenía nada que ver con eso.
Lo que realmente sucedió fue que usé accidentalmente la misma constante para ambos nombres de @Bean (y, en consecuencia, @Qualifier ), por lo que presumiblemente Spring no pudo notar la diferencia cuando estaba calculando su gráfico de dependencia para esta clase de @Configuration ... porque los dos @Autowire d beans parecían ser lo mismo (porque tenían el mismo nombre).
(Hay una razón secundaria para usar @Qualifier en este caso, que no abordaré aquí, pero baste decir que es posible tener muchos mapas del mismo tipo).
Una vez que califiqué mejor los nombres, el código hizo exactamente lo que yo quería, aunque de una manera algo poco elegante/fea.
En algún momento regresaré y veré si se ve más elegante/menos feo y funciona igual de bien para usar @Conditional y @Primary en lugar de if/else foulness.
La lección aquí es que si nombra beans explícitamente, asegúrese absolutamente de que sus nombres sean únicos en su aplicación, incluso si planea intercambiar cosas como esta.