web-dev-qa-db-fra.com

Comment instancier des haricots gérés au printemps lors de l'exécution?

J'ai collé avec un simple refactoring de Java au printemps. L'application comporte un objet "Container" qui instancie ses pièces au moment de l'exécution. Laissez-moi vous expliquer avec le code:

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
    }
}

Pendant le chargement, le conteneur demande à un système externe de lui fournir des informations sur le nombre et la configuration de chaque RuntimeBean, puis crée des beans conformément aux spécifications données.

Le problème est: d'habitude quand on fait au printemps

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

notre objet est entièrement configuré et toutes les dépendances sont injectées. Mais dans mon cas, je dois instancier des objets qui nécessitent également une injection de dépendance après l'exécution de la méthode load () . Comment puis-je y parvenir?

J'utilise une configuration basée sur Java. J'ai déjà essayé de créer une usine pour RuntimeBeans:

public class BeanRuntimeFactory {

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

s'attendre à ce que @Bean fonctionne en mode dit "allégé". http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/Bean.html Malheureusement, je n'ai trouvé aucune différence avec le simple fait de créer RuntimeBean (); Voici un article avec un problème similaire: Comment faire pour gérer les haricots créés par FactoryBean spring?

Il existe également http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/factory/annotation/Configurable.html mais cela ressemble à un marteau dans mon cas.

J'ai aussi essayé ApplicationContext.getBean ("runtimeBean", arguments) où runtimeBean a une portée "Prototype", mais getBean est une solution épouvantable.

Upd1. Pour être plus concret, j'essaie de reformuler cette classe: https://github.com/Apache/lucene-solr/blob/trunk/solr/core/src/Java/org/ Apache/solr/core/CoreContainer.Java @ Voir la méthode #load () et trouver "return create (cd, false);"

Upd2. J'ai trouvé quelque chose d'assez intéressant appelé "injection de méthode de recherche" dans la documentation de printemps: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans .html # méthode d'injection de haricots à l'usine

Et aussi un billet jira intéressant https://jira.spring.io/browse/SPR-5192 où Phil Webb dit https://jira.spring.io/browse/SPR-5192?focusedCommentId=86051&page = com.atlassian.jira.plugin.system.issuetabpanels: comment-tabpanel # comment-86051 javax.inject.Provider doit être utilisé ici (cela me rappelle Guice).

Upd3. Il existe également http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/factory/config/ServiceLocatorFactoryBean.html

Upd4. Le problème avec toutes ces méthodes de "recherche" est qu'elles ne prennent pas en charge la transmission d'arguments .. J'ai également besoin de transmettre des arguments comme je le ferais avec applicationContext.getBean ("runtimeBean", arg1, arg2). On dirait que cela a été corrigé à un moment donné avec https://jira.spring.io/browse/SPR-7431

Upd5. Google Guice possède une fonctionnalité intéressante appelée AssistedInject. https://github.com/google/guice/wiki/AssistedInject

12
Vadim Kirilchuk

On dirait que j'ai trouvé une solution. Comme j'utilise une configuration basée sur Java, c'est encore plus simple que vous ne pouvez l'imaginer. Une méthode alternative en xml serait lookup-method, mais uniquement à partir de la version 4.1.X de Spring, car elle supporte le passage d'arguments à la méthode.

Voici un exemple de travail complet:

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

C'est tout.

Merci tout le monde.

5
Vadim Kirilchuk

Vous n'avez pas besoin de Container car tous les objets d'exécution doivent être créés, conservés et gérés par ApplicationContext. Pensez à une application Web, elles sont sensiblement les mêmes. Chaque demande contient données externes/informations sur l’environnement comme vous l’avez mentionné plus haut. Ce dont vous avez besoin est un bean de type prototype/requête tel que ExternalData ou EnvironmentInfo, capable de lire et de conserver les données d'exécution de manière statique, par exemple une méthode fabrique statique.

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

Si vous avez besoin d’un conteneur pour enregistrer les objets d’exécution, le code doit être

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
    }
}

Doc officiel Haricots Singleton avec dépendances prototype-haricots .

2
Anderson

Il est possible d’enregistrer les beans de manière dynamique en utilisant BeanFactoryPostProcesor. Ici, vous pouvez le faire pendant le démarrage de l’application (le contexte d’application de spring est initialisé). Vous ne pouvez pas enregistrer les haricots latet, mais vous pouvez utiliser l'injection de dépendance pour vos haricots, car ils deviennent de "vrais" haricots de printemps.

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;

}

Comme présenté ci-dessus, vous pouvez toujours utiliser l'injection de dépendances de Spring, car le post-processeur fonctionne dans le contexte d'application actuel.

1
walkeros

Une approche simple:

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

Au lieu d'utiliser SingletonBeanRegistry, vous pouvez utiliser ceci:

BeanFactory beanFactory = configContext.getBeanFactory();

Quoi qu'il en soit SingletonBeanBuilder étend HierarchicalBeanFactory et HierarchicalBeanFactory étend BeanFactory

1
Rzv Razvan