春季 - 以编程方式生成一组豆类

2022-09-01 02:55:05

我有一个Dropwizard应用程序,它需要为配置列表中的每个配置生成十几个bean。诸如运行状况检查,石英调度程序等。

像这样:

@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();
    }
}

我有多个 MyConfiguration 实例,它们都需要这样的 bean。现在,我必须复制并粘贴这些定义,并为每个新配置重命名它们。

我可以以某种方式迭代我的配置类并为每个配置类生成一组Bean定义吗?

我对子类化解决方案或任何类型安全的东西都很好,而不会让我在必须添加新服务时复制和粘贴相同的代码并重命名方法。

编辑:我应该补充一点,我有其他依赖于这些豆子的组件(例如,它们注入)。Collection<HealthCheck>


答案 1

因此,您需要即时声明新的bean,并将它们注入Spring的应用程序上下文中,就好像它们只是普通的Bean一样,这意味着它们必须受到代理,后处理等的影响,即它们必须受Spring Bean生命周期的影响。

请参阅BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry() 方法javadocs。这正是您所需要的,因为它允许您在加载正常Bean定义之后但在实例化任何单个Bean之前修改Spring的应用程序上下文。

@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);
        }
    }

    @Override
    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...
}

这有效地声明了您需要的bean,并将它们注入Spring的应用程序上下文中,每个配置都有一组bean。您必须依靠一些命名模式,然后在需要时按名称自动连接bean

@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;

    ...

}

笔记:

  1. 如果您通过从文件中手动读取配置名称,请使用Spring的ClassPathResource.getInputStream()。

  2. 如果您自己扫描类路径,我强烈建议您使用令人惊叹的反射库

  3. 您必须手动设置每个 Bean 定义的所有属性和依赖项。每个Bean定义都独立于其他Bean定义,即您无法重用它们,将它们一个地设置在另一个内部,等等。把它们想象成以旧的XML方式声明bean。

  4. 检查BeanDefinitionBuilder javadocsGenericBeanDefinition javadocs以获取更多详细信息。


答案 2

您应该能够执行如下操作:

@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);
        }
     }

}

推荐