深入解析Spring依赖注入 DI 的三种方式

前言

在之前的一篇文章Spring Bean的配置方式中,我们学习到了如何配置Bean并将其放到IOC容器里的几种注解,这篇文章我们主要讲注入的相关注解,这些注解负责从容器里取出Bean并注入到需要的地方

先配置Bean(@Service、@Repository、@Component...),再注入Bean,二者协同完成Spring的依赖注入的完整流程,缺一不可

这篇主要讲解注入相关的几个注解:@Autowired、@Inject、@Resource

一、@Autowired

1.定义

@Autowired是Spring框架自带的依赖注入注解,其核心作用是:自动装配符合条件的Bean到当前类的属性、方法或构造器中,无需手动创建Bean实例,由Spring容器统一管理和注入。

@Autowired可用于字段、构造器、setter方法、方法参数,四种场景各有适用场景,其中构造器注入是Spring官方推荐的方式、而参数注入适用于局部方法需要依赖Bean的场景,灵活性更高

2.核心原理

@Autowired的注入逻辑遵循"先byType,后byName"的规则,具体流程如下:

  1. Spring容器启动时,会扫描所有带有@Autowired注解的字段、方法或构造器;
  2. 首先按照"类型匹配"(byType)查找容器中是否存在唯一的、与目标类型一致(或兼容,如子类、实现类)的Bean;
  3. 若找到唯一匹配的Bean,直接注入;
  4. 若找到多个类型匹配的Bean,会切换为"名称匹配"(byName),即根据目标字段名、方法参数名,匹配Bean的id(默认是类名首字母小写);若多个Bean名称相同,会抛出NoUniqueBeanDefinitionException异常,有两种解决方案:@Primary、@Qualifier
  5. 若既没有唯一类型匹配,也没有名称匹配的Bean,且@Autowired未设置required=false,则抛出NoSuchBeanDefinitionException异常;若设置required=false,则注入null(不推荐,易引发空指针)。

3.字段注入

直接在类的成员变量上添加@Autowired注解,无需setter方法,Spring会自动将匹配的Bean注入到该字段中。

注意:

  • 优点:字段注入无需手动编写setter方法,代码简洁;

  • 缺点:可读性稍差,且不利于单元测试(无法通过setter方法手动注入模拟Bean);

  • 不能用于静态字段(如private static UserDao userDao; 注入无效,会为null)。

java 复制代码
// 服务层Bean
@Service
public class UserService {
    // 字段注入:自动注入UserDao类型的Bean
    @Autowired
    private UserDao userDao;
    
    // 业务方法,直接使用注入的userDao
    public User getUserById(Long id) {
        return userDao.selectById(id);
    }
}

// 持久层Bean(@Repository会自动注册到Spring容器)
@Repository
public class UserDaoImpl implements UserDao {
    @Override
    public User selectById(Long id) {
        // 模拟数据库查询
        return new User(id, "张三", 20);
    }
}

4.构造器注入(官方推荐)

在类的构造器上添加@Autowired注解,Spring会在实例化该Bean时,通过构造器将匹配的Bean注入。

注意:

  • 如果bean只有一个有参构造函数(这个有参构造函数会覆盖掉默认的无参构造函数),可以省略@Autowired,因为会自动注入构造函数的参数;
  • 如果有多个构造函数并且没有无参构造函数时,不添加@Autowired,就会报错,因此需要添加这个注解来指定默认

优势:

  • 构造器注入可以保证Bean实例化时,所有依赖都已注入完成,避免了字段注入可能出现的空指针问题;
  • 依赖关系明确,通过构造器参数就能清晰看到该Bean需要哪些依赖;
java 复制代码
@Service
public class UserService {
    private final UserDao userDao;
    private final RoleDao roleDao;
    
    // 构造器注入:注入多个依赖
    @Autowired // 当只有一个有参构造器时,可省略此注解
    public UserService(UserDao userDao, RoleDao roleDao) {
        this.userDao = userDao;
        this.roleDao = roleDao;
    }
    
    // 业务方法
    public User getUserWithRole(Long userId) {
        User user = userDao.selectById(userId);
        List<Role> roles = roleDao.selectByUserId(userId);
        user.setRoles(roles);
        return user;
    }
}

5.setter方法注入

在setter方法上添加@Autowired注解,Spring会调用该setter方法,将匹配的Bean注入到字段中。适用于依赖可选(可通过required=false设置)的场景。

注意:

  • required=false表示该依赖不是必须的,若Spring容器中没有匹配的Bean,不会抛出异常,而是注入null,使用时需做非空判断。
java 复制代码
@Service
public class UserService {
    private UserDao userDao;
    
    // setter方法注入
    @Autowired(required = false) // 依赖可选,没有匹配Bean时注入null
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    
    // 业务方法,需先判断userDao是否为null
    public User getUserById(Long id) {
        if (userDao == null) {
            throw new RuntimeException("UserDao未注入");
        }
        return userDao.selectById(id);
    }
}

6.方法参数注入

在普通方法(非setter、非构造器)的参数上添加@Autowired注解,Spring会自动将匹配的Bean注入到该参数中,适用于局部方法需要临时依赖某个Bean的场景,无需将依赖注入为类的全局字段,减少不必要的全局依赖。

注意:

  • 参数注入的Bean仅在当前方法内有效,不属于类的全局属性,避免全局字段冗余;
  • 可同时为多个方法参数添加@Autowired,Spring会分别匹配注入对应的Bean;

7.@Qualifier & @Primary

在讲核心原理的第四步时,提到了当Spring容器中存在多个相同类型的Bean时,@Autowired的"先byType,后byName"规则可能无法满足需求(比如字段名与Bean的id不匹配)

此时可以通过 @Qualifier(精准指定) 或 **@Primary(默认首选)**解决冲突

(1)@Qualifier:精准指定

@Qualifier必须与@Autowired配合使用,通过value属性指定要注入的Bean的id,精准匹配,彻底解决多Bean冲突问题。

java 复制代码
// 两个相同类型的Bean(UserDao的两个实现类)
@Repository("userDaoMysql") // Bean的id为userDaoMysql
public class UserDaoMysqlImpl implements UserDao { 
    @Override
    public User selectById(Long id) {
        return new User(id, "张三(Mysql)", 20);
    }
}

@Repository("userDaoOracle") // Bean的id为userDaoOracle
public class UserDaoOracleImpl implements UserDao {
    @Override
    public User selectById(Long id) {
        return new User(id, "张三(Oracle)", 20);
    }
}

// 服务层注入,指定注入id为userDaoMysql的Bean
@Service
public class UserService {
    // @Qualifier配合@Autowired,指定Bean的id
    @Autowired
    @Qualifier("userDaoMysql")
    private UserDao userDao;
    
    // 业务方法
    public User getUserById(Long id) {
        return userDao.selectById(id); // 使用的是Mysql实现
    }
}

(2)@Primary:默认首选

@Primary添加在Bean的实现类上,标识该Bean为"默认首选Bean",当存在多个同类型Bean时,Spring会自动选择带有@Primary注解的Bean注入,无需在注入端额外配置。

java 复制代码
// 两个相同类型的Bean,其中一个添加@Primary
@Repository
@Primary // 标记为默认首选Bean
public class UserDaoMysqlImpl implements UserDao { 
    @Override
    public User selectById(Long id) {
        return new User(id, "张三(Mysql)", 20);
    }
}

@Repository
public class UserDaoOracleImpl implements UserDao {
    @Override
    public User selectById(Long id) {
        return new User(id, "张三(Oracle)", 20);
    }
}

// 服务层注入,无需指定@Qualifier,自动注入@Primary标记的Bean
@Service
public class UserService {
    @Autowired
    private UserDao userDao; // 自动注入UserDaoMysqlImpl(@Primary标记)
    
    public User getUserById(Long id) {
        return userDao.selectById(id); // 输出:张三(Mysql)
    }
}

二、@Inject

1.定义

@Inject是JSR-330(Java Dependency Injection)标准注解,位于javax.inject包下,不属于Spring框架,是一种通用的依赖注入注解,可用于替代@Autowired,适用于需要脱离Spring框架、追求标准化的场景。

2.与@Autowired主要区别

无赖Spring依赖,没有属性(在@Autowired中有required属性),因此依赖必须存在,不存在就抛出异常

3.使用方式

有字段注入、构造器注入,示例如下:

java 复制代码
// 需导入依赖(Maven)
<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1.0</version>
</dependency>

@Service
public class UserService {
    // 字段注入
    @Inject
    private UserDao userDao;
    
    // 构造器注入(无需额外配置,与@Autowired一致)
    @Inject
    public UserService(RoleDao roleDao) {
        this.roleDao = roleDao;
    }
}

三、@Resource

1.定义

@Resource是JSR-250(Java Platform Enterprise Edition)标准注解,位于javax.annotation包下,也是一种通用的依赖注入注解

2.与@Autowired主要区别

与@Autowired的核心区别是:默认按名称匹配(byName),而非按类型匹配

有name、type两个属性,可以单独或组合使用

3.使用方式

有字段注入、setter方法注入,不支持构造器注入、方法参数注入,示例如下:

java 复制代码
@Service
public class UserService {
    // 1. 只指定name,按Bean的id匹配(name属性值=Bean的id)
    @Resource(name = "userDaoMysql")
    private UserDao userDao;
    
    // 2. 不指定name,默认按字段名匹配(字段名=Bean的id)
    @Resource
    private RoleDao roleDao; // 匹配id为roleDao的Bean
    
    // 3. 指定type,按类型匹配(与@Autowired的byType一致)
    @Resource(type = UserDao.class)
    private UserDao userDao2;
    
    // 4. 同时指定name和type,必须同时匹配才会注入
    @Resource(name = "userDaoOracle", type = UserDao.class)
    private UserDao userDao3;
}
相关推荐
葫芦和十三33 分钟前
图解 MongoDB 05|文档模型设计:内嵌 vs 引用,反范式不是免费午餐
后端·mongodb·agent
不能放弃治疗4 小时前
单 Agent 实现模式
后端
IT_陈寒6 小时前
Redis内存爆了,原来我漏掉了这个致命配置
前端·人工智能·后端
小bo波7 小时前
从"任意文件复制"深挖Java I/O:字符流与字节流的本质抉择
java·nio·io流·后端开发·文件复制
fliter7 小时前
最后一块拼图:用 bitvec 构造 IPv4 包,真正做出自己的 Ping
后端
fliter8 小时前
用 Rust 解析并生成 ICMP 包:checksum、nom 与 cookie-factory
后端
蝎子莱莱爱打怪8 小时前
XZLL-IM干货系列 03|消息 ID 设计:一个 UUID 搞不定的事,我用两个 ID 解决了
后端·面试·开源
fliter8 小时前
从 panic 到 Result:用 Rust 重新整理一个 ping 项目的错误处理
后端
森蓝情丶9 小时前
我给 AI 搭了个法庭:一个前端仔的 LangGraph 实战全记录
前端·后端
JensCS猿9 小时前
从 Spring Boot 回看 SSM 框架:手动挡与自动挡的驾驶哲学
后端