我目前正在开发一个具有相互依赖服务的应用程序,通过Spring Java Configuration类注入,有点像这样:
@Configuration
public class ExampleConfiguration {
@Bean
public IFirstService firstService() {
return new FirstServiceImpl();
}
@Bean
public ISecondService secondService() {
return new SecondServiceImpl();
}
}
@Service
public class FirstServiceImpl implements IFirstService{
...
}
@Service
public class SecondServiceImpl implements ISecondService{
@Inject
private IFirstService firstService;
...
}
这是按预期工作的,每个服务的单个实例都在整个应用程序中创建和注入。但是,我有兴趣转换为构造函数注入 - 似乎它可以为单元/模拟测试模式提供更好的支持。据我了解,它会将SecondServiceImpl代码更改为:
@Service
public class SecondServiceImpl implements ISecondService {
private IFirstService firstService;
@Inject
public SecondServiceImpl(IFirstService firstService){
this.firstService = firstService;
}
...
}
我遇到的问题是确定它如何与上面的Configuration类交互/工作。我见过的所有例子都是这样的:
@Configuration
public class ExampleConfiguration {
@Bean
public IFirstService firstService() {
return new FirstServiceImpl();
}
@Bean
public ISecondService secondService() {
return new SecondServiceImpl(firstService());
}
}
但是这似乎会破坏整个应用程序中应该注入一个IFirstService实例的想法,因为每次调用firstService()
都会实例化一个新的IFirstService实例。
我不知道我是否遗漏了一个关于Spring如何处理这样的事情,或者错误依赖注入错误的细节。任何建议将不胜感激!
编辑:
虽然接受的答案是正确的,但我最近发现有一种更强大的方法可以做到这一点 - 您可以将所需的项目指定为使用@Bean
注释的方法的参数,并且它将从相同或其他可用配置。所以上面会变成:
@Configuration
public class ExampleConfiguration {
@Bean
public IFirstService firstService() {
return new FirstServiceImpl();
}
@Bean
public ISecondService secondService(IFirstService firstService) {
return new SecondServiceImpl(firstService);
}
}
请注意,如果需要特定的bean id,@Qualifier
注释可以与成员参数一起使用
答案 0 :(得分:1)
您的配置类不会按原样使用。 Spring会将您的配置包装到所谓的代理类中。此代理类将拦截原始配置类的所有方法调用,这些方法标记为@Bean
注释。让我们考虑一下您的配置代码:
@Configuration
public class ExampleConfiguration {
@Bean
public IFirstService firstService() {
return new FirstServiceImpl();
}
@Bean
public ISecondService secondService() {
return new SecondServiceImpl(
firstService() //actually here is will be invoked method of proxy class
);
}
}
以下是firstService()
注释@Bean
。因此,因为您的配置类包装到代理中,所以当您调用firstService()
时,它将调用代理方法,但不调用原始配置类的方法。
代理类的简化逻辑如下所示。当代理类拦截方法的调用时,它会检查(在单例的情况下):是否已经创建了bean的实例。如果bean存在,则返回此bean。如果bean不存在,则通过调用原始配置类的方法来创建新实例。
这意味着每次调用firstService()
都不会实例化新的IFirstService
实例。它将在第一次调用时创建,并且所有后续调用都将返回相同的实例。
答案 1 :(得分:1)
@Qualifier
注释是你的朋友:
@Configuration
public class ExampleConfiguration {
@Bean(name="first-service") // #1 - put a name on it
public IFirstService firstService() {
return new FirstServiceImpl();
}
@Bean
public ISecondService secondService(
@Qualifier("first-service") // #2 - inject it here
IFirstService service {
return new SecondServiceImpl(service);
}
}
请注意,此示例使用默认的单例原型,它可能适用于所有情况,也可能不适用。例如,您可能需要在每次咨询Bean工厂时创建一个新的ISecondService
实例,但希望使用IFirstService
单例。或者您可能希望两者都充当原型bean。
然后,您必须明智地使用配置对象的范围作为整体或特定的bean声明(但现在这是另一个主题。)
实际上,让我扩大我的回应。您可以使用@Qualifier,但我建议使用@Named,如下所示。
@Configuration
public class ExampleConfiguration {
@Bean(name="first-service") // #1 - put a name on it
public IFirstService firstService() {
return new FirstServiceImpl();
}
@Bean
public ISecondService secondService(
@Named("first-service") // #2 - inject it here
IFirstService service {
return new SecondServiceImpl(service);
}
}
为什么呢?好吧,我们有两个版本的@Qualifier
注释:
JSR-330 @Qualifier
不能像我的例子那样在参数中使用。它仅限于方法和属性。
然而,我的示例中的一个是Spring中的一个,可以应用于参数。
一般来说,我们希望坚持使用JSR-330注释(就像Spring注释一样。)为此,我们改为使用来自JSR-330的@Named
,2)它等同于Spring的@Qualifier
。
答案 2 :(得分:0)
Spring使用CGLIB
个代理来解决配置阶段的依赖注入问题。 firstService()
的结果最初将成为一个代理,一旦所有依赖关系得到解决并适当注入,它将被合并为一个单例。