2010-05-25 11 views
38

Dzięki skonfigurowanej w języku XML fabryce ziaren sprężyn, mogę łatwo utworzyć instancje wielu wystąpień tej samej klasy o różnych parametrach. Jak mogę zrobić to samo z adnotacjami? Chciałbym coś takiego:Tworzenie wielu ziaren tej samej klasy z adnotacjami wiosennymi

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

Nie sądzę, że możesz. '@ Component' jest lekką wygodą, ale nie zastępuje konfiguracji XML. – skaffman

+7

Myślę, że szkoda, że ​​XML jest uważany za właściwy sposób konfigurowania aplikacji. –

+0

To, że '@ Component' nie może tego zrobić, nie oznacza, że ​​XML jest rozwiązaniem. Nie wiem o 2011 r., Ale można uzyskać ten sam efekt w java '@ Configuration". – apottere

Odpowiedz

15

Tak, można zrobić to z pomocą swojej niestandardowej implementacji BeanFactoryPostProcessor.

Oto prosty przykład.

Załóżmy, że mamy dwa elementy. Jednym z nich jest zależność dla drugiego.

Pierwszy składnik:

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

Jak można zobaczyć, nie ma nic szczególnego w tym składnikiem. Ma zależność od dwóch różnych instancji MySecondComponent.

Drugi komponent:

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

To trochę bardziej skomplikowane. Oto dwie rzeczy do wyjaśnienia. Pierwszy - @Qualifier - adnotacja, która zawiera nazwy komponentów bean MySecondComponent. Jest to standardowa, ale możesz zaimplementować własną. Zobaczysz nieco później, dlaczego.

Drugą rzeczą, o której warto wspomnieć, jest wdrożenie FactoryBean. Jeśli komponent bean implementuje ten interfejs, ma on na celu utworzenie kilku innych instancji. W naszym przypadku tworzy wystąpienia z typem MySecondComponent.

Najtrudniejsza część jest realizacja 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(","); 
    } 
} 

Co on robi? Przechodzi przez wszystkie fasole opatrzone komentarzem @Qualifier, wyodrębnia nazwy z adnotacji, a następnie ręcznie tworzy fasole tego typu o określonych nazwach.

Oto Wiosna config:

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

Ostatnią rzeczą, aby zauważyć tutaj jest chociaż może zrobić to ty nie powinny chyba że jest to konieczne, ponieważ jest to naprawdę nie sposób naturalny konfiguracji. Jeśli masz więcej niż jedną instancję klasy, lepiej trzymać się konfiguracji XML.

+0

Czy 'isSingleton' nie powinien zwracać' fałszu'? – OrangeDog

+1

Żaden z tworzonych przez to ziarno nie zostanie przetworzony, prawdopodobnie dlatego, że 'BeanFactoryPostProcessor' działa przed utworzeniem jakiegokolwiek' BeanPostProcessora'. Dokumentacja mówi również, aby nie tworzyć instancji żadnych komponentów bean (np. 'GetBeansWithAnnotation') podczas' postProcessBeanFactory'. – OrangeDog

+1

Dowiedziałem się, jak uniknąć niektórych z tych problemów: http://stackoverflow.com/a/41489377/476716 – OrangeDog

28

To niemożliwe. Otrzymasz duplikat wyjątku.

Jest także daleki od optymalnego z takimi danymi konfiguracyjnymi jak w klasach implementacji.

Jeśli chcesz użyć adnotacji, można skonfigurować swoją klasę z 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

To naprawdę niemożliwe, ale jest niepotrzebnie trudne. – wax

+2

Ale używasz oddzielnej fabryki dla każdego innego komponentu Spring Spring, który tworzysz. Sądzę, że chce, aby konfiguracja adnotacji znajdowała się w klasie Person. I widzę tylko jedną adnotację w twoim stosunkowo zaawansowanym przykładzie i ta adnotacja nie obsługuje kilku różnych komponentów. Ale jestem pod wrażeniem złożoności twojego rozwiązania :-) – Espen

+0

jak używasz wartości Joe z pliku właściwości? –

8

Po prostu musiałem rozwiązać podobny przypadek. Może to działać, jeśli możesz ponownie zdefiniować klasę.

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

Następnie wystarczy użyć PersonOne lub PersonTwo, gdy trzeba zainicjować konkretną instancję, wszędzie indziej wystarczy użyć Person.

+4

To jest zwykłe podejście Java - o wiele więcej Wiosenny i mniejszy kod korzystałby z adnotacji Spring @Qualifier. – Pavel

1

Zainspirowany wax's answer, realizacja może być bezpieczniejsze i nie pominąć innych post-processing, czy definicje są dodawane, a nie zbudowane 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"); 
     } 
    }; 
    } 
} 

Nazwy fasoli nadal może pochodzić z dowolnego miejsca, takie jak wosk za @Qualifier przykład . Istnieją różne inne właściwości definicji fasoli, w tym możliwość dziedziczenia z samej fabryki.

+0

To jest fajne. Waham się naruszyć ograniczenia związane z cyklem życia sprężyny na typach "BeanFactoryPostProcessor", generując komponenty bean w fazie procesora po produkcji fasoli. Chociaż generowane tutaj fasole mają jedynie zamiar zmienić definicje fasoli (które same w sobie powinny być bezpieczne podczas tej fazy), ważne jest, aby wiedzieć, że inne komponenty wymagane przez klasę '@ Configuration' mogą być również tworzone. Może to prowadzić do problemów, ponieważ ograniczenia cyklu życia są naruszone. Dopóki jest to dobrze udokumentowane, zrozumiałe, a zmiany są dokładnie sprawdzane, jest to dobre rozwiązanie. –

Powiązane problemy