Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to inject a dependency that will use a specific bean depending on where it's injected (with Spring boot)?

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.

like image 546
Quentin Lerebours Avatar asked Oct 19 '25 08:10

Quentin Lerebours


1 Answers

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!"

like image 59
Mario Codes Avatar answered Oct 21 '25 23:10

Mario Codes



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!