一、Java 核心
1. 基础语法
final
关键字的作用- 修饰类:类不可被继承(如
String
类),保证类的稳定性和安全性。 - 修饰方法:方法不可被重写(防止子类篡改父类核心逻辑,如工具类方法)。
- 修饰变量:
- 基本类型:值不可修改(如
final int a = 10
,a
的值终身不变)。 - 引用类型:引用地址不可变(对象内容可修改,如
final List<String> list = new ArrayList<>()
,list
可添加元素但不能指向新集合)。
- 基本类型:值不可修改(如
- 修饰类:类不可被继承(如
深拷贝 vs 浅拷贝
类型 特点 实现方式 适用场景 浅拷贝 仅复制对象本身及基本类型字段,引用类型字段与原对象共享同一份引用(新旧对象关联) Object.clone()
默认实现(需实现Cloneable
接口)、BeanUtils.copyProperties()
简单对象复制,无需完全隔离引用关系 深拷贝 递归复制所有字段(包括引用类型),新旧对象完全独立(无任何关联) 手动递归复制、序列化( Serializable
)、JSON 工具(如 Jackson)复杂对象复制,需彻底隔离引用关系
2. 集合框架
HashMap
底层结构(Java 8+)
采用数组 + 链表 + 红黑树混合结构,平衡查询效率与内存开销:- 数组(桶):初始容量为 16(
DEFAULT_INITIAL_CAPACITY
),扩容时按 2 倍增长(newCap = oldCap << 1
),存储链表头节点或红黑树根节点。 - 链表:哈希冲突时(不同 key 计算出相同索引),用链表存储元素,查询时间复杂度为 O (n)。
- 红黑树:当链表长度超过 8(
TREEIFY_THRESHOLD
)且数组长度≥64(MIN_TREEIFY_CAPACITY
)时,链表转为红黑树(查询时间复杂度优化为 O (log n));当长度≤6(UNTREEIFY_THRESHOLD
)时,红黑树转回链表(降低树结构维护成本)。
优化点:
- 哈希计算:
(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)
,通过高位与低位异或,减少哈希冲突(将高位信息融入低位)。 - 扩容机制:使用高位掩码计算新索引(
newIndex = oldCap + (index & (oldCap - 1))
),避免节点重新哈希,直接定位新位置。
- 数组(桶):初始容量为 16(
HashMap
线程安全性
HashMap
非线程安全,多线程并发操作可能导致:- 数据覆盖:多线程同时
put
时,可能覆盖彼此的结果。 - Java 7 死循环:链表采用头插法,扩容时可能导致链表成环(Java 8 改为尾插法解决,但仍非线程安全)。
线程安全的 Map 实现:
ConcurrentHashMap
(推荐):- Java 7:基于分段锁(
Segment
,每个 Segment 是独立的哈希表),并发度为 Segment 数量(默认 16)。 - Java 8:移除 Segment,改用 CAS +
synchronized
(只锁单个桶),并发效率更高,支持computeIfAbsent
等原子操作。
- Java 7:基于分段锁(
Hashtable
:全表锁(所有方法用synchronized
修饰),并发性能差,已淘汰。Collections.synchronizedMap(Map)
:包装普通 Map,通过同步代码块(synchronized (mutex)
)保证安全,性能一般(锁整个 Map)。
- 数据覆盖:多线程同时
ArrayList
vsLinkedList
特性 ArrayList
LinkedList
底层结构 动态数组(连续内存空间) 双向链表(非连续内存) 随机访问 O (1)(通过索引直接访问) O (n)(需遍历链表) 插入 / 删除 中间操作 O (n)(需移动元素),尾部操作 O (1) 中间操作 O (1)(修改指针),但定位节点需 O (n) 内存占用 较少(连续空间,有扩容冗余) 较多(每个节点含前后指针) 扩容机制 容量不足时扩容为 1.5 倍( oldCap + (oldCap >> 1)
)无扩容(动态添加节点) 适用场景 频繁查询、尾部增删 频繁中间插入 / 删除
3. JVM
内存区域划分
区域类型 包含区域 特点 可能的异常 线程私有 虚拟机栈 存储栈帧(局部变量表、操作数栈、返回地址等),方法调用时创建,结束时销毁。 栈深度超过限制→ StackOverflowError
;内存不足→OutOfMemoryError
。本地方法栈 为 native
方法(如Object.hashCode()
)提供内存空间。同虚拟机栈。 程序计数器 记录当前线程执行的字节码行号(如分支、循环、跳转)。 唯一不会抛出 OOM 的区域。 线程共享 堆 存储对象实例、数组,GC 主要工作区域(分新生代、老年代)。 内存不足→ OutOfMemoryError
。方法区(元空间) 存储类信息(结构、方法数据)、常量池、静态变量、JIT 编译后的代码。 JDK 8 前(永久代)可能 OOM;JDK 8 后元空间使用本地内存,默认无上限(可通过 -XX:MaxMetaspaceSize
限制)。垃圾回收(GC)判定机制
采用可达性分析算法:- GC Roots(根对象):
- 虚拟机栈中局部变量表引用的对象(如方法内的
User user = new User()
)。 - 方法区中类静态变量引用的对象(如
static User user = new User()
)。 - 方法区中常量引用的对象(如
String s = "abc"
中的"abc"
)。 - 本地方法栈中
native
方法引用的对象。
- 虚拟机栈中局部变量表引用的对象(如方法内的
- 判定流程:从 GC Roots 出发,遍历所有可达对象并标记;未被标记的对象为不可达对象,可被回收(但并非立即回收,需经历多次标记)。
引用类型对回收的影响:
- 强引用(
User u = new User()
):永不回收,OOM 也不释放。 - 软引用(
SoftReference<User>
):内存不足时回收,用于缓存(如图片缓存)。 - 弱引用(
WeakReference<User>
):GC 时立即回收,用于临时关联(如ThreadLocal
的 key)。 - 虚引用(
PhantomReference<User>
):仅用于跟踪对象回收,必须配合引用队列,无实际引用意义(如堆外内存回收)。
- GC Roots(根对象):
类加载机制
遵循双亲委派模型(防止类重复加载),流程:- 加载(Loading):通过类全限定名(如
com.xxx.User
)读取.class
文件到内存,生成Class
对象(存方法区)。 - 验证(Verification):检查字节码合法性(格式、语义、字节码指令、符号引用),防止恶意 class 文件。
- 准备(Preparation):为静态变量(
static
)分配内存并赋默认值(如static int a = 10
,此处赋 0)。 - 解析(Resolution):将符号引用(如类名、方法名)转为直接引用(内存地址)。
- 初始化(Initialization):执行
<clinit>()
方法(合并静态变量赋值与静态代码块,按顺序执行)。 - 使用(Using):实例化对象、调用方法。
- 卸载(Unloading):类不再被引用且类加载器被回收时,
Class
对象被卸载(极少发生)。
- 加载(Loading):通过类全限定名(如
垃圾回收机制
分代收集理论:
- 新生代(Young Generation):对象存活时间短(如方法内临时变量),约 90% 对象会被回收,频繁触发
Minor GC
(年轻代 GC)。- 结构:Eden 区(80%) + From Survivor 区(10%) + To Survivor 区(10%)。
- 算法:复制算法(将 Eden 和 From Survivor 存活对象复制到 To Survivor,清空原区域,无碎片)。
- 老年代(Old Generation):对象存活时间长(如缓存对象),触发
Full GC
(全局 GC)频率低。- 算法:标记 - 清除(标记存活对象,清除未标记对象,可能产生碎片)或标记 - 整理(标记后将存活对象压缩到一端,消除碎片)。
- 新生代(Young Generation):对象存活时间短(如方法内临时变量),约 90% 对象会被回收,频繁触发
GC 算法实现:
Serial GC
:单线程回收,STW(Stop The World)时间长,适合客户端应用(如桌面程序)。Parallel GC
:多线程回收,以吞吐量(用户线程运行时间 / 总时间)为目标,JDK 8 默认年轻代收集器。CMS GC
(Concurrent Mark Sweep):低延迟优先,步骤:初始标记(STW)→并发标记→重新标记(STW)→并发清除(仅初始和重新标记 STW),缺点:内存碎片多、CPU 消耗高(JDK 9 后废弃)。G1 GC
(Garbage-First):区域化分代式收集器(取代 CMS),将堆分为多个 Region,兼顾吞吐量和延迟,JDK 9 + 默认,适合大堆场景(如 8G 以上)。
JVM 调优经验
- 核心参数:
bash
-Xms2g -Xmx2g # 堆初始和最大大小(设为相同,避免动态扩容) -XX:NewRatio=2 # 老年代:新生代=2:1(默认) -XX:SurvivorRatio=8 # Eden:From:To=8:1:1 -XX:MaxMetaspaceSize=256m # 元空间上限(默认无上限) -XX:+UseG1GC # 使用G1垃圾回收器 -XX:MaxGCPauseMillis=200 # G1目标最大STW时间(毫秒) -XX:+HeapDumpOnOutOfMemoryError # OOM时生成堆转储文件 -XX:HeapDumpPath=/path/to/dump # 堆转储文件路径
- 调优场景:
Full GC
频繁:检查是否有大对象频繁进入老年代(如大集合未释放),增大老年代内存(-Xmx
)或调整晋升阈值(-XX:MaxTenuringThreshold
,默认 15)。- OOM(
Java heap space
):通过jmap -heap <pid>
分析堆使用,结合堆转储文件(jhat
或 MAT 工具)定位内存泄漏(如静态集合未清理)。 - 延迟过高:切换至 G1 或 ZGC(超低延迟),调小
-XX:MaxGCPauseMillis
。
- 核心参数:
4. 多线程
线程安全集合
ConcurrentHashMap
:线程安全的哈希表(见上文)。CopyOnWriteArrayList
:读写分离,写时复制新数组(add
/set
时),读无需锁,适合读多写少场景(如配置列表)。ConcurrentLinkedQueue
:无锁并发队列,基于 CAS 实现,高效(生产者 - 消费者模型)。BlockingQueue
:阻塞队列(ArrayBlockingQueue
、LinkedBlockingQueue
),支持put
(满时阻塞)和take
(空时阻塞),适合异步通信。
线程池
核心参数(
ThreadPoolExecutor
):java
new ThreadPoolExecutor( corePoolSize, // 核心线程数(长期存活,即使空闲) maximumPoolSize, // 最大线程数(核心 + 临时线程) keepAliveTime, // 临时线程空闲存活时间 unit, // 时间单位(如TimeUnit.SECONDS) workQueue, // 工作队列(存放等待执行的任务) threadFactory, // 线程工厂(自定义线程名、优先级等) handler // 拒绝策略(队列满且线程达最大时触发) )
参数设置策略:
- 核心线程数(corePoolSize):
- CPU 密集型任务(如计算):
CPU核心数 + 1
(减少线程切换,充分利用 CPU)。 - IO 密集型任务(如网络请求、文件读写):
CPU核心数 * 2
(线程多处于等待状态,需更多线程提高吞吐量)。
- CPU 密集型任务(如计算):
- 最大线程数(maximumPoolSize):通常为核心线程数的 2 倍,需结合队列容量(队列满时才创建临时线程)。
- 工作队列:
LinkedBlockingQueue
:无界队列(默认容量 Integer.MAX_VALUE),适合高吞吐但需控制内存(避免 OOM)。ArrayBlockingQueue
:有界队列,适合资源有限场景(需合理设置容量,避免队列过大导致内存溢出)。SynchronousQueue
:无缓冲队列(直接移交任务给线程),适合快速响应场景(配合maximumPoolSize
为 Integer.MAX_VALUE)。
- 拒绝策略:
AbortPolicy
:抛RejectedExecutionException
(默认)。CallerRunsPolicy
:由提交任务的线程执行(减缓提交速度,自带限流)。DiscardPolicy
:默默丢弃新任务。DiscardOldestPolicy
:丢弃队列中最旧的任务,尝试提交新任务。
- 核心线程数(corePoolSize):
使用示例:
java
// 构建线程池 ExecutorService executor = new ThreadPoolExecutor( 5, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy() ); // 提交任务 executor.submit(() -> System.out.println("Task running")); // 返回Future // 关闭线程池 executor.shutdown(); // 执行完现有任务后关闭,不再接受新任务
synchronized
锁升级
synchronized
通过锁升级机制优化性能(从低效到高效):- 无锁状态:对象刚创建,无线程竞争,Mark Word 存储对象哈希码。
- 偏向锁:同一线程多次获取锁,Mark Word 记录线程 ID,避免 CAS 操作(适用于单线程场景)。
- 轻量级锁:多线程交替获取锁,通过 CAS 竞争锁(自旋尝试),避免重量级锁开销(适用于短时间竞争)。
- 重量级锁:多线程并发竞争激烈,自旋失败后膨胀为重量级锁(依赖操作系统互斥量),线程阻塞等待(适用于长时间竞争)。
二、MySQL 数据库
1. 索引
底层结构(InnoDB)
采用B + 树,原因:- 磁盘友好:非叶子节点仅存索引键,叶子节点存储数据(聚集索引)或主键(二级索引),单节点存储更多键,减少 I/O 次数。
- 范围查询高效:叶子节点通过双向链表连接,支持快速范围扫描(如
WHERE id > 100 AND id < 200
)。 - 平衡性好:B + 树为平衡树,查询时间稳定在 O (log n)。
对比 B 树:B 树叶子节点和非叶子节点都存数据,单节点存储键数少,树高更高,I/O 效率低。
索引类型
- 聚集索引(主键索引):按主键排序,叶子节点存储完整数据行,InnoDB 表必存在(无主键则选唯一索引,否则隐式生成 6 字节 ROWID)。
- 二级索引(非主键索引):按索引列排序,叶子节点存储主键值,查询时需通过主键回表(到聚集索引查完整数据)。
- 联合索引:多列组合索引(如
(a,b,c)
),遵循最左前缀原则(查询条件含a
或a+b
或a+b+c
时生效)。
索引失效场景
- 未遵循最左前缀原则(如联合索引
(a,b,c)
,查询WHERE b=1
则失效)。 - 对索引列进行计算 / 函数操作(如
WHERE YEAR(create_time) = 2023
,create_time
索引失效)。 - 使用
!=
、<>
、NOT IN
(优化器可能放弃索引,选择全表扫描)。 - 隐式类型转换(如
varchar
列用数字查询:WHERE name = 123
,索引失效)。 OR
条件中存在非索引列(如WHERE a=1 OR b=2
,仅a
有索引则失效)。LIKE
以通配符开头(如WHERE name LIKE '%张'
,无法使用索引)。- 数据量极小(优化器认为全表扫描比走索引更快)。
IS NULL
/IS NOT NULL
(取决于字段是否允许为 NULL 及数据分布)。
- 未遵循最左前缀原则(如联合索引
索引设计原则
- 适合建索引:高频查询条件(
WHERE
)、排序 / 分组字段(ORDER BY
/GROUP BY
)、联合索引需满足最左前缀。 - 不适合建索引:低区分度字段(如性别、状态,基数低)、频繁更新字段(索引维护成本高)、表数据量极少(全表扫描更快)。
- 适合建索引:高频查询条件(
2. 事务
ACID 特性
- 原子性(Atomicity):事务要么全执行,要么全回滚。由
Undo Log
实现(记录数据修改前状态,回滚时恢复)。 - 一致性(Consistency):事务执行前后数据状态合法(如转账后总金额不变)。由原子性、隔离性、持久性共同保证。
- 隔离性(Isolation):多事务并发时,操作互不干扰。由锁(行锁、间隙锁)和 MVCC(多版本并发控制)实现。
- 持久性(Durability):事务提交后数据永久保存。由
Redo Log
实现(先写日志再刷盘,崩溃后重放日志恢复)。
- 原子性(Atomicity):事务要么全执行,要么全回滚。由
隔离级别
隔离级别 脏读(读未提交数据) 不可重复读(重读数据变) 幻读(新增数据被读到) 实现方式 读未提交(RU) 存在 存在 存在 无锁,直接读取最新数据 读已提交(RC) 不存在 存在 存在 MVCC(快照读,每次读新快照) 可重复读(RR) 不存在 不存在 不存在(InnoDB) MVCC + 间隙锁(防止新增数据) 串行化(Serial) 不存在 不存在 不存在 全表锁(事务按顺序执行) 幻读解决:InnoDB 在 RR 级别通过间隙锁(锁定索引范围,如
WHERE id > 10
会锁定 10 以上的间隙)防止其他事务插入新行,避免幻读。显式加锁SELECT ... FOR UPDATE
也可解决。
3. 日志
日志类型 | 作用 | 特点 |
---|---|---|
Binlog(二进制日志) | 记录所有写操作(DDL、DML),用于主从复制(从库同步主库日志)和数据恢复(mysqlbinlog 回放)。 |
逻辑日志(记录 SQL 语句或行变更),追加写入,可通过expire_logs_days 自动清理。 |
Redo Log(重做日志) | InnoDB 引擎日志,记录数据页修改,保证事务持久性(崩溃后重放日志恢复数据)。 | 物理日志(记录页地址和修改内容),循环写入(固定大小),有innodb_log_file_size 控制大小。 |
Undo Log(回滚日志) | InnoDB 引擎日志,记录数据修改前状态,用于事务回滚和 MVCC(提供历史版本)。 | 事务提交后可删除,存储在 undo 表空间。 |
Slow Query Log(慢查询日志) | 记录执行时间超过long_query_time 的 SQL,用于优化慢查询。 |
默认关闭,需手动开启(slow_query_log = ON )。 |
Error Log(错误日志) | 记录 MySQL 启动、运行、关闭过程中的错误信息,用于排查故障。 | 必开启,路径由log_error 指定。 |
4. 慢查询优化
定位慢查询
- 开启慢查询日志:
sql
SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 2; -- 阈值(秒) SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
- 使用
EXPLAIN
分析执行计划,关注:type
:访问类型(ALL
全表扫描,ref
/range
走索引)。key
:实际使用的索引(NULL
表示未走索引)。rows
:预估扫描行数(值越小越好)。Extra
:Using filesort
(文件排序,需优化)、Using index
(覆盖索引,优)。
- 开启慢查询日志:
优化措施
- 索引优化:添加缺失索引,修复失效索引(如避免函数操作索引列)。
- SQL 改写:拆分复杂查询、避免
SELECT *
(用覆盖索引)、用JOIN
代替子查询。 - 数据库调优:调整
innodb_buffer_pool_size
(建议设为内存 50%-70%)、分库分表(数据量超千万时)。
三、Redis 缓存
1. 数据结构
数据类型 | 底层结构(核心) | 特点 | 应用场景 |
---|---|---|---|
String | 简单动态字符串(SDS) | 二进制安全(可存文本、图片),支持INCR /DECR 自增自减 |
缓存(用户信息)、计数器(阅读量)、分布式 ID |
Hash | 哈希表(数组 + 链表) | 键值对集合(field-value),适合存储对象 | 存储用户信息(user:1 {name: "a", age: 20} ) |
List | 双向链表 + 压缩列表(元素少且小) | 有序,可重复,支持LPUSH /RPOP 两端操作 |
消息队列、最新列表(热搜前 10) |
Set | 哈希表 + 整数集合(元素为整数且少) | 无序,唯一元素,支持SINTER (交集)/SUNION (并集) |
标签(文章标签去重)、共同好友 |
ZSet | 跳跃表 + 哈希表 | 按score 排序,元素唯一,支持ZRANK (排名) |
排行榜(游戏积分)、延迟队列(按时间 score 排序) |
2. 持久化
持久化方式 | 原理 | 优点 | 缺点 |
---|---|---|---|
RDB | 定时生成内存快照(.rdb 文件),通过SAVE (阻塞)或BGSAVE (后台线程)触发。 |
恢复速度快(直接加载二进制文件)、文件小 | 可能丢失最后一次快照后的修改(如突发宕机)。 |
AOF | 记录所有写命令(追加到.aof 文件),通过appendfsync 控制同步策略(always /everysec /no )。 |
数据安全性高(everysec 最多丢 1 秒数据) |
文件大(命令冗余)、恢复速度慢(需重放命令)。 |
混合持久化(Redis 4.0+):AOF 文件开头为 RDB 快照,后续为增量命令,兼顾两者优点(aof-use-rdb-preamble yes
开启)。
3. 主从复制
流程:
- 从节点执行
SLAVEOF <master-ip> <port>
,建立连接。 - 主节点执行
BGSAVE
生成 RDB,发送给从节点(全量复制)。 - 从节点加载 RDB 后,主节点通过
repl_backlog_buffer
同步增量写命令(增量复制)。
- 从节点执行
问题及解决:
- 数据延迟:主从异步复制导致从库数据滞后,适合读多写少场景(写主读从)。
- 脑裂:网络分区时,从库被误认为主库,导致数据不一致。解决:
min-slaves-to-write = 1
(主库至少 1 个从库连接才允许写)。
4. 缓存问题(缓存三兄弟)
问题 | 原因 | 解决方案 |
---|---|---|
缓存穿透 | 查询不存在的数据(如id=-1 ),缓存和 DB 都无数据,请求直达 DB。 |
1. 布隆过滤器(提前过滤不存在的 key);2. 缓存空值(短期有效,如 5 分钟)。 |
缓存击穿 | 热点 key 过期瞬间,大量并发请求直达 DB。 | 1. 互斥锁(Redis SETNX ,只让一个线程更新缓存);2. 热点 key 永不过期(后台异步更新)。 |
缓存雪崩 | 大量 key 同时过期或缓存集群宕机,请求全压到 DB。 | 1. 随机过期时间(避免集中过期);2. 缓存集群(主从 + 哨兵);3. 服务降级 / 限流。 |
5. 常见用途
- 缓存:存储高频访问数据(如商品详情),减轻数据库压力(设置合理 TTL 避免雪崩)。
- 分布式锁:
SET key value NX PX 30000
(NX:不存在才设置,PX:30 秒过期),释放锁时验证 value(避免误删)。 - 排行榜:ZSET 的
ZADD
(更新分数)、ZREVRANGE
(获取 TopN)实现实时排名。 - 计数器:
INCR
/DECR
原子操作,适合阅读量、点赞数等。 - 消息队列:List 的
LPUSH
(生产者)和BRPOP
(消费者,阻塞弹出)实现简单队列。
6. 引入组件的考虑因素
- 必要性:是否解决核心问题(如缓存是否真能提升性能,避免过度设计)。
- 性能:吞吐量(QPS)、延迟(响应时间)是否满足业务需求(Redis 单机 QPS 可达 10 万 +)。
- 运维成本:部署、监控(
INFO
命令、Prometheus)、扩缩容难度(集群是否支持动态添加节点)。 - 社区支持:文档完善度、版本更新频率(Redis 社区活跃,问题解决快)。
- 数据一致性:与数据库的同步策略(Cache-Aside、Write-Through)是否可控。
7. 高可用方案
模式 | 原理 | 特点 | 适用场景 |
---|---|---|---|
主从复制 | 主节点写入,从节点同步数据并提供读服务(一主多从)。 | 部署简单,提升读吞吐量;主节点故障需手动切换。 | 读多写少,可用性要求不高。 |
哨兵(Sentinel) | 哨兵集群监控主从节点,主节点故障时自动选举新主节点(故障转移)。 | 自动故障转移,高可用;需至少 3 个哨兵节点(避免脑裂)。 | 生产环境基础高可用方案。 |
Cluster 集群 | 数据分片到 16384 个槽(Slot),每个节点负责部分槽,多主多从(每个主节点有从节点)。 | 水平扩展(动态添加节点)、高可用;支持跨节点操作(MGET 需指定槽)。 |
数据量大(超 10GB)、高并发场景。 |
8. Redis Cluster 的插槽(Slot)
- 作用:
- 数据分片:
CRC16(key) % 16384
计算 key 所属槽,每个节点负责部分槽(如 3 个主节点各负责~5461 个槽),实现数据分布式存储。 - 路由转发:客户端连接任一节点,若 key 不在当前节点负责的槽,节点返回
MOVED
指令指引至目标节点。 - 动态扩容:槽可在节点间迁移(
CLUSTER MIGRATE
),无需停机(迁移过程中双写保证数据一致性)。
- 数据分片:
四、Spring 框架
1. IOC(控制反转)
- 核心思想:将对象创建、依赖管理交给 Spring 容器,而非手动
new
对象,降低耦合。 - 依赖注入方式:
@Autowired
:按类型注入(默认),配合@Qualifier
按名称注入。@Resource
:按名称注入(默认),无名称时按类型注入(JDK 注解,非 Spring)。- 构造器注入:通过构造方法传递依赖(推荐,避免循环依赖问题)。
- Bean 初始化流程:
- 读取配置(XML / 注解)→ 2. 解析为
BeanDefinition
→ 3. 实例化(new
对象)→ 4. 依赖注入(填充属性)→ 5. 初始化(@PostConstruct
、InitializingBean
等)→ 6. 放入容器。
- 读取配置(XML / 注解)→ 2. 解析为
2. AOP(面向切面编程)
原理:基于动态代理,在不修改源码的情况下为方法添加额外功能(如日志、事务)。
- JDK 代理:目标类实现接口时,
Proxy.newProxyInstance()
生成代理类(实现同一接口)。 - CGLIB 代理:目标类无接口时,
Enhancer
生成子类代理(重写目标方法),无法代理final
类 / 方法。
- JDK 代理:目标类实现接口时,
核心概念:
- 切面(
@Aspect
):封装横切逻辑的类(如日志切面)。 - 切点(
@Pointcut
):定义拦截哪些方法(如execution(* com.xxx.service.*(..))
)。 - 通知(Advice):切点执行时的动作(
@Before
前置、@After
后置、@Around
环绕等)。
- 切面(
应用场景:日志记录、事务管理(
@Transactional
)、权限校验、性能监控。
3. Bean 生命周期
- 实例化:容器通过构造方法创建 Bean 对象(
new
)。 - 属性赋值:注入依赖(
@Autowired
/setter 方法)。 - 初始化:
- 执行
Aware
接口回调(BeanNameAware
、ApplicationContextAware
等)。 - 执行
@PostConstruct
注解方法(JSR-250 规范,初始化前)。 - 执行
InitializingBean.afterPropertiesSet()
方法(Spring 接口)。 - 执行自定义
init-method
(XML 配置或@Bean(initMethod)
)。
- 执行
- 使用:Bean 放入容器,供其他组件调用。
- 销毁:
- 执行
@PreDestroy
注解方法(JSR-250 规范,销毁前)。 - 执行
DisposableBean.destroy()
方法(Spring 接口)。 - 执行自定义
destroy-method
(XML 配置或@Bean(destroyMethod)
)。
- 执行
4. 循环依赖解决
场景:A 依赖 B,B 依赖 A。
Spring 通过三级缓存解决单例 Bean 循环依赖:
- 一级缓存(
singletonObjects
):存放完全初始化的 Bean。 - 二级缓存(
earlySingletonObjects
):存放半成品 Bean(已实例化未赋值)。 - 三级缓存(
singletonFactories
):存放 Bean 工厂(用于生成代理对象)。
流程:
- A 实例化后,放入三级缓存 → 2. A 需要注入 B,容器初始化 B → 3. B 需要注入 A,从三级缓存获取 A 的早期引用(若 A 需代理,通过工厂生成代理),放入二级缓存 → 4. B 初始化完成,注入 A,放入一级缓存 → 5. A 注入 B,完成初始化,放入一级缓存(移除二、三级缓存)。
五、消息队列(Kafka)
1. 消息不丢失保证
环节 | 措施 |
---|---|
生产者 | - acks=all :消息需被所有 ISR(同步副本)确认后才算发送成功。- 开启重试( retries=N ):发送失败自动重试。- 用 linger.ms 批量发送,减少网络开销。 |
Broker | - replication.factor≥3 :每个分区至少 3 个副本。- min.insync.replicas≥2 :ISR 中至少 2 个副本存活才允许写入。- 禁用 unclean.leader.election.enable (不允许非 ISR 副本成为 leader)。 |
消费者 | - 关闭自动提交(enable.auto.commit=false ),手动提交偏移量(commitSync() )。- 处理完消息后再提交,避免消费失败但已提交偏移量。 - 幂等处理:通过消息 ID 去重,防止重复消费。 |
六、设计模式
1. 单例模式(Singleton Pattern)
- 概念:保证一个类在整个应用中只有一个实例,并提供一个全局访问点。
- 核心思想:通过私有化构造方法,防止外部直接创建实例;在类内部自行创建唯一实例,并提供静态方法供外部获取。
- 适用场景:
- 全局配置类(如读取配置文件的工具类),确保配置信息一致。
- 数据库连接池,避免频繁创建和关闭连接带来的性能损耗。
- 日志工具类,保证日志输出的一致性和顺序性。
- 注意点:需考虑线程安全问题(如懒汉式需加锁),以及反射、序列化对单例的破坏。
2. 工厂模式(Factory Pattern)
包括简单工厂、工厂方法、抽象工厂三种形式:
- 简单工厂模式:
- 概念:由一个工厂类根据传入的参数,动态决定创建哪一种产品类的实例。
- 适用场景:产品种类较少且固定,如不同类型的日志记录器(文件日志、控制台日志)的创建。
- 工厂方法模式:
- 概念:定义一个创建产品的接口,由子类决定具体创建哪种产品,将产品创建延迟到子类。
- 适用场景:产品种类可能扩展,如不同数据库(MySQL、Oracle)的连接工厂,新增数据库时只需新增对应的工厂子类。
- 抽象工厂模式:
- 概念:提供一个接口,用于创建一系列相关或相互依赖的产品族,而无需指定具体类。
- 适用场景:需要创建多个产品族,且产品族内的产品相互关联,如不同操作系统(Windows、Mac)下的按钮、文本框等组件族的创建。
3. 观察者模式(Observer Pattern)
- 概念:定义对象间的一种一对多依赖关系,当一个对象(被观察者)状态发生改变时,所有依赖它的对象(观察者)会自动收到通知并更新。
- 核心思想:解耦被观察者和观察者,两者通过抽象接口交互,互不依赖具体实现。
- 适用场景:
- 事件监听机制(如 GUI 中的按钮点击事件,多个组件监听同一按钮)。
- 消息通知系统(如公众号推送,订阅者接收消息)。
- 状态变化同步(如股票价格更新,多个指标面板实时刷新)。
4. 装饰器模式(Decorator Pattern)
- 概念:动态地给一个对象添加额外的职责,同时不改变其原有的结构。相比继承,更灵活地扩展功能。
- 核心思想:通过创建装饰器类包裹原对象,装饰器实现与原对象相同的接口,并在调用原对象方法前后添加新功能。
- 适用场景:
- 功能需要动态扩展且可组合的场景,如 IO 流(BufferedInputStream 装饰 FileInputStream,增加缓冲功能)。
- 避免使用多层继承导致的类爆炸,如给咖啡添加不同配料(牛奶、糖),每种配料作为一个装饰器。
5. 适配器模式(Adapter Pattern)
- 概念:将一个类的接口转换成客户端期望的另一个接口,使原本因接口不兼容而无法一起工作的类能够协同工作。
- 核心思想:创建适配器类,实现目标接口,并持有被适配类的实例,在适配器方法中调用被适配类的对应方法。
- 适用场景:
- 集成第三方组件,其接口与本地系统接口不匹配时(如第三方支付接口适配本地支付服务)。
- 旧系统改造,需复用旧类但接口不符合新需求时。
- 分类:
- 类适配器:通过继承被适配类实现适配(单继承限制,较少使用)。
- 对象适配器:通过持有被适配类实例实现适配(更灵活,常用)。
6. 代理模式(Proxy Pattern)
- 概念:为其他对象提供一种代理,以控制对原对象的访问。代理对象在原对象和客户端之间起到中介作用。
- 核心思想:代理类与原对象实现同一接口,客户端通过代理类访问原对象,代理类可在调用原对象方法前后添加额外逻辑。
- 适用场景:
- 远程代理:为远程对象(如分布式服务)提供本地代理,屏蔽网络通信细节。
- 安全代理:控制对敏感对象的访问(如权限校验)。
- 延迟加载:在真正需要时才创建 heavy 资源(如大图片加载前的代理)。
- AOP(面向切面编程):如 Spring 的事务管理,通过代理在方法执行前后添加事务控制逻辑。
7. 模板方法模式(Template Method Pattern)
- 概念:定义一个操作中的算法骨架,将某些步骤延迟到子类中实现。子类可重写具体步骤,但不改变算法的整体结构。
- 核心思想:父类中定义模板方法(包含算法骨架),并将可变步骤声明为抽象方法,由子类实现。
- 适用场景:
- 多个子类有共同的操作流程,但部分步骤实现不同,如数据库访问(连接、执行 SQL、关闭连接的流程固定,SQL 语句和结果处理可变)。
- 框架设计中,定义核心流程,允许用户自定义部分逻辑(如 Servlet 的 doGet/doPost 方法,由框架控制调用时机)。
8. 策略模式(Strategy Pattern)
- 概念:定义一系列算法,将每个算法封装起来并使它们可相互替换,让算法的变化独立于使用算法的客户端。
- 核心思想:创建策略接口,不同算法实现该接口;客户端持有策略接口的引用,可动态切换不同的策略实现。
- 适用场景:
- 存在多种类似的算法,且需要根据场景动态选择时(如排序算法,可根据数据量选择冒泡、快排、归并等)。
- 避免使用多重条件判断(if-else/switch),如电商平台的折扣策略(新用户折扣、会员折扣、节日折扣等)。
9. 迭代器模式(Iterator Pattern)
- 概念:提供一种方法顺序访问聚合对象中的元素,而无需暴露聚合对象的内部结构。
- 核心思想:定义迭代器接口(包含 hasNext、next 等方法),聚合对象提供获取迭代器的方法,客户端通过迭代器遍历元素。
- 适用场景:
- 需统一遍历不同聚合结构(如数组、链表、集合)的场景,如 Java 中的 Iterator 接口,可遍历 List、Set 等不同集合。
- 避免聚合对象暴露内部实现,如自定义容器类,通过迭代器提供安全的遍历方式。
10. 命令模式(Command Pattern)
- 概念:将一个请求封装为一个对象,使请求的发送者和接收者解耦。发送者只需发送命令对象,无需知道接收者如何处理。
- 核心思想:创建命令接口(包含执行方法),具体命令类实现接口并持有接收者引用;发送者持有命令对象,通过调用命令的执行方法完成请求。
- 适用场景:
- 需记录、撤销、重做请求的场景(如文本编辑器的撤销操作,每个编辑命令可被保存和反向执行)。
- 事件驱动系统(如 GUI 中的按钮点击,按钮持有命令对象,点击时执行命令)。
- 批量任务处理(如任务队列,每个任务封装为命令,依次执行)。