I agree this is some kind on anti pattern as you're wiring the @Bean, but I've needed to do this a couple of times and there's no obligation to hardcode the interface implementation.
The answer is, you can name your beans when you declare them at your Spring Config with @Bean(name = "customName"). This way, you can choose which interface implementation you're going to use.
For the following example (based on your case), I have an interface dependency called Dependency.java. For this example, let's keep it simple so it just has one method print(), which will print something depending on the implementation we're using at runtime.
public interface Dependency {
void print();
}
I have two possible implementations for this dependency. Those are Child1 and Child2.
This is Child1.java
public class Child1 implements Dependency {
@Override
public void print() {
System.out.println("I'm Child1!");
}
}
This is Child2.java
public class Child2 implements Dependency {
@Override
public void print() {
System.out.println("I'm Child2!");
}
}
As you see, they only implement from our Dependency.java and do nothing else. They implement the method, which prints an statement to differentiate which one is being implemented at runtime.
Then I have a Spring Config class called SpringConfig.java
@Configuration
@ComponentScan("you.packages.go.here.for.discovery.**.*")
public class SpringConfig {
@Bean(name = "myBean1")
public Dependency dependency() {
return new Child1();
}
@Bean(name = "myBean2")
public Dependency dependency2() {
return new Child2();
}
}
Here I have my 2 beans declared, each one with an unique name. The first implementation (Child1), for this example, will be called myBean1 and the second implementation (Child2) will be myBean2.
Then I have the @Service class which will use one of those two @Beans. Instead of a hardwire with new Child1(), I use Spring to wire the Context here. This way I can choose which one I want to choose, depending on logic.
@Service
public class MyService implements ApplicationContextAware {
private ApplicationContext context;
public void useDependency() {
Dependency dependency = (Dependency) context.getBean("myBean2");
dependency.print();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
}
The context is being implemented by implements ApplicationContextAware. This adds the setApplicationContext() method which holds the ApplicationContext. Once you have this context you just need to choose which of the interface implementations are you going to use with the previous line
Dependency dependency = (Dependency) context.getBean("myBean2");
If you change myBean2 for myBean1 (or any other name you set at SpringConfig's parameter @Bean(name = "something") you will change the runtime implementation to that @Bean).
I implemented the @Bean myBean2, so as expected, this will print
"I'm Child2!"