7

Given two (or more) implementations of a particular service API, what's the best way to pick which one to use at runtime in my app based on an application property?

Example API:

public interface Greeting { String sayHello(String username); } 

Implementations:

public class FriendlyGreeting implements Greeting { public String sayHello(String username) { return "Hello, " + username; } } public class HostileGreeting implements Greeting { public String sayHello(String username) { return "Go away, " + username; } } 

I've got a separate service class with an @Autowired constructor that takes an instance of Greeting. What I want, is based upon a configuration property, to decide which greeting implementation gets injected and used. I came up with using a configuration class to make that decision:

@Configuration public class GreetingConfiguration { private String selection; @Autowired public GreetingConfiguration(@Value("${greeting.type}") String type) { this.selection = type; } @Bean public Greeting provideGreeting() { if ("friendly".equals(selection)) { return new FriendlyGreeting(); } else { return new HostileGreeting(); } } } 

Is this the right way to do what I want? I went down the road of using @Qualifier on the implementations, and ended up with a mess where Spring saw 3 instances of my Greeting API, and I needed a configuration anyway to pick which implementation to use and return it with a unique qualifier name on it, and that feels worse than what I settled on.

4
  • 2
    Does it have to be a configuration property or can it be a Spring profile? Because using a Profile would be the nice and calm Springy way to do this. After that, depending on profile, you would provide either Hostile or Friendly Configuration to handle the bean. Commented Jan 23, 2019 at 20:18
  • 1
    Specifically: have suitable @Profile annotations on your components and pass the spring.profiles.active and/or spring.profiles.include property at runtime... Commented Jan 23, 2019 at 20:22
  • I'll look into profiles, but I think they're defined by environment that we deploy the app into and not really changeable. Commented Jan 23, 2019 at 20:22
  • Also possibly helpful is using @Conditional stackoverflow.com/a/34351004/2958086 Commented Jan 23, 2019 at 20:29

4 Answers 4

2

You can mark both Greeting as @Service and select the chosen one with @Qualifier("yourServiceHere") like this:

@Autowired @Qualifier("friendlyGreeting") private Greeting greeting; 

Another way you can do it is with profile. You can mark your FriendlyGreeting service with @Service and @Profile("friendly") and the HostileGreeting service with @Service and @Profile("hostileGreeting") and just put in the application.properties the following:

spring.profiles.active=friendly 
Sign up to request clarification or add additional context in comments.

4 Comments

i doubt, that SpEL works in qualifier... (stackoverflow.com/a/29773117/592355)
Good thought, but the evaluation of "${app.greeting}" doesn't happen, and spring goes looking for a bean with the literal qualifier name of ${app.greeting}
So you can put your qualifier as a string @Qualifier("friendlyGreeting")
so you cannot manage which implementation to use at runtime without using Profile: Qualifier("friendlyGreeting") is hardcoded; and Qualifier("${app.greetig}") won't work. Is that correct, Profile is the only way (except initial solution in the question)?
2

Answering my own question.

@Compass and @user268396 were correct - using Profiles got this working as expected.

I created both implementations, annotated with @Service and @Profile("friendly") or @Profile("hostile"), and could change the property spring.profiles.active to dev,friendly for example, and get what I wanted.

Comments

2

You can use @Conditional annotations described at https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Conditional.html and https://reflectoring.io/spring-boot-conditionals/

@Profile annotations mentioned above are based upon @Conditional(from Spring Framework); see also Spring Boot: org.springframework.boot.autoconfigure.condition

Comments

2

Here is a full solution using ideas mentioned by David and Vitor above with @Profile and @Qualifer annotations.

Two beans with same name but Only one is activated based on which profile is defined.

@Profile("profile1") @Bean("greeting") public class FriendlyGreeting implements Greeting { --- @Profile("profile2") @Bean("greeting") public class HostileGreeting implements Greeting { --- @Configuration public class GreetingConfiguration { private Greeting greeting; @Autowired public GreetingConfiguration(@Qualifier("greeting") Greeting greeting) { this.greeting = greeting; } } 

Notes:

  • you can remove the intermediate class GreetingConfiguration and stick the "greeting" bean wherever you need
  • i prefer the @Autowired on the constructor instead of the class member to make it easier for unit testing.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.