web-dev-qa-db-ja.com

実行時にSpring管理Beanをインスタンス化する方法は?

プレーンJavaからspringへ。アプリケーションは実行時にその部分をインスタンス化する「コンテナ」オブジェクトを持っています。コードで説明しましょう。

public class Container {
    private List<RuntimeBean> runtimeBeans = new ArrayList<RuntimeBean>();

    public void load() {
        // repeated several times depending on external data/environment
        RuntimeBean beanRuntime = createRuntimeBean();
        runtimeBeans.add(beanRuntime);
    }

    public RuntimeBean createRuntimeBean() {
         // should create bean which internally can have some 
         // spring annotations or in other words
         // should be managed by spring
    }
}

基本的に、ロードコンテナーは外部システムに各RuntimeBeanの数と構成に関する情報を提供するよう要求し、指定された仕様に従ってBeanを作成します。

問題は、通常、春に行うときです。

ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfiguration.class);
Container container = (Container) context.getBean("container");

オブジェクトは完全に構成されており、すべての依存関係が注入されています。しかし、私の場合、load()メソッドを実行した後、依存関係の注入も必要とするいくつかのオブジェクトをインスタンス化する必要があります。どうすればそれを達成できますか?

Javaベースの構成を使用しています。 RuntimeBeansのファクトリーを作成しようとしました:

public class BeanRuntimeFactory {

    @Bean
    public RuntimeBean createRuntimeBean() {
        return new RuntimeBean();
    }
}

@liteがいわゆる「ライト」モードで動作することを期待しています。 http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/Bean.html 残念ながら、新しいRuntimeBean( );同様の問題の投稿は次のとおりです。 FactoryBean spring管理によって作成されたBeanを取得する方法

http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/factory/annotation/Configurable.html もありますが、私の場合。

また、runtimeBeanに「プロトタイプ」スコープがあるApplicationContext.getBean( "runtimeBean"、args)を試しましたが、getBeanはひどい解決策です。

Upd1。具体的には、このクラスをリファクタリングしようとしています: https://github.com/Apache/lucene-solr/ blob/trunk/solr/core/src/Java/org/Apache/solr/core/CoreContainer.Java @see #load()メソッドで「return create(cd、false);」を見つけます

Upd2。春のドキュメントで「lookup method injection」と呼ばれる非常に興味深いものを見つけました: http://docs.spring.io/spring /docs/current/spring-framework-reference/html/beans.html#beans-factory-lookup-method-injection

また、興味深いjiraチケット https://jira.spring.io/browse/SPR-5192 Phil Webbが言うところ https://jira.spring.io/browse/SPR- 5192?focusedCommentId = 86051&page = com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-86051 ここではjavax.inject.Providerを使用する必要があります(Guiceを思い出させます)。

Upd3。http://docs.spring.io/spring/docs/current/javadoc-api/org/springframeworkもあります/beans/factory/config/ServiceLocatorFactoryBean.html

Upd4。これらのすべての「ルックアップ」メソッドの問題は、引数の受け渡しをサポートしていないことです。また、引数を渡す必要もあります。 applicationContext.getBean( "runtimeBean"、arg1、arg2)を使用します。 https://jira.spring.io/browse/SPR-7431 で修正されたようです

Upd5。Google Guiceには、AssistedInjectと呼ばれるすばらしい機能があります。 https://github.com/google/guice/wiki/AssistedInject

14
Vadim Kirilchuk

私は解決策を見つけたようです。私はJavaベースの設定を使用しているので、想像以上に簡単です。xmlの代替方法はlookup-methodです。方法。

完全な作業例は次のとおりです。

public class Container {
    private List<RuntimeBean> runtimeBeans = new ArrayList<RuntimeBean>();
    private RuntimeBeanFactory runtimeBeanFactory;

    public void load() {
        // repeated several times depending on external data/environment
        runtimeBeans.add(createRuntimeBean("Some external info1"));
        runtimeBeans.add(createRuntimeBean("Some external info2"));
    }

    public RuntimeBean createRuntimeBean(String info) {
         // should create bean which internally can have some 
         // spring annotations or in other words
         // should be managed by spring
         return runtimeBeanFactory.createRuntimeBean(info)
    }

    public void setRuntimeBeanFactory(RuntimeBeanFactory runtimeBeanFactory) {
        this.runtimeBeanFactory = runtimeBeanFactory
    }
}

public interface RuntimeBeanFactory {
    RuntimeBean createRuntimeBean(String info);
}

//and finally
@Configuration
public class ApplicationConfiguration {

    @Bean
    Container container() {
        Container container = new Container(beanToInject());
        container.setBeanRuntimeFactory(runtimeBeanFactory());
        return container;
    }

    // LOOK HOW IT IS SIMPLE IN THE Java CONFIGURATION
    @Bean 
    public BeanRuntimeFactory runtimeBeanFactory() {
        return new BeanRuntimeFactory() {
            public RuntimeBean createRuntimeBean(String beanName) {
                return runtimeBean(beanName);
            }
        };
    }

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    RuntimeBean runtimeBean(String beanName) {
        return new RuntimeBean(beanName);
    }
}

class RuntimeBean {
    @Autowired
    Container container;
}

それでおしまい。

みんな、ありがとう。

5
Vadim Kirilchuk

すべてのランタイムオブジェクトをContainerで作成、保持、管理する必要があるため、ApplicationContextは必要ありません。 Webアプリケーションについて考えてみてください。それらはほとんど同じです。上記のように、各リクエストには外部データ/環境情報が含まれます。必要なのは、ExternalDataEnvironmentInfoのようなプロトタイプ/リクエストスコープBeanです。これは、staticの方法で実行時データを読み取り、保持できます。ファクトリーメソッド。

<bean id="externalData" class="ExternalData"
    factory-method="read" scope="prototype"></bean>

<bean id="environmentInfo" class="EnvironmentInfo"
    factory-method="read" scope="prototype/singleton"></bean>

<bean class="RuntimeBean" scope="prototype">
    <property name="externalData" ref="externalData">
    <property name="environmentInfo" ref="environmentInfo">
</bean> 

ランタイムオブジェクトを保存するコンテナが必要な場合、コードは

class Container {

    List list;
    ApplicationContext context;//injected by spring if Container is not a prototype bean

    public void load() {// no loop inside, each time call load() will load a runtime object
        RuntimeBean bean = context.getBean(RuntimeBean.class); // see official doc
        list.add(bean);// do whatever
    }
}

公式ドキュメント プロトタイプBean依存関係を持つシングルトンBean

2
Anderson

私はあなたの概念が間違っていると思う
RuntimeBean beanRuntime = createRuntimeBean();
Springコンテナをバイパスし、通常のJavaコンストラクタを使用することに頼っています。したがって、ファクトリメソッドの注釈は無視され、このBeanはSpringによって管理されません。

これは1つのメソッドで複数のプロトタイプBeanを作成するソリューションです。見た目はよくありませんが、動作するはずです、ログに表示される自動配線の証拠としてRuntimeBeanのコンテナを自動配線しました。

'

@Configuration
@ComponentScan
@EnableAutoConfiguration
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);

        ApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        Container container = (Container) context.getBean("container");
        container.load();
    }
}

@Component
class Container {
    private List<RuntimeBean> runtimeBeans = new ArrayList<RuntimeBean>();
    @Autowired
    ApplicationContext context;

    @Autowired
    private ObjectFactory<RuntimeBean> myBeanFactory;

    public void load() {

        // repeated several times depending on external data/environment
        for (int i = 0; i < 10; i++) {
            // **************************************
            // COMENTED OUT THE WRONG STUFFF 
            // RuntimeBean beanRuntime = context.getBean(RuntimeBean.class);
            // createRuntimeBean();
            // 
            // **************************************

            RuntimeBean beanRuntime = myBeanFactory.getObject();
            runtimeBeans.add(beanRuntime);
            System.out.println(beanRuntime + "  " + beanRuntime.container);
        }
    }

    @Bean
    @Scope(BeanDefinition.SCOPE_PROTOTYPE)
    public RuntimeBean createRuntimeBean() {
        return new RuntimeBean();
    }
}

// @Component

class RuntimeBean {
    @Autowired
    Container container;

} '
2
mariubog

BeanFactoryPostProcesorを使用して、Beanを動的に登録できます。ここでは、アプリケーションの起動中にそれを行うことができます(Springのアプリケーションコンテキストは初期化されています)。 Beanを遅延登録することはできませんが、一方で、Beanが「真の」Spring Beanになるので、Beanの依存性注入を利用できます。

public class DynamicBeansRegistar implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        if (! (beanFactory instanceof BeanDefinitionRegistry))  {
            throw new RuntimeException("BeanFactory is not instance of BeanDefinitionRegistry);
        }   
        BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;

        // here you can fire your logic to get definition for your beans at runtime and 
        // then register all beans you need (possibly inside a loop)

        BeanDefinition dynamicBean = BeanDefinitionBuilder.    
             .rootBeanDefinition(TheClassOfYourDynamicBean.class) // here you define the class
             .setScope(BeanDefinition.SCOPE_SINGLETON)
             .addDependsOn("someOtherBean") // make sure all other needed beans are initialized

             // you can set factory method, constructor args using other methods of this builder

             .getBeanDefinition();

        registry.registerBeanDefinition("your.bean.name", dynamicBean);           

}

@Component
class SomeOtherClass {

    // NOTE: it is possible to autowire the bean
    @Autowired
    private TheClassOfYourDynamicBean myDynamicBean;

}

上記のように、ポストプロセッサは実際のアプリケーションコンテキストで動作するため、Springの依存性注入を引き続き使用できます。

1
walkeros

簡単なアプローチ:

@Component
public class RuntimeBeanBuilder {

    @Autowired
    private ApplicationContext applicationContext;

    public MyObject load(String beanName, MyObject myObject) {
        ConfigurableApplicationContext configContext = (ConfigurableApplicationContext) applicationContext;
        SingletonBeanRegistry beanRegistry = configContext.getBeanFactory();

        if (beanRegistry.containsSingleton(beanName)) {
            return beanRegistry.getSingleton(beanName);
        } else {
            beanRegistry.registerSingleton(beanName, myObject);

            return beanRegistry.getSingleton(beanName);
        }
    }
}


@Service
public MyService{

   //inject your builder and create or load beans
   @Autowired
   private RuntimeBeanBuilder builder;

   //do something
}

SingletonBeanRegistryを使用する代わりに、これを使用できます。

BeanFactory beanFactory = configContext.getBeanFactory();

とにかく、SingletonBeanBuilderはHierarchicalBeanFactoryを拡張し、HierarchicalBeanFactoryはBeanFactoryを拡張します

1
Rzv Razvan