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