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

noa*_*oah 25 java spring spring-mvc dropwizard

我有一个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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

Fed*_*ner 38

所以你需要动态地声明新的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);
        }
    }

    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...
}
Run Code Online (Sandbox Code Playgroud)

这有效地声明了您需要的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;

    ...

}
Run Code Online (Sandbox Code Playgroud)

笔记:

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

  2. 如果你自己扫描类路径,我强烈建议你使用惊人的Reflections库.

  3. 您必须手动设置每个bean定义的所有属性和依赖项.每个bean定义都与其他bean定义无关,即你不能重用它们,将它们放在另一个bean中,等等.把它们想象成你用旧的XML方式声明bean.

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


mic*_*cha 7

你应该可以做这样的事情:

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

}
Run Code Online (Sandbox Code Playgroud)

  • 这不起作用,因为我需要构造 bean,将它们注入,然后使用它们来创建其他 bean。 (2认同)

Apo*_*psa 6

只是扩展 Michas 的答案 - 如果我这样设置,他的解决方案就有效:

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

}
Run Code Online (Sandbox Code Playgroud)

需要注意的一件事是,我正在创建自定义 bean 作为配置类的属性,并在 @PostConstruct 方法中初始化它们。通过这种方式,我将对象注册为 bean(因此 @Autowire 和 @Inject 按预期工作),稍后我可以在需要它的 bean 的构造函数注入中使用相同的实例。属性可见性设置为受保护,以便子类可以使用创建的对象。

由于我们持有的实例实际上不是 Spring 代理,因此可能会出现一些问题(方面未触发等)。在注册后检索 bean 实际上可能是一个好主意,如下所示:

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


Ric*_*ard 6

我就在这凑钱。其他人提到您需要创建一个 bean,将您的配置注入其中。然后该 bean 将使用您的配置创建其他 bean 并将它们插入到上下文中(您还需要以一种或另一种形式注入)。

我认为其他人没有注意到的是,您已经说过其他 bean 将依赖于这些动态创建的 bean。这意味着您的动态 bean 工厂必须在依赖 bean 之前实例化。您可以使用(在注释世界中)执行此操作

@DependsOn("myCleverBeanFactory")
Run Code Online (Sandbox Code Playgroud)

至于你的聪明豆工厂是什么类型的对象,其他人已经推荐了更好的方法来做到这一点。但是,如果我没记错的话,您实际上可以在旧的 Spring 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
   } 
Run Code Online (Sandbox Code Playgroud)

..


noa*_*oah -5

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

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

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