使用Spring注释实例化同一类的多个bean

时间:2010-05-25 05:55:51

标签: spring annotations

使用XML配置的Spring bean工厂,我可以轻松地使用不同的参数实例化同一个类的多个实例。我怎么能用注释做同样的事情?我想要这样的东西:

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

6 个答案:

答案 0 :(得分:31)

这是不可能的。你得到一个重复的例外。

在您的实现类中,这与配置数据相差甚远。

如果您想使用注释,可以使用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 :(得分:16)

是的,您可以在自定义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 bean名称的注释。这是标准的,但您可以自由实施。稍后你会看到原因。

第二件事是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(",");
    }
}

它做什么?它遍历所有使用@Qualifier注释的bean,从注释中提取名称,然后手动创建具有指定名称的此类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配置。

答案 2 :(得分:8)

我只需解决类似案件。如果您可以重新定义该课程,这可能会有效。

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

答案 3 :(得分:1)

wax's 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定义还有其他各种属性,包括从工厂本身继承的能力。

答案 4 :(得分:0)

是否需要在新创建的对象中注入spring上下文中的bean或属性,可以查看下面的代码部分,其中我通过注入一个bean扩展了Espen answer是从spring上下文创建的:

@Configuration
public class PersonConfig {

@Autowired 
private OtherBean other;

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

在所有可能的情况下查看此article

答案 5 :(得分:0)

继续@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;
}