Reputation: 756
I've got a service (let's call it TaskExecutorService
), that requires a dependency TaskService
.
I'm used to inject dependency through constructor, so I've go the following code:
@Service
class TaskExecutorService {
private final TaskService taskService;
public TaskExecutorService(TaskService taskService) {
this.taskService = taskService;
}
void function1() {...}
void function2() {...}
}
The TaskService
have a dependency TaskRetrieverService
which is an interface, implemented by multiple "retrievers" annotated with @Service.
@Service
class TaskService {
private final TaskRetrieverService taskRetrieverService;
public TaskService(TaskRetrieverService taskRetrieverService) {
this.taskRetrieverService = taskRetrieverService;
}
}
What's the best way to inject the TaskService
in TaskExecutorService
choosing which retriever to use?
Moreover, my real use case is that depending on the function in TaskExecutorService
(function1
or function2
), I'd like to be able to use either a "retriever" or another.
I think I could instantiate the TaskService
directly in the methods using its constructor but I hope that there's a best way to do this.
Upvotes: 4
Views: 1786
Reputation: 511
I think you can do something like this.
// Quick typing, not tested, please ignore typo or incorrect syntax. I think you can get the idea.
@Service class TaskExecutorService {
private final TaskService taskService;
@Autowired
public TaskExecutorService(TaskService taskService) {
this.taskService = taskService;
}
void function1() {
taskService.getTaskRetrieverService(RetrieverType.FIRST).doSomething();
}
void function2() {
taskService.getTaskRetrieverService(RetrieverType.SECOND).doSomething();
}
}
@Service
class TaskService {
private final TaskRetrieverService firstTaskRetrieverService;
private final TaskRetrieverService secondTaskRetrieverService;
@Autowired
public TaskService(
TaskRetrieverService firstTaskRetrieverService, //param name matter or use @Qualifier
TaskRetrieverService secondTaskRetrieverService //param name matter or use @Qualifier
) {
this.firstTaskRetrieverService = firstTaskRetrieverService;
this.secondTaskRetrieverService = secondTaskRetrieverService;
}
public TaskRetrieverService getTaskRetrieverService(RetrieverType retrieverType) {
switch (retrieverType) {
case RetrieverType.FIRST:
return this.firstTaskRetrieverService;
case RetrieverType.SECOND:
return this.secondTaskRetrieverService;
default:
return this.firstTaskRetrieverService;
}
}
}
@Service
class FirstTaskRetrieverService implements TaskRetrieverService {
void doSomething() {
}
}
@Service
class SecondTaskRetrieverService implements TaskRetrieverService {
void doSomething() {
}
}
Upvotes: 1
Reputation: 717
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!"
Upvotes: 1