Создание нескольких bean-компонентов одного класса с помощью аннотаций Spring

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

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

person Francois    schedule 25.05.2010    source источник
comment
Я не думаю, что вы можете. @Component — это легкое удобство, но оно не заменяет конфигурацию XML.   -  person skaffman    schedule 25.05.2010
comment
Я считаю прискорбным тот факт, что XML считается правильным способом настройки приложения.   -  person Jon Lorusso    schedule 23.07.2011
comment
Тот факт, что @Component не может этого сделать, не означает, что XML является решением. Я не знаю насчет 2011 года, но сейчас вы можете добиться того же эффекта в java @Configuration.   -  person apottere    schedule 24.03.2017
comment
@apottere Можете ли вы опубликовать ответ? Если вы имеете в виду именно то, что Эспен говорит в своем ответе, это не совсем ответ на этот конкретный вопрос. Только рядом..   -  person Koray Tugay    schedule 02.11.2018
comment
@KorayTugay именно это я и имел в виду. Как это нет ответа на этот вопрос? XML больше не является правильным способом настройки приложения.   -  person apottere    schedule 03.11.2018


Ответы (6)


Да, вы можете сделать это с помощью собственной реализации 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 — аннотация, содержащая имена bean-компонентов MySecondComponent. Он стандартный, но вы можете реализовать свой собственный. Чуть позже вы увидите, почему.

Второе, что следует упомянуть, это реализация FactoryBean. Если компонент реализует этот интерфейс, он предназначен для создания некоторых других экземпляров. В нашем случае он создает экземпляры с типом 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.

person wax    schedule 25.05.2010
comment
Разве isSingleton не должно возвращать false? - person OrangeDog; 04.01.2017
comment
Ни один из создаваемых bean-компонентов не будет подвергнут постобработке, предположительно потому, что BeanFactoryPostProcessor запускается до того, как будут созданы какие-либо BeanPostProcessor. В документации также говорится, что не следует создавать экземпляры каких-либо bean-компонентов (например, getBeansWithAnnotation) во время postProcessBeanFactory. - person OrangeDog; 05.01.2017
comment
Я придумал, как избежать некоторых из этих проблем: stackoverflow.com/a/41489377/476716 - person OrangeDog; 05.01.2017
comment
Но вы все равно использовали XML. На самом деле я предпочитаю XML, но пришел сюда, чтобы посмотреть, можно ли перейти на полную аннотацию. Но если мне все еще нужно создавать XML, а также создавать Фабрики и т. д., то почему бы просто не использовать XML? - person Dojo; 12.06.2020

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

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

Если вы хотите использовать аннотации, вы можете настроить свой класс с помощью Конфигурация Java:

@Configuration
public class PersonConfig {

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

    @Bean
    public Person personTwo() {
        return new Person("Mary", "Williams");
    }
}
person Espen    schedule 25.05.2010
comment
Но вы используете отдельную фабрику для каждого отдельного компонента Spring, который вы создаете. Я считаю, что он хочет, чтобы конфигурация аннотации находилась в классе Person. И я вижу только одну аннотацию в вашем относительно сложном примере, и эта аннотация не поддерживает несколько разных bean-компонентов. Но я впечатлен сложностью вашего решения :-) - person Espen; 25.05.2010
comment
как вы используете значение Джо из файла свойств? - person Kalpesh Soni; 21.04.2015
comment
Это точный эквивалент XML, самое простое решение. - person Michael Técourt; 24.04.2015
comment
это работает, возможно, билдер, а не новый, улучшит качество кода, просто мое мнение - person Enrico Giurin; 13.01.2019

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

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

person huherto    schedule 23.01.2014
comment
Это простой подход Java - гораздо больше похоже на Spring и меньше кода будет использовать аннотации Spring @Qualifier. - person Pavel; 27.02.2014
comment
@Pavel Но это странно, потому что мне нужны экземпляры одного и того же класса, а не классы, представляющие отдельные экземпляры (как здесь...) - person Koray Tugay; 02.11.2018
comment
Это не отвечает на вопрос, он хочет иметь 2 экземпляра одного и того же класса, но с разными атрибутами. - person Enrico Giurin; 13.01.2019

Продолжая отвечать @espen, добавляя bean-компоненты с квалификаторами и настраивая их по-разному с внешними значениями.

public class Person{
  @Configuration
  public static class PersonConfig{
    @Bean
    //@Qualifier("personOne") - doesn't work - bean qualifier is method name
    public Person personOne() {
        return new Person("Joe", "Smith");
    }

    @Bean
    //@Qualifier("personTwo") - doesn't work - bean qualifier is method name
    public Person personTwo(@Value("${myapp.second.lastName}") String lastName) {
        return new Person("Mary", lastName);
    }
  }
  /* blah blah */
}

@Component
public class SomePersonReference{
  @Autowired
  @Qualifier("personTwo")
  Person marry;
}
person raisercostin    schedule 15.07.2019
comment
Если вы называете переменную так, чтобы она точно соответствовала имени метода, то я не думаю, что вам даже нужна аннотация @Qualifier.... EG: @Autowired Person personTwo; - person Coder1224; 20.02.2020

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

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

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

person OrangeDog    schedule 05.01.2017
comment
Это здорово. Я не решаюсь нарушить ограничение жизненного цикла Spring для типов BeanFactoryPostProcessor, генерируя bean-компоненты на этапе постпроцессора bean factory. Хотя сгенерированные здесь bean-компоненты предназначены только для изменения определений bean-компонентов (что само по себе должно быть безопасным на этом этапе), важно знать, что любые другие bean-компоненты, требуемые классом @Configuration, могут также быть инстанцированным. Это может привести к проблемам, поскольку ограничения жизненного цикла нарушаются. Пока это хорошо задокументировано, понятно и изменения тщательно просматриваются, это хорошее решение. - person Mike Hill; 15.02.2018

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

@Configuration
public class PersonConfig {

@Autowired 
private OtherBean other;

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

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

person Enrico Giurin    schedule 13.01.2019