@Autowired 和 @Resource:从简单入手聊聊依赖注入的那些事儿
在 Java 开发中,尤其是用 Spring 框架的时候,依赖注入(Dependency Injection,简称 DI)是个绕不开的话题。Spring 提供了不少工具让我们把对象之间的依赖关系管理得井井有条,其中最常见的就是 @Autowired
和 @Resource
这两个注解。今天咱们就从最简单的用法聊起,一步步看看它们有啥区别,遇到啥问题,再聊聊怎么优化,最后对接上现在主流的方案。
先从最简单的开始:直接用起来
假设我们有个超简单的场景:一个 UserService
,里面需要调用 UserRepository
来查用户信息。最朴素的办法是直接 new 一个对象:
java
public class UserService {
private UserRepository userRepository = new UserRepository();
public String getUserName(int id) {
return userRepository.findNameById(id);
}
}
这代码看起来没啥毛病,运行也正常。但问题很快就来了:UserRepository
如果是个接口,后面想换成不同的实现(比如从内存查数据改成从数据库查),咋办?直接改代码?那不得累死。而且如果 UserRepository
本身还有别的依赖(比如数据库连接),这手动 new 的方式就更麻烦了,得一层一层自己去创建。
这时候,Spring 的依赖注入就派上场了。咱们把创建对象的事儿交给 Spring,用注解来告诉它:"嘿,这个字段你帮我填一下。"
@Autowired:Spring 家的老朋友
Spring 提供了 @Autowired
,用起来特别简单:
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public String getUserName(int id) {
return userRepository.findNameById(id);
}
}
加上 @Autowired
,Spring 就会在启动的时候自动找到 UserRepository
的实例,注入到 UserService
里。简单吧?确实省事儿,默认是按照类型(by type)去找匹配的 bean。
但这朴素的用法有隐患。比如说,如果 Spring 容器里有两个 UserRepository
的实现类(比如 MemoryUserRepository
和 DbUserRepository
),Spring 就懵了,会抛个异常:NoUniqueBeanDefinitionException
,意思是"哥们儿,我不知道该挑哪个"。
再比如,如果 UserRepository
压根没被 Spring 管理(没加 @Component
或者没配置成 bean),Spring 又会报错:NoSuchBeanDefinitionException
,这时候你还得手动去检查配置。
@Resource:J2EE 的通用选手
那 @Resource
呢?它是 Java EE 标准里的东西,不像 @Autowired
是 Spring 专属的。用法也很直白:
java
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Resource
private UserRepository userRepository;
public String getUserName(int id) {
return userRepository.findNameById(id);
}
}
跟 @Autowired
比,@Resource
默认是按名字(by name)匹配的。如果没指定名字,它会先找字段名(比如 userRepository
)对应的 bean,找不到再退回到按类型匹配。这听着是不是更聪明点儿?
但它也有自己的小麻烦。比如你字段名叫 userRepository
,但容器里压根没有叫这个名字的 bean,只有个类型匹配的,那它还得 fallback 到类型匹配,这时候又可能撞上多个 bean 的问题,跟 @Autowired
一样翻车。
朴素策略的问题暴露出来了
咱们先停下来想想,这两个注解用最简单的方式上手虽然方便,但问题不少:
- 模糊匹配的尴尬 :不管是
@Autowired
按类型还是@Resource
按名字优先,容器里 bean 一多就容易乱套。 - 空指针风险:字段注入(field injection)看着简洁,但如果 bean 没注入成功(比如配置漏了),调用的时候就得小心空指针。
- 测试不友好 :直接在字段上加注解,单元测试时没法轻松 mock 出
UserRepository
,得依赖 Spring 容器。
这些不利因素咋解决呢?咱们得往更靠谱的方向优化。
第一步优化:加上限定符
针对"多个 bean 不知道选哪个"的问题,Spring 给 @Autowired
配了个帮手:@Qualifier
。比如:
java
@Service
public class UserService {
@Autowired
@Qualifier("dbUserRepository")
private UserRepository userRepository;
}
这时候 Spring 会去找容器里名字叫 dbUserRepository
的那个 bean,类型还得是 UserRepository
,这样就精准多了。
@Resource
也有类似玩法,直接指定 name:
java
@Service
public class UserService {
@Resource(name = "dbUserRepository")
private UserRepository userRepository;
}
这步优化已经能解决模糊匹配的问题了,跟现在主流做法也靠得更近了:明确指定依赖,避免容器自己瞎猜。
再进一步:构造器注入
光解决匹配还不够,字段注入的空指针和测试问题咋办?主流方案是改用构造器注入。看看咋写:
java
@Service
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public String getUserName(int id) {
return userRepository.findNameById(id);
}
}
为啥这招好?
- 强制依赖:构造器里要啥,Spring 必须给啥,少一个都不行,启动时就能发现问题,不像字段注入可能运行时才炸。
- 不可变性 :加个
final
,userRepository
就不会被改,稳得一批。 - 测试方便 :单元测试时直接 new 一个
UserService
,传个 mock 的UserRepository
就行,不用 Spring 容器。
其实 Spring 官方文档也推荐构造器注入,@Autowired
可以省掉(Spring 4.3 之后单构造器默认自动注入),代码还能更简洁:
java
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
@Resource
没法用在构造器上,所以这块是 @Autowired
的主场。
补充注解:@Primary 和 @Named
有时候你不想每次都写 @Qualifier
,可以给某个 bean 加个 @Primary
,告诉 Spring:"多个同类型 bean 的时候,默认挑我。"
java
@Component
@Primary
public class DbUserRepository implements UserRepository {
// 实现略
}
这样 @Autowired
直接用,不用额外指定也能挑到 DbUserRepository
。
如果是 Java EE 环境,@Named
跟 @Qualifier
有点像,用来指定 bean 的名字,常跟 @Inject
(JSR-330 标准)搭配。不过 Spring 也支持 @Named
,跟 @Resource
的 name 效果差不多。
聊聊优化的方向和主流方案
从最朴素的 new 对象,到字段注入,再到构造器注入,咱们一步步逼近了现代开发的标配。主流方案里,构造器注入 + @Primary
+ 显式限定符已经是标配了。为啥?这背后其实是面向对象设计里的"依赖倒置"和"单一职责"思想:
- 解耦:类不自己创建依赖,全交给容器,改实现不改代码。
- 可测性:构造器注入让测试变得简单,mock 啥都行。
- 安全性:启动时检查依赖,运行时少踩坑。
再往深了走,像 Spring Boot 这种现代框架,还会结合自动配置(AutoConfiguration)和条件注解(@Conditional
),让依赖注入更智能。不过那是另一话题了,咱们今天就先聊到这儿。
总结一下
@Autowired
是 Spring 的原生选手,按类型匹配,灵活但需要 @Qualifier
帮忙精准定位;@Resource
是 J2EE 的通用方案,按名字优先,跨框架用着顺手。简单用都行,但想做得扎实,构造器注入加上显式指定是王道。代码少点坑,开发少点泪,这不就是咱们想要的吗?