web-dev-qa-db-fra.com

Comment injecter différents services lors de l'exécution en fonction d'une propriété avec Spring sans XML

J'utilise Spring Boot pour Java. J'ai un bean qui utilise un service. Je veux injecter différentes implémentations de ce service au moment de l'exécution, en fonction d'une propriété dans un fichier de propriétés avec Spring (4 d'ailleurs).


Cela ressemble au modèle Factory, mais Spring permet également d'utiliser des annotations pour résoudre le problème, comme ceci.

@Autowired @Qualifier("selectorProperty") private MyService myService;

Ensuite, dans le fichier beans.xml, j'ai un alias, afin de pouvoir utiliser la propriété dans @Qualifier.

<alias name="${selector.property}" alias="selectorProperty" />

Et dans mes différentes implémentations, j'aurais différents qualificatifs.

@Component("Selector1")
public class MyServiceImpl1

@Component("Selector2")
public class MyServiceImpl2

application.properties

selector.property = Selector1

selector.property = Selector2

Alors qu'en ce qui concerne le modèle d'usine, au printemps, vous pouvez utiliser ServiceLocatorFactoryBean pour créer une usine qui vous offrirait les mêmes fonctionnalités.

<bean
  class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean"
  id="myServiceFactory">
  <property
    name="serviceLocatorInterface"
    value="my.company.MyServiceFactory">
  </property>
</bean>

public interface MyServiceFactory
{
    MyService getMyService(String selector);
}

Et puis, dans votre bean, vous pouvez utiliser quelque chose comme ça pour obtenir la bonne implémentation au moment de l'exécution en fonction de la valeur de la propriété.

@Value("${selector.property}") private String selectorProperty;

@Autowired private MyServiceFactory myServiceFactory;

private MyService myService;

@PostConstruct
public void postConstruct()
{
    this.myService = myServiceFactory.getMyService(selectorProperty);
}

Mais le problème avec cette solution est que je ne pouvais pas trouver un moyen d'éviter d'utiliser XML pour définir l'usine, et je voudrais utiliser uniquement des annotations.


La question serait donc: existe-t-il un moyen d'utiliser ServiceLocatorFactoryBean (ou quelque chose d'équivalent) en utilisant uniquement des annotations, ou suis-je obligé d'utiliser la méthode @Autowired @Qualifier si je ne veux pas définir de beans en XML? Ou existe-t-il un autre moyen d'injecter différents services lors de l'exécution en fonction d'une propriété avec Spring 4 en évitant XML? Si votre réponse est simplement d'utiliser le @Autowired @Qualifier Avec l'alias, veuillez expliquer pourquoi cela vaut mieux que d'utiliser un modèle d'usine bien connu.

L'utilisation du XML supplémentaire me force à utiliser @ImportResource("classpath:beans.xml") dans ma classe Launcher, que je préfère ne pas utiliser non plus.

Merci.

18
Pedro Lopez

En fait, vous pouvez utiliser ServiceLocatorFactory sans XML en le déclarant en tant que bean dans votre fichier de configuration.

@Bean
public ServiceLocatorFactoryBean myFactoryServiceLocatorFactoryBean()
{
    ServiceLocatorFactoryBean bean = new ServiceLocatorFactoryBean();
    bean.setServiceLocatorInterface(MyServiceFactory.class);
    return bean;
}

@Bean
public MyServiceFactory myServiceFactory()
{
    return (MyServiceFactory) myFactoryServiceLocatorFactoryBean().getObject();
}

Ensuite, vous pouvez toujours utiliser l'usine comme d'habitude, mais aucun XML n'est impliqué.

@Value("${selector.property}") private String selectorProperty;

@Autowired @Qualifier("myServiceFactory") private MyServiceFactory myServiceFactory;

private MyService myService;

@PostConstruct
public void postConstruct()
{
    this.myService = myServiceFactory.getMyService(selectorProperty);
}
9
Pedro Lopez

J'utilise des profils Spring

Par exemple, avec dataSources En l'utilisant, vous pouvez définir autant de sources de données que vous le souhaitez

@Configuration
@Profile("dev")
public class StandaloneDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }

}

@Configuration
@Profile("cloud")
public class CloudDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }

}

Et en runtime, en spécifiant

-Dspring.profiles.active = "monProfil"

vous activez l'une ou l'autre configuration (elles doivent toutes être importées dans votre configuration principale, elles sont simplement ignorées en fonction du profil actif).

Voici un bon article: http://spring.io/blog/2011/02/14/spring-3-1-m1-introducing-profile/

8
mavarazy