@Autowired 和 @Resource:从简单入手聊聊依赖注入的那些事儿/@Primary @Qualifer

@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 的实现类(比如 MemoryUserRepositoryDbUserRepository),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 一样翻车。


朴素策略的问题暴露出来了

咱们先停下来想想,这两个注解用最简单的方式上手虽然方便,但问题不少:

  1. 模糊匹配的尴尬 :不管是 @Autowired 按类型还是 @Resource 按名字优先,容器里 bean 一多就容易乱套。
  2. 空指针风险:字段注入(field injection)看着简洁,但如果 bean 没注入成功(比如配置漏了),调用的时候就得小心空指针。
  3. 测试不友好 :直接在字段上加注解,单元测试时没法轻松 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 必须给啥,少一个都不行,启动时就能发现问题,不像字段注入可能运行时才炸。
  • 不可变性 :加个 finaluserRepository 就不会被改,稳得一批。
  • 测试方便 :单元测试时直接 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 的通用方案,按名字优先,跨框架用着顺手。简单用都行,但想做得扎实,构造器注入加上显式指定是王道。代码少点坑,开发少点泪,这不就是咱们想要的吗?

相关推荐
clk660710 分钟前
Spring Boot
java·spring boot·后端
皮皮高1 小时前
itvbox绿豆影视tvbox手机版影视APP源码分享搭建教程
android·前端·后端·开源·tv
弱冠少年1 小时前
golang入门
开发语言·后端·golang
Humbunklung1 小时前
Rust 函数
开发语言·后端·rust
喜欢踢足球的老罗1 小时前
在Spring Boot 3.3中使用Druid数据源及其监控功能
java·spring boot·后端·druid
jakeswang1 小时前
StarRocks
后端·架构
龙云飞谷1 小时前
从原理到调参,小白也能读懂的大模型微调算法Lora
后端
荣江1 小时前
【实战】基于 Tauri 和 Rust 实现基于无头浏览器的高可用网页抓取
后端·rust
寻月隐君2 小时前
Web3实战:Solana CPI全解析,从Anchor封装到PDA转账
后端·web3·github
程序员小假2 小时前
说一说 SpringBoot 中 CommandLineRunner
java·后端