2010-05-25 12 views
38

Con un factory bean bean Spring configurato, posso facilmente istanziare più istanze della stessa classe con parametri diversi. Come posso fare lo stesso con le annotazioni? Vorrei qualcosa del genere:Istanziazione di più bean della stessa classe con le annotazioni Spring

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

Non penso che tu possa. '@ Componente' è una comodità leggera, ma non sostituisce la configurazione XML. – skaffman

+7

Penso sia deplorevole che XML sia considerato il modo corretto di configurare un'applicazione. –

+0

Solo perché '@ Component' non può farlo, non significa che XML sia la soluzione. Non so il 2011, ma ora puoi ottenere lo stesso effetto in una 'configurazione @ java '. – apottere

risposta

15

Sì, lo si può fare con l'aiuto della tua implementazione personalizzata BeanFactoryPostProcessor.

Ecco un semplice esempio.

Supponiamo di avere due componenti. Uno è dipendenza per un altro.

Prima componente:

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

Come si può vedere, non c'è niente di speciale in questo componente. Ha dipendenza da due diverse istanze di MySecondComponent.

Seconda componente:

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

E 'un po' più complicato. Qui ci sono due cose da spiegare. Il primo - @Qualifier - annotazione che contiene i nomi dei bean MySecondComponent. È uno standard, ma sei libero di implementare il tuo. Vedrai un po 'più tardi perché.

La seconda cosa da menzionare è l'implementazione di FactoryBean. Se il bean implementa questa interfaccia, è prevista la creazione di altre istanze. Nel nostro caso crea istanze con tipo MySecondComponent.

La parte più difficile è la realizzazione 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(","); 
    } 
} 

Che cosa fa? Passa attraverso tutti i bean annotati con @Qualifier, estrae i nomi dall'annotazione e quindi crea manualmente i bean di questo tipo con nomi specifici.

Ecco una configurazione Primavera:

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

Ultima cosa da notare qui è anche se si può farlo si non dovrebbe meno che non si è d'obbligo, perché questo è un modo non proprio naturale di configurazione. Se hai più di un'istanza di classe, è meglio attenersi alla configurazione XML.

+0

Non dovrebbe 'isSingleton' return' false'? – OrangeDog

+1

Nessuno dei bean creati verrà post-elaborato, presumibilmente perché 'BeanFactoryPostProcessor' viene eseguito prima che sia stato creato qualsiasi' BeanPostProcessor'. La documentazione dice anche di non istanziare alcun bean (ad esempio 'getBeansWithAnnotation') durante' postProcessBeanFactory'. – OrangeDog

+1

Ho scoperto come evitare alcuni di questi problemi: http://stackoverflow.com/a/41489377/476716 – OrangeDog

28

Non è possibile. Ottieni un'eccezione duplicata.

Inoltre, non è ottimale con i dati di configurazione come questo nelle classi di implementazione.

Se si desidera utilizzare le annotazioni, è possibile configurare la classe con 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

Non è davvero impossibile, ma è inutilmente ingannevole. – wax

+2

Ma stai usando una factory separata per ogni bean Spring che crei. Credo che voglia che la configurazione dell'annotazione sia nella classe Person. E posso vedere solo un'annotazione nel tuo esempio relativamente avanzato e quell'annotazione non supporta molti fagioli diversi. Ma sono comunque impressionato dalla complessità della tua soluzione :-) – Espen

+0

come usi il valore di Joe da un file di proprietà? –

8

Ho dovuto solo risolvere un caso simile. Questo può funzionare se puoi ridefinire 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"); 
    } 
} 

Poi basta utilizzare PersonOne o PersonTwo ogni volta che dovete autowire una specifica istanza, tutto il resto basta usare persona.

+4

Questo è un semplice approccio di Java: molto più Spring e il meno codice utilizzerebbero le annotazioni Spring @Qualifier. – Pavel

1

Ispirato wax's answer, l'attuazione può essere più sicuro e non saltare altri post-elaborazione, se si aggiungono le definizioni, single non costruiti:

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

I nomi di soia potrebbe ancora venire da qualsiasi luogo, come @Qualifier esempio di cera . Ci sono varie altre proprietà sulla definizione del bean, inclusa la possibilità di ereditare dalla fabbrica stessa.

+0

Questo è bello. Esito a violare il vincolo del ciclo di vita di Spring sui tipi 'BeanFactoryPostProcessor' generando i bean nella fase di post processor del bean factory. Anche se i bean qui generati hanno solo l'intenzione di cambiare le definizioni dei bean (che, da solo, dovrebbero essere sicuri durante questa fase), è importante sapere che qualsiasi altro bean richiesto dalla classe '@ Configuration' può anche essere istanziato. Questo può portare a problemi poiché i vincoli del ciclo di vita sono violati. Finché questo è ben documentato, compreso e le modifiche vengono esaminate attentamente, questa è una buona soluzione. –

Problemi correlati