前言
在使用 Spring 框架时,依赖注入(DI)是一个非常重要的概念。通过注解,我们可以方便地将类的实例注入到其他类中,提升开发效率。@Autowired
又是被大家最为熟知的方式,但很多开发者在使用 IntelliJ IDEA 时,常常看到 IDEA 提示不推荐使用@Autowired
,这是为什么呢?今天讲一下我对于@Autowired
字段注入的理解。
1. 为什么不推荐使用@Autowired
1.1. 隐式依赖
@Autowired
会根据类型自动注入依赖,可能导致依赖不明确。特别是在存在多个符合类型的候选对象时,可能会导致注入失败或错误地注入实例。
1.2. 循环依赖
@Autowired
可能导致循环依赖,尤其在单例 Bean 中,虽然 Spring 有解决方案,但仍需额外处理。
1.3. 生命周期不透明
通过@Autowired
注入的 Bean 由 Spring 管理,类无法显式了解其生命周期。相比之下,构造器注入可以显式管理依赖的生命周期。
1.4. 不符合不可变性原则
字段注入使得依赖关系在对象生命周期内可能发生变化,而构造器注入确保依赖在对象创建时设定,符合不可变性原则。
经常看到的争议点 :不推荐使用
@Autowired
,所以就使用@Resource
(使用@Resource
时,IDEA不会有波浪线提示)。
2. @Autowired
和@Resource
的基本用法
2.1. @Autowired
@Autowired
是 Spring 提供的注解,用于自动注入依赖项。Spring 会根据类型 (默认情况下)或者名称 (通过 @Qualifier
)来自动注入所需的 Bean。
java
@Autowired
private UserService userService;
在这个例子中,Spring 会自动将 UserService
类型的 Bean 注入到 userService
属性中。
2.2. @Resource
@Resource
是 Java 规范的一部分,来自 javax/jakarta.annotation
包,它更注重按照名称来注入 Bean。默认情况下,它会尝试按名称 匹配,但如果没有找到匹配的名称,再通过类型注入。
java
@Resource
private UserService userService;
与@Autowired
不同@Resource
会首先查找名为userService
的 Bean,如果找不到,再通过类型注入。
可以看出其实两者本质上是没有区别的,只是匹配顺序不一样。
3. @Autowired
和@Resource
实测对比
假如你有一个接口类,如下:
java
@Service
public interface UserService {
// 用户服务...
}
两个实现类:如下:
java
@Component
public class UserServiceImpl implements UserService {
// 实现细节
}
@Component
public class AnotherUserServiceImpl implements UserService {
// 另一个实现
}
3.1. 使用@Autowired
做测试
在测试接口里面加入@Autowired
,如下:
java
/**
* 测试接口
*/
@RestController("test")
public class TestController {
@Autowired
private UserService userService;
}
启动项目,控制台输出错误,如下:
错误提示:需要一个bean,但是找到了两个bean。
修改之后指定具体的bean名,如下:
java
@RestController("test")
public class TestController {
@Autowired
// @Qualifier("userServiceImpl") // 或者使用Qualifier来指定bean名
private UserService userServiceImpl;
}
启动项目,项目正常启动,断点测试如下:
正常获取到需要的bean实例。
3.2. 使用@Resource
做测试
在测试接口里面加入@Resource
,如下:
java
/**
* 测试接口
*/
@RestController("test")
public class TestController {
@Resource
private UserService userService;
}
启动项目,控制台输出错误,如下:
错误提示:需要一个bean,但是找到了两个bean。
修改之后指定具体的bean名,如下:
java
@RestController("test")
public class TestController {
@Resource
// @Resource(name = "userServiceImpl") // 或者使用name来指定bean名
private UserService userServiceImpl;
}
启动项目,项目正常启动,断点测试如下:
正常获取到需要的bean实例。
可以看出实际上
@Autowired
和@Resource
的使用效果是一样的。(@Resource
并不优于@Autowired
,两者都是基于字段注入。甚至于在Spring框架中,@Autowired
性能更好,例如:自动装配的细粒度控制 。)
4. 推荐注入的方式
构造器注入是推荐的首选方式,尤其在处理复杂依赖关系、不可变对象、单元测试等场景时,构造器注入非常有用。
4.1. 推荐原因
- 显式依赖关系:构造器注入使依赖关系在对象创建时就显式地列出,代码更清晰。
- 不可变性 :依赖可以声明为
final
,避免修改。 - 避免循环依赖:能及时发现并避免循环依赖问题。
- 明确依赖关系:构造器清晰暴露依赖,增强代码可读性。
4.2. 示例
java
@RestController("test")
public class TestController {
private final UserService userService;
public TestController(UserService userServiceImpl) {
this.userService = userServiceImpl;
}
}
// 或者使用lombok简化代码。
@RestController("test")
@RequiredArgsConstructor
public class TestController {
private final UserService userServiceImpl;
}
伪逻辑说明:
- 显式依赖关系 :
应该关注的是,整个项目的代码规范,而不是针对具体的某一个类去优化,使用构造器注入。- 不可变性 :
应该关注的是,合理的设计,项目中bean在初始时,如果使用@Autowired
注入某个类,就应该不在初始化期间去修改它。- 避免循环依赖 :
也是和项目的架构相关,如果无法中设计上避免,也可以通过代理和懒加载去解决。- 明确依赖关系 :
如果项目类名命名规范,不使用相同的类名,或者使用一些前缀后缀去区分,也能够不影响依赖关系。
5. 构造器一定优于@Autowired吗?
@Autowired
在一些方面要优于构造器注入?
-
简化代码:自动注入依赖,无需手动编写构造器或 setter 方法,代码更简洁。
-
快速开发:灵活且快速,不需要修改构造函数,可以直接通过字段注入依赖。
-
自动依赖管理:Spring 自动管理依赖,简化了依赖的选择和注入过程。
-
避免构造函数冗长:对于依赖较多的类,字段注入避免了构造函数过长的问题。
综合来看 :
@Autowired
依旧是最主流的注入方式。
结语
虽然 IDEA 提示不推荐使用 @Autowired
,但这并不意味着它是一个糟糕的选择。不同的注入方式有其各自的优缺点,选择依赖注入方式时,应该根据项目规模、复杂性以及团队编码习惯来决定,而不是盲目跟随趋势。