用Spring注解实例化同一个类的多个bean
使用XMLconfiguration的Spring bean工厂,我可以轻松实例化具有不同参数的同一类的多个实例。 我怎样才能做同样的注释? 我想这样的东西:
@Component(firstName="joe", lastName="smith") @Component(firstName="mary", lastName="Williams") public class Person { /* blah blah */ }
是的,你可以在你自定义的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实现了这个接口,它的目的是创build一些其他的实例。 在我们的例子中,它使用MySecondComponenttypes创build实例。
最棘手的部分是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,从注解中提取名字,然后用指定的名字手动创build这个types的bean。
这是一个Springconfiguration:
<?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>
最后要注意的是,虽然你可以做到这一点,但是除非是必须的,否则不应该这样做,因为这是一种不太自然的configuration方式。 如果您有多个类的实例,最好坚持使用XMLconfiguration。
这是不可能的。 你会得到一个重复的例外。
在实现类中,这样的configuration数据也不是最优的。
如果你想使用注释,你可以用Javaconfiguration你的类:
@Configuration public class PersonConfig { @Bean public Person personOne() { return new Person("Joe", "Smith"); } @Bean public Person personTwo() { return new Person("Mary", "Williams"); } }
我只是要解决类似的情况。 如果你可以重新定义class级,这可能会奏效。
// 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。
受到蜡的回答的启发,如果添加了定义,而不是构build的单例,则实现可以更安全,不会跳过其他后处理:
public interface MultiBeanFactory<T> { // NB 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定义还有其他各种属性,包括从工厂本身inheritance的能力。