Spring Framework 核心概念学习指南 (IoC/DI)

3 阅读7分钟

一、Spring Framework 概述

1. 为什么需要 Spring?

问题传统的解决方案Spring 的解决方案
对象创建混乱手动 new 对象容器统一管理
依赖关系复杂硬编码依赖依赖注入
代码重复相同逻辑重复编写AOP 面向切面
资源管理困难手动管理数据库连接自动资源管理

2. Spring 的核心价值

价值说明
解耦降低组件间的耦合度
开发效率减少样板代码
可测试性便于单元测试
扩展性易于扩展和维护

二、IoC(控制反转)详解

1. 什么是控制反转?

传统方式:

public class OrderService {
    // 主动创建依赖对象
    private ProductService productService = new ProductService();
    
    private PaymentService paymentService = new PaymentService();
}

Spring 方式:

@Service
public class OrderService {
    // 声明需要什么依赖,由 Spring 提供
    private final ProductService productService;
    
    private final PaymentService paymentService;
    
    public OrderService(ProductService productService, 
                        PaymentService paymentService) {
        this.productService = productService;
        this.paymentService = paymentService;
    }
}

2. 控制反转的三个关键点

关键点传统方式Spring 方式
对象创建开发者手动 newSpring 容器创建
依赖管理开发者主动查找Spring 容器注入
生命周期开发者手动管理Spring 容器管理

3. IoC 容器

IoC 容器是 Spring 的核心,它负责:

  1. 实例化对象:根据配置创建对象实例
  2. 配置对象:设置对象的属性和依赖关系
  3. 组装对象:将对象之间的依赖关系注入
  4. 管理生命周期:控制对象的创建、初始化、销毁

三、DI(依赖注入)详解

1. 什么是依赖注入?

定义: 依赖注入是实现控制反转的一种方式,通过将对象的依赖关系从内部转移到外部,由容器在运行时动态注入。

示例:

// 被依赖的对象
@Repository
public class UserRepository {
    public User findById(Long id) {
        // 查询逻辑
        return new User();
    }
}

// 需要依赖的对象
@Service
public class UserService {
    private final UserRepository userRepository;
    
    // 构造器注入
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    public User getUser(Long id) {
        return userRepository.findById(id);
    }
}

2. 三种依赖注入方式

方式一:构造器注入(推荐)

@Service
public class UserService {
    private final UserRepository userRepository;
    private final EmailService emailService;
    
    // 构造器注入
    public UserService(UserRepository userRepository, 
                        EmailService emailService) {
        this.userRepository = userRepository;
        this.emailService = emailService;
    }
}

优点:

  • ✅ 确保依赖不可变(final)
  • ✅ 确保依赖非空
  • ✅ 清晰表达依赖关系
  • ✅ 便于单元测试(不需要反射)

方式二:Setter 注入

@Service
public class UserService {
    private UserRepository userRepository;
    private EmailService emailService;
    
    @Autowired
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    @Autowired
    public void setEmailService(EmailService emailService) {
        this.emailService = emailService;
    }
}

适用场景:

  • 可选依赖
  • 可能会变化的依赖

方式三:字段注入(不推荐)

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private EmailService emailService;
}

缺点:

  • ❌ 依赖可能为空
  • ❌ 无法使用 final
  • ❌ 难以单元测试
  • ❌ 违反单一职责原则

3. 依赖注入的好处

好处说明
解耦降低组件间的耦合度
可测试性容易注入 Mock 对象
灵活性依赖可以动态替换
代码简洁减少手动管理依赖的代码

四、Bean 的管理

1. 什么是 Bean?

Bean 是 Spring 容器管理的对象,也称为 Spring Bean。

Bean 的生命周期:

实例化 → 属性赋值 → 初始化 → 使用 → 销毁

2. Bean 的注解

注解作用使用场景
@Component通用组件注解任何需要 Spring 管理的类
@Service服务层注解业务逻辑层
@Repository数据访问层注解数据库访问、DAO
@Controller控制器注解Web 层、处理 HTTP 请求
@Configuration配置类注解Spring 配置类

注意: 这些注解本质上是 @Component 的特化版本。


3. Bean 的注册方式

方式一:注解扫描(推荐)

@Service
public class UserService {
    // Spring 自动扫描并注册
}

@RestController
@RequestMapping("/api/users")
public class UserController {
    @Autowired
    private UserService userService;
}

方式二:Java 配置类

@Configuration
public class AppConfig {
    
    @Bean
    public UserService userService(UserRepository userRepository) {
        return new UserService(userRepository);
    }
    
    @Bean
    public UserRepository userRepository() {
        return new UserRepositoryImpl();
    }
}

4. Bean 的作用域

作用域说明使用场景
singleton(默认)单例,整个容器只有一个实例无状态的服务
prototype原型,每次请求创建新实例有状态的对象
request每次 HTTP 请求创建一个实例Web 应用中的请求作用域
session每个 HTTP Session 创建一个实例Web 应用中的会话作用域

示例:

@Service
@Scope("prototype")  // 原型作用域
public class PrototypeService {
    // 每次注入都会创建新实例
}

五、依赖注入的高级特性

1. 循环依赖

什么是循环依赖?

@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
}

@Service
public class ServiceB {
    @Autowired
    private ServiceA serviceA;
}

循环依赖的问题:

  • 违反了良好的设计原则
  • 代码耦合度高
  • 难以测试和维护

解决方案:

  1. 重构代码(推荐):提取公共依赖,消除循环
  2. 使用 @Lazy:延迟初始化
  3. 使用 Setter 注入:允许循环依赖

2. @Qualifier 注解

作用: 当有多个相同类型的 Bean 时,指定使用哪一个。

@Configuration
public class AppConfig {
    
    @Bean("primaryEmailService")
    public EmailService primaryEmailService() {
        return new PrimaryEmailService();
    }
    
    @Bean("backupEmailService")
    public EmailService backupEmailService() {
        return new BackupEmailService();
    }
}

@Service
public class UserService {
    @Autowired
    @Qualifier("primaryEmailService")  // 指定使用哪个 Bean
    private EmailService emailService;
}

3. @Primary 注解

作用: 指定当有多个相同类型的 Bean 时,默认使用哪一个。

@Configuration
public class AppConfig {
    
    @Primary  // 默认使用这个 Bean
    @Bean
    public EmailService primaryEmailService() {
        return new PrimaryEmailService();
    }
    
    @Bean
    public EmailService backupEmailService() {
        return new BackupEmailService();
    }
}

4. @Autowired 的可选性

@Service
public class UserService {
    @Autowired(required = false)  // 如果没有匹配的 Bean,不报错
    private OptionalEmailService optionalEmailService;
}

六、最佳实践

1. 依赖注入最佳实践

实践说明
使用构造器注入优先使用构造器注入
使用 final 字段确保依赖不可变
避免循环依赖通过重构消除循环依赖
明确依赖关系通过构造器参数清晰表达依赖

2. Bean 命名规范

类型命名规范示例
ServiceXxxServiceUserService
RepositoryXxxRepositoryUserRepository
ControllerXxxControllerUserController
ComponentXxxComponentEmailComponent

3. 避免的反模式

反模式问题解决方案
字段注入难以测试、依赖可能为空使用构造器注入
过度依赖一个类依赖太多其他类拆分职责、使用门面模式
循环依赖设计问题重构代码、提取公共依赖
滥用 @Autowired让代码难以阅读优先使用构造器注入

七、实战示例

1. 完整的依赖注入示例

UserRepository.java:

package com.example.myapp.repository;

import org.springframework.stereotype.Repository;

@Repository
public class UserRepository {
    public User findById(Long id) {
        // 模拟数据库查询
        return new User(id, "John Doe", "john@example.com");
    }
    
    public User save(User user) {
        // 模拟保存逻辑
        return user;
    }
}

EmailService.java:

package com.example.myapp.service;

import org.springframework.stereotype.Service;

@Service
public class EmailService {
    public void sendEmail(String to, String subject, String body) {
        System.out.println("发送邮件到: " + to);
        System.out.println("主题: " + subject);
        System.out.println("内容: " + body);
    }
}

UserService.java:

package com.example.myapp.service;

import com.example.myapp.repository.UserRepository;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    private final UserRepository userRepository;
    private final EmailService emailService;
    
    // 构造器注入(推荐)
    public UserService(UserRepository userRepository, 
                      EmailService emailService) {
        this.userRepository = userRepository;
        this.emailService = emailService;
    }
    
    public User getUser(Long id) {
        return userRepository.findById(id);
    }
    
    public User register(User user) {
        User savedUser = userRepository.save(user);
        emailService.sendEmail(user.getEmail(), 
                              "欢迎注册", 
                              "欢迎加入我们的平台!");
        return savedUser;
    }
}

UserController.java:

package com.example.myapp.controller;

import com.example.myapp.model.User;
import com.example.myapp.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    private final UserService userService;
    
    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }
    
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.getUser(id);
    }
    
    @PostMapping
    public User register(@RequestBody User user) {
        return userService.register(user);
    }
}

八、总结

概念核心要点
IoC(控制反转)将对象创建和依赖管理的控制权从开发者转移到 Spring 容器
DI(依赖注入)Spring 容器在运行时动态地将依赖对象注入到目标对象中
BeanSpring 容器管理的对象
Bean 的作用域singleton(默认)、prototype、request、session
依赖注入方式构造器注入(推荐)、Setter 注入、字段注入(不推荐)
高级特性循环依赖、@Qualifier、@Primary、@Autowired 可选性

总结问题

1. 什么是 IoC 和 DI?有什么区别?

答案:

  • IoC(控制反转):一种设计思想,将对象创建和依赖管理的控制权从开发者转移到容器
  • DI(依赖注入):实现 IoC 的一种方式,通过注入依赖来实现控制反转
  • 区别:IoC 是思想,DI 是实现方式

2. Spring 有哪些依赖注入方式?推荐使用哪种?

答案:

  • 三种方式:构造器注入、Setter 注入、字段注入
  • 推荐使用构造器注入
  • 原因:确保依赖不可变、非空、清晰表达依赖关系、便于测试

3. 什么是 Bean?Bean 的作用域有哪些?

答案:

  • Bean 是 Spring 容器管理的对象
  • 作用域:singleton(单例)、prototype(原型)、request、session

4. 什么是循环依赖?如何解决?

答案:

  • 循环依赖:两个或多个 Bean 之间相互依赖
  • 解决方案
    1. 重构代码,消除循环依赖(推荐)
    2. 使用 @Lazy 延迟初始化
    3. 使用 Setter 注入