2010-05-25 7 views
38

Avec une fabrique de beans Spring configurée en XML, je peux facilement instancier plusieurs instances de la même classe avec différents paramètres. Comment puis-je faire de même avec les annotations? Je voudrais quelque chose comme ceci:Instanciation de plusieurs beans de la même classe avec des annotations Spring

@Component(firstName="joe", lastName="smith") 
@Component(firstName="mary", lastName="Williams") 
public class Person { /* blah blah */ } 
+2

Je ne pense pas que vous le pouvez. '@ Component' est une commodité légère, mais pas de substitut pour la config XML. – skaffman

+7

Je pense qu'il est regrettable que XML soit considéré comme le bon moyen de configurer une application. –

+0

Ce n'est pas parce que '@ Component' ne le peut pas que XML est la solution. Je ne sais pas à propos de 2011, mais vous pouvez obtenir le même effet dans un 'Java Configuration' juste maintenant. – apottere

Répondre

15

Oui, vous pouvez le faire avec l'aide de votre implémentation BeanFactoryPostProcessor personnalisée.

Voici un exemple simple.

Supposons que nous ayons deux composants. L'un est la dépendance pour l'autre.

Première composante:

import org.springframework.beans.factory.InitializingBean; 
import org.springframework.util.Assert; 

public class MyFirstComponent implements InitializingBean{ 

    private MySecondComponent asd; 

    private MySecondComponent qwe; 

    public void afterPropertiesSet() throws Exception { 
     Assert.notNull(asd); 
     Assert.notNull(qwe); 
    } 

    public void setAsd(MySecondComponent asd) { 
     this.asd = asd; 
    } 

    public void setQwe(MySecondComponent qwe) { 
     this.qwe = qwe; 
    } 
} 

Comme vous pouvez le voir, il n'y a rien de spécial sur ce composant. Il dépend de deux instances différentes de MySecondComponent.

Deuxième composante:

import org.springframework.beans.factory.FactoryBean; 
import org.springframework.beans.factory.annotation.Qualifier; 


@Qualifier(value = "qwe, asd") 
public class MySecondComponent implements FactoryBean { 

    public Object getObject() throws Exception { 
     return new MySecondComponent(); 
    } 

    public Class getObjectType() { 
     return MySecondComponent.class; 
    } 

    public boolean isSingleton() { 
     return true; 
    } 
} 

Il est un peu plus délicat. Voici deux choses à expliquer. Premier - @Qualifier - annotation qui contient les noms des beans MySecondComponent. C'est un standard, mais vous êtes libre de mettre en place le vôtre. Vous verrez un peu plus tard pourquoi.

La deuxième chose à mentionner est l'implémentation FactoryBean. Si bean implémente cette interface, il est prévu de créer d'autres instances. Dans notre cas, il crée des instances avec le type MySecondComponent.

La partie la plus délicate est la mise en œuvre BeanFactoryPostProcessor:

import java.util.Map; 

import org.springframework.beans.BeansException; 
import org.springframework.beans.factory.annotation.Qualifier; 
import org.springframework.beans.factory.config.BeanFactoryPostProcessor; 
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 


public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { 
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { 
     Map<String, Object> map = configurableListableBeanFactory.getBeansWithAnnotation(Qualifier.class); 
     for(Map.Entry<String,Object> entry : map.entrySet()){ 
      createInstances(configurableListableBeanFactory, entry.getKey(), entry.getValue()); 
     } 

    } 

    private void createInstances(
      ConfigurableListableBeanFactory configurableListableBeanFactory, 
      String beanName, 
      Object bean){ 
     Qualifier qualifier = bean.getClass().getAnnotation(Qualifier.class); 
     for(String name : extractNames(qualifier)){ 
      Object newBean = configurableListableBeanFactory.getBean(beanName); 
      configurableListableBeanFactory.registerSingleton(name.trim(), newBean); 
     } 
    } 

    private String[] extractNames(Qualifier qualifier){ 
     return qualifier.value().split(","); 
    } 
} 

Que faut-il faire? Il passe en revue tous les beans annotés avec @Qualifier, extrait les noms de l'annotation, puis crée manuellement des beans de ce type avec des noms spécifiés.

Voici une configuration Spring:

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" 
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> 

    <bean class="MyBeanFactoryPostProcessor"/> 

    <bean class="MySecondComponent"/> 


    <bean name="test" class="MyFirstComponent"> 
     <property name="asd" ref="asd"/> 
     <property name="qwe" ref="qwe"/> 
    </bean> 

</beans> 

La dernière chose à remarquer ici est bien que vous pouvez le faire vous ne devrait pas à moins qu'il est indispensable, parce que c'est un moyen pas vraiment naturel de configuration. Si vous avez plusieurs instances de classe, il est préférable de s'en tenir à la configuration XML.

+0

Est-ce que 'isSingleton' ne devrait pas retourner' false'? – OrangeDog

+1

Aucun des beans créés ne sera traité, probablement parce que 'BeanFactoryPostProcessor' est exécuté avant la création de' BeanPostProcessor'. La documentation dit aussi de ne pas instancier de beans (par exemple 'getBeansWithAnnotation') pendant' postProcessBeanFactory'. – OrangeDog

+1

J'ai cherché comment éviter certains de ces problèmes: http://stackoverflow.com/a/41489377/476716 – OrangeDog

28

Ce n'est pas possible. Vous obtenez une exception en double.

Il est également loin d'être optimal avec des données de configuration comme celle-ci dans vos classes d'implémentation.

Si vous voulez utiliser les annotations, vous pouvez configurer votre classe avec Java config:

@Configuration 
public class PersonConfig { 

    @Bean 
    public Person personOne() { 
     return new Person("Joe", "Smith"); 
    } 

    @Bean 
    public Person personTwo() { 
     return new Person("Mary", "Williams"); 
    } 
} 
+1

Ce n'est pas vraiment impossible, mais c'est inutilement difficile. – wax

+2

Mais vous utilisez une usine distincte pour chaque haricot de printemps différent que vous créez. Je crois qu'il veut que la configuration d'annotation soit dans la classe Person. Et je ne vois qu'une annotation dans votre exemple relativement avancé et cette annotation ne supporte pas plusieurs beans différents. Mais je suis impressionné par la complexité de votre solution :-) – Espen

+0

Comment utilisez-vous la valeur Joe à partir d'un fichier de propriétés? –

8

Je viens de devoir résoudre un cas similaire. Cela peut fonctionner si vous pouvez redéfinir la classe.

// This is not a @Component 
public class Person { 

} 

@Component 
public PersonOne extends Person { 
    public PersonOne() { 
     super("Joe", "Smith"); 
    } 
} 

@Component 
public PersonTwo extends Person { 
    public PersonTwo() { 
    super("Mary","Williams"); 
    } 
} 

Ensuite, il suffit d'utiliser PersonOne ou PersonTwo chaque fois que vous devez lier automatiquement une instance spécifique, partout ailleurs utiliser simplement personne.

+4

Il s'agit d'une approche Java simple - beaucoup plus semblable à Spring et moins de code utiliserait les annotations de Spring @Qualifier. – Pavel

1

Inspiré par wax's answer, la mise en œuvre peut être plus sûr et pas sauter d'autres post-traitement si les définitions sont ajoutées, non construits singletons:

public interface MultiBeanFactory<T> { // N.B. should not implement FactoryBean 
    T getObject(String name) throws Exception; 
    Class<?> getObjectType(); 
    Collection<String> getNames(); 
} 

public class MultiBeanFactoryPostProcessor implements BeanFactoryPostProcessor { 
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { 
    BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; 
    Map<String, MultiBeanFactory> factories = beanFactory.getBeansOfType(MultiBeanFactory.class); 

    for (Map.Entry<String, MultiBeanFactory> entry : factories.entrySet()) { 
     MultiBeanFactory factoryBean = entry.getValue(); 
     for (String name : factoryBean.getNames()) { 
     BeanDefinition definition = BeanDefinitionBuilder 
      .genericBeanDefinition(factoryBean.getObjectType()) 
      .setScope(BeanDefinition.SCOPE_SINGLETON) 
      .setFactoryMethod("getObject") 
      .addConstructorArgValue(name) 
      .getBeanDefinition(); 
     definition.setFactoryBeanName(entry.getKey()); 
     registry.registerBeanDefinition(entry.getKey() + "_" + name, definition); 
     } 
    } 
    } 
} 

@Configuration 
public class Config { 
    @Bean 
    public static MultiBeanFactoryPostProcessor() { 
    return new MultiBeanFactoryPostProcessor(); 
    } 

    @Bean 
    public MultiBeanFactory<Person> personFactory() { 
    return new MultiBeanFactory<Person>() { 
     public Person getObject(String name) throws Exception { 
     // ... 
     } 
     public Class<?> getObjectType() { 
     return Person.class; 
     } 
     public Collection<String> getNames() { 
     return Arrays.asList("Joe Smith", "Mary Williams"); 
     } 
    }; 
    } 
} 

Les noms de haricots pourrait encore venir de partout, comme @Qualifier exemple de cire . Il existe diverses autres propriétés sur la définition du bean, y compris la possibilité d'hériter de l'usine elle-même.

+0

C'est cool. J'hésite à violer la contrainte Spring cycle de vie sur les types 'BeanFactoryPostProcessor' en générant des beans dans la phase de post-traitement de l'usine de haricots. Bien que les beans générés ici aient seulement l'intention de changer les définitions de beans (qui, à eux seuls, devraient être sûrs pendant cette phase), il est important de savoir que tous les autres beans requis par la classe '@ Configuration' peuvent également être instanciés. Cela peut entraîner des problèmes car les contraintes de cycle de vie sont violées. Tant que cela est bien documenté, compris et que les changements sont examinés attentivement, c'est une bonne solution. –

Questions connexes