it-swarm.com.de

Wie kann man Spring Managed Beans zur Laufzeit instanziieren?

Ich habe mich mit einem einfachen Refactoring von reinem Java bis zum Frühling befunden. Die Anwendung verfügt über ein "Container" -Objekt, das seine Teile zur Laufzeit instanziiert. Lassen Sie mich mit dem Code erklären:

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

Während des Ladebehälters werden einige externe Systeme aufgefordert, Informationen zu Anzahl und Konfiguration der einzelnen RuntimeBeans bereitzustellen. Anschließend werden Beans gemäß der angegebenen Spezifikation erstellt.

Das Problem ist: normalerweise, wenn wir im Frühling tun

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

unser Objekt ist vollständig konfiguriert und es werden alle Abhängigkeiten eingefügt. In meinem Fall muss ich jedoch einige Objekte instanziieren, die nach dem Ausführen der load () -Methode auch Abhängigkeitseinspritzung benötigen .. Wie kann ich das erreichen?

Ich verwende Java-basierte Konfiguration. Ich habe bereits versucht, eine Factory für RuntimeBeans zu erstellen:

public class BeanRuntimeFactory {

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

erwarten, dass @Bean im sogenannten "Lite" -Modus arbeitet. http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/Bean.html Leider fand ich keinen Unterschied, wenn ich einfach neues RuntimeBean () mache. Hier ist ein Beitrag mit einer ähnlichen Ausgabe: Wie erhält man Beans, die von FactoryBean Spring erstellt werden?

Es gibt auch http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/factory/annotation/Configurable.html , aber es sieht in meinem Fall wie ein Hammer aus.

Ich habe auch ApplicationContext.getBean ("runtimeBean", args) ausprobiert, bei der RuntimeBean einen Prototypbereich hat, aber GetBean ist eine schreckliche Lösung.

Upd1. Um konkreter zu sein, versuche ich, diese Klasse umzuwandeln: https://github.com/Apache/lucene-solr/blob/trunk/solr/core/src/Java/org/ Apache/solr/core/CoreContainer.Java @ Siehe #load () - Methode und finde "return create (cd, false);"

Upd2. Ich habe in der Frühjahrsdokumentation recht interessante Dinge namens "Lookup Method Injection" gefunden: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans .html # Beans-Factory-Lookup-Methode-Injektion

Und auch ein interessantes Jira-Ticket https://jira.spring.io/browse/SPR-5192 wo Phil Webb sagt https://jira.spring.io/browse/SPR-5192?focusedCommentId=86051&page = com.atlassian.jira.plugin.system.issuetabpanels: comment-tabpanel # comment-86051 dass javax.inject.Provider hier verwendet werden sollte (es erinnert mich an Guice).

Upd3. Es gibt auch http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/factory/config/ServiceLocatorFactoryBean.html

Upd4. Das Problem bei all diesen 'Lookup'-Methoden ist, dass sie keine Argumente übergeben. Ich muss auch Argumente übergeben, wie ich es mit applicationContext.getBean ("runtimeBean", arg1, arg2) tun würde. Sieht aus, als wäre es irgendwann mit https://jira.spring.io/browse/SPR-7431 behoben worden.

Upd5. Google Guice verfügt über eine praktische Funktion namens AssistedInject. https://github.com/google/guice/wiki/AssistedInject

12
Vadim Kirilchuk

Sieht aus, als hätte ich eine Lösung gefunden. Da ich Java-basierte Konfiguration verwende, ist sie noch einfacher als Sie sich vorstellen können. Ein alternativer Weg in XML wäre die Lookup-Methode, jedoch nur ab der Frühjahrsversion 4.1.X, da sie die Übergabe von Argumenten an die Methode unterstützt.

Hier ist ein vollständiges Arbeitsbeispiel:

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

Das ist es.

Vielen Dank an alle.

5
Vadim Kirilchuk

Sie benötigen die Variable Container nicht, da alle Laufzeitobjekte von ApplicationContext erstellt, gehalten und verwaltet werden sollen. Stellen Sie sich eine Webanwendung vor, sie ist ziemlich gleich. Jede Anfrage enthält externe Daten/Umgebungsinformationen, wie Sie oben erwähnt haben. Was Sie benötigen, ist ein Bean für Prototyp-/Anforderungsbereiche wie ExternalData oder EnvironmentInfo, das Laufzeitdaten auf static - Weise lesen und halten kann, beispielsweise eine statische Factory-Methode.

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

Wenn Sie einen Container zum Speichern der Laufzeitobjekte benötigen, sollte der Code verwendet werden

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

Offizielle doc Singleton-Beans mit Abhängigkeiten von Prototyp-Beans .

2
Anderson

ich denke, dass Ihr Konzept durch die Verwendung falsch ist
RuntimeBean beanRuntime = createRuntimeBean();
Sie umgehen den Spring-Container und verwenden den regulären Konstruktor Java). Daher werden alle Anmerkungen zur Factory-Methode ignoriert und diese Bean wird niemals von Spring verwaltet

hier ist die Lösung, um mehrere Prototyp-Beans in einer Methode zu erstellen. Sie sieht nicht besonders gut aus, sollte aber funktionieren. Ich habe den Container in RuntimeBean als Beweis für das automatische Verdrahten im Protokoll angezeigt. Außerdem können Sie im Protokoll sehen, dass jedes Bean eine neue Instanz des Prototyps ist, wenn Sie dies ausführen .

'

@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

Es ist möglich, Beans dynamisch zu registrieren, indem Sie BeanFactoryPostProcesor verwenden. Hier können Sie das tun, während die Anwendung bootet (der Anwendungskontext von Spring wird initialisiert). Bohnen können Sie nicht registrieren, aber Sie können auch Abhängigkeitsinjektion für Ihre Bohnen verwenden, da diese zu "echten" Spring-Bohnen werden.

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;

}

Wie oben dargestellt, können Sie weiterhin Spring's Dependency Injection verwenden, da der Postprozessor mit dem tatsächlichen Anwendungskontext arbeitet.

1
walkeros

Ein einfacher Ansatz:

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

Anstelle von SingletonBeanRegistry können Sie Folgendes verwenden:

BeanFactory beanFactory = configContext.getBeanFactory();

Wie auch immer, SingletonBeanBuilder erweitert HierarchicalBeanFactory und HierarchicalBeanFactory BeanFactory

1
Rzv Razvan