深入解析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;
}
相关推荐
庞轩px1 小时前
第一篇:Spring IoC容器——控制反转的本质与Bean的生命周期
spring·ioc·di·控制反转·bean生命周期·循环依赖
geovindu1 小时前
go:Condition Variable Pattern
开发语言·后端·设计模式·golang·条件变量模式
亚马逊云开发者1 小时前
Lambda 冷启动改善了,你的 Provisioned Concurrency 可能白花钱了
java
无风听海1 小时前
UseForwardedHeaders 与 UsePathBase:深入理解 ASP.NET Core 代理感知中间件
后端·中间件·asp.net
C雨后彩虹1 小时前
猴子爬山问题
java·数据结构·算法·华为·面试
天真吴邪xie1 小时前
Claude Code安装
java·git
小新同学^O^1 小时前
简单学习 --> Spring统一处理
java·学习·spring·统一功能处理
程序猿乐锅1 小时前
【Tilas|第七篇】学员管理实现
java·笔记·idea·tlias
huohuopro1 小时前
Spring MVC 的核心知识点梳理
spring·mvc·状态模式