web-dev-qa-db-fra.com

Instanciation de plusieurs beans de la même classe avec des annotations Spring

Avec une usine de beans Spring configurée en XML, je peux facilement instancier plusieurs instances de la même classe avec des paramètres différents. Comment puis-je faire la même chose avec des annotations? Je voudrais quelque chose comme ça:

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

Oui, vous pouvez le faire à l'aide de votre implémentation BeanFactoryPostProcessor personnalisée. 

Voici un exemple simple.

Supposons que nous ayons deux composants. L'un est la dépendance d'un autre. 

Premier composant:

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

Comme vous avez pu le constater, cette composante n’a rien de spécial. Il dépend de deux instances différentes de MySecondComponent.

Deuxième composant:

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

C'est un peu plus compliqué. Voici deux choses à expliquer. Le premier - @Qualifier - est une annotation qui contient les noms des beans MySecondComponent. C'est un standard, mais vous êtes libre d'implémenter le vôtre. Vous verrez un peu plus tard pourquoi. 

La deuxième chose à mentionner est l'implémentation de FactoryBean. Si bean implémente cette interface, il est destiné à créer d'autres instances. Dans notre cas, il crée des instances avec le type MySecondComponent.

La partie la plus délicate est l'implémentation 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'est ce que ça fait? Il parcourt tous les beans annotés avec @Qualifier, extrait les noms de l'annotation, puis crée manuellement des beans de ce type avec les noms spécifiés. 

Voici une config de printemps:

<?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>

La dernière chose à noter ici est que vous pouvez le faites vous ne devriez pas à moins que ce ne soit une nécessité, car cette configuration n’est pas vraiment naturelle. Si vous avez plus d'une instance de classe, il est préférable de s'en tenir à la configuration XML. 

16
wax

Ce n'est pas possible. Vous obtenez une exception en double.

Les données de configuration comme celle-ci sont loin d’être optimales.

Si vous souhaitez utiliser des annotations, vous pouvez configurer votre classe avec Java config :

@Configuration
public class PersonConfig {

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

    @Bean
    public Person personTwo() {
        return new Person("Mary", "Williams");
    }
}
30
Espen

Je devais juste résoudre un cas similaire. Cela peut fonctionner si vous pouvez redéfinir la classe.

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

Ensuite, utilisez simplement PersonOne ou PersonTwo chaque fois que vous avez besoin de transférer automatiquement une instance spécifique, partout où vous utilisez simplement Personne.

8
huherto

Inspiré par la réponse de wax , la mise en œuvre peut être plus sûre et ne pas ignorer d'autres post-traitements si des définitions sont ajoutées, et non des singletons construits:

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

Les noms de haricots peuvent toujours provenir de n'importe où, tel que l'exemple @Qualifier de wax. Il existe diverses autres propriétés dans la définition du bean, notamment la possibilité d'hériter de l'usine elle-même.

1
OrangeDog

Si vous devez injecter dans le nouvel objet créé des beans ou des propriétés du contexte Spring, vous pouvez consulter la section de code suivante dans laquelle j'ai étendu la réponse Espen en injectant un bean créé. à partir du contexte de printemps:

@Configuration
public class PersonConfig {

@Autowired 
private OtherBean other;

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

Regardez ce article pour tous les scénarios possibles.

0
Enrico Giurin