阶段八:进阶与调优(持续学习)
作为资深前端开发者,你已经具备了扎实的工程基础。本阶段将深入学习JVM原理、性能优化、架构设计和源码阅读,帮助你从"会用"进阶到"理解本质",成为真正的高级Java开发者。
8.1 JVM深入(第1-2周)
8.1.1 JVM内存模型
JVM(Java Virtual Machine)内存模型是Java程序运行的基础,理解它对于性能调优和问题排查至关重要。
内存区域划分
┌─────────────────────────────────────────────────────────┐
│ JVM 内存结构 │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────┐ ┌─────────────────────────┐ │
│ │ 线程私有区域 │ │ 线程共享区域 │ │
│ │ │ │ │ │
│ │ ┌───────────────┐ │ │ ┌───────────────────┐ │ │
│ │ │ 程序计数器 │ │ │ │ 堆(Heap) │ │ │
│ │ │ (PC Register) │ │ │ │ ┌─────────────┐ │ │ │
│ │ └───────────────┘ │ │ │ │ 新生代(Young) │ │ │ │
│ │ ┌───────────────┐ │ │ │ │ ├─Eden区 │ │ │ │
│ │ │ 虚拟机栈 │ │ │ │ │ ├─Survivor0 │ │ │ │
│ │ │ (VM Stack) │ │ │ │ │ └─Survivor1 │ │ │ │
│ │ │ ├─栈帧 │ │ │ │ └─────────────┘ │ │ │
│ │ │ │ ├─局部变量表│ │ │ │ ┌─────────────┐ │ │ │
│ │ │ │ ├─操作数栈 │ │ │ │ │ 老年代(Old) │ │ │ │
│ │ │ │ └─动态链接 │ │ │ │ └─────────────┘ │ │ │
│ │ │ └─────────────│ │ │ └───────────────────┘ │ │
│ │ └───────────────┘ │ │ ┌───────────────────┐ │ │
│ │ ┌───────────────┐ │ │ │ 方法区 │ │ │
│ │ │ 本地方法栈 │ │ │ │ (Method Area) │ │ │
│ │ │ (Native Stack)│ │ │ │ ├─类信息 │ │ │
│ │ └───────────────┘ │ │ │ ├─常量池 │ │ │
│ │ │ │ │ ├─静态变量 │ │ │
│ │ │ │ │ └─JIT编译代码 │ │ │
│ │ │ │ └───────────────────┘ │ │
│ └─────────────────────┘ └─────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 直接内存(Direct Memory) │ │
│ │ (NIO直接分配的堆外内存) │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘与前端对比:
| 前端概念 | JVM概念 | 说明 |
|---|---|---|
| V8堆内存 | JVM堆 | 对象分配的主要区域 |
| 调用栈(Call Stack) | 虚拟机栈 | 方法调用的栈帧管理 |
| 全局作用域 | 方法区 | 存储类信息和静态变量 |
| Chrome内存限制 | JVM内存参数 | 可通过参数调整上限 |
| Web Worker线程 | JVM线程 | 每个线程有独立的栈空间 |
堆(Heap)详解
堆是JVM中最大的内存区域,用于存储所有对象实例。
java
/**
* 堆内存分配示例
* 通过JVM参数观察堆内存变化
*/
public class HeapDemo {
public static void main(String[] args) {
// 查看堆内存信息
Runtime runtime = Runtime.getRuntime();
long maxMemory = runtime.maxMemory(); // 最大堆内存
long totalMemory = runtime.totalMemory(); // 当前堆内存
long freeMemory = runtime.freeMemory(); // 空闲内存
System.out.println("最大堆内存: " + maxMemory / 1024 / 1024 + " MB");
System.out.println("当前堆内存: " + totalMemory / 1024 / 1024 + " MB");
System.out.println("可用空闲内存: " + freeMemory / 1024 / 1024 + " MB");
// 对象在堆中的分配
// 所有new出来的对象都在堆中
byte[] data1 = new byte[10 * 1024 * 1024]; // 分配10MB
System.out.println("分配10MB后空闲: " + runtime.freeMemory() / 1024 / 1024 + " MB");
byte[] data2 = new byte[50 * 1024 * 1024]; // 分配50MB
System.out.println("分配50MB后空闲: " + runtime.freeMemory() / 1024 / 1024 + " MB");
// 触发GC
data1 = null;
data2 = null;
System.gc();
System.out.println("GC后空闲: " + runtime.freeMemory() / 1024 / 1024 + " MB");
}
}堆的分代结构:
堆内存分代模型:
新生代(Young Generation)—— 占堆的1/3
├── Eden区(80%) —— 新对象首先分配在这里
├── Survivor 0(10%) —— Minor GC后存活的对象
└── Survivor 1(10%) —— 与S0交替使用
老年代(Old Generation)—— 占堆的2/3
└── 长期存活的对象(经过多次GC仍然存活)
├── 大对象直接进入老年代
└── 年龄达到阈值的对象(默认15岁)虚拟机栈(VM Stack)详解
每个线程创建时都会分配一个虚拟机栈,存储方法调用的栈帧。
java
/**
* 虚拟机栈示例
* 每个方法调用创建一个栈帧
*/
public class StackDemo {
public static void main(String[] args) {
methodA();
}
public static void methodA() {
int a = 1; // 局部变量表
int b = 2;
methodB(a, b);
}
public static void methodB(int x, int y) {
int sum = x + y;
methodC(sum);
}
public static void methodC(int value) {
System.out.println("结果: " + value);
// 此时调用栈为:methodC -> methodB -> methodA -> main
}
}
/*
* 栈帧结构(以methodB为例):
* ┌─────────────────────────┐
* │ 局部变量表 │
* │ [0] x = 1 │
* │ [1] y = 2 │
* │ [2] sum = 3 │
* ├─────────────────────────┤
* │ 操作数栈 │
* │ [ ] │
* ├─────────────────────────┤
* │ 动态链接 → 方法区引用 │
* ├─────────────────────────┤
* │ 返回地址 → methodA下一行 │
* └─────────────────────────┘
*/栈溢出示例:
java
/**
* StackOverflowError 示例
* 类比前端:递归组件没有终止条件导致栈溢出
*/
public class StackOverflowDemo {
// 无限递归 —— 类比前端无限递归渲染
public static void infiniteRecursion() {
infiniteRecursion(); // 每次调用都会在栈中新增一个栈帧
}
public static void main(String[] args) {
try {
infiniteRecursion();
} catch (StackOverflowError e) {
System.out.println("栈溢出!");
// 类似前端的 "Maximum call stack size exceeded"
}
}
}方法区(Method Area)详解
方法区存储类的元数据信息、常量池、静态变量等。
java
/**
* 方法区存储内容示例
*/
public class MethodAreaDemo {
// 静态变量存储在方法区
private static String staticField = "静态变量";
// 常量存储在方法区(运行时常量池)
private static final String CONSTANT = "常量";
// 类信息存储在方法区
static {
System.out.println("静态代码块,类加载时执行");
}
public static void main(String[] args) {
// 反射获取类信息(这些信息存储在方法区)
Class<?> clazz = MethodAreaDemo.class;
System.out.println("类名: " + clazz.getName());
System.out.println("字段数量: " + clazz.getDeclaredFields().length);
System.out.println("方法数量: " + clazz.getDeclaredMethods().length);
}
}
/*
* 方法区在JDK不同版本的变化:
* JDK 7及之前:永久代(PermGen),使用JVM自己的堆内存
* JDK 8及之后:元空间(Metaspace),使用操作系统本地内存
*
* 前端对比:
* 方法区类似于JavaScript引擎中的"全局环境"
* 存储了函数定义、全局变量等元信息
*/8.1.2 垃圾回收算法与收集器
如何判断对象可以回收
1. 引用计数法(简单但有循环引用问题)
java
/**
* 引用计数法问题:循环引用
* JavaScript使用"标记-清除"解决此问题
*/
public class ReferenceCountDemo {
private Object partner; // 互相引用
public static void main(String[] args) {
ReferenceCountDemo a = new ReferenceCountDemo();
ReferenceCountDemo b = new ReferenceCountDemo();
a.partner = b;
b.partner = a;
// a和b的引用计数都为1
// 即使a和b都不再被外部使用,引用计数也不为0
// 这就是循环引用问题
a = null;
b = null;
// 引用计数法无法回收,但可达性分析可以
}
}2. 可达性分析算法(JVM实际使用)
java
/**
* 可达性分析算法
* 从GC Roots出发,不可达的对象即为垃圾
*
* GC Roots包括:
* 1. 虚拟机栈中引用的对象(局部变量)
* 2. 方法区中静态属性引用的对象
* 3. 方法区中常量引用的对象
* 4. 本地方法栈中引用的对象
* 5. 被同步锁(synchronized)持有的对象
*/
public class ReachabilityDemo {
// 静态变量是GC Root
private static Object gcRoot;
public static void main(String[] args) {
Object obj = new Object(); // 局部变量是GC Root可达
gcRoot = obj; // 通过GC Root可达,不会被回收
obj = null; // 局部变量不再引用
// 但gcRoot仍然引用,所以对象不会被回收
gcRoot = null; // GC Root不再引用
// 对象变为不可达,下次GC时会被回收
}
}四种引用类型
java
import java.lang.ref.*;
public class ReferenceDemo {
public static void main(String[] args) {
// 1. 强引用(Strong Reference)—— 最常见,不会被回收
Object strongRef = new Object();
// 2. 软引用(Soft Reference)—— 内存不足时回收
// 适合做缓存
SoftReference<Object> softRef = new SoftReference<>(new Object());
Object cached = softRef.get(); // 可能返回null(已被回收)
// 3. 弱引用(Weak Reference)—— 下次GC时回收
// 适合做观察者模式等临时引用
WeakReference<Object> weakRef = new WeakReference<>(new Object());
Object weakObj = weakRef.get(); // 可能返回null
// 4. 虚引用(Phantom Reference)—— 随时可能回收
// 用于跟踪对象被回收的活动
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), queue);
// 实际使用:WeakHashMap
WeakHashMap<String, Object> weakMap = new WeakHashMap<>();
weakMap.put("key", new Object());
// 当key不再被外部强引用时,整个entry会被回收
}
}垃圾回收算法
1. 标记-清除(Mark-Sweep)
┌───┬───┬───┬───┬───┬───┬───┐
│ 用│ 垃圾│ 用│ 垃圾│ 垃圾│ 用│ 用│
└───┴───┴───┴───┴───┴───┴───┘
标记后:
┌───┬───┬───┬───┬───┬───┬───┐
│ ✓ │ ✗ │ ✓ │ ✗ │ ✗ │ ✓ │ ✓ │
└───┴───┴───┴───┴───┴───┴───┘
清除后(产生碎片):
┌───┬ ┬───┬ ┬ ┬───┬───┐
│ ✓ │ │ ✓ │ │ │ ✓ │ ✓ │
└───┴───┴───┴───┴───┴───┴───┘
缺点:内存碎片
2. 标记-复制(Copying)
From区:┌───┬───┬───┬───┐ To区:┌───┬───┬───┬───┐
│ 用│垃圾│ 用│垃圾│ │ │ │ │ │
└───┴───┴───┴───┘ └───┴───┴───┴───┘
复制存活对象:
From区:┌───┬───┬───┬───┐ To区:┌───┬───┬───┬───┐
│ 用│垃圾│ 用│垃圾│ │ 用│ 用│ │ │
└───┴───┴───┴───┘ └───┴───┴───┴───┘
优点:无碎片 缺点:浪费一半空间
3. 标记-整理(Mark-Compact)
清除后整理:
┌───┬───┬───┬───┬ │ │ │
│ ✓ │ ✓ │ ✓ │ │ │ │ │
└───┴───┴───┴───┴───┴───┴───┘
优点:无碎片,空间利用率高 缺点:移动对象开销大
4. 分代收集(Generational Collection)
新生代 → 标记-复制(对象存活率低,复制成本小)
老年代 → 标记-清除 或 标记-整理垃圾收集器
CMS收集器(Concurrent Mark Sweep)—— 追求最短停顿时间
CMS工作流程:
1. 初始标记(STW) → 标记GC Roots直接关联的对象,速度快
2. 并发标记 → 与用户线程并发执行,遍历对象图
3. 重新标记(STW) → 修正并发标记期间的变化
4. 并发清除 → 与用户线程并发执行,清除垃圾
STW = Stop The World(暂停所有用户线程)
优点:低停顿时间
缺点:
- 对CPU资源敏感(并发阶段占用CPU)
- 无法处理浮动垃圾(重新标记后新产生的垃圾)
- 内存碎片(使用标记-清除算法)G1收集器(Garbage First)—— JDK 9默认收集器
G1将堆划分为多个大小相等的Region:
┌───┬───┬───┬───┬───┬───┬───┬───┐
│ E │ E │ O │ O │ E │ H │ E │ O │
└───┴───┴───┴───┴───┴───┴───┴───┘
E = Eden O = Old H = Humongous(大对象)
G1工作流程:
1. 初始标记(STW)
2. 并发标记
3. 最终标记(STW)
4. 筛选回收(STW)—— 选择回收价值最高的Region优先回收
特点:
- 可预测的停顿时间(通过-XX:MaxGCPauseMillis设置)
- 整体使用标记-整理,局部使用标记-复制
- 适合大堆内存(6GB+)ZGC收集器(JDK 15正式发布)—— 超低停顿
ZGC特点:
- 停顿时间不超过10ms(与堆大小无关)
- 支持TB级别的堆
- 使用染色指针和读屏障技术
适用场景:
- 对延迟极度敏感的应用
- 超大堆内存的应用
- 实时性要求高的系统收集器选择建议:
| 场景 | 推荐收集器 | 说明 |
|---|---|---|
| 小型应用(堆<2G) | Serial | 简单高效 |
| Web应用(堆2-8G) | G1 | 平衡吞吐和延迟 |
| 大堆低延迟(堆>8G) | ZGC | 超低停顿 |
| 后台计算(高吞吐) | Parallel | 最大化吞吐量 |
8.1.3 类加载机制
类的生命周期
加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载
└──────── 连接阶段 ────────┘双亲委派模型
java
/**
* 双亲委派模型
*
* 类加载器层次:
*
* Bootstrap ClassLoader(启动类加载器)
* │ 加载 rt.jar(java.lang.*, java.util.* 等核心类)
* ▼
* Extension ClassLoader(扩展类加载器)
* │ 加载 ext 目录下的jar包
* ▼
* Application ClassLoader(应用类加载器)
* │ 加载classpath下的类(我们的代码)
* ▼
* Custom ClassLoader(自定义类加载器)
*
* 双亲委派流程:
* 1. 收到类加载请求,先委派给父加载器
* 2. 父加载器无法加载,才自己尝试加载
* 3. 保证核心类不被篡改(安全机制)
*
* 前端对比:
* 类似于模块加载器的解析链
* webpack resolve.modules 的查找顺序
* 或者Node.js require的查找路径
*/
public class ClassLoaderDemo {
public static void main(String[] args) {
// 查看类加载器
ClassLoader loader = ClassLoaderDemo.class.getClassLoader();
System.out.println("当前类加载器: " + loader);
// AppClassLoader
System.out.println("父加载器: " + loader.getParent());
// PlatformClassLoader (JDK 9+) / ExtClassLoader (JDK 8)
System.out.println("祖父加载器: " + loader.getParent().getParent());
// null (Bootstrap由C++实现,Java中返回null)
// 核心类由Bootstrap加载
System.out.println("String加载器: " + String.class.getClassLoader());
// null(Bootstrap加载的类返回null)
}
}打破双亲委派
java
/**
* 自定义类加载器 —— 打破双亲委派
*
* 应用场景:
* 1. Tomcat:每个Web应用有独立的类加载器
* 2. OSGi/热部署:运行时替换类
* 3. SPI机制:JDBC驱动加载
*/
public class CustomClassLoader extends ClassLoader {
private String classPath;
public CustomClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
// 读取.class文件的字节码
String fileName = classPath + "/" + name.replace('.', '/') + ".class";
java.io.FileInputStream fis = new java.io.FileInputStream(fileName);
byte[] bytes = new byte[fis.available()];
fis.read(bytes);
fis.close();
// 调用defineClass将字节码转为Class对象
return defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
throw new ClassNotFoundException(name, e);
}
}
// 使用示例
public static void main(String[] args) throws Exception {
CustomClassLoader myLoader = new CustomClassLoader("D:/classes");
Class<?> clazz = myLoader.loadClass("com.example.MyService");
Object instance = clazz.getDeclaredConstructor().newInstance();
System.out.println("加载的类: " + instance.getClass().getName());
System.out.println("加载器: " + instance.getClass().getClassLoader());
}
}8.1.4 JVM调优参数
常用JVM参数
bash
# ===== 堆内存参数 =====
-Xms512m # 初始堆大小(建议与-Xmx相同,避免动态扩缩)
-Xmx2g # 最大堆大小
-Xmn512m # 新生代大小(一般为堆的1/3到1/4)
# ===== 栈内存参数 =====
-Xss256k # 每个线程的栈大小(默认1MB)
# ===== 方法区参数 =====
-XX:MetaspaceSize=128m # 元空间初始大小
-XX:MaxMetaspaceSize=256m # 元空间最大大小
# ===== GC相关参数 =====
-XX:+UseG1GC # 使用G1收集器
-XX:+UseZGC # 使用ZGC收集器
-XX:MaxGCPauseMillis=200 # 期望最大GC停顿时间(毫秒)
-XX:G1HeapRegionSize=4m # G1 Region大小
# ===== GC日志参数 =====
-Xlog:gc*:file=gc.log # GC日志输出到文件(JDK 9+)
-verbose:gc # 打印GC信息(JDK 8)
-XX:+PrintGCDetails # 打印GC详细信息(JDK 8)
-XX:+PrintGCDateStamps # 打印GC时间戳
# ===== OOM时自动dump =====
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/tmp/heapdump.hprof
# ===== 完整的生产环境配置示例 =====
java -server \
-Xms4g \
-Xmx4g \
-Xmn1g \
-Xss512k \
-XX:MetaspaceSize=256m \
-XX:MaxMetaspaceSize=512m \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/data/logs/heapdump.hprof \
-Xlog:gc*:file=/data/logs/gc.log:time,tags:filecount=5,filesize=50m \
-jar app.jar调优参数实战示例
bash
# 场景1:小内存应用(Spring Boot微服务)
java -Xms512m -Xmx512m -Xmn128m \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=100 \
-jar service.jar
# 场景2:大内存应用(电商系统)
java -Xms8g -Xmx8g -Xmn2g \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:G1HeapRegionSize=8m \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/data/dumps/heap.hprof \
-jar app.jar
# 场景3:低延迟应用(实时系统)
java -Xms16g -Xmx16g \
-XX:+UseZGC \
-XX:+UnlockExperimentalVMOptions \
-jar realtime-app.jar
# 场景4:高吞吐应用(批处理)
java -Xms4g -Xmx4g \
-XX:+UseParallelGC \
-XX:ParallelGCThreads=8 \
-jar batch-app.jar8.1.5 JVM排查工具
jps —— 查看Java进程
bash
# 列出所有Java进程
jps
# 输出示例:
# 12345 MyApp
# 67890 Jps
# 显示详细信息
jps -l
# 输出示例:
# 12345 com.example.MyApp
# 67890 sun.tools.jps.Jps
# 显示JVM参数
jps -vjstat —— 监控GC状态
bash
# 监控GC情况(每1秒输出一次,共100次)
jstat -gcutil <pid> 1000 100
# 输出示例:
# S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
# 0.00 45.23 67.89 23.45 95.67 89.12 120 2.345 3 0.456 2.801
#
# S0/S1 = Survivor区使用率
# E = Eden区使用率
# O = 老年代使用率
# M = 元空间使用率
# YGC = Young GC次数
# YGCT = Young GC耗时(秒)
# FGC = Full GC次数
# FGCT = Full GC耗时(秒)
# GCT = GC总耗时
# 监控类加载
jstat -class <pid> 1000jstack —— 线程堆栈分析
bash
# 导出线程堆栈
jstack <pid> > thread_dump.txt
# 典型使用场景:排查死锁
jstack <pid> | grep -A 20 "deadlock"
# 排查CPU占用高的线程
# 1. 找到Java进程PID
jps -l
# 2. 找到进程内CPU最高的线程TID
top -Hp <pid>
# 3. 将TID转为16进制
printf "%x\n" 12345
# 4. 在jstack输出中搜索
jstack <pid> | grep -A 30 "nid=0x3039"jmap —— 堆内存分析
bash
# 查看堆内存使用情况
jmap -heap <pid>
# 导出堆dump文件(用于离线分析)
jmap -dump:format=b,file=heap.hprof <pid>
# 查看对象统计信息
jmap -histo <pid> | head -20
# 输出示例:
# num #instances #bytes class name
# 1 500000 24000000 [B
# 2 200000 12800000 com.example.User
# 3 100000 6400000 java.util.HashMap$NodeArthas —— 阿里开源的Java诊断神器
bash
# 安装Arthas
# Windows:
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar
# 常用命令
# 1. dashboard —— 实时查看系统面板
dashboard
# 显示线程、内存、GC、运行时信息
# 2. thread —— 查看线程信息
thread # 列出所有线程
thread -n 3 # 列出CPU占用最高的3个线程
thread <thread-id> # 查看指定线程堆栈
thread -b # 找出阻塞其他线程的线程
# 3. jad —— 反编译类(热修复排查)
jad com.example.UserService
# 输出反编译后的源代码
# 4. watch —— 观察方法调用
watch com.example.UserService getUser '{params, returnObj, throwExp}' -x 3
# 观察getUser方法的入参、返回值和异常
# 5. trace —— 方法调用链路追踪
trace com.example.OrderService createOrder '#cost > 100'
# 追踪createOrder方法内部调用耗时,只显示耗时>100ms的
# 6. sc/sm —— 搜索类/方法
sc *UserService* # 搜索类
sm com.example.UserService # 查看类的方法信息
# 7. monitor —— 方法调用统计
monitor -c 5 com.example.UserService getUser
# 每5秒统计一次getUser的调用次数、成功率、平均耗时
# 8. heapdump —— 导出堆内存
heapdump /tmp/dump.hprof前端对比:
| JVM工具 | 前端对应工具 | 用途 |
|---|---|---|
| jps | 浏览器进程管理器 | 查看运行中的进程 |
| jstat | Performance面板 | 监控性能指标 |
| jstack | Performance面板Threads | 线程分析 |
| jmap | Memory面板 | 内存分析 |
| Arthas | Chrome DevTools全家桶 | 综合诊断 |
| VisualVM | Lighthouse | 可视化分析 |
| MAT(Memory Analyzer) | Memory面板Heap Snapshot | 堆内存深度分析 |
8.2 性能优化(第3-4周)
8.2.1 SQL优化
执行计划分析
sql
-- 查看执行计划(MySQL)
EXPLAIN SELECT * FROM users WHERE username = 'test';
-- 输出字段说明:
-- id: 查询序号
-- select_type: 查询类型(SIMPLE/PRIMARY/SUBQUERY/UNION)
-- table: 访问的表
-- type: 连接类型(性能从好到差)
-- system > const > eq_ref > ref > range > index > ALL
-- possible_keys: 可能使用的索引
-- key: 实际使用的索引
-- key_len: 索引长度
-- rows: 预计扫描行数(越少越好)
-- Extra: 额外信息
-- Using index(覆盖索引)
-- Using where(需要过滤)
-- Using filesort(需要排序,需优化)
-- Using temporary(使用临时表,需优化)执行计划示例分析:
sql
-- 慢查询示例
EXPLAIN SELECT u.username, o.order_no, o.amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.age > 20 AND o.status = 1
ORDER BY o.amount DESC;
-- 分析结果:
-- users表:type=ALL(全表扫描!需要加索引)
-- orders表:type=ref(使用了索引)
-- Extra: Using filesort(需要额外排序,考虑加索引)
-- 优化方案:
-- 1. users表age字段加索引
ALTER TABLE users ADD INDEX idx_age (age);
-- 2. orders表联合索引
ALTER TABLE orders ADD INDEX idx_user_status_amount (user_id, status, amount);
-- 3. 优化后的查询
EXPLAIN SELECT u.username, o.order_no, o.amount
FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE u.age > 20 AND o.status = 1
ORDER BY o.amount DESC;索引优化
sql
-- ===== 索引类型 =====
-- 1. 普通索引
CREATE INDEX idx_username ON users(username);
-- 2. 唯一索引
CREATE UNIQUE INDEX uk_email ON users(email);
-- 3. 联合索引(最左前缀原则)
CREATE INDEX idx_name_age_status ON users(username, age, status);
-- 可以匹配的查询:
-- WHERE username = 'test' ✓
-- WHERE username = 'test' AND age = 25 ✓
-- WHERE username = 'test' AND age = 25 AND status = 1 ✓
-- WHERE age = 25 ✗(缺少最左列)
-- WHERE age = 25 AND status = 1 ✗(缺少最左列)
-- 4. 覆盖索引(避免回表)
-- 如果索引包含了查询需要的所有字段,不需要回表查数据
CREATE INDEX idx_covering ON users(username, age, email);
-- 这个查询可以使用覆盖索引:
SELECT username, age, email FROM users WHERE username = 'test';
-- Extra显示:Using index
-- 5. 前缀索引(节省空间)
CREATE INDEX idx_email_prefix ON users(email(10));
-- ===== 索引失效场景 =====
-- 1. 对索引列使用函数
SELECT * FROM users WHERE UPPER(username) = 'TEST'; -- 索引失效
-- 优化:存储时就统一格式
-- 2. 隐式类型转换
SELECT * FROM users WHERE phone = 13800138000; -- phone是VARCHAR,传入数字
-- 优化:传入字符串 WHERE phone = '13800138000'
-- 3. LIKE以%开头
SELECT * FROM users WHERE username LIKE '%test'; -- 索引失效
SELECT * FROM users WHERE username LIKE 'test%'; -- 索引有效
-- 4. OR条件(部分列没有索引)
SELECT * FROM users WHERE username = 'test' OR age = 25; -- 如果age没有索引则全表扫描
-- 5. NOT IN / NOT EXISTS
SELECT * FROM users WHERE id NOT IN (SELECT user_id FROM orders);
-- 优化为 LEFT JOIN:
SELECT u.* FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE o.user_id IS NULL;
-- 6. 不等于
SELECT * FROM users WHERE status != 1; -- 可能索引失效
-- ===== 索引设计原则 =====
-- 1. 选择性高的列优先(区分度高的列)
-- 2. 频繁查询的列加索引
-- 3. 联合索引注意列顺序(区分度高的在前)
-- 4. 避免过多索引(影响写入性能)
-- 5. 长字符串考虑前缀索引SQL优化实战
sql
-- ===== 优化1:避免SELECT * =====
-- 慢
SELECT * FROM users WHERE age > 20;
-- 快(减少数据传输量,可能使用覆盖索引)
SELECT id, username, email FROM users WHERE age > 20;
-- ===== 优化2:分页优化 =====
-- 深分页问题(越往后越慢)
SELECT * FROM users LIMIT 1000000, 10; -- 扫描1000010行,丢弃1000000行
-- 优化方案1:延迟关联
SELECT u.* FROM users u
INNER JOIN (SELECT id FROM users ORDER BY id LIMIT 1000000, 10) tmp
ON u.id = tmp.id;
-- 优化方案2:游标分页(推荐)
-- 前端传入上一页最后一条的id
SELECT * FROM users WHERE id > #{lastId} ORDER BY id LIMIT 10;
-- ===== 优化3:COUNT优化 =====
-- 慢
SELECT COUNT(*) FROM users WHERE status = 1;
-- 如果只需要判断是否有数据
SELECT 1 FROM users WHERE status = 1 LIMIT 1;
-- ===== 优化4:JOIN优化 =====
-- 小表驱动大表
-- 确保JOIN字段有索引
-- 避免在JOIN条件中使用函数
-- ===== 优化5:子查询优化 =====
-- 慢(相关子查询)
SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE amount > 100);
-- 快(改为JOIN)
SELECT DISTINCT u.* FROM users u INNER JOIN orders o ON u.id = o.user_id WHERE o.amount > 100;
-- ===== 优化6:批量操作 =====
-- 逐条插入(慢)
INSERT INTO users (username, email) VALUES ('a', 'a@test.com');
INSERT INTO users (username, email) VALUES ('b', 'b@test.com');
INSERT INTO users (username, email) VALUES ('c', 'c@test.com');
-- 批量插入(快)
INSERT INTO users (username, email) VALUES
('a', 'a@test.com'),
('b', 'b@test.com'),
('c', 'c@test.com');8.2.2 连接池调优
HikariCP配置优化
yaml
# application.yml - HikariCP配置(Spring Boot默认连接池)
spring:
datasource:
hikari:
# ===== 核心参数 =====
maximum-pool-size: 20 # 最大连接数(默认10)
minimum-idle: 5 # 最小空闲连接数
connection-timeout: 30000 # 获取连接超时时间(毫秒,默认30s)
idle-timeout: 600000 # 空闲连接存活时间(毫秒,默认10min)
max-lifetime: 1800000 # 连接最大存活时间(毫秒,默认30min)
# ===== 高级参数 =====
pool-name: MyHikariPool # 连接池名称(方便日志排查)
validation-timeout: 5000 # 连接验证超时
leak-detection-threshold: 60000 # 连接泄漏检测阈值(0为禁用)
# ===== 连接测试 =====
connection-test-query: SELECT 1
# ===== 数据源配置 =====
jdbc-url: jdbc:mysql://localhost:3306/test_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: yourpassword
driver-class-name: com.mysql.cj.jdbc.Driver连接池参数调优公式:
最大连接数 = ((核心数 * 2) + 有效磁盘数)
参考公式:connections = ((CPU核心数 * 2) + 磁盘数)
示例:
- 8核CPU,1块SSD:((8 * 2) + 1) = 17,取整20
- 4核CPU,1块HDD:((4 * 2) + 1) = 9,取整10
注意:
- 连接数不是越大越好(过多连接反而降低性能)
- 多个服务共用一个数据库时,总连接数不能超过数据库上限
- MySQL默认max_connections=151,需要根据实际情况调整连接池监控
java
import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.HikariPoolMXBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PoolMonitorController {
@Autowired
private HikariDataSource hikariDataSource;
@GetMapping("/pool/status")
public String getPoolStatus() {
HikariPoolMXBean pool = hikariDataSource.getHikariPoolMXBean();
return String.format(
"活跃连接: %d, 空闲连接: %d, 总连接: %d, 等待线程: %d",
pool.getActiveConnections(),
pool.getIdleConnections(),
pool.getTotalConnections(),
pool.getThreadsAwaitingConnection()
);
}
}8.2.3 缓存策略优化
Spring Cache + Redis
java
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;
@Service
@CacheConfig(cacheNames = "users") // 默认缓存名称
public class UserService {
// ===== @Cacheable:先查缓存,没有再查数据库并缓存 =====
@Cacheable(key = "#id")
public User findById(Long id) {
return userMapper.selectById(id);
}
// 自定义key生成规则
@Cacheable(key = "'user:' + #id")
public User findByIdWithPrefix(Long id) {
return userMapper.selectById(id);
}
// 条件缓存(只缓存age>18的)
@Cacheable(key = "#id", condition = "#id > 0")
public User findByIdConditional(Long id) {
return userMapper.selectById(id);
}
// 指定缓存过期时间(需要自定义CacheManager)
@Cacheable(key = "#id", unless = "#result == null")
public User findByIdNotNull(Long id) {
return userMapper.selectById(id);
}
// ===== @CachePut:更新缓存 =====
@CachePut(key = "#user.id")
public User updateUser(User user) {
userMapper.updateById(user);
return user; // 返回值会放入缓存
}
// ===== @CacheEvict:删除缓存 =====
@CacheEvict(key = "#id")
public void deleteUser(Long id) {
userMapper.deleteById(id);
}
// 删除整个缓存
@CacheEvict(allEntries = true)
public void clearAllCache() {
// 清空users缓存
}
// ===== @Caching:组合多个缓存操作 =====
@Caching(evict = {
@CacheEvict(key = "#user.id"),
@CacheEvict(key = "'name:' + #user.username")
})
public void updateUserEvictMultiple(User user) {
userMapper.updateById(user);
}
}Redis缓存配置
java
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@Configuration
@EnableCaching // 开启缓存
public class CacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
// 默认缓存配置
RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30)) // 默认过期时间30分钟
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.disableCachingNullValues(); // 不缓存null值
// 针对不同缓存名设置不同的过期时间
return RedisCacheManager.builder(factory)
.cacheDefaults(defaultConfig)
.withCacheConfiguration("users",
defaultConfig.entryTtl(Duration.ofHours(1)))
.withCacheConfiguration("orders",
defaultConfig.entryTtl(Duration.ofMinutes(10)))
.withCacheConfiguration("products",
defaultConfig.entryTtl(Duration.ofHours(2)))
.build();
}
}缓存常见问题与解决方案
java
/**
* 缓存三大问题及解决方案
*/
// ===== 1. 缓存穿透:查询不存在的数据 =====
// 问题:每次都穿透缓存到数据库
// 解决:缓存空值
@Service
public class UserServiceWithPenetrationFix {
@Cacheable(key = "#id", unless = "#result != null")
public User findById(Long id) {
User user = userMapper.selectById(id);
if (user == null) {
// 缓存空值,设置较短过期时间
// 防止缓存穿透
return null; // 配合unless不缓存null,需要手动处理
}
return user;
}
// 使用布隆过滤器(Bloom Filter)预防
// RedisBloom模块可以在缓存层判断key是否可能存在
}
// ===== 2. 缓存击穿:热点key过期,大量请求打到数据库 =====
// 解决:互斥锁(分布式锁)
@Service
public class UserServiceWithBreakdownFix {
@Autowired
private StringRedisTemplate redisTemplate;
public User findByIdWithLock(Long id) {
String key = "user:" + id;
// 先查缓存
String cached = redisTemplate.opsForValue().get(key);
if (cached != null) {
return JSON.parseObject(cached, User.class);
}
// 获取分布式锁
String lockKey = "lock:user:" + id;
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", Duration.ofSeconds(10));
if (Boolean.TRUE.equals(locked)) {
try {
// 双重检查
cached = redisTemplate.opsForValue().get(key);
if (cached != null) {
return JSON.parseObject(cached, User.class);
}
// 查数据库
User user = userMapper.selectById(id);
redisTemplate.opsForValue().set(key, JSON.toJSONString(user), Duration.ofHours(1));
return user;
} finally {
redisTemplate.delete(lockKey);
}
} else {
// 获取锁失败,短暂休眠后重试
try { Thread.sleep(50); } catch (InterruptedException e) {}
return findByIdWithLock(id);
}
}
}
// ===== 3. 缓存雪崩:大量缓存同时过期 =====
// 解决:过期时间加随机值
@Service
public class UserServiceWithAvalancheFix {
private final Random random = new Random();
public void cacheUser(User user) {
String key = "user:" + user.getId();
// 基础时间 + 随机时间(避免同时过期)
int baseTtl = 3600; // 1小时
int randomTtl = random.nextInt(600); // 0-10分钟随机
redisTemplate.opsForValue().set(key,
JSON.toJSONString(user),
Duration.ofSeconds(baseTtl + randomTtl));
}
}8.2.4 异步编程与响应式(Spring WebFlux)
Spring WebFlux入门
java
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.Duration;
/**
* WebFlux Controller
*
* 前端对比:
* Spring MVC(Servlet模型)→ 类似Express/Koa(同步阻塞)
* WebFlux(响应式模型)→ 类似Node.js事件循环(非阻塞)
*
* Mono → Promise/Observable(0或1个元素)
* Flux → Observable(0到N个元素)
*/
@RestController
@RequestMapping("/api/users")
public class UserReactiveController {
@Autowired
private UserReactiveService userService;
// ===== 基本用法 =====
// Mono:返回单个对象
@GetMapping("/{id}")
public Mono<User> getUser(@PathVariable Long id) {
return userService.findById(id);
}
// Flux:返回多个对象(流式返回)
@GetMapping("")
public Flux<User> getAllUsers() {
return userService.findAll();
}
// ===== 服务端推送(SSE)=====
@GetMapping(value = "/stream", produces = "text/event-stream")
public Flux<String> stream() {
return Flux.interval(Duration.ofSeconds(1))
.map(seq -> "data: 消息 " + seq + "\n\n");
}
// ===== 创建Mono =====
public Mono<String> createMonoExamples() {
// 空值
Mono<String> empty = Mono.empty();
// 固定值
Mono<String> just = Mono.just("Hello");
// 异步操作
Mono<String> fromCallable = Mono.fromCallable(() -> {
Thread.sleep(1000); // 模拟耗时操作
return "Result";
});
// 从Future转换
Mono<String> fromFuture = Mono.fromFuture(() -> CompletableFuture.supplyAsync(() -> "Async"));
return just;
}
// ===== 操作符 =====
public Mono<String> operatorExamples() {
return Mono.just("Hello")
.map(s -> s + " World") // 转换
.filter(s -> s.length() > 5) // 过滤
.flatMap(s -> Mono.just(s.toUpperCase())) // 异步转换
.doOnNext(s -> System.out.println("结果: " + s)) // 副作用
.onErrorResume(ex -> Mono.just("Error")) // 错误处理
.timeout(Duration.ofSeconds(5)); // 超时
}
}响应式Service
java
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
@Service
public class UserReactiveService {
// 模拟异步数据库操作
public Mono<User> findById(Long id) {
return Mono.fromCallable(() -> {
// 模拟数据库查询
Thread.sleep(100);
return new User(id, "user" + id);
}).subscribeOn(Schedulers.boundedElastic()); // 在弹性线程池执行
}
public Flux<User> findAll() {
return Flux.range(1, 100)
.map(id -> new User((long) id, "user" + id))
.delayElements(Duration.ofMillis(10)); // 模拟流式返回
}
// 组合多个异步操作
public Mono<UserProfile> getUserProfile(Long userId) {
Mono<User> userMono = findById(userId);
Mono<List<Order>> ordersMono = findOrdersByUserId(userId).collectList();
Mono<Integer> countMono = countPosts(userId);
// 并行执行,组合结果
return Mono.zip(userMono, ordersMono, countMono)
.map(tuple -> {
User user = tuple.getT1();
List<Order> orders = tuple.getT2();
Integer postCount = tuple.getT3();
return new UserProfile(user, orders, postCount);
});
}
public Flux<Order> findOrdersByUserId(Long userId) {
return Flux.just(
new Order(1L, "ORDER001", 99.9),
new Order(2L, "ORDER002", 199.9)
);
}
public Mono<Integer> countPosts(Long userId) {
return Mono.just(42);
}
}WebFlux配置
yaml
# application.yml
spring:
webflux:
base-path: /api
# Netty服务器配置
server:
port: 8080
netty:
connection-timeout: 20sxml
<!-- pom.xml - WebFlux依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- 响应式Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!-- 响应式MongoDB -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>8.2.5 高并发场景处理
限流
java
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.stereotype.Component;
/**
* 限流算法实现
*
* 前端对比:
* 类似于请求队列、防抖节流的概念
* 但限流是在服务端控制请求速率
*/
// ===== 1. 固定窗口限流 =====
@Component
public class FixedWindowRateLimiter {
// key -> [计数器, 窗口开始时间]
private final ConcurrentHashMap<String, long[]> counters = new ConcurrentHashMap<>();
public boolean tryAcquire(String key, int limit, long windowMs) {
long now = System.currentTimeMillis();
counters.putIfAbsent(key, new long[]{0, now});
long[] counter = counters.get(key);
// 窗口过期,重置
if (now - counter[1] > windowMs) {
counter[0] = 0;
counter[1] = now;
}
// 判断是否超限
if (counter[0] < limit) {
counter[0]++;
return true;
}
return false;
}
}
// ===== 2. 滑动窗口限流 =====
@Component
public class SlidingWindowRateLimiter {
private final ConcurrentHashMap<String, LinkedList<Long>> requestTimes = new ConcurrentHashMap<>();
public boolean tryAcquire(String key, int limit, long windowMs) {
long now = System.currentTimeMillis();
requestTimes.putIfAbsent(key, new LinkedList<>());
LinkedList<Long> times = requestTimes.get(key);
synchronized (times) {
// 移除窗口外的记录
while (!times.isEmpty() && now - times.getFirst() > windowMs) {
times.removeFirst();
}
if (times.size() < limit) {
times.addLast(now);
return true;
}
return false;
}
}
}
// ===== 3. 令牌桶限流(使用Guava RateLimiter)=====
import com.google.common.util.concurrent.RateLimiter;
@Component
public class TokenBucketLimiter {
// 每秒产生10个令牌
private final RateLimiter rateLimiter = RateLimiter.create(10.0);
public boolean tryAcquire() {
return rateLimiter.tryAcquire(); // 非阻塞
// rateLimiter.acquire(); // 阻塞等待
}
}使用注解实现限流
java
// 自定义限流注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
int limit() default 100; // 限流数量
long window() default 60; // 时间窗口(秒)
String key() default ""; // 限流key(默认使用方法名)
RateLimitType type() default RateLimitType.IP; // 限流维度
}
public enum RateLimitType {
IP, // 按IP限流
USER, // 按用户限流
GLOBAL // 全局限流
}
// AOP实现
@Aspect
@Component
public class RateLimitAspect {
@Autowired
private StringRedisTemplate redisTemplate;
@Around("@annotation(rateLimit)")
public Object around(ProceedingJoinPoint point, RateLimit rateLimit) throws Throwable {
String key = "rate_limit:" + rateLimit.key();
// 根据限流维度设置key
switch (rateLimit.type()) {
case IP:
key += ":" + getClientIp();
break;
case USER:
key += ":" + getCurrentUserId();
break;
}
Long count = redisTemplate.opsForValue().increment(key);
if (count == 1) {
redisTemplate.expire(key, Duration.ofSeconds(rateLimit.window()));
}
if (count > rateLimit.limit()) {
throw new RuntimeException("请求过于频繁,请稍后再试");
}
return point.proceed();
}
}
// 使用
@RestController
public class OrderController {
@RateLimit(limit = 10, window = 60, type = RateLimitType.USER)
@PostMapping("/create")
public Result createOrder(@RequestBody OrderRequest request) {
// 每个用户60秒最多10次下单请求
return orderService.createOrder(request);
}
}异步任务处理
java
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;
import java.util.concurrent.Future;
@Service
@EnableAsync // 开启异步支持
public class AsyncService {
// ===== 基本异步方法 =====
@Async
public void sendEmail(String to, String content) {
// 异步发送邮件,不阻塞主线程
System.out.println("发送邮件线程: " + Thread.currentThread().getName());
// 发送邮件逻辑...
}
// ===== 有返回值的异步方法 =====
@Async("taskExecutor") // 指定线程池
public Future<String> processFile(String filePath) {
// 异步处理文件
String result = doProcess(filePath);
return new AsyncResult<>(result);
}
// ===== 异步编排(CompletableFuture)=====
@Async("taskExecutor")
public CompletableFuture<UserInfo> getUserInfo(Long userId) {
User user = userMapper.selectById(userId);
return CompletableFuture.completedFuture(toUserInfo(user));
}
@Async("taskExecutor")
public CompletableFuture<List<Order>> getUserOrders(Long userId) {
return CompletableFuture.completedFuture(orderMapper.selectByUserId(userId));
}
@Async("taskExecutor")
public CompletableFuture<Integer> getUserPoints(Long userId) {
return CompletableFuture.completedFuture(100);
}
// 并行获取用户信息
public UserProfile getUserProfile(Long userId) {
CompletableFuture<UserInfo> infoFuture = getUserInfo(userId);
CompletableFuture<List<Order>> ordersFuture = getUserOrders(userId);
CompletableFuture<Integer> pointsFuture = getUserPoints(userId);
// 等待所有完成
CompletableFuture.allOf(infoFuture, ordersFuture, pointsFuture).join();
return new UserProfile(
infoFuture.join(),
ordersFuture.join(),
pointsFuture.join()
);
}
}自定义异步线程池
java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
public class AsyncConfig {
@Bean("taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心参数
executor.setCorePoolSize(5); // 核心线程数
executor.setMaxPoolSize(20); // 最大线程数
executor.setQueueCapacity(200); // 队列容量
executor.setKeepAliveSeconds(60); // 空闲线程存活时间
// 线程名前缀(方便排查问题)
executor.setThreadNamePrefix("async-task-");
// 拒绝策略:由调用线程执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 应用关闭时等待任务完成
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}
}8.3 架构设计(第5-6周)
8.3.1 设计模式
创建型模式
1. 单例模式(Singleton)
java
/**
* 单例模式:确保一个类只有一个实例
*
* 前端对比:Vue的全局store、Redux的store都是单例思想
* 在JavaScript中通过闭包或模块系统实现
*/
// 方式1:饿汉式(线程安全,类加载时创建)
public class Singleton1 {
private static final Singleton1 INSTANCE = new Singleton1();
private Singleton1() {} // 私有构造方法
public static Singleton1 getInstance() {
return INSTANCE;
}
}
// 方式2:双重检查锁(推荐,懒加载+线程安全)
public class Singleton2 {
private static volatile Singleton2 instance; // volatile防止指令重排
private Singleton2() {}
public static Singleton2 getInstance() {
if (instance == null) { // 第一次检查(无锁)
synchronized (Singleton2.class) { // 加锁
if (instance == null) { // 第二次检查(有锁)
instance = new Singleton2();
}
}
}
return instance;
}
}
// 方式3:静态内部类(推荐,优雅且线程安全)
public class Singleton3 {
private Singleton3() {}
// 静态内部类,只有调用getInstance时才加载
private static class Holder {
private static final Singleton3 INSTANCE = new Singleton3();
}
public static Singleton3 getInstance() {
return Holder.INSTANCE;
}
}
// 方式4:枚举(最简洁,天然防反射和反序列化破坏)
public enum Singleton4 {
INSTANCE;
public void doSomething() {
System.out.println("doing something...");
}
}
// Spring中的单例(默认scope=singleton)
@Component
public class SpringSingleton {
// Spring容器保证单例
// 注意:不要持有可变状态!
}2. 工厂模式(Factory)
java
/**
* 工厂模式:将对象创建与使用分离
*
* 前端对比:
* React.createElement() 就是一种工厂方法
* Vue的createApp() 也是工厂模式
*/
// ===== 简单工厂 =====
public interface Payment {
void pay(double amount);
}
public class AliPay implements Payment {
@Override
public void pay(double amount) {
System.out.println("支付宝支付: " + amount);
}
}
public class WechatPay implements Payment {
@Override
public void pay(double amount) {
System.out.println("微信支付: " + amount);
}
}
public class UnionPay implements Payment {
@Override
public void pay(double amount) {
System.out.println("银联支付: " + amount);
}
}
// 简单工厂
public class PaymentFactory {
public static Payment create(String type) {
switch (type) {
case "ali": return new AliPay();
case "wechat": return new WechatPay();
case "union": return new UnionPay();
default: throw new IllegalArgumentException("不支持的支付方式: " + type);
}
}
}
// 使用
Payment payment = PaymentFactory.create("ali");
payment.pay(100.0);
// ===== 工厂方法(Spring大量使用)=====
public interface PaymentFactory2 {
Payment create();
}
public class AliPayFactory implements PaymentFactory2 {
@Override
public Payment create() {
return new AliPay();
}
}
public class WechatPayFactory implements PaymentFactory2 {
@Override
public Payment create() {
return new WechatPay();
}
}
// ===== 抽象工厂 =====
public interface UIFactory {
Button createButton();
Input createInput();
Dialog createDialog();
}
// 不同主题的实现
public class LightThemeFactory implements UIFactory {
@Override
public Button createButton() { return new LightButton(); }
@Override
public Input createInput() { return new LightInput(); }
@Override
public Dialog createDialog() { return new LightDialog(); }
}
public class DarkThemeFactory implements UIFactory {
@Override
public Button createButton() { return new DarkButton(); }
@Override
public Input createInput() { return new DarkInput(); }
@Override
public Dialog createDialog() { return new DarkDialog(); }
}3. 建造者模式(Builder)
java
/**
* 建造者模式:分步构建复杂对象
*
* 前端对比:
* 类似于链式调用配置对象
* 或者Vue Router的router配置
*/
// Lombok的@Builder注解(最常用)
@Builder
@Data
public class HttpRequest {
private String url;
private String method;
private Map<String, String> headers;
private String body;
private int timeout;
private boolean retry;
}
// 使用
HttpRequest request = HttpRequest.builder()
.url("https://api.example.com/users")
.method("POST")
.headers(Map.of("Content-Type", "application/json"))
.body("{\"name\": \"test\"}")
.timeout(5000)
.retry(true)
.build();
// 手动实现Builder模式
public class QueryBuilder {
private String table;
private List<String> columns = new ArrayList<>();
private List<String> conditions = new ArrayList<>();
private String orderBy;
private int limit;
public QueryBuilder from(String table) {
this.table = table;
return this;
}
public QueryBuilder select(String... columns) {
this.columns.addAll(Arrays.asList(columns));
return this;
}
public QueryBuilder where(String condition) {
this.conditions.add(condition);
return this;
}
public QueryBuilder orderBy(String orderBy) {
this.orderBy = orderBy;
return this;
}
public QueryBuilder limit(int limit) {
this.limit = limit;
return this;
}
public String build() {
StringBuilder sql = new StringBuilder("SELECT ");
sql.append(columns.isEmpty() ? "*" : String.join(", ", columns));
sql.append(" FROM ").append(table);
if (!conditions.isEmpty()) {
sql.append(" WHERE ").append(String.join(" AND ", conditions));
}
if (orderBy != null) {
sql.append(" ORDER BY ").append(orderBy);
}
if (limit > 0) {
sql.append(" LIMIT ").append(limit);
}
return sql.toString();
}
}
// 使用(链式调用,类似Stream API)
String sql = new QueryBuilder()
.select("id", "username", "email")
.from("users")
.where("age > 18")
.where("status = 1")
.orderBy("created_at DESC")
.limit(10)
.build();
// SELECT id, username, email FROM users WHERE age > 18 AND status = 1 ORDER BY created_at DESC LIMIT 10结构型模式
4. 代理模式(Proxy)
java
/**
* 代理模式:为对象提供替身以控制访问
*
* 前端对比:
* Vue 3的Proxy响应式就是代理模式
* JavaScript的Proxy对象
* 前端拦截器(Axios interceptor)
*/
// ===== JDK动态代理(Spring AOP核心)=====
import java.lang.reflect.*;
// 接口
public interface UserService {
void save(User user);
User findById(Long id);
}
// 实现类
public class UserServiceImpl implements UserService {
@Override
public void save(User user) {
System.out.println("保存用户: " + user.getName());
}
@Override
public User findById(Long id) {
return new User(id, "test");
}
}
// 代理处理器
public class LogProxyHandler implements InvocationHandler {
private final Object target; // 被代理对象
public LogProxyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("[LOG] 开始执行: " + method.getName());
try {
Object result = method.invoke(target, args);
long elapsed = System.currentTimeMillis() - start;
System.out.println("[LOG] 执行完成: " + method.getName() + ", 耗时: " + elapsed + "ms");
return result;
} catch (Exception e) {
System.out.println("[LOG] 执行异常: " + method.getName() + ", 异常: " + e.getMessage());
throw e;
}
}
}
// 使用
UserService userService = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
new LogProxyHandler(userService)
);
proxy.save(new User(1L, "张三"));
// 输出:
// [LOG] 开始执行: save
// 保存用户: 张三
// [LOG] 执行完成: save, 耗时: 0ms5. 装饰器模式(Decorator)
java
/**
* 装饰器模式:动态添加功能
*
* 前端对比:
* 高阶组件(HOC)
* 中间件链(Koa中间件、Express中间件)
*/
// 组件接口
public interface DataSource {
String getData();
}
// 基础组件
public class FileDataSource implements DataSource {
private String filename;
public FileDataSource(String filename) {
this.filename = filename;
}
@Override
public String getData() {
return "文件内容";
}
}
// 装饰器基类
public abstract class DataSourceDecorator implements DataSource {
protected DataSource wrappee;
public DataSourceDecorator(DataSource source) {
this.wrappee = source;
}
@Override
public String getData() {
return wrappee.getData();
}
}
// 加密装饰器
public class EncryptionDecorator extends DataSourceDecorator {
@Override
public String getData() {
return encrypt(super.getData());
}
private String encrypt(String data) {
return "加密(" + data + ")";
}
}
// 压缩装饰器
public class CompressionDecorator extends DataSourceDecorator {
@Override
public String getData() {
return compress(super.getData());
}
private String compress(String data) {
return "压缩(" + data + ")";
}
}
// 使用(类似Koa中间件的洋葱模型)
DataSource source = new CompressionDecorator(
new EncryptionDecorator(
new FileDataSource("data.txt")
)
);
String result = source.getData();
// 结果:压缩(加密(文件内容))行为型模式
6. 策略模式(Strategy)
java
/**
* 策略模式:定义一系列算法,使它们可以互相替换
*
* 前端对比:
* 表单验证策略
* 排序算法选择
* 不同环境的配置策略
*/
// 策略接口
public interface DiscountStrategy {
double calculate(double price);
}
// 具体策略
public class NoDiscount implements DiscountStrategy {
@Override
public double calculate(double price) {
return price;
}
}
public class PercentDiscount implements DiscountStrategy {
private double percent;
public PercentDiscount(double percent) {
this.percent = percent;
}
@Override
public double calculate(double price) {
return price * (1 - percent / 100);
}
}
public class FullReduction implements DiscountStrategy {
private double threshold;
private double reduction;
public FullReduction(double threshold, double reduction) {
this.threshold = threshold;
this.reduction = reduction;
}
@Override
public double calculate(double price) {
return price >= threshold ? price - reduction : price;
}
}
// 上下文
public class PriceCalculator {
private DiscountStrategy strategy;
public void setStrategy(DiscountStrategy strategy) {
this.strategy = strategy;
}
public double calculatePrice(double price) {
return strategy.calculate(price);
}
}
// 使用
PriceCalculator calculator = new PriceCalculator();
// 无折扣
calculator.setStrategy(new NoDiscount());
System.out.println(calculator.calculatePrice(200)); // 200
// 8折
calculator.setStrategy(new PercentDiscount(20));
System.out.println(calculator.calculatePrice(200)); // 160
// 满200减50
calculator.setStrategy(new FullReduction(200, 50));
System.out.println(calculator.calculatePrice(200)); // 150
// Spring中的策略模式(结合Map自动注册)
@Component
public class DiscountStrategyFactory {
private final Map<String, DiscountStrategy> strategies = new ConcurrentHashMap<>();
// Spring自动注入所有DiscountStrategy实现
@Autowired
public DiscountStrategyFactory(List<DiscountStrategy> strategyList) {
for (DiscountStrategy strategy : strategyList) {
strategies.put(strategy.getClass().getSimpleName(), strategy);
}
}
public DiscountStrategy getStrategy(String name) {
return strategies.getOrDefault(name, new NoDiscount());
}
}7. 观察者模式(Observer)
java
/**
* 观察者模式:一对多的依赖关系,当一个对象改变时通知所有依赖者
*
* 前端对比:
* 事件监听(addEventListener)
* Vue的事件系统($emit / $on)
* Redux的subscribe
* EventEmitter
* RxJS的Observable
*/
// ===== Spring事件机制 =====
// 1. 定义事件
public class OrderCreatedEvent extends ApplicationEvent {
private final Order order;
public OrderCreatedEvent(Object source, Order order) {
super(source);
this.order = order;
}
public Order getOrder() {
return order;
}
}
// 2. 发布事件
@Service
public class OrderService {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void createOrder(Order order) {
// 保存订单
orderMapper.insert(order);
// 发布事件(解耦后续操作)
eventPublisher.publishEvent(new OrderCreatedEvent(this, order));
}
}
// 3. 监听事件(多个监听器独立执行)
@Component
public class OrderEventListener {
// 发送通知
@EventListener
@Async
public void onOrderCreated(OrderCreatedEvent event) {
Order order = event.getOrder();
System.out.println("发送订单通知: " + order.getOrderNo());
}
// 扣减库存
@EventListener
@Async
public void onOrderCreatedDeductStock(OrderCreatedEvent event) {
Order order = event.getOrder();
System.out.println("扣减库存: " + order.getProductId());
}
// 增加积分
@EventListener(condition = "#event.order.amount > 100")
public void onOrderCreatedAddPoints(OrderCreatedEvent event) {
System.out.println("增加积分");
}
}
// ===== 条件监听 =====
// 只处理特定类型的事件
@EventListener(classes = {OrderCreatedEvent.class})
public void handle(OrderCreatedEvent event) {
// ...
}8. 模板方法模式(Template Method)
java
/**
* 模板方法模式:定义算法式骨架,将具体步骤延迟到子类
*
* 前端对比:
* React的组件生命周期
* Vue的钩子函数(created, mounted等)
*/
// 抽象模板
public abstract class AbstractExportService<T> {
// 模板方法(固定流程)
public final void export(List<T> data, String filename) {
// 1. 验证数据
validate(data);
// 2. 转换数据
List<Map<String, Object>> rows = convert(data);
// 3. 生成文件(子类实现)
byte[] fileBytes = generateFile(rows);
// 4. 上传文件
String url = upload(fileBytes, filename);
// 5. 记录日志
log(filename, url, data.size());
}
private void validate(List<T> data) {
if (data == null || data.isEmpty()) {
throw new IllegalArgumentException("导出数据不能为空");
}
}
protected abstract List<Map<String, Object>> convert(List<T> data);
protected abstract byte[] generateFile(List<Map<String, Object>> rows);
private String upload(byte[] bytes, String filename) {
// 上传逻辑
return "https://example.com/files/" + filename;
}
private void log(String filename, String url, int count) {
System.out.printf("导出完成: %s, 共%d条, 地址: %s%n", filename, count, url);
}
}
// Excel导出
public class ExcelExportService extends AbstractExportService<User> {
@Override
protected List<Map<String, Object>> convert(List<User> data) {
return data.stream().map(user -> {
Map<String, Object> row = new LinkedHashMap<>();
row.put("用户名", user.getName());
row.put("邮箱", user.getEmail());
row.put("年龄", user.getAge());
return row;
}).collect(Collectors.toList());
}
@Override
protected byte[] generateFile(List<Map<String, Object>> rows) {
// 使用POI生成Excel
return new byte[0]; // 简化
}
}
// CSV导出
public class CsvExportService extends AbstractExportService<User> {
@Override
protected List<Map<String, Object>> convert(List<User> data) {
return data.stream().map(user -> {
Map<String, Object> row = new LinkedHashMap<>();
row.put("name", user.getName());
row.put("email", user.getEmail());
row.put("age", user.getAge());
return row;
}).collect(Collectors.toList());
}
@Override
protected byte[] generateFile(List<Map<String, Object>> rows) {
// 生成CSV
return new byte[0]; // 简化
}
}常用设计模式总结
| 模式 | 用途 | Spring中的使用 |
|---|---|---|
| 单例 | Bean默认scope | 所有Spring Bean |
| 工厂 | 创建对象 | BeanFactory, FactoryBean |
| 代理 | AOP | JDK动态代理, CGLIB |
| 模板方法 | 固定流程 | JdbcTemplate, RestTemplate |
| 观察者 | 事件驱动 | ApplicationEvent |
| 策略 | 算法切换 | Resource接口 |
| 适配器 | 接口转换 | HandlerAdapter |
| 装饰器 | 增强功能 | BeanWrapper |
8.3.2 DDD领域驱动设计
DDD核心概念
DDD分层架构:
┌─────────────────────────────────────────┐
│ 用户接口层(Interface) │
│ Controller, DTO, VO, 参数校验 │
├─────────────────────────────────────────┤
│ 应用层(Application) │
│ Service, 事务管理, 事件发布 │
├─────────────────────────────────────────┤
│ 领域层(Domain) │
│ Entity, ValueObject, Aggregate │
│ DomainService, Repository接口 │
├─────────────────────────────────────────┤
│ 基础设施层(Infrastructure) │
│ Repository实现, 数据库, 消息队列 │
│ 外部服务调用, 配置 │
└─────────────────────────────────────────┘
前端对比:
- 前端分层:UI层 → 业务逻辑层 → 数据层 → API层
- DDD分层思想类似,但更加严谨
- 领域层相当于前端的业务逻辑核心DDD代码示例
java
// ===== 1. 实体(Entity)—— 有唯一标识 =====
public class Order {
private OrderId orderId; // 值对象作为ID
private UserId userId; // 值对象
private OrderStatus status; // 枚举
private Money totalAmount; // 值对象
private List<OrderItem> items; // 实体集合
private LocalDateTime createdAt;
// 实体行为(充血模型,而非贫血模型)
public void pay() {
if (this.status != OrderStatus.PENDING) {
throw new BusinessException("订单状态不允许支付");
}
this.status = OrderStatus.PAID;
// 领域事件
registerEvent(new OrderPaidEvent(this.orderId));
}
public void cancel() {
if (this.status == OrderStatus.SHIPPED) {
throw new BusinessException("已发货订单不能取消");
}
this.status = OrderStatus.CANCELLED;
}
public void addItem(Product product, int quantity) {
if (this.status != OrderStatus.PENDING) {
throw new BusinessException("只能修改待支付订单");
}
OrderItem item = new OrderItem(product.getProductId(), product.getName(),
product.getPrice(), quantity);
this.items.add(item);
recalculateAmount();
}
private void recalculateAmount() {
this.totalAmount = items.stream()
.map(OrderItem::getSubtotal)
.reduce(Money.ZERO, Money::add);
}
}
// ===== 2. 值对象(Value Object)—— 无唯一标识,不可变 =====
public class Money {
public static final Money ZERO = new Money(BigDecimal.ZERO);
private final BigDecimal amount;
private final Currency currency;
public Money(BigDecimal amount, Currency currency) {
if (amount.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("金额不能为负数");
}
this.amount = amount;
this.currency = currency;
}
public Money(BigDecimal amount) {
this(amount, Currency.CNY);
}
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("不同币种不能相加");
}
return new Money(this.amount.add(other.amount), this.currency);
}
public Money multiply(int quantity) {
return new Money(this.amount.multiply(BigDecimal.valueOf(quantity)), this.currency);
}
// equals和hashCode基于值(而非引用)
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Money money = (Money) o;
return amount.compareTo(money.amount) == 0 && currency.equals(money.currency);
}
@Override
public int hashCode() {
return Objects.hash(amount.stripTrailingZeros(), currency);
}
}
// ===== 3. 聚合根(Aggregate Root)=====
public class OrderAggregate {
private Order order;
// 聚合根保证一致性
public void submitOrder(CreateOrderCommand command) {
// 1. 创建订单
Order order = Order.create(command.getUserId(), command.getItems());
// 2. 校验库存(调用领域服务)
inventoryService.checkStock(command.getItems());
// 3. 计算优惠(调用领域服务)
Money discount = promotionService.calculateDiscount(order.getTotalAmount());
order.applyDiscount(discount);
// 4. 发布领域事件
order.registerEvent(new OrderCreatedEvent(order.getOrderId()));
this.order = order;
}
}
// ===== 4. 领域服务(Domain Service)=====
@Service
public class OrderDomainService {
// 跨聚合的业务逻辑放在领域服务
public void transferOrder(Order source, Order target, User operator) {
// 涉及多个聚合的操作
source.transfer();
target.receive();
// 发布事件
}
}
// ===== 5. 仓储接口(Repository)=====
public interface OrderRepository {
Order findById(OrderId orderId);
void save(Order order);
void remove(OrderId orderId);
List<Order> findByUserId(UserId userId);
}
// ===== 6. 应用服务(Application Service)=====
@Service
public class OrderApplicationService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private EventPublisher eventPublisher;
@Transactional
public OrderDTO createOrder(CreateOrderCommand command) {
// 1. 创建聚合根
OrderAggregate aggregate = new OrderAggregate();
aggregate.submitOrder(command);
// 2. 保存
Order order = aggregate.getOrder();
orderRepository.save(order);
// 3. 发布事件
eventPublisher.publish(order.getEvents());
// 4. 返回DTO
return OrderConverter.toDTO(order);
}
}8.3.3 六边形架构 / 整洁架构
java
/**
* 六边形架构(端口与适配器架构)
*
* 核心思想:业务逻辑不依赖任何外部技术
*
* ┌──────────────────────────┐
* │ 外部适配器(驱动侧) │
* │ REST API / gRPC / MQ │
* ├──────────────────────────┤
* │ 应用层(端口) │
* │ 入端口:Service接口 │
* │ 出端口:Repository接口 │
* ├──────────────────────────┤
* │ 领域层(核心) │
* │ Entity / ValueObject │
* │ DomainService │
* ├──────────────────────────┤
* │ 外部适配器(被驱动侧) │
* │ MySQL / Redis / ES │
* └──────────────────────────┘
*
* 前端对比:
* 类似于前端的依赖注入和接口抽象
* 比如Service层定义接口,具体实现可以切换(axios/fetch)
*/
// ===== 端口(Port)=====
// 入端口(Driving Port)—— 外部调用领域的入口
public interface OrderService {
OrderDTO createOrder(CreateOrderCommand command);
OrderDTO getOrder(Long orderId);
}
// 出端口(Driven Port)—— 领域调用外部的接口
public interface OrderRepository {
Order save(Order order);
Order findById(OrderId id);
}
public interface NotificationPort {
void sendOrderConfirmation(Order order);
}
// ===== 适配器(Adapter)=====
// 驱动侧适配器
@RestController
public class OrderController implements OrderService {
@Autowired
private OrderApplicationService orderService;
@Override
@PostMapping("/orders")
public OrderDTO createOrder(@RequestBody CreateOrderRequest request) {
CreateOrderCommand command = OrderMapper.toCommand(request);
return orderService.createOrder(command);
}
}
// 被驱动侧适配器
@Repository
public class MysqlOrderRepository implements OrderRepository {
@Autowired
private OrderMapper orderMapper;
@Override
public Order save(Order order) {
OrderPO po = OrderConverter.toPO(order);
orderMapper.insert(po);
return OrderConverter.toDomain(po);
}
@Override
public Order findById(OrderId id) {
OrderPO po = orderMapper.selectById(id.getValue());
return OrderConverter.toDomain(po);
}
}
// 可以方便地切换实现
@Repository
public class MongoOrderRepository implements OrderRepository {
@Autowired
private MongoTemplate mongoTemplate;
@Override
public Order save(Order order) {
return mongoTemplate.save(order);
}
@Override
public Order findById(OrderId id) {
return mongoTemplate.findById(id.getValue(), Order.class);
}
}8.3.4 微服务拆分原则
拆分原则
微服务拆分原则:
1. 单一职责原则(SRP)
- 每个服务只负责一个业务领域
- 类比前端:组件单一职责
2. 高内聚低耦合
- 相关功能放在同一个服务
- 服务间通过API通信
3. 以业务领域为边界(DDD限界上下文)
- 电商系统拆分示例:
├── 用户服务(注册、登录、用户信息)
├── 商品服务(商品管理、分类、库存)
├── 订单服务(下单、支付、退款)
├── 营销服务(优惠券、活动、积分)
├── 支付服务(支付渠道、对账)
└── 物流服务(物流跟踪、签收)
4. 数据独立
- 每个服务有自己的数据库
- 不直接访问其他服务的数据库
5. 渐进式拆分
- 不要一开始就过度拆分
- 从单体开始,逐步拆分Spring Cloud微服务示例
yaml
# Nacos注册中心配置
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: dev
group: DEFAULT_GROUP
# Gateway网关配置
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/users/**
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/orders/**java
// Feign远程调用
@FeignClient(name = "user-service")
public interface UserClient {
@GetMapping("/api/users/{id}")
UserDTO getUserById(@PathVariable Long id);
@GetMapping("/api/users/{id}/vip")
Boolean isVipUser(@PathVariable Long id);
}
// 使用Feign客户端
@Service
public class OrderService {
@Autowired
private UserClient userClient;
public OrderDTO createOrder(CreateOrderCommand command) {
// 调用用户服务获取用户信息
UserDTO user = userClient.getUserById(command.getUserId());
// 检查VIP
Boolean isVip = userClient.isVipUser(command.getUserId());
// 创建订单逻辑...
return orderDTO;
}
}
// Sentinel熔断降级
@FeignClient(name = "user-service", fallback = UserClientFallback.class)
public interface UserClient {
@GetMapping("/api/users/{id}")
UserDTO getUserById(@PathVariable Long id);
}
@Component
public class UserClientFallback implements UserClient {
@Override
public UserDTO getUserById(Long id) {
// 降级逻辑:返回缓存数据或默认值
return UserDTO.defaultUser();
}
}8.3.5 高可用架构设计
核心策略
高可用架构策略:
1. 冗余(Redundancy)
- 多实例部署
- 数据库主从复制
- 类比前端:CDN多节点
2. 熔断(Circuit Breaker)
- 类比前端:请求超时重试、错误降级
- 当下游服务异常时快速失败
3. 限流(Rate Limiting)
- 类比前端:防抖节流
- 保护系统不被流量冲垮
4. 降级(Degradation)
- 类比前端:骨架屏、默认数据
- 非核心功能暂时不可用
5. 异步(Asynchronous)
- 消息队列解耦
- 类比前端:Web Worker、异步渲染熔断器实现
java
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import java.time.Duration;
/**
* Resilience4j熔断器
*
* 前端对比:
* 类似于Axios的请求重试和超时配置
* 但熔断器更加智能
*/
public class CircuitBreakerDemo {
// 熔断器配置
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50) // 失败率阈值50%
.slowCallRateThreshold(80) // 慢调用比例阈值80%
.slowCallDurationThreshold(Duration.ofSeconds(2)) // 慢调用定义:2秒
.waitDurationInOpenState(Duration.ofSeconds(30)) // 熔断打开后等待30秒
.permittedNumberOfCallsInHalfOpenState(10) // 半开状态允许10个请求
.minimumNumberOfCalls(20) // 最少20次调用后才计算失败率
.build();
CircuitBreaker circuitBreaker = CircuitBreaker.of("userService", config);
// 使用熔断器
public User getUser(Long userId) {
return CircuitBreaker.decorateSupplier(circuitBreaker, () -> {
return userService.getUser(userId);
}).recover(throwable -> {
// 降级处理
return User.defaultUser();
}).get();
}
}
/*
* 熔断器状态机:
*
* CLOSED(关闭)──失败率超阈值──→ OPEN(打开)
* ↑ │
* │ 等待30秒
* │ ↓
* └──成功率达标──── HALF_OPEN(半开)
*
* CLOSED:正常状态,请求正常通过
* OPEN:熔断状态,直接返回降级结果
* HALF_OPEN:探测状态,允许少量请求探测下游是否恢复
*/8.4 源码阅读(第7-8周)
8.4.1 Spring Framework核心源码
Bean生命周期
java
/**
* Spring Bean生命周期
*
* 实例化 → 属性填充 → 初始化 → 使用 → 销毁
*
* 详细流程:
* 1. 实例化(Instantiation)
* - 通过构造方法创建对象
* - 对应前端:new Vue()
*
* 2. 属性填充(Population)
* - 依赖注入(@Autowired)
* - 对应前端:props传入
*
* 3. 初始化(Initialization)
* - Aware接口回调
* - BeanPostProcessor前置处理
* - @PostConstruct
* - InitializingBean.afterPropertiesSet()
* - init-method
* - BeanPostProcessor后置处理(AOP代理在这里创建)
* - 对应前端:mounted钩子
*
* 4. 使用
* - 对应前端:组件运行中
*
* 5. 销毁(Destruction)
* - @PreDestroy
* - DisposableBean.destroy()
* - destroy-method
* - 对应前端:beforeUnmount钩子
*/
@Component
public class MyBean implements InitializingBean, DisposableBean, BeanNameAware, ApplicationContextAware {
private String name;
// 1. 构造方法
public MyBean() {
System.out.println("1. 构造方法");
}
// 2. Aware接口回调
@Override
public void setBeanName(String name) {
System.out.println("2. BeanNameAware: " + name);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("3. ApplicationContextAware");
}
// 3. BeanPostProcessor前置处理(框架内部)
// 4. @PostConstruct
@PostConstruct
public void postConstruct() {
System.out.println("4. @PostConstruct");
}
// 5. InitializingBean
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("5. afterPropertiesSet");
}
// 6. 自定义init-method(XML配置)
public void customInit() {
System.out.println("6. customInit");
}
// 7. BeanPostProcessor后置处理(AOP代理创建)
// 8. 使用阶段
// 9. @PreDestroy
@PreDestroy
public void preDestroy() {
System.out.println("7. @PreDestroy");
}
// 10. DisposableBean
@Override
public void destroy() throws Exception {
System.out.println("8. destroy");
}
}
/*
* 执行顺序:
* 1. 构造方法
* 2. BeanNameAware: myBean
* 3. ApplicationContextAware
* 4. @PostConstruct
* 5. afterPropertiesSet
* 6. customInit
* 7. @PreDestroy
* 8. destroy
*/Spring事务源码核心
java
/**
* Spring事务传播机制
*
* 前端对比:
* 类似于Promise链中的错误处理
* 或者组件树中的事件冒泡
*/
@Service
public class TransactionDemo {
@Autowired
private UserService userService;
@Autowired
private LogService logService;
// REQUIRED(默认):有事务就加入,没有就新建
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 在事务中执行
userService.updateUser();
}
// REQUIRES_NEW:总是新建事务,挂起当前事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// 独立事务,不受外部事务影响
logService.addLog("操作日志");
}
// NESTED:嵌套事务(基于Savepoint)
@Transactional(propagation = Propagation.NESTED)
public void methodC() {
// 如果外部事务回滚,嵌套事务也回滚
// 但嵌套事务回滚,不影响外部事务
}
// 实际场景:下单流程
@Transactional
public void placeOrder(OrderRequest request) {
try {
// 创建订单(主事务)
createOrder(request);
// 扣减库存(加入当前事务)
deductStock(request);
// 记录日志(独立事务,REQUIRES_NEW)
// 即使订单失败,日志也要记录
logService.addLog("下单操作");
} catch (Exception e) {
// 订单和库存一起回滚
// 但日志不回滚(独立事务)
throw new BusinessException("下单失败", e);
}
}
}8.4.2 Spring Boot自动配置源码
自动配置原理
java
/**
* Spring Boot自动配置原理
*
* 核心注解链:
* @SpringBootApplication
* ├── @SpringBootConfiguration → @Configuration
* ├── @EnableAutoConfiguration → @Import(AutoConfigurationImportSelector.class)
* └── @ComponentScan
*
* 自动配置流程:
* 1. @EnableAutoConfiguration触发AutoConfigurationImportSelector
* 2. 读取META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
* (JDK 8及之前是 META-INF/spring.factories)
* 3. 获取所有自动配置类
* 4. 通过条件注解过滤(@ConditionalOnClass, @ConditionalOnProperty等)
* 5. 加载符合条件的配置类
*
* 前端对比:
* 类似于webpack/vite的插件系统
* 通过配置文件自动加载和注册插件
*/
// ===== 自定义自动配置 =====
// 1. 定义配置属性
@ConfigurationProperties(prefix = "my.service")
public class MyServiceProperties {
private String prefix = "[MyService]";
private String suffix = "";
private boolean enabled = true;
// getter/setter
}
// 2. 定义服务类
public class MyService {
private MyServiceProperties properties;
public MyService(MyServiceProperties properties) {
this.properties = properties;
}
public String wrap(String content) {
return properties.getPrefix() + content + properties.getSuffix();
}
}
// 3. 自动配置类
@AutoConfiguration
@ConditionalOnClass(MyService.class) // classpath下有这个类才生效
@ConditionalOnProperty(prefix = "my.service", name = "enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(MyServiceProperties.class)
public class MyServiceAutoConfiguration {
@Bean
@ConditionalOnMissingBean // 容器中不存在时才创建
public MyService myService(MyServiceProperties properties) {
return new MyService(properties);
}
}
// 4. 注册自动配置
// 在 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 中添加:
// com.example.MyServiceAutoConfiguration
// 5. 使用方只需引入依赖并配置
// application.yml:
// my:
// service:
// prefix: "[Hello]"
// suffix: "[World]"8.4.3 MyBatis源码
核心执行流程
java
/**
* MyBatis核心执行流程
*
* 前端对比:
* 类似于Axios请求拦截器链
* Configuration → 拦截器链 → 执行器 → 结果处理
*
* 核心组件:
* 1. SqlSessionFactory:创建SqlSession的工厂
* 2. SqlSession:与数据库交互的会话
* 3. Executor:执行器(Simple/Reuse/Batch)
* 4. StatementHandler:SQL语句处理器
* 5. ParameterHandler:参数处理器
* 6. ResultSetHandler:结果集处理器
* 7. Interceptor:拦截器(插件机制)
*
* 执行流程:
* SqlSession → Executor → StatementHandler → ParameterHandler → JDBC
* ↓
* ResultSetHandler → 返回结果
*/
// ===== MyBatis插件原理 =====
// MyBatis允许拦截四大对象的方法:
// 1. Executor(update, query, commit, rollback等)
// 2. StatementHandler(prepare, parameterize, batch, update, query等)
// 3. ParameterHandler(setParameter)
// 4. ResultSetHandler(handleResultSets)
// 示例:分页插件原理
@Intercepts({
@Signature(
type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class}
)
})
public class PageInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 1. 获取StatementHandler
StatementHandler handler = (StatementHandler) invocation.getTarget();
// 2. 获取原始SQL
BoundSql boundSql = handler.getBoundSql();
String originalSql = boundSql.getSql();
// 3. 获取分页参数
Page<?> page = PageContext.getCurrentPage();
if (page != null) {
// 4. 改写SQL(添加LIMIT)
String pageSql = originalSql + " LIMIT " + page.getOffset() + ", " + page.getSize();
// 5. 通过反射修改SQL
ReflectUtil.setFieldValue(boundSql, "sql", pageSql);
}
// 6. 执行原方法
return invocation.proceed();
}
}8.4.4 源码阅读方法
源码阅读方法论:
1. 带着问题读
- 不要从头到尾逐行读
- 先有一个具体问题,然后追踪源码
- 例如:@Autowired是怎么注入的?
2. 从断点开始
- 在关键位置打断点
- Debug模式跟踪调用链
- 类比前端:Chrome DevTools断点调试
3. 关注核心流程
- Spring:Bean生命周期、AOP实现、事务管理
- MyBatis:SQL执行流程、插件机制
- Spring Boot:自动配置原理
4. 画图辅助
- 类图:理解类的继承关系
- 时序图:理解方法调用顺序
- 流程图:理解核心逻辑
5. 推荐源码阅读顺序
第一阶段:
- Spring Bean生命周期
- Spring AOP实现
- Spring MVC请求处理流程
第二阶段:
- Spring Boot自动配置
- Spring事务实现
- MyBatis插件机制
第三阶段:
- Spring循环依赖解决(三级缓存)
- Spring Cloud网关路由
- Netty线程模型8.5 练习任务
练习1:JVM调优实战
- 编写一个内存泄漏的程序(不断创建对象不释放)
- 使用jmap导出堆dump
- 使用MAT分析内存泄漏原因
- 调整JVM参数观察GC行为变化
练习2:SQL优化实战
- 创建百万级数据的测试表
- 使用EXPLAIN分析慢查询
- 优化索引设计
- 对比优化前后的查询时间
练习3:缓存系统设计
- 实现一个完整的缓存层
- 处理缓存穿透、击穿、雪崩问题
- 实现缓存一致性(延迟双删策略)
- 编写缓存监控接口
练习4:设计模式实践
- 用策略模式实现多种排序算法切换
- 用观察者模式实现事件总线
- 用建造者模式实现复杂查询构建器
- 用代理模式实现方法执行时间统计
练习5:DDD建模
- 设计一个图书管理系统的领域模型
- 实现实体、值对象、聚合根
- 划分限界上下文
- 实现仓储模式
练习6:源码阅读
- Debug跟踪Spring Bean创建过程
- 阅读Spring MVC请求分发流程
- 分析MyBatis-SQL执行链路
- 理解Spring Boot自动配置加载过程
8.6 学习检查清单
JVM相关
- [ ] 理解JVM内存模型各区域的作用
- [ ] 掌握垃圾回收算法(标记-清除、复制、标记-整理)
- [ ] 了解主流垃圾收集器的特点和适用场景
- [ ] 理解双亲委派模型及打破方式
- [ ] 能使用JVM调优参数配置堆内存和GC
- [ ] 熟练使用jps、jstat、jstack、jmap排查问题
- [ ] 掌握Arthas基本使用
性能优化相关
- [ ] 能使用EXPLAIN分析SQL执行计划
- [ ] 掌握索引优化原则和失效场景
- [ ] 理解连接池参数调优
- [ ] 掌握缓存策略(穿透、击穿、雪崩解决方案)
- [ ] 了解Spring WebFlux响应式编程
- [ ] 掌握限流、熔断、降级的实现
架构设计相关
- [ ] 掌握常用设计模式(单例、工厂、代理、策略、观察者)
- [ ] 理解DDD核心概念(实体、值对象、聚合根、限界上下文)
- [ ] 了解六边形架构的分层思想
- [ ] 理解微服务拆分原则
- [ ] 掌握高可用架构设计策略
源码阅读相关
- [ ] 理解Spring Bean生命周期
- [ ] 理解Spring AOP实现原理
- [ ] 理解Spring Boot自动配置原理
- [ ] 理解MyBatis核心执行流程
- [ ] 掌握源码阅读方法论
完成本阶段后,你应该具备Java进阶能力,能够进行性能调优、架构设计,并具备源码阅读能力。这是一个持续学习的过程,需要在实际项目中不断积累和深化理解。
评论
正在加载评论…