英文:
Is there a way to inject a dependency that will use a specific bean depending on where it's injected (with Spring boot)?
问题
我有一个服务(我们称之为`TaskExecutorService`),它需要一个依赖项`TaskService`。
我习惯通过构造函数注入依赖项,因此我有以下代码:
```java
@Service
class TaskExecutorService {
private final TaskService taskService;
public TaskExecutorService(TaskService taskService) {
this.taskService = taskService;
}
void function1() {...}
void function2() {...}
}
TaskService
有一个依赖项TaskRetrieverService
,这是一个接口,由多个用@Service
注释的“检索器”实现。
@Service
class TaskService {
private final TaskRetrieverService taskRetrieverService;
public TaskService(TaskRetrieverService taskRetrieverService) {
this.taskRetrieverService = taskRetrieverService;
}
}
在选择要使用的检索器的情况下,注入TaskService
到TaskExecutorService
的最佳方法是什么?
此外,我的真实用例是,根据TaskExecutorService
中的函数(function1
或function2
),我希望能够使用一个“检索器”或另一个“检索器”。
我认为我可以在方法中直接使用构造函数实例化TaskService
,但我希望有更好的方法来做到这一点。
<details>
<summary>英文:</summary>
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.
</details>
# 答案1
**得分**: 1
我同意这是一种反模式,因为您在`@Bean`中进行了连接,但我有几次需要这样做,而且没有义务硬编码接口实现。
答案是,您可以在Spring配置中声明bean时使用`@Bean(name = "customName")`为您的bean命名。这样,您可以选择要使用的*接口实现*。
对于以下示例(基于您的情况),我有一个名为`Dependency.java`的接口依赖项。为了简单起见,让我们保持简单,它只有一个`print()`方法,根据运行时使用的实现打印出不同的内容。
``` java
public interface Dependency {
void print();
}
我有两种可能的实现方式。它们分别是Child1
和Child2
。
这是Child1.java
:
public class Child1 implements Dependency {
@Override
public void print() {
System.out.println("I'm Child1!");
}
}
这是Child2.java
:
public class Child2 implements Dependency {
@Override
public void print() {
System.out.println("I'm Child2!");
}
}
如您所见,它们只是从我们的Dependency.java
进行实现,没有其他操作。它们实现了一个方法,该方法根据在运行时实现的内容打印一条语句来区分哪个实现正在被使用。
然后我有一个名为SpringConfig.java
的Spring配置类:
@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();
}
}
在这里,我声明了两个bean,每个bean都有一个唯一的名称。第一个实现(Child1
)在这个示例中将被称为myBean1
,第二个实现(Child2
)将是myBean2
。
然后我有一个将使用这两个@Beans
中的一个的@Service
类。我没有使用new Child1()
进行硬连接,而是在这里使用Spring进行连接。这样我就可以根据逻辑选择要使用的实现。
@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;
}
}
上下文是通过implements ApplicationContextAware
来实现的。这会添加setApplicationContext()
方法,其中包含ApplicationContext
。一旦您拥有了这个上下文,您只需要使用前面的代码选择要使用哪个接口实现:
Dependency dependency = (Dependency) context.getBean("myBean2");
如果您将myBean2
更改为myBean1
(或者将任何其他名称设置为SpringConfig
中的参数@Bean(name = "something")
),您将会改变运行时的实现为该@Bean
。
我实现了@Bean
myBean2
,所以预期的结果是,它将打印:
"I'm Child2!"
英文:
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!"
答案2
得分: 0
我认为你可以做类似这样的事情。
// 快速输入,未经测试,请忽略错别字或不正确的语法。我认为你可以理解这个想法。
@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, // 参数名称重要,或使用 @Qualifier
TaskRetrieverService secondTaskRetrieverService // 参数名称重要,或使用 @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() {
}
}
注意:上述内容是你提供的代码的翻译部分。
英文:
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() {
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论