Создание нескольких бинов одного класса с помощью аннотаций Spring - PullRequest
45 голосов
/ 25 мая 2010

С фабрикой bean-компонентов Spring, настроенной на XML, я могу легко создать несколько экземпляров одного и того же класса с разными параметрами. Как я могу сделать то же самое с аннотациями? Я хотел бы что-то вроде этого:

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

Ответы [ 5 ]

31 голосов
/ 25 мая 2010

Это невозможно. Вы получаете двойное исключение.

Это также далеко не оптимально с такими данными конфигурации в ваших классах реализации.

Если вы хотите использовать аннотации, вы можете настроить свой класс на Java config :

@Configuration
public class PersonConfig {

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

    @Bean
    public Person personTwo() {
        return new Person("Mary", "Williams");
    }
}
16 голосов
/ 25 мая 2010

Да, вы можете сделать это с помощью вашей пользовательской реализации BeanFactoryPostProcessor.

Вот простой пример.

Предположим, у нас есть два компонента. Одно это зависимость для другого.

Первый компонент:

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

Как вы могли заметить, в этом компоненте нет ничего особенного. Он зависит от двух разных экземпляров MySecondComponent.

Второй компонент:

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

Это немного сложнее. Вот две вещи, которые нужно объяснить. Первая - @Qualifier - аннотация, которая содержит имена бинов MySecondComponent. Это стандартный, но вы можете реализовать свой собственный. Чуть позже вы увидите почему.

Второе, что следует упомянуть, - реализация FactoryBean. Если bean-компонент реализует этот интерфейс, он предназначен для создания некоторых других экземпляров. В нашем случае он создает экземпляры с типом MySecondComponent.

Самая сложная часть - реализация 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(",");
    }
}

Что это делает? Он просматривает все bean-компоненты, аннотированные @Qualifier, извлекает имена из аннотации, а затем вручную создает bean-компоненты этого типа с указанными именами.

Вот конфигурация 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>

Последнее, на что следует обратить внимание, это то, что вы можете сделать это, но вы не должны , если это не является обязательным, потому что это не совсем естественный способ конфигурации. Если у вас есть более одного экземпляра класса, лучше придерживаться конфигурации XML.

8 голосов
/ 23 января 2014

Мне просто нужно было решить аналогичный случай. Это может сработать, если вы можете переопределить класс.

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

Тогда просто используйте PersonOne или PersonTwo всякий раз, когда вам нужно автоматически связать конкретный экземпляр, а везде используйте Person.

1 голос
/ 05 января 2017

Вдохновленный ответом wax , реализация может быть более безопасной и не пропускать другую постобработку, если добавлены определения, а не сконструированные одиночные символы:

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

Имена бинов могут по-прежнему приходить откуда угодно, например, пример воска @Qualifier. В определении компонента есть и другие свойства, в том числе возможность наследования от самой фабрики.

0 голосов
/ 13 января 2019

Если вам нужно внедрить в новый созданный объект, bean-компоненты или свойства из контекста Spring, вы можете взглянуть на следующий раздел кода, в котором я расширил Espen ответ , введя бин, который создается из контекста весны:

@Configuration
public class PersonConfig {

@Autowired 
private OtherBean other;

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

Посмотрите на эту статью для всех возможных сценариев.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...