Quentin Lerebours
Quentin Lerebours

Reputation: 756

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.

Upvotes: 4

Views: 1786

Answers (2)

Phong Bui
Phong Bui

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

Mario Codes
Mario Codes

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

Related Questions