How to inject different services at runtime based on a property with Spring without XML

28,820

Solution 1

Actually, you can use ServiceLocatorFactory without XML by declaring it as a bean in your configuration file.

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

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

Then you can still use the factory as usual, but no XML is involved.

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

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

private MyService myService;

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

Solution 2

I'm using Spring profiles

For example with dataSources Using it you can define as many dataSources, as you like

@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();
    }

}

And in runtime, by specifying

-Dspring.profiles.active="myProfile"

you active one or another configuration (All of them must be imported in your main Configuration, they are just ignored based on active profile).

Here is a good article: http://spring.io/blog/2011/02/14/spring-3-1-m1-introducing-profile/

Share:
28,820

Related videos on Youtube

Pedro Lopez
Author by

Pedro Lopez

Engineering Manager based in London.

Updated on September 01, 2020

Comments

  • Pedro Lopez
    Pedro Lopez over 3 years

    I am using Spring Boot for Java standalone application. I have a bean which makes use of a service. I want to inject different implementations of that service at runtime, based on a property in a properties file with Spring (4 for that matter).


    This sounds like the Factory pattern, but Spring also allows using annotations to solve the problem, like this.

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

    Then in the beans.xml file I have an alias, so that I can use the property in the @Qualifier.

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

    And in my different implementations I would have different qualifiers.

    @Component("Selector1")
    public class MyServiceImpl1
    
    @Component("Selector2")
    public class MyServiceImpl2
    

    application.properties

    selector.property = Selector1
    
    selector.property = Selector2
    

    Whereas regarding the factory pattern, in Spring you can use ServiceLocatorFactoryBean to create a factory that would give you the same functionality.

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

    And then in your bean you can use something like this to get the right implementation at runtime depending on the value of the property.

    @Value("${selector.property}") private String selectorProperty;
    
    @Autowired private MyServiceFactory myServiceFactory;
    
    private MyService myService;
    
    @PostConstruct
    public void postConstruct()
    {
        this.myService = myServiceFactory.getMyService(selectorProperty);
    }
    

    But the problem with this solution is that I could not find a way to avoid using XML to define the factory, and I would like to use only annotations.


    So the question would be, is there a way to use the ServiceLocatorFactoryBean (or something equivalent) using only annotations, or am I forced to use the @Autowired @Qualifier way if I do not want to define beans in XML? Or is there any other way to inject different services at runtime based on a property with Spring 4 avoiding XML? If your answer is just use the @Autowired @Qualifier with the alias, please give a reason why that is better than using a well known factory pattern.

    Using the extra XML is forcing me to use @ImportResource("classpath:beans.xml") in my Launcher class, which I'd rather not use either.

    Thanks.

    • Nadir
      Nadir over 9 years
      Did you tried @Qualifier("${selector.property}")? It's possible to dynamically name beans, so maybe it's possible to set up qualifiers this way too... Haven't tried that myself though..
    • Pedro Lopez
      Pedro Lopez over 9 years
      I tried it, but it does not work. The @Autowired @Qualifier("${selector.property}") resolves to null. It needs the alias as a mid-step. Thanks for the suggestion anyway.
    • mavarazy
      mavarazy over 9 years
      Not sure, what you are trying to archive, have you looked at spring configuration separation with profiles?
    • Pedro Lopez
      Pedro Lopez over 9 years
      I am trying to avoid XML using the ServiceLocatorFactoryBean. I found a way to get a similar result, but not using the Factory pattern (first half of the post). Unless someone else gives me a reason why not to, I would like to use a Factory because I think it fits better here.
    • Yougesh
      Yougesh about 2 years
      Have you found any solution?
  • Pedro Lopez
    Pedro Lopez over 9 years
    I like this solution. Although in this particular case I do not have different profiles. I could use something like this, but then I would need a different profile for each implementation of my interface. If I have two factories then it becomes messy. Thanks a lot for the answer anyway, I might find it useful for a different thing.
  • Lokesh
    Lokesh over 8 years
    Autowiring MyServiceFactory without any Qualifier works even without creating additional bean myServiceFactory(). I doubt function name and the Qualifier name are related in this case. But yes, overall your solution is good.