跳到主要内容

Spring注入使用规范

首先本文将列出Spring中常见的依赖注入形式,并说明哪些注入形式推荐使用,哪些注入形式不推荐使用。并且会列出各种注入形式之间的差异。

私有字段上使用@Autowired注入(不推荐)

是否推荐:不推荐

@Service
public class UserServiceImpl implements UserService {

@Autowired
private UserMapper userMapper;
}
java

这种注入形式违背了 SnoarLint中的 java:S6813规则:Field dependency injection should be avoided

代码检查中会提示:Remove this field injection and use constructor injection instead.(移除字段的形式的注入,使用构造函数注入代替)。

Field dependency injection should be avoided的详细描述

Dependency injection frameworks such as Spring support dependency injection by using annotations such as @Inject and @Autowired. These annotations can be used to inject beans via constructor, setter, and field injection.

Generally speaking, field injection is discouraged. It allows the creation of objects in an invalid state and makes testing more difficult. The dependencies are not explicit when instantiating a class that uses field injection.

In addition, field injection is not compatible with final fields. Keeping dependencies immutable where possible makes the code easier to understand, easing development and maintenance.

Finally, because values are injected into fields after the object has been constructed, they cannot be used to initialize other non-injected fields inline.

This rule raises an issue when the @Autowired or @Inject annotations are used on a field.

翻译

依赖注入框架(如 Spring)通过使用 @Inject 和 @Autowired 等注解来支持依赖注入。这些注解可用于通过构造器、设置器和字段注入来注入 Bean。

一般来说,我们不鼓励字段注入。它允许在无效状态下创建对象,使测试变得更加困难。在实例化使用字段注入的类时,依赖关系是不明确的。

此外,字段注入与最终字段不兼容。在可能的情况下,保持依赖关系不可变会使代码更容易理解,从而简化开发和维护工作。

最后,由于值是在对象构造完成后注入字段的,因此不能用于内联初始化其他非注入字段。

当在字段上使用 @Autowired 或 @Inject 注解时,这条规则就会引起问题。

除了违背了 SnoarLint中的 java:S6813规则外,这种注入方式还有一个问题,就是它破坏了破坏类的封装性。使得私有字段被强行访问。

使用构造函数注入

是否推荐:推荐

@Service
public class UserServiceImpl implements UserService {

@Autowired
private final UserMapper userMapper;

public UserServiceImpl(UserMapper userMapper) {
this.userMapper = userMapper;
}
}
java

定义的字段上必须加上 final 关键字来修饰。推荐使用这种方式注入,这也是当前Spring推荐使用的注入方式。

这种注入形式会带来循环依赖无法解决的问题。但是如果出现循环依赖的问题,首先要考虑自己的程序设计是否合理。

在Setter上使用@Autowired来注入

是否推荐:推荐

@Service
public class UserServiceImpl implements UserService {

private UserMapper userMapper;

@Autowired
public void setUserMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}
}
java

这种方式不仅可以解决循环依赖问题,而且不会出现在私有字段上使用@Autowired注入的问题。

但是会增加一定的代码量。如果注入的Bean比较多,Setter方法会使整个类看起来很乱。

使用lombok优化Setter注入

@Service
public class UserServiceImpl implements UserService {

@Setter(onMethod_={@Autowired})
private UserMapper userMapper;

}
java

以上是JDK8+的使用方法,JDK7的使用方法如下:

@Service
public class UserServiceImpl implements UserService {

@Setter(onMethod=@__({@Autowired}))
private UserMapper userMapper;

}
java

使用@Inject注入(不推荐)

是否推荐:不推荐

@Inject 是 JSR330 (Dependency Injection for Java) 规范中的一个注解。与@Autowired的功能基本一致。通常与@Named一起使用

<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
xml
@Service
public class UserServiceImpl implements UserService {

private UserMapper userMapper;

@Inject
public void setUserMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}
}
java

当需要指定要注入Bean的具体名称时,需要和@Named一起使用。

@Service
public class UserServiceImpl implements UserService {

private UserMapper userMapper;

@Named("userMapper")
@Inject
public void setUserMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}
}
java

但是这个注解不常使用,容易使其他开发者产生疑惑,故不推荐使用。

抽象类(或父类)构造函数注入问题

如果抽象类的构造函数注入的Bean比较多,那这种注入方式就会带来一些弊端。由于子类需要覆盖父类的构造函数,这也会导致子类构造函数中需要注入的Bean比较多。而且父类构造函数修改,会影响到所有子类。

为解决这个问题,我们可以让父类通过Setter来注入,这样子类在继承父类之后不需要再通过父类的构造函数注入,而是相当于也有了一个Setter注入方法。且父类的修改不会影响到子类。

AbstractUserService.java
@Service
public abstract class AbstractUserService {

@Setter(onMethod_={@Autowired})
private UserMapper userMapper;

}
java
UserServiceImpl.java
@Service
public class UserServiceImpl extends AbstractUserService implements UserService {

@Setter(onMethod_={@Autowired})
private OtherMapper otherMapper;

}
java