web-dev-qa-db-ja.com

Spring-一連のBeanをプログラムで生成する

構成リスト内の構成ごとに12個程度のBeanを生成する必要があるDropwizardアプリケーションがあります。ヘルスチェック、クォーツスケジューラなどのようなもの。

このようなもの:

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

このようなBeanを必要とするMyConfigurationのインスタンスが複数あります。今、これらの定義をコピーして貼り付け、新しい構成ごとに名前を変更する必要があります。

何らかの方法で構成クラスを反復処理し、各クラスのBean定義のセットを生成できますか?

サブクラス化ソリューションや、同じコードをコピーして貼り付け、新しいサービスを追加するたびにメソッドの名前を変更することなく、タイプセーフなものであれば問題ありません。

編集:これらのBeanに依存する他のコンポーネントがあることを追加する必要があります(それらはCollection<HealthCheck> 例えば。)

25
noah

私が思いついた「最良の」アプローチは、すべてのQuartz構成とスケジューラを1つのuber beanにラップし、それをすべて手動で接続してから、コードをリファクタリングしてuber beanインターフェースで動作させることでした。

Uber Beanは、PostConstructで必要なすべてのオブジェクトを作成し、それらを自動接続できるようにApplicationContextAwareを実装します。それは理想的ではありませんが、私が思いつく最高のものでした。

Springには、タイプセーフな方法でBeanを動的に追加する良い方法がありません。

0
noah

そのため、新しいBeanをオンザフライで宣言し、それらを一般的なBeanであるかのようにSpringのアプリケーションコンテキストに注入する必要があります。つまり、プロキシ、後処理などの対象となります。 。

BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry() method javadocsを参照してください。これはまさに必要なものです。Springのアプリケーションコンテキストを変更できるためです通常のBean定義がロードされた後but単一の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...
}
_

これにより、必要なBeanを効率的に宣言し、それらをSpringのアプリケーションコンテキストに挿入します(構成ごとに1セットの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. 自分でクラスパスをスキャンする場合は、すばらしい Reflections library を使用することを強くお勧めします。

  3. すべてのプロパティと依存関係を各Bean定義に手動で設定する必要があります。各Bean定義は他のBean定義から独立しています。つまり、それらを再利用したり、相互に設定したりすることはできません。古いXMLの方法でBeanを宣言しているかのように考えてください。

  4. 詳細については、 BeanDefinitionBuilder javadocs および GenericBeanDefinition javadocs を確認してください。

次のようなことができるはずです。

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

}
7
micha

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

}

注意すべきことの1つは、構成クラスの属性としてカスタムBeanを作成し、@ PostConstructメソッドで初期化することです。このようにして、オブジェクトをBeanとして登録し(@Autowireおよび@Injectが期待どおりに機能する)、後でそれを必要とするBeanのコンストラクター注入で同じインスタンスを使用できます。サブクラスが作成されたオブジェクトを使用できるように、属性の可視性はprotectedに設定されます。

保持しているインスタンスは実際にはSpringプロキシではないため、いくつかの問題が発生する可能性があります(起動しないなどの問題)。次のように、登録後にBeanを取得することをお勧めします。

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

ここにチップを入れます。他の人は、設定をインジェクトするBeanを作成する必要があると述べています。次に、そのBeanは設定を使用して他のBeanを作成し、それらをコンテキストに挿入します(何らかの形で注入する必要があります)。

他の誰かが取り上げたとは思いませんが、他のBeanはこれらの動的に作成されたBeanに依存するということです。つまり、動的Beanファクトリは、依存Beanの前にインスタンス化する必要があります。あなたはこれを(アノテーションの世界で)行うことができます

@DependsOn("myCleverBeanFactory")

賢いBeanファクトリがどのタイプのオブジェクトであるかについては、他の人がこれを行うより良い方法を推奨しています。しかし、私が正しく覚えていれば、あなたは実際に古い春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
   } 

..

2
Richard

すべてのConfigurationクラスによって拡張される基本構成クラスを作成する必要があります。その後、次のようにすべての構成クラスを反復処理できます。

// 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.
}
0
Mithun