Spring 依赖注入 - 私有字段 - 反模式?为什么它甚至可以工作?
Spring Dependency Injection - Private fields - Anti Pattern? Why does it even work?
我通常是一名 C# 开发人员,但时不时地在 Java 上工作,然后我看到很多使用 Spring 对 private 属性进行依赖注入,其中没有 public 设置值的方式。我很惊讶这真的有效,但我想这可能是通过反射实现的?
这肯定是糟糕的做法?!我看不出任何单元测试或检查 class 的人怎么可能知道需要从某些外部框架设置私有成员。
在进行单元测试时,您甚至如何设置 属性?或者只是单独使用 class?
我猜你必须在你的单元测试中使用 spring,这似乎有点过分了。当然,您应该能够在没有 IOC 容器的情况下进行单元测试? class 变得完全依赖于 spring...
我是不是漏掉了什么?
依赖注入不应该总是涉及某种public setter,并且如果可能最好使用构造函数吗?或者有什么关于 Java 我想念的......?
谢谢
有基于字段的注入、setter基于注入、基于注解的注入和基于构造函数的注入。基于构造函数的注入最适合测试,因为您可以轻松地模拟所需的依赖项。尽可能使用 final 字段定义服务总是好的。
class MyService {
private final MyDependency dependency;
@Autowired // not needed, explicit just for this example
public MyService(MyDependency dependency) {
this.dependency = dependency;
}
}
即使您有私有字段,也可以随时模拟注入的 bean。您应该从 Spring 文档中查看 @MockBean
。基本上,您可以执行以下操作:
@ExtendWith({SpringExtension.class})
class MyServiceTest{
@MockBean
private RepositoryInterface repository;
@Autowired
private MyService service;
}
假设 RepositoryInterface
是在 MyService
中注入的接口(而不是具体的 class)。如果你从 Spring Initialzr 创建你的 pom.xml,那么 JUnit5 的 SpringExtension
应该已经在你的依赖项中,将会使用另一个框架为该接口构建一个模拟,该框架被称为Mockito(也许看看它)。然后 Spring IoC 将在服务中注入创建的模拟。这适用于现场注入:
@Service
public class MyService{
@Autowired
private RepositoryInterface repositoryInterface
}
setter注入:
@Service
public class MyService{
private RepositoryInterface repositoryInterface
@Autowired
public void setRepository(RepositoryInterface repositoryInterface){
this.repositoryInterface = repositoryInterface;
}
}
或构造函数注入:
@Service
public class MyService{
private RepositoryInterface repositoryInterface
public MyService(RepositoryInterface repositoryInterface){
this.repositoryInterface = repositoryInterface;
}
}
基本上,最后一个是推荐的,因为这样你的服务的依赖关系将是明确的。它更多的是关于代码风格。不推荐使用字段注入,因为它会隐藏你的 class 依赖项。因此,使用构造函数注入构建测试的推荐方法如下:
@ExtendWith({SpringExtension.class})
class MyServiceTest{
@MockBean
private RepositoryInterface repository;
private MyService service;
@BeforeEach
void setup(){
service = new MyService(repository);
}
}
希望这有助于您的理解。
是的,它有效。一些测试框架允许注入私有字段。
是的,它是反模式,增加了技术债务 - 易于编写,但难以维护此类代码,而不是 compile-time 错误,您将遇到运行时错误。不要那样做。使用构造函数注入。
我通常是一名 C# 开发人员,但时不时地在 Java 上工作,然后我看到很多使用 Spring 对 private 属性进行依赖注入,其中没有 public 设置值的方式。我很惊讶这真的有效,但我想这可能是通过反射实现的?
这肯定是糟糕的做法?!我看不出任何单元测试或检查 class 的人怎么可能知道需要从某些外部框架设置私有成员。
在进行单元测试时,您甚至如何设置 属性?或者只是单独使用 class?
我猜你必须在你的单元测试中使用 spring,这似乎有点过分了。当然,您应该能够在没有 IOC 容器的情况下进行单元测试? class 变得完全依赖于 spring...
我是不是漏掉了什么?
依赖注入不应该总是涉及某种public setter,并且如果可能最好使用构造函数吗?或者有什么关于 Java 我想念的......?
谢谢
有基于字段的注入、setter基于注入、基于注解的注入和基于构造函数的注入。基于构造函数的注入最适合测试,因为您可以轻松地模拟所需的依赖项。尽可能使用 final 字段定义服务总是好的。
class MyService {
private final MyDependency dependency;
@Autowired // not needed, explicit just for this example
public MyService(MyDependency dependency) {
this.dependency = dependency;
}
}
即使您有私有字段,也可以随时模拟注入的 bean。您应该从 Spring 文档中查看 @MockBean
。基本上,您可以执行以下操作:
@ExtendWith({SpringExtension.class})
class MyServiceTest{
@MockBean
private RepositoryInterface repository;
@Autowired
private MyService service;
}
假设 RepositoryInterface
是在 MyService
中注入的接口(而不是具体的 class)。如果你从 Spring Initialzr 创建你的 pom.xml,那么 JUnit5 的 SpringExtension
应该已经在你的依赖项中,将会使用另一个框架为该接口构建一个模拟,该框架被称为Mockito(也许看看它)。然后 Spring IoC 将在服务中注入创建的模拟。这适用于现场注入:
@Service
public class MyService{
@Autowired
private RepositoryInterface repositoryInterface
}
setter注入:
@Service
public class MyService{
private RepositoryInterface repositoryInterface
@Autowired
public void setRepository(RepositoryInterface repositoryInterface){
this.repositoryInterface = repositoryInterface;
}
}
或构造函数注入:
@Service
public class MyService{
private RepositoryInterface repositoryInterface
public MyService(RepositoryInterface repositoryInterface){
this.repositoryInterface = repositoryInterface;
}
}
基本上,最后一个是推荐的,因为这样你的服务的依赖关系将是明确的。它更多的是关于代码风格。不推荐使用字段注入,因为它会隐藏你的 class 依赖项。因此,使用构造函数注入构建测试的推荐方法如下:
@ExtendWith({SpringExtension.class})
class MyServiceTest{
@MockBean
private RepositoryInterface repository;
private MyService service;
@BeforeEach
void setup(){
service = new MyService(repository);
}
}
希望这有助于您的理解。
是的,它有效。一些测试框架允许注入私有字段。
是的,它是反模式,增加了技术债务 - 易于编写,但难以维护此类代码,而不是 compile-time 错误,您将遇到运行时错误。不要那样做。使用构造函数注入。