que - Crear instancias de beans mĂșltiples de la misma clase con anotaciones de Spring
spring mvc ejemplo (4)
Inspirada por la respuesta de cera , la implementación puede ser más segura y no omitir otro postproceso si se agregan definiciones, no construcciones simples:
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");
}
};
}
}
Los nombres de los @Qualifier
aún pueden venir de cualquier parte, como el ejemplo @Qualifier
de cera. Hay varias otras propiedades en la definición de bean, incluida la capacidad de heredar de la propia fábrica.
Con un XML configurado Spring Bean Factory, puedo instanciar fácilmente varias instancias de la misma clase con diferentes parámetros. ¿Cómo puedo hacer lo mismo con las anotaciones? Me gustaría algo como esto:
@Component(firstName="joe", lastName="smith")
@Component(firstName="mary", lastName="Williams")
public class Person { /* blah blah */ }
No es posible. Obtiene una excepción duplicada.
También está lejos de ser óptimo con datos de configuración como este en sus clases de implementación.
Si desea utilizar anotaciones, puede configurar su clase con la configuración de Java :
@Configuration
public class PersonConfig {
@Bean
public Person personOne() {
return new Person("Joe", "Smith");
}
@Bean
public Person personTwo() {
return new Person("Mary", "Williams");
}
}
Sí, puede hacerlo con la ayuda de su implementación personalizada de BeanFactoryPostProcessor.
Aquí hay un ejemplo simple.
Supongamos que tenemos dos componentes. Una es dependencia para otra.
Primer componente:
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;
}
}
Como puede ver, no hay nada de especial en este componente. Tiene dependencia en dos instancias diferentes de MySecondComponent.
Segundo componente:
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;
}
}
Es un poco más complicado. Aquí hay dos cosas para explicar. Primero uno - @Qualifier - anotación que contiene nombres de beans MySecondComponent. Es uno estándar, pero usted es libre de implementar el suyo. Verás un poco más tarde por qué.
Lo segundo que mencionar es la implementación de FactoryBean. Si bean implementa esta interfaz, tiene la intención de crear algunas otras instancias. En nuestro caso, crea instancias con el tipo MySecondComponent.
La parte más complicada es la implementación de 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(",");
}
}
¿Qué hace? Pasa por todos los beans anotados con @Qualifier, extrae los nombres de la anotación y luego crea manualmente los beans de este tipo con los nombres especificados.
Aquí hay una configuración de 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>
Lo último que hay que notar aquí es que aunque puedes hacerlo, no debes hacerlo a menos que sea obligatorio, porque esta es una forma de configuración no muy natural. Si tiene más de una instancia de clase, es mejor seguir con la configuración XML.
Solo tuve que resolver un caso similar. Esto puede funcionar si puedes redefinir la clase.
// 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");
}
}
Luego solo use PersonOne o PersonTwo cada vez que necesite conectar automáticamente una instancia específica, en cualquier otro lugar solo use Person.