David
David

Reputation: 2702

Decide which of multiple implementations to use in Spring Boot app

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.

Upvotes: 6

Views: 7978

Answers (4)

toddcscar
toddcscar

Reputation: 1225

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.

Upvotes: 2

Fuad Efendi
Fuad Efendi

Reputation: 147

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

Upvotes: 2

David
David

Reputation: 2702

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.

Upvotes: 2

Vitor Gabriel Carrilho
Vitor Gabriel Carrilho

Reputation: 365

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

Upvotes: 2

Related Questions