Spring 中的依赖注入(DI)详解

📌 摘要

在现代 Java 开发中,依赖注入(Dependency Injection, DI) 是 Spring 框架最核心的功能之一。它通过解耦对象之间的依赖关系,提高了代码的可维护性、可测试性和可扩展性。

本文将全面讲解 Spring 中依赖注入的核心概念、实现方式、常见注解与 XML 配置方法,并结合实际示例演示如何在不同场景下灵活使用 DI。适合初学者入门,也适合中高级开发者查漏补缺和深入理解底层机制。


🎯 一、引言:什么是依赖注入?

在传统的 Java 应用中,一个类往往需要依赖其他类的对象来完成其功能。例如:

java 复制代码
public class UserService {
    private UserRepository userRepository = new UserRepository();
}

这种方式的问题在于:UserService 强耦合了 UserRepository 的具体实现,一旦 UserRepository 被修改或替换,UserService 也需要改动。

✅ 依赖注入的本质:

由外部容器负责创建依赖对象并注入到目标对象中,而不是由目标对象自己创建依赖。

这正是 Spring IOC 容器所做的事情。


🧱 二、Spring 依赖注入的核心组件

组件 描述
BeanFactory 最基础的容器接口,支持延迟加载 Bean
ApplicationContext BeanFactory 的子接口,提供更多企业级功能(如事件发布、国际化等)
BeanDefinition 描述 Bean 的元数据信息(类名、作用域、构造参数等)
ObjectFactory / ObjectProvider 支持懒加载和按需获取 Bean
@Autowired@Inject@Resource 常见的 DI 注解

🔁 三、Spring 依赖注入的三种方式

Spring 提供了多种方式进行依赖注入,主要包括以下三种:

1. 构造器注入(Constructor Injection)

适用于强制依赖项,即必须存在的依赖。

示例代码:
java 复制代码
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

XML 配置方式:

xml 复制代码
<bean id="userService" class="com.example.UserService">
    <constructor-arg ref="userRepository"/>
</bean>

Java Config 方式:

java 复制代码
@Bean
public UserService userService(UserRepository userRepository) {
    return new UserService(userRepository);
}

2. Setter 注入(Setter Injection)

适用于可选依赖项,或者希望后期动态修改的情况。

示例代码:
java 复制代码
public class UserService {
    private UserRepository userRepository;

    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

XML 配置方式:

xml 复制代码
<bean id="userService" class="com.example.UserService">
    <property name="userRepository" ref="userRepository"/>
</bean>

Java Config 方式:

java 复制代码
@Bean
public UserService userService(UserRepository userRepository) {
    UserService service = new UserService();
    service.setUserRepository(userRepository);
    return service;
}

3. 字段注入(Field Injection)

直接通过注解注入字段,简洁但不利于测试和扩展。

示例代码:
java 复制代码
public class UserService {

    @Autowired
    private UserRepository userRepository;
}

⚠️ 注意事项:

  • 不推荐在生产环境中大量使用字段注入,因为它隐藏了依赖关系,难以 mock 和测试。
  • 推荐使用构造器注入,尤其是在需要确保依赖不为空的情况下。

🛠️ 四、Spring 中常用的依赖注入注解

注解 来源 说明
@Autowired Spring 自动装配 Bean,默认按类型匹配
@Qualifier Spring 配合 @Autowired 使用,按名称匹配 Bean
@Resource JSR-250 Java 标准注解,默认按名称匹配 Bean
@Inject JSR-330 类似于 @Autowired,需引入额外依赖(如 Dagger、Guice)
@Primary Spring 当有多个同类型的 Bean 时,优先选择此 Bean
@Value Spring 注入基本类型值或 SpEL 表达式
@Required Spring 已废弃,曾用于标记某个 setter 方法必须被注入

🔄 五、依赖注入的进阶使用

1. 多个相同类型的 Bean 注入问题

当存在多个相同类型的 Bean 时,可以通过 @Qualifier@Resource 明确指定名称。

示例:
java 复制代码
@Component("mysqlUserRepo")
public class MysqlUserRepository implements UserRepository { ... }

@Component("mongoUserRepo")
public class MongoUserRepository implements UserRepository { ... }

@Service
public class UserService {

    @Autowired
    @Qualifier("mysqlUserRepo")
    private UserRepository userRepository;
}

2. 懒加载注入(Lazy Injection)

使用 @Lazy 可以延迟初始化 Bean,直到第一次调用时才创建。

java 复制代码
@Autowired
@Lazy
private UserRepository userRepository;

3. 使用 ObjectProvider 实现可选注入

避免注入失败抛出异常,适用于可选依赖。

java 复制代码
@Autowired
private ObjectProvider<UserRepository> userRepositoryProvider;

public void someMethod() {
    UserRepository repo = userRepositoryProvider.getIfAvailable();
    if (repo != null) {
        // 使用 repo
    }
}

4. 使用 @Value 注入配置属性

java 复制代码
@Value("${app.config.max-retry}")
private int maxRetryCount;

配合 application.properties 使用:

properties 复制代码
app.config.max-retry=3

💡 六、Spring Boot 中的自动装配原理简析

Spring Boot 的自动装配机制本质上也是基于 DI 实现的。它通过 @ConditionalOnClass@ConditionalOnMissingBean 等条件注解,根据类路径中的类和已注册的 Bean 动态决定是否注册某些默认 Bean。

例如:

java 复制代码
@Configuration
@ConditionalOnClass(DataSource.class)
public class DataSourceAutoConfiguration {
    // ...
}

这种机制大大简化了手动配置的过程,使得 Spring Boot 成为开箱即用的典范。


🧪 七、实战案例:用户登录系统的依赖注入设计

场景描述:

一个简单的用户登录系统,包含以下组件:

  • UserService:处理业务逻辑
  • UserRepository:操作数据库
  • PasswordEncoder:密码加密工具
示例代码:
java 复制代码
@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    public boolean login(String username, String rawPassword) {
        User user = userRepository.findByUsername(username);
        return passwordEncoder.matches(rawPassword, user.getPassword());
    }
}

通过依赖注入,我们实现了组件间的松耦合,便于后续更换数据库访问层或加密算法。


⚙️ 八、最佳实践与注意事项

实践 说明
优先使用构造器注入 更利于单元测试,且能明确依赖关系
尽量避免字段注入 不利于 mock 测试
合理使用 @Primary@Qualifier 解决多个同类型 Bean 冲突
对可选依赖使用 ObjectProvider 避免注入失败导致启动失败
使用 @Lazy 优化启动性能 特别是在大型项目中
配合 Spring Boot 的自动装配机制 减少重复配置
使用 @Value + 配置中心管理环境变量 如 Nacos、Apollo 等

💬 九、常见面试题解析

Q1: Spring 中有哪些依赖注入的方式?各有什么优缺点?

答:

  • 构造器注入:推荐使用,适合强依赖,不可变;
  • Setter 注入:适合可选依赖,方便修改;
  • 字段注入:简洁但不利于测试。

Q2: @Autowired@Resource 的区别?

答:

  • @Autowired 是 Spring 提供的,按类型注入;
  • @Resource 是 Java EE 标准,按名称注入,找不到再按类型。

Q3: @Autowired 注解可以标注在哪些地方?

答:可以标注在:

  • 构造器
  • 方法(setter、普通方法)
  • 字段
  • 参数(如方法参数、构造器参数)

Q4: 如果有多个同类型的 Bean,Spring 如何选择注入哪一个?

答:可以通过 @Primary 标记首选 Bean,也可以使用 @Qualifier 指定名称。

Q5: @Autowired(required = false) 是什么意思?

答:表示该依赖不是必须的,如果没有找到对应的 Bean,也不会抛出异常。


💥 十、总结

Spring 的依赖注入机制是其强大功能的基础之一。通过 DI,我们可以实现模块之间的松耦合,提高系统的灵活性和可维护性。

通过本文的学习,你应该已经掌握了:

  • 依赖注入的基本概念与工作原理
  • 构造器、Setter、字段注入的区别与使用
  • Spring 中常用注解的作用与使用方式
  • 多 Bean 注入冲突的解决方案
  • 实战项目中的 DI 设计
  • 面试高频考点解析


  • 如果你在学习过程中遇到任何疑问,欢迎在评论区留言交流!
  • 👍 如果你觉得这篇文章对你有帮助,别忘了点赞、收藏、转发哦!