Spring核心概念
2025/10/5大约 7 分钟
Spring核心概念
原文链接:https://www.yuque.com/yopai/pp6bv5/oex5nd4emukqqkpt
Spring 是什么
Spring 是一个轻量级的 Java 开发框架,它的主要功能包括:
- IoC 容器:管理 Bean 的创建和依赖注入
- AOP 支持:面向切面编程,分离横切关注点
- 事务支持:统一的事务管理接口
- MVC 开发:Web 请求处理框架
- 第三方集成:方便整合各种框架
IOC 控制反转
IoC - Inversion of Control 控制反转,将对象的创建和管理权转移给第三方。
什么是 IOC
┌─────────────────────────────────────────────────────────────┐
│ 传统方式 vs IOC 方式 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 传统方式:自己创建对象 │
│ ┌─────────────────────────────────┐ │
│ │ Service service = new Service(); ← 自己 new │
│ └─────────────────────────────────┘ │
│ 问题:对象耦合,测试困难 │
│ │
│ IOC 方式:容器创建并注入对象 │
│ ┌─────────────────────────────────┐ │
│ │ Spring 容器 │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ Service service │ │ │
│ │ │ (容器创建和管理) │ │ │
│ │ └─────────────────────────┘ │ │
│ └─────────────────────────────────┘ │
│ 好处:对象由容器管理,耦合度低 │
│ │
└─────────────────────────────────────────────────────────────┘核心问题解答
何为控制,控制的是什么?
答:是 bean 的创建、管理的权利,控制 bean 的整个生命周期。
何为反转,反转了什么?
答:把这个权利交给了 Spring 容器,而不是自己去控制。由之前的自己主动创建对象,变成被动接收别人给我们的对象。
IOC 容器启动流程
┌─────────────────────────────────────────────────────────────┐
│ Spring IOC 容器启动流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 读取配置元数据(XML / 注解 / Java Config) │
│ ↓ │
│ 2. 创建 BeanDefinition(描述 Bean 的配置信息) │
│ ↓ │
│ 3. BeanFactoryPostProcessor 后置处理 │
│ ↓ │
│ 4. 实例化 Bean(创建 Bean 实例) │
│ ↓ │
│ 5. 依赖注入(注入属性和协作者) │
│ ↓ │
│ 6. 生命周期回调(初始化方法) │
│ ↓ │
│ 7. Bean 就绪(可以使用) │
│ ↓ │
│ 8. 容器关闭(销毁 Bean,调用销毁方法) │
│ │
└─────────────────────────────────────────────────────────────┘DI 依赖注入
DI(Dependency Injection,依赖注入) 是 IoC 的具体实现方式。
依赖注入方式对比
┌─────────────────────────────────────────────────────────────┐
│ 三种依赖注入方式 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 构造器注入(推荐) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ @Service │ │
│ │ public class UserService { │ │
│ │ private final UserDao userDao; │ │
│ │ │ │
│ │ @Autowired │ │
│ │ public UserService(UserDao userDao) { │ │
│ │ this.userDao = userDao; │ │
│ │ } │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────┘ │
│ 优点:依赖不可变,循环依赖可检测,利于测试 │
│ │
│ 2. Setter 注入 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ @Service │ │
│ │ public class UserService { │ │
│ │ private UserDao userDao; │ │
│ │ │ │
│ │ @Autowired │ │
│ │ public void setUserDao(UserDao userDao) { │ │
│ │ this.userDao = userDao; │ │
│ │ } │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────┘ │
│ 优点:可选依赖,灵活 │
│ │
│ 3. 字段注入(不推荐) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ @Service │ │
│ │ public class UserService { │ │
│ │ @Autowired │ │
│ │ private UserDao userDao; │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────┘ │
│ 缺点:难以测试,可能出现 NPE │
│ │
└─────────────────────────────────────────────────────────────┘依赖注入流程
┌─────────────────────────────────────────────────────────────┐
│ 依赖注入执行流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Spring 容器 │
│ │ │
│ ▼ │
│ 1. 创建 UserService Bean │
│ │ │
│ ▼ │
│ 2. 检查 UserService 的依赖 │
│ │ │
│ ├── 需要 UserDao ──→ 查找 UserDao Bean │
│ │ │ │
│ │ ▼ │
│ │ 3. 创建 UserDao Bean │
│ │ │ │
│ │ ▼ │
│ │ 4. 注入到 UserService │
│ │ │
│ ▼ │
│ 5. UserService 初始化完成 │
│ │
└─────────────────────────────────────────────────────────────┘Bean 作用域
Spring 容器中的 Bean 有不同的作用域:
| 作用域 | 说明 | 使用场景 |
|---|---|---|
singleton | 单例,整个容器只有一个实例 | 默认值,适合无状态的 Bean |
prototype | 原型,每次获取创建新实例 | 有状态的 Bean,需要每次新创建 |
request | 每次 HTTP 请求创建一个 | Web 请求相关 |
session | 每次 HTTP Session 创建一个 | Session 相关 |
application | 整个 ServletContext 一个 | 全局共享 |
@Service
@Scope("singleton") // 单例(默认)
public class UserService { }
@Service
@Scope("prototype") // 原型,每次新创建
public class OrderService { }自动装配
Spring 如何知道该注入哪个 Bean?
┌─────────────────────────────────────────────────────────────┐
│ 自动装配方式 │
├─────────────────────────────────────────────────────────────┤
│ │
│ @Autowired 默认 byType │
│ │ │
│ ├── 找到 1 个匹配 ──→ 注入 │
│ ├── 找到多个 ──→ 按 byName 再匹配 │
│ │ │ │
│ │ ├── 找到 ──→ 注入 │
│ │ └── 没找到 ──→ 报错 │
│ │ │
│ └── 没找到 ──→ 报错(可以设置 required=false) │
│ │
└─────────────────────────────────────────────────────────────┘自动装配方式:
| 方式 | 说明 |
|---|---|
byType | 按类型匹配,Spring Boot 默认 |
byName | 按属性名匹配 Bean 名称 |
constructor | 按构造器参数类型匹配 |
default | 优先 constructor,否则 byType |
AOP 面向切面编程
AOP(Aspect-Oriented Programming)将横切关注点分离出来。
什么是横切关注点
┌─────────────────────────────────────────────────────────────┐
│ 横切关注点示例 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 业务代码: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 用户服务: │ │
│ │ saveUser() ← 核心业务 │ │
│ │ deleteUser() ← 核心业务 │ │
│ │ updateUser() ← 核心业务 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 横切关注点(所有服务都需要): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 日志记录 ━━━ 性能监控 ━━━ 事务管理 ━━━ 安全校验 │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ ↓ ↓ ↓ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ saveUser() deleteUser() updateUser() │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 问题:横切关注点分散在各个业务中,代码重复,难以维护 │
│ │
└─────────────────────────────────────────────────────────────┘AOP 解决方案
┌─────────────────────────────────────────────────────────────┐
│ AOP 面向切面编程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 切面(Aspect) │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ 切入点(Pointcut):哪些方法需要增强 │ │ │
│ │ │ 通知(Advice):增强什么(之前/之后/异常) │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 目标对象(Target) │ │
│ │ saveUser() / deleteUser() │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 结果:业务逻辑和横切关注点分离 │
│ │
└─────────────────────────────────────────────────────────────┘AOP 术语详解
| 术语 | 说明 | 类比 |
|---|---|---|
| Aspect | 切面,通知和切入点的组合 | 完整的一句话 |
| Join Point | 连接点,程序执行的某个位置 | 织入代码的时机 |
| Pointcut | 切入点,匹配连接点的规则 | 在哪里织入 |
| Advice | 通知,织入的代码 | 织入什么 |
| Target | 目标对象,被通知的对象 | 增强谁 |
| Weaving | 织入,将切面应用到目标对象 | 怎么组合 |
通知类型
┌─────────────────────────────────────────────────────────────┐
│ 通知类型(Advice) │
├─────────────────────────────────────────────────────────────┤
│ │
│ @Before 前置通知 │
│ ┌──────┐ ┌──────────────────┐ │
│ │ Before │ → │ 目标方法 │ → 继续 │
│ └──────┘ └──────────────────┘ │
│ │
│ @AfterReturning 返回通知 │
│ ┌──────────────────┐ ┌──────┐ │
│ │ 目标方法 │ → │ After │ → 继续 │
│ └──────────────────┘ └──────┘ │
│ │
│ @AfterThrowing 异常通知 │
│ ┌──────────────────┐ ┌──────────┐ │
│ │ 目标方法 │ → │ 抛出异常 │ │
│ └──────────────────┘ └──────────┘ │
│ │
│ @After 最终通知 │
│ ┌──────────────────┐ ┌──────┐ │
│ │ 目标方法 │ → │ Finally│ → 继续 │
│ └──────────────────┘ └──────┘ │
│ │
│ @Around 环绕通知(最强大) │
│ ┌──────┐ ┌──────────────────────────────┐ ┌──────┐ │
│ │ Around│ → │ 前置 + 目标方法 + 后置 │ → │ Around│ │
│ └──────┘ └──────────────────────────────┘ └──────┘ │
│ │
└─────────────────────────────────────────────────────────────┘切入点表达式
// 匹配指定类的方法
@Before("execution(* com.example.service.UserService.*(..))")
// 解读:返回值任意 / 包路径 / 类名 / 方法名 / 参数任意
// 匹配所有 save 开头的方法
@Before("execution(* com.example..*.save*(..))")
// 匹配 UserService 中所有 public 方法
@Before("execution(public * com.example.service.UserService.*(..))")
// 匹配参数是 User 的方法
@Before("execution(* com.example.service.*.*(com.example.entity.User))")
// 多个切入点组合
@Before("execution(* com.example..*.save*(..)) || execution(* com.example..*.delete*(..))")
// 匹配所有类上标注 @Log 注解的方法
@Before("@annotation(com.example.annotation.Log)")动态代理
AOP 的底层实现依赖动态代理:
┌─────────────────────────────────────────────────────────────┐
│ JDK 动态代理 vs CGLIB 代理 │
├─────────────────────────────────────────────────────────────┤
│ │
│ JDK 动态代理(基于接口) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 目标对象必须实现接口 │ │
│ │ │ │
│ │ 接口 ──────────────────→ 实现类 │ │
│ │ │ │ │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ ┌────────────────┐ ┌─────────────┐ │ │
│ │ │ UserService │ │ 代理对象 │ │ │
│ │ │ (接口) │ │ (implements)│ │ │
│ │ └────────────────┘ └─────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ CGLIB 代理(基于继承) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 目标对象不需要实现接口,代理类继承目标类 │ │
│ │ │ │
│ │ UserService(父类) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 代理对象(继承) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Spring 选择策略: │
│ - 目标对象有接口 → JDK 动态代理 │
│ - 目标对象无接口 → CGLIB 代理 │
│ - proxy-target-class="true" → 强制使用 CGLIB │
│ │
└─────────────────────────────────────────────────────────────┘动态代理执行流程
┌─────────────────────────────────────────────────────────────┐
│ 动态代理执行流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 客户端调用 │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 代理对象 │ │
│ │ │ │
│ │ 1. 调用方法(如 saveUser) │ │
│ │ 2. 根据注解判断需要增强 │ │
│ │ 3. 执行前置通知(@Before) │ │
│ │ 4. 执行目标方法(saveUser 实际逻辑) │ │
│ │ 5. 执行返回通知(@AfterReturning) │ │
│ │ 6. 执行后置通知(@After) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 返回结果给客户端 │
│ │
└─────────────────────────────────────────────────────────────┘自定义 AOP 切面示例
定义日志注解
@Target(ElementType.METHOD) // 作用在方法上
@Retention(RetentionPolicy.RUNTIME) // 运行时保留
public @interface MyLog {
String value() default ""; // 日志描述
}定义日志切面
@Aspect
@Component
@Slf4j
public class LogAspect {
// 切入点:所有带 @MyLog 注解的方法
@Pointcut("@annotation(com.example.anno.MyLog)")
public void logPointcut() {}
// 环绕通知
@Around("logPointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取注解
MyLog myLog = signature.getMethod().getAnnotation(MyLog.class);
// 获取类名和方法名
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = signature.getName();
log.info("【{}】开始执行:{}", myLog.value(), className + "." + methodName);
long startTime = System.currentTimeMillis();
try {
// 执行目标方法
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
log.info("【{}】执行完成,耗时:{}ms", myLog.value(), endTime - startTime);
return result;
} catch (Exception e) {
long endTime = System.currentTimeMillis();
log.error("【{}】执行异常,耗时:{}ms", myLog.value(), endTime - startTime, e);
throw e;
}
}
}使用注解
@Service
public class UserService {
@MyLog("保存用户")
public void saveUser(User user) {
// 业务逻辑
}
@MyLog("删除用户")
public void deleteUser(Long id) {
// 业务逻辑
}
}事务传播行为
当一个事务方法被另一个事务方法调用时,事务如何传播?
┌─────────────────────────────────────────────────────────────┐
│ 事务传播行为 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 外层方法(事务) │
│ │ │
│ ├── 内层方法 propagation_required ──→ 加入外层事务 │
│ │ │
│ ├── 内层方法 propagation_requires_new ──→ 开启新事务 │
│ │ │
│ └── 内层方法 propagation_nested ──→ 嵌套事务 │
│ │
└─────────────────────────────────────────────────────────────┘| 传播行为 | 说明 |
|---|---|
REQUIRED | 有事务加入事务,没有则新建(默认) |
REQUIRES_NEW | 每次都新建事务,挂起外层事务 |
NESTED | 嵌套事务,外层回滚内层也回滚 |
SUPPORTS | 跟随调用者,有事务则加入,没有则无事务 |
NOT_SUPPORTED | 以非事务运行,挂起存在的事务 |
MANDATORY | 必须在事务中运行,否则抛异常 |
NEVER | 必须在非事务中运行,否则抛异常 |
总结
┌─────────────────────────────────────────────────────────────┐
│ Spring 核心概念全景图 │
├─────────────────────────────────────────────────────────────┤
│ │
│ IOC 控制反转 ──→ 把对象创建权交给容器 │
│ │ │
│ ▼ │
│ DI 依赖注入 ──→ 容器自动注入依赖 │
│ │ │
│ ▼ │
│ Bean 作用域 ──→ singleton / prototype │
│ │ │
│ ▼ │
│ AOP 面向切面 ──→ 分离横切关注点 │
│ │ │
│ ├── 通知类型:Before / After / Around / ... │
│ ├── 切入点表达式:execution / @annotation │
│ └── 动态代理:JDK / CGLIB │
│ │
└─────────────────────────────────────────────────────────────┘核心要点:
- IOC 将对象创建权交给 Spring 容器,降低耦合
- DI 是 IOC 的实现方式,构造器注入是最佳实践
- Bean 作用域决定实例创建策略,singleton 是默认
- AOP 将日志、事务等横切关注点分离,提高代码复用
- 动态代理是 AOP 的底层实现,Spring 自动选择 JDK 或 CGLIB
