深入解析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;
}
相关推荐
ps酷教程19 小时前
Jackson 解决没有无参构造函数的反序列化问题
java
NiceCloud喜云20 小时前
Opus 4.8 的 Effort Control 怎么选:Low 到 Max 五档策略
android·java·大数据·前端·c++·python·spring
为思念酝酿的痛20 小时前
POSIX信号量
linux·运维·服务器·后端
小羊在睡觉20 小时前
力扣84. 柱状图中最大的矩形
后端·算法·leetcode·golang·go
_日拱一卒21 小时前
LeetCode:994腐烂的橘子
java·数据结构·算法·leetcode·深度优先
swipe21 小时前
Neo4j + Graph RAG 医疗知识图谱工程实践:患者教育问答真正需要的是“关系可追溯”
后端·langchain·llm
隔窗听雨眠21 小时前
Nginx网关响应慢排查手记
java·服务器·nginx
智慧物业老杨21 小时前
智慧物业合同周期管理系统:从风险预警到智能交接的全流程数智化落地方案
java·人工智能·python
源码宝21 小时前
MES系统源码:Java8 + SpringBoot2.7 + MySQL8 + Redis,后端源码清爽易扩展
java·后端·源码·springboot·mes系统·源码二开·mes源码
JAVA社区1 天前
Java高级全套教程(十)—— SpringCloudAlibaba超详细实战详解
java·开发语言·spring cloud·面试·职场和发展