java spring spring-mvc dropwizard

java - Primavera-Generar programáticamente un conjunto de frijoles.



spring spring-mvc (6)

Tengo una aplicación Dropwizard que necesita generar una docena de beans para cada una de las configuraciones en una lista de configuración. Cosas como chequeos de salud, programadores de cuarzo, etc.

Algo como esto:

@Component class MyModule { @Inject private MyConfiguration configuration; @Bean @Lazy public QuartzModule quartzModule() { return new QuartzModule(quartzConfiguration()); } @Bean @Lazy public QuartzConfiguration quartzConfiguration() { return this.configuration.getQuartzConfiguration(); } @Bean @Lazy public HealthCheck healthCheck() throws SchedulerException { return this.quartzModule().quartzHealthCheck(); } }

Tengo varias instancias de MyConfiguration que todos necesitan beans como este. Ahora mismo tengo que copiar y pegar estas definiciones y renombrarlas para cada nueva configuración.

¿Puedo iterar de alguna manera sobre mis clases de configuración y generar un conjunto de definiciones de beans para cada una?

Estaría bien con una solución de subclasificación o cualquier cosa que sea de tipo seguro sin hacerme copiar y pegar el mismo código y renombrar los métodos cada vez que tenga que agregar un nuevo servicio.

EDITAR: Debo agregar que tengo otros componentes que dependen de estos beans (por ejemplo, inyectan Collection<HealthCheck> ).


Debe crear una clase de configuración base que se extienda a todas sus clases de Configuration . Luego, puede iterar sobre todas las clases de configuración de la siguiente manera:

// Key - name of the configuration class // value - the configuration object Map<String, Object> configurations = applicationContext.getBeansWithAnnotation(Configuration.class); Set<String> keys = configurations.keySet(); for(String key: keys) { MyConfiguration conf = (MyConfiguration) configurations.get(key); // Implement the logic to use this configuration to create other beans. }


Deberías poder hacer algo como esto:

@Configuration public class MyConfiguration implements BeanFactoryAware { private BeanFactory beanFactory; @Override public void setBeanFactory(BeanFactory beanFactory) { this.beanFactory = beanFactory; } @PostConstruct public void onPostConstruct() { ConfigurableBeanFactory configurableBeanFactory = (ConfigurableBeanFactory) beanFactory; for (..) { // setup beans programmatically String beanName= .. Object bean = .. configurableBeanFactory.registerSingleton(beanName, bean); } } }


El "mejor" enfoque que se me ocurrió fue envolver todos mis programadores y configuraciones de Quartz en 1 uber bean y cablearlo todo manualmente, luego refactorizar el código para que funcione con la interfaz de uber bean.

El bean uber crea todos los objetos que necesito en su PostConstruct e implementa ApplicationContextAware para que pueda auto-cablearlos. No es lo ideal, pero fue lo mejor que pude encontrar.

La primavera simplemente no tiene una buena manera de agregar dinámicamente frijoles de una manera segura.


Por lo tanto, debe declarar nuevos beans sobre la marcha e inyectarlos en el contexto de la aplicación Spring como si fueran solo beans comunes, lo que significa que deben estar sujetos a procesos proxy, procesamiento posterior, etc., es decir, deben estar sujetos al ciclo de vida de los beans Spring .

Consulte el método javadocs de BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry() . Esto es exactamente lo que necesita, ya que le permite modificar el contexto de la aplicación Spring después de que se hayan cargado las definiciones normales de los beans, pero antes de que se haya creado una instancia de un solo bean .

@Configuration public class ConfigLoader implements BeanDefinitionRegistryPostProcessor { private final List<String> configurations; public ConfigLoader() { this.configurations = new LinkedList<>(); // TODO Get names of different configurations, just the names! // i.e. You could manually read from some config file // or scan classpath by yourself to find classes // that implement MyConfiguration interface. // (You can even hardcode config names to start seeing how this works) // Important: you can''t autowire anything yet, // because Spring has not instantiated any bean so far! for (String readConfigurationName : readConfigurationNames) { this.configurations.add(readConfigurationName); } } public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { // iterate over your configurations and create the beans definitions it needs for (String configName : this.configurations) { this.quartzConfiguration(configName, registry); this.quartzModule(configName, registry); this.healthCheck(configName, registry); // etc. } } private void quartzConfiguration(String configName, BeanDefinitionRegistry registry) throws BeansException { String beanName = configName + "_QuartzConfiguration"; BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(QuartzConfiguration.class).setLazyInit(true); // TODO Add what the bean needs to be properly initialized // i.e. constructor arguments, properties, shutdown methods, etc // BeanDefinitionBuilder let''s you add whatever you need // Now add the bean definition with given bean name registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); } private void quartzModule(String configName, BeanDefinitionRegistry registry) throws BeansException { String beanName = configName + "_QuartzModule"; BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(QuartzModule.class).setLazyInit(true); builder.addConstructorArgReference(configName + "_QuartzConfiguration"); // quartz configuration bean as constructor argument // Now add the bean definition with given bean name registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); } private void healthCheck(String configName, BeanDefinitionRegistry registry) throws BeansException { String beanName = configName + "_HealthCheck"; BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(HealthCheck.class).setLazyInit(true); // TODO Add what the bean needs to be properly initialized // i.e. constructor arguments, properties, shutdown methods, etc // BeanDefinitionBuilder let''s you add whatever you need // Now add the bean definition with given bean name registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); } // And so on for other beans... }

Esto declara efectivamente los beans que necesita y los inyecta en el contexto de la aplicación Spring, un conjunto de beans para cada configuración. Debe confiar en algún patrón de denominación y, a continuación, realizar el cableado automático de sus beans cuando sea ​​necesario:

@Service public class MyService { @Resource(name="config1_QuartzConfiguration") private QuartzConfiguration config1_QuartzConfiguration; @Resource(name="config1_QuartzModule") private QuartzModule config1_QuartzModule; @Resource(name="config1_HealthCheck") private HealthCheck config1_HealthCheck; ... }

Notas:

  1. Si va leyendo los nombres de configuración manualmente desde un archivo, use Spring''s ClassPathResource.getInputStream() .

  2. Si vas escaneando el classpath por ti mismo, te recomiendo que uses la increíble biblioteca de Reflections .

  3. Debe configurar manualmente todas las propiedades y dependencias para cada definición de bean. Cada definición de bean es independiente de otras definiciones de bean, es decir, no puede reutilizarlas, ubicarlas una dentro de otra, etc. Piense en ellas como si estuviera declarando los beans a la manera antigua de XML.

  4. Consulte BeanDefinitionBuilder javadocs y GenericBeanDefinition javadocs para obtener más detalles.


Sólo voy a entrar aquí. Otros han mencionado que necesitas crear un bean, en el que se inyecta tu configuración. Ese bean utilizará su configuración para crear otros beans e insertarlos en el contexto (que también necesitará inyectar de una forma u otra).

Lo que no creo que nadie más haya notado es que usted ha dicho que otros beans dependerán de estos beans creados dinámicamente. Esto significa que su fábrica dinámica de beans debe ser instanciada antes que los beans dependientes. Puedes hacer esto (en el mundo de las anotaciones) usando

@DependsOn("myCleverBeanFactory")

En cuanto a qué tipo de objeto es su inteligente fábrica de frijoles, otros han recomendado mejores formas de hacerlo. Pero si recuerdo correctamente, puedes hacerlo en el viejo mundo de la primavera 2:

public class MyCleverFactoryBean implements ApplicationContextAware, InitializingBean { @Override public void afterPropertiesSet() { //get bean factory from getApplicationContext() //cast bean factory as necessary //examine your config //create beans //insert beans into context }

..


Simplemente ampliando la respuesta de Michas, su solución funciona si la configuro de esta manera:

public class ToBeInjected { } public class PropertyInjected { private ToBeInjected toBeInjected; public ToBeInjected getToBeInjected() { return toBeInjected; } @Autowired public void setToBeInjected(ToBeInjected toBeInjected) { this.toBeInjected = toBeInjected; } } public class ConstructorInjected { private final ToBeInjected toBeInjected; public ConstructorInjected(ToBeInjected toBeInjected) { this.toBeInjected = toBeInjected; } public ToBeInjected getToBeInjected() { return toBeInjected; } } @Configuration public class BaseConfig implements BeanFactoryAware{ private ConfigurableBeanFactory beanFactory; protected ToBeInjected toBeInjected; @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = (ConfigurableBeanFactory) beanFactory; } @PostConstruct public void addCustomBeans() { toBeInjected = new ToBeInjected(); beanFactory.registerSingleton(this.getClass().getSimpleName() + "_quartzConfiguration", toBeInjected); } @Bean public ConstructorInjected test() { return new ConstructorInjected(toBeInjected); } @Bean public PropertyInjected test2() { return new PropertyInjected(); } }

Una cosa a tener en cuenta es que estoy creando los beans personalizados como atributos de la clase de configuración y los estoy inicializando en el método @PostConstruct. De esta manera, tengo el objeto registrado como un bean (por lo que @Autowire y @Inject funcionan como se esperaba) y luego puedo usar la misma instancia en la inyección del constructor para los beans que lo requieren. La visibilidad del atributo se establece en protegida, de modo que las subclases pueden usar los objetos creados.

Como la instancia que tenemos no es realmente el proxy de Spring, pueden surgir algunos problemas (aspectos que no se activan, etc.). Puede ser una buena idea recuperar el bean después de registrarlo, como en:

toBeInjected = new ToBeInjected(); String beanName = this.getClass().getSimpleName() + "_quartzConfiguration"; beanFactory.registerSingleton(beanName, toBeInjected); toBeInjected = beanFactory.getBean(beanName, ToBeInjected.class);