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

相关推荐
撸猫7911 小时前
HttpSession 的运行原理
前端·后端·cookie·httpsession
嘵奇2 小时前
Spring Boot中HTTP连接池的配置与优化实践
spring boot·后端·http
子燕若水2 小时前
Flask 调试的时候进入main函数两次
后端·python·flask
程序员爱钓鱼2 小时前
跳转语句:break、continue、goto -《Go语言实战指南》
开发语言·后端·golang·go1.19
Persistence___3 小时前
SpringBoot中的拦截器
java·spring boot·后端
嘵奇3 小时前
Spring Boot 跨域问题全解:原理、解决方案与最佳实践
java·spring boot·后端
景天科技苑5 小时前
【Rust泛型】Rust泛型使用详解与应用场景
开发语言·后端·rust·泛型·rust泛型
lgily-12257 小时前
常用的设计模式详解
java·后端·python·设计模式
意倾城8 小时前
Spring Boot 配置文件敏感信息加密:Jasypt 实战
java·spring boot·后端
火皇4058 小时前
Spring Boot 使用 OSHI 实现系统运行状态监控接口
java·spring boot·后端