@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 的通用方案,按名字优先,跨框架用着顺手。简单用都行,但想做得扎实,构造器注入加上显式指定是王道。代码少点坑,开发少点泪,这不就是咱们想要的吗?

相关推荐
程序员岳焱7 分钟前
Java 与 MySQL 性能优化:Java 实现百万数据分批次插入的最佳实践
后端·mysql·性能优化
麦兜*37 分钟前
Spring Boot启动优化7板斧(延迟初始化、组件扫描精准打击、JVM参数调优):砍掉70%启动时间的魔鬼实践
java·jvm·spring boot·后端·spring·spring cloud·系统架构
大只鹅1 小时前
解决 Spring Boot 对 Elasticsearch 字段没有小驼峰映射的问题
spring boot·后端·elasticsearch
ai小鬼头1 小时前
AIStarter如何快速部署Stable Diffusion?**新手也能轻松上手的AI绘图
前端·后端·github
IT_10242 小时前
Spring Boot项目开发实战销售管理系统——数据库设计!
java·开发语言·数据库·spring boot·后端·oracle
bobz9652 小时前
动态规划
后端
stark张宇2 小时前
VMware 虚拟机装 Linux Centos 7.9 保姆级教程(附资源包)
linux·后端
亚力山大抵3 小时前
实验六-使用PyMySQL数据存储的Flask登录系统-实验七-集成Flask-SocketIO的实时通信系统
后端·python·flask
超级小忍3 小时前
Spring Boot 中常用的工具类库及其使用示例(完整版)
spring boot·后端
CHENWENFEIc4 小时前
SpringBoot论坛系统安全测试实战报告
spring boot·后端·程序人生·spring·系统安全·安全测试