Spring - 以编程方式生成一组 bean

新手上路,请多包涵

我有一个 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 定义?

我可以使用子类化解决方案或任何类型安全的解决方案,而无需在每次我必须添加新服务时复制和粘贴相同的代码并重命名方法。

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

原文由 noah 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 781
2 个回答

我能想到的“最佳”方法是将我所有的 Quartz 配置和调度程序包装在 1 个超级 bean 中,然后手动将其连接起来,然后重构代码以使用超级 bean 接口。

uber bean 在其 PostConstruct 中创建我需要的所有对象,并实现 ApplicationContextAware 以便它可以自动连接它们。这并不理想,但这是我能想到的最好的。

Spring 根本没有以类型安全的方式动态添加 bean 的好方法。

原文由 noah 发布,翻译遵循 CC BY-SA 3.0 许可协议

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

请参阅 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...
}

这有效地声明了您需要的 beans 并将它们注入到 Spring 的应用程序上下文中,每个配置一组 beans。您必须依赖一些 _命名模式_,然后在需要 时按名称自动装配 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. 如果你自己扫描类路径,我强烈建议你使用令人惊叹的 Reflections 库

  3. 您必须为每个 bean 定义手动设置所有属性和依赖项。每个 bean 定义都独立于其他 bean 定义,即您不能重用它们,不能将它们一个一个地放在另一个里面,等等。将它们想象成您正在以旧的 XML 方式声明 beans。

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

原文由 fps 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题