📌 本文由 10 篇相关文章智能合并整理而成
Spring Boot 核心机制源码级分析:IoC 容器、AOP 代理与事务管理
title: “Spring Boot 核心机制源码级分析:IoC 容器、AOP 代理与事务管理的底层实现”
一、引言
Spring Boot 的”开箱即用”背后,是一套精巧的核心机制在支撑:IoC(控制反转)容器管理着所有 Bean 的生命周期,依赖注入框架织就了组件间的协作网络,AOP 代理在运行时透明地增强方法,而事务管理则借助 AOP 实现了声明式的事务边界控制。本文将逐一切入 Spring Boot 的这些核心机制,深入源码进行剖析。
二、IoC 容器与 Bean 生命周期
2.1 容器初始化流程
Spring 容器的初始化始于 ApplicationContext 的创建。以 AnnotationConfigApplicationContext 为例:
// 入口代码
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
// 此时容器已初始化完毕
}
}
内部经历了三大步骤:
1. 扫描(Scan)
└─ 找到所有 @Component 类 → BeanDefinition 注册到 BeanFactory
2. 推断构建方法
└─ 确定每个 Bean 使用哪个构造器
3. 实例化 + 初始化(生命周期回调)
flowchart TD
A[创建 ApplicationContext] --> B[注册配置类 BeanDefinition]
B --> C[调用 refresh 方法]
C --> D[invokeBeanFactoryPostProcessors]
D --> E[扫描包路径, 注册所有 BeanDefinition]
E --> F[registerBeanPostProcessors]
F --> G[finishBeanFactoryInitialization]
G --> H[实例化所有非懒加载单例 Bean]
D --> D1[BeanFactoryPostProcessor 处理 @Configuration]
E --> E1[{
'beanDefinitions': 所有类都被扫描为 BeanDefinition
}]
H --> H1[调用 getBean → doGetBean → createBean]
2.2 Bean 生命周期完整流程
flowchart LR
A[BeanDefinition] --> B[实例化 Instantiation]
B --> C[属性赋值 Populate]
C --> D[Aware 接口回调]
D --> E[BeanPostProcessor before]
E --> F[初始化方法 Init]
F --> G[BeanPostProcessor after]
G --> H[就绪 Ready]
H --> I[销毁 Destroy]
D --> D1[BeanNameAware / BeanFactoryAware<br/>ApplicationContextAware]
E --> E1[@PostConstruct / InitializingBean]
F --> F1[@Bean(initMethod)]
G --> G1[AOP 代理创建点]
I --> I1[@PreDestroy / DisposableBean]
2.3 三级缓存与循环依赖的解决
Spring 通过三级缓存解决构造器注入之外的循环依赖:
// DefaultSingletonBeanRegistry
public class DefaultSingletonBeanRegistry {
// 一级缓存:完整初始化的单例 Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:早期暴露的 Bean(还未完全初始化)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 三级缓存:ObjectFactory 延迟生成代理
private final Map<String, ObjectFactory>> singletonFactories = new HashMap<>(16);
}
| 级别 | 缓存名称 | 内容 | 用途 |
|---|---|---|---|
| 一级 | singletonObjects | 完全初始化好的 Bean | 最终成品 |
| 二级 | earlySingletonObjects | 实例化但未初始化的 Bean | 暴露半成品 |
| 三级 | singletonFactories | 生成代理的工厂 | 延迟创建 AOP 代理 |
循环依赖解决示例:
@Component
public class A {
@Autowired
private B b;
}
@Component
public class B {
@Autowired
private A a;
}
解决过程:
1. 创建 A:实例化 A → A 放入三级缓存 → 属性注入 B
2. 创建 B:实例化 B → B 放入三级缓存 → 属性注入 A
3. B 注入 A:从三级缓存取 A → 执行 ObjectFactory → 放入二级缓存
4. B 完成初始化:从二级缓存/或者finalAOP代理→put一级
5. A 完成初始化:从一级取B→注入→A完成→put一级
注意: 构造器注入无法解决循环依赖,因为构造器调用在实例化之前,此时三级缓存还未放入工厂。会导致 BeanCurrentlyInCreationException。
三、依赖注入实现原理
3.1 @Autowired 注入处理
AutowiredAnnotationBeanPostProcessor 负责处理 @Autowired 和 @Value 注解:
class AutowiredAnnotationBeanPostProcessor {
@Override
public PropertyValues postProcessProperties(
PropertyValues pvs, Object bean, String beanName) {
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass());
metadata.inject(bean, beanName, pvs);
return pvs;
}
}
注入方式对比:
| 注入方式 | 处理时机 | 实现类 | 优缺点 |
|---|---|---|---|
| @Autowired 字段 | BeanPostProcessor 阶段 | AutowiredAnnotationBeanPostProcessor | 简洁,不可测试 |
| setter 注入 | BeanPostProcessor 阶段 | AutowiredAnnotationBeanPostProcessor | 可选依赖 |
| 构造器注入 | 实例化阶段显式初始化参数 | AnnotationConfigUtils | 推荐,不可变 |
构造器注入的实现:
// Spring 遍历所有构造器,选最优的
@Component
public class UserService {
private final UserRepository userRepository;
// Spring 自动选这个构造器(唯一或最"肥"的)
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
内部通过 ConstructorResolver.autowireConstructor() 方法解析,按参数类型和 @Qualifier 查找匹配的 Bean。
四、AOP 代理机制
4.1 代理创建流程
Spring AOP 的入口是 AbstractAutoProxyCreator(一个 BeanPostProcessor),在每个 Bean 初始化之后判断是否生成代理:
public abstract class AbstractAutoProxyCreator {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof AopInfrastructureBean) {
return bean;
}
// 检查是否有 Advisor 匹配该 Bean
Advisor[] advisors = getAdvicesAndAdvisorsForBean(
bean.getClass(), beanName, null);
if (advisors != null) {
// 创建代理对象
return createProxy(bean.getClass(), beanName,
specificInterceptors, new SingletonTargetSource(bean));
}
return bean;
}
}
4.2 JDK 动态代理 vs CGLIB
| 特性 | JDK 动态代理 | CGLIB |
|---|---|---|
| 实现方式 | 基于接口,运行时生成 Proxy 类 | 基于子类,继承增强 |
| 要求 | 目标类必须实现接口 | 无要求 |
| 代理方法调用效率 | 反射 + Method.invoke | FastClass 机制(索引调用) |
| 无法代理的场景 | – | final 类、final 方法、static 方法 |
| Spring Boot 默认 | 2.0 之前 | 2.0 之后(需设置 proxyTargetClass=true) |
Spring Boot 2.0+ 默认使用 CGLIB(即使有接口),因为:
- 现代框架普遍基于类(
@Configuration类等),而不是接口- CGLIB 的 FastClass 机制比 JDK 反射更高效
- 统一代理方式可避免接口与实现类不一致导致的诡异 BUG
4.3 拦截器链执行
sequenceDiagram
participant Client as 调用方
participant Proxy as AOP 代理对象
participant Chain as 拦截器链
participant Target as 目标方法
Client->>Proxy: userService.findUser()
Proxy->>Chain: 调用拦截器链
Note over Chain: 第一个拦截器: @Around
Chain->>Chain: 第二个拦截器: @Before
Chain->>Target: proceed() 调用目标
Chain->>Chain: 第三个拦截器: @AfterReturning
Chain-->>Proxy: 返回结果
Proxy-->>Client: 结果
Note over Chain: 责任链模式
Note over Chain: 顺序: Around → Before → 目标 → After → AfterReturning
4.4 代理自调用失效问题
@Service
public class UserService {
@Transactional
public void methodA() {
methodB(); // 自调用:不会触发事务
}
@Transactional
public void methodB() {
// 数据库操作
}
}
原因: methodA() 调用 methodB() 时,是通过 this.methodB() 调用的,this 是目标对象而不是代理对象,因此不会触发事务增强。
解决方案:
// 1. 注入自身代理(Spring 5+ 可用 @Lazy 解决循环依赖)
@Autowired
@Lazy
private UserService self;
public void methodA() {
self.methodB(); // 通过代理调用
}
// 2. 提取到另一个 Service(推荐)
public void methodA() {
auditService.methodB(); // 独立 Bean
}
五、事务管理底层
5.1 事务代理的创建
@EnableTransactionManagement 会向容器中注册 BeanFactoryTransactionAttributeSourceAdvisor,加上 TransactionInterceptor(实现了 MethodInterceptor),共同组成一个事务 Advisor。
当某个 Bean 有 @Transactional 方法时,InfrastructureAdvisorAutoProxyCreator 创建代理,事务拦截器被织入:
public class TransactionInterceptor extends TransactionAspectSupport
implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 获取事务属性
TransactionAttribute txAttr = getTransactionAttributeSource()
.getTransactionAttribute(invocation.getMethod(), targetClass);
// 获取 PlatformTransactionManager
PlatformTransactionManager tm = getTransactionManager();
// 执行带事务的调用
return invokeWithinTransaction(invocation.getMethod(), targetClass,
invocation::proceed, txAttr, tm);
}
}
5.2 事务传播行为实现
声明式事务的传播行为(Propagation)是 Spring 区别于原生 JDBC 事务的核心能力:
| 传播行为 | 含义 | 实现逻辑 |
|---|---|---|
| REQUIRED(默认) | 支持当前事务,没有则新建 | doGetTransaction() → 有则加入 |
| REQUIRES_NEW | 必须新建事务,挂起当前事务 | 挂起当前事务,创建新 Connection |
| SUPPORTS | 有就用,没有就不开 | 简单判断是否已有事务 |
| MANDATORY | 必须要有事务,否则抛异常 | 抛 NoTransactionException |
| NOT_SUPPORTED | 必须以非事务方式执行 | 挂起当前事务 |
| NESTED | 嵌套事务(JDBC Savepoint) | 创建数据库级保存点 |
REQUIRES_NEW 的实现:
@Override
protected Object doBeginTransaction(
Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject =
(DataSourceTransactionObject) transaction;
Connection con = txObject.getConnectionHolder().getConnection();
// REQUIRES_NEW:挂起当前事务,获取新连接
if (txObject.hasTransaction()) {
suspend(txObject); // 保存当前事务状态
}
// 设置自动提交关闭、隔离级别等
con.setAutoCommit(false);
// ...
}
5.3 事务隔离级别的底层实现
flowchart TD
subgraph "隔离级别与问题"
READ_UNCOMMITTED -->|脏读+不可重复读+幻读| LOW[并发高, 一致性低]
READ_COMMITTED -->|不可重复读+幻读| MID[PG/默认]
REPEATABLE_READ -->|幻读| HIGH
SERIALIZABLE -->|无并发问题| HIGHEST[并发极低]
end
实现差异:
| 数据库 | 如何实现 REPEATABLE_READ |
|---|---|
| MySQL InnoDB | MVCC + Next-Key Lock(行锁 + Gap Lock) |
| PostgreSQL | MVCC(不会幻读,但允许更新冲突) |
| Oracle | 只支持 READ_COMMITTED 和 SERIALIZABLE |
六、总结
| 机制 | 核心组件 | 关键源码入口 | 常见陷阱 |
|---|---|---|---|
| IoC 容器 | DefaultListableBeanFactory | refresh() → finishBeanFactoryInitialization() |
构造器循环依赖 |
| DI 注入 | AutowiredAnnotationBeanPostProcessor | postProcessProperties() |
字段注入无法测试 |
| AOP 代理 | AbstractAutoProxyCreator | postProcessAfterInitialization() |
自调用失效 |
| 事务管理 | TransactionInterceptor | invokeWithinTransaction() |
自调用 + REQUIRES_NEW 误解 |
掌握 Spring Boot 的底层机制不仅仅是”看源码”,而是建立对框架行为模式的预期。当你理解了 BeanPostProcessor 的链式调用、AbstractAutoProxyCreator 的触发时机,以及事务 TransactionInterceptor 在代理链中的位置之后,绝大多数 Spring Boot 的”奇怪问题”都会变得清晰可预测。
MyBatis 源码解析:从配置文件到 SQL 执行的完整链路
一、引言:从 JDBC 到 MyBatis
Java 开发者早期的数据库操作记忆可以用一个词概括:重复性劳动。每次数据库操作都需要:获取连接 → 创建 Statement → 设置参数 → 执行 SQL → 处理 ResultSet 映射为对象 → 关闭连接。这只是最基本的 CRUD 操作,就已经需要十几行样板代码。
// JDBC 的原始写法——每个方法都要重复的样板代码
public class UserJdbcDao {
public User findById(Long id) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// 1. 获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db",
"user", "password");
// 2. 创建 PreparedStatement,设置参数
ps = conn.prepareStatement("SELECT * FROM user WHERE id = ?");
ps.setLong(1, id);
// 3. 执行 SQL
rs = ps.executeQuery();
// 4. 处理结果集,手动映射为对象
User user = null;
if (rs.next()) {
user = new User();
user.setId(rs.getLong("id"));
user.setName(rs.getString("name"));
user.setEmail(rs.getString("email"));
// 如果表有 20 列,这里就要写 20 行
}
return user;
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
// 5. 关闭资源(最容易忘记的一步)
try { if (rs != null) rs.close(); } catch (Exception e) {}
try { if (ps != null) ps.close(); } catch (Exception e) {}
try { if (conn != null) conn.close(); } catch (Exception e) {}
}
}
// 每个方法都要重复上述整个流程!
}
MyBatis 解决了这个问题,但不是简单地封装了 JDBC。它的核心价值在于将 SQL 的灵活性 与 ORM 的便捷性 结合在了一起。
flowchart LR
JDBC[纯 JDBC\n灵活但代码冗长] -->|MyBatis\n封装了这层| SEMI[SQL 写在 XML\n映射交给框架]
Hibernate[全自动 ORM\nSQL 由框架生成] -->|但 SQL 调优困难| SEMI
SEMI -->|MyBatis 的定位| M[半自动 ORM\nSQL 可控\n映射自动]
本文将从 MyBatis 的源码出发,深入拆解 MyBatis 是如何封装 JDBC 的——从配置加载、SQL 解析、参数绑定、结果集映射到核心执行器。
二、MyBatis 核心架构总览
2.1 整体分层
flowchart TD
subgraph 接口层
API[SqlSession\n核心 API]
API2[SqlSessionFactory\n工厂]
end
subgraph 核心处理层
C1[Configuration\n全局配置]
MAP[MapperRegistry\n映射器注册]
SQL[SqlSource\nSQL 语句封装]
PA[ParameterHandler\n参数处理器]
RS[ResultSetHandler\n结果集处理器]
STMT[StatementHandler\nStatement 处理器]
EXEC[Executor\n执行器]
end
subgraph 基础支持层
D1[数据源模块]
D2[事务管理]
D3[缓存模块\n一级/二级]
D4[TypeHandler\n类型处理器]
D5[日志模块]
D6[反射工具箱\nMetaObject]
end
subgraph JDBC
JDBC_PKG[java.sql.*]
end
API --> EXEC
EXEC --> STMT
STMT --> PA
STMT --> RS
EXEC --> D3
STMT --> JDBC_PKG
2.2 一次 SQL 查询的流转过程
// 一个典型的 MyBatis 查询
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.findById(1L);
// 内部流转:
// 1. mapper.findById(1L) → MapperProxy.invoke()
// 2. → MapperMethod.execute()
// 3. → SqlSession.selectOne()
// 4. → Executor.query()
// 5. → StatementHandler.query()
// 6. → ParameterHandler.setParameters()
// 7. → PreparedStatement.executeQuery()
// 8. → ResultSetHandler.handleResultSets()
// 9. → 返回 User 对象
三、配置加载——SqlSessionFactoryBuilder
3.1 XMLConfigBuilder——配置文件的解析
MyBatis 启动时,SqlSessionFactoryBuilder 使用 XMLConfigBuilder 解析配置文件:
public class XMLConfigBuilder extends BaseBuilder {
// 解析 MyBatis 全局配置文件的入口
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 从 XML 根节点 开始,按顺序解析子元素:
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
// 配置元素解析顺序(必须遵循这个顺序,否则报错)
private void parseConfiguration(XNode root) {
try {
// 1. 解析 (外部属性文件)
propertiesElement(root.evalNode("properties"));
// 2. 解析 (全局配置参数)
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 3. 解析
loadCustomVfs(settings);
loadCustomLogImpl(settings);
// 4. 解析 (类型别名)
typeAliasesElement(root.evalNode("typeAliases"));
// 5. 解析 (拦截器插件)
pluginElement(root.evalNode("plugins"));
// 6. 解析
objectFactoryElement(root.evalNode("objectFactory"));
// 7. 解析
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 8. 解析
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 9. 解析 应用到 Configuration
settingsElement(settings);
// 10. 解析 (数据源 + 事务管理器)
environmentsElement(root.evalNode("environments"));
// 11. 解析
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 12. 解析 (自定义类型处理器)
typeHandlerElement(root.evalNode("typeHandlers"));
// 13. 解析 (映射器文件)
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration.", e);
}
}
}
元素解析顺序的重要性: MyBatis 要求配置文件中各元素的书写顺序必须与 parseConfiguration 方法中的解析顺序一致。比如, 必须在 之前,因为 environments 可能引用 properties 中的变量。
四、Mapper 注册与代理
4.1 MapperRegistry——注册 Mapper 接口
public class MapperRegistry {
private final Configuration config;
// Mapper 接口 → MapperProxyFactory 的映射
private final Map<Class>, MapperProxyFactory>> knownMappers = new HashMap<>();
// 注册 Mapper 接口
public <T> void addMapper(Class<T> type) {
// 检查:必须是接口
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 创建 MapperProxyFactory
knownMappers.put(type, new MapperProxyFactory<>(type));
// 解析 Mapper 接口上的注解(@Options 等)
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(
type.getResourceAsStream(type.getName().replace('.', '/') + ".xml"),
config, type.getName(), config.getSqlFragments());
xmlMapperBuilder.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
}
4.2 MapperProxy——JDK 动态代理的实现
// 为什么 Mapper 接口不需要实现类?
// 答案:通过 JDK 动态代理
public class MapperProxy<T> implements InvocationHandler, Serializable {
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 如果是 Object 类的方法,直接调用
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 处理默认方法(JDK 8+)
if (method.isDefault()) {
return invokeDefaultMethod(proxy, method, args);
}
// 核心:通过缓存获取 MapperMethodInvoker
// 每个 MapperMethod 对应一个 SQL 操作
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
动态代理的组成:
flowchart LR
subgraph 应用层
M[UserMapper 接口] -->|getMapper 返回代理对象| MP[MapperProxy]
end
subgraph MyBatis 内部
MP -->|invoke 调起| MM[MapperMethod]
MM -->|execute| SS[SqlSession]
SS -->|selectOne / selectList| EX[Executor]
end
subgraph JDBC 层
EX --> ST[StatementHandler]
ST -->|PreparedStatement| DB[(Database)]
DB --> RS[ResultSet]
RS -->|handleResultSets| MM
MM -->|返回结果| MP
MP -->|返回| M
end
五、SQL 解析——从 XML 到 SqlSource
5.1 XMLStatementBuilder——解析 Mapper XML
public class XMLStatementBuilder extends BaseBuilder {
// 解析
public void parseStatementNode() {
// 提取 SQL 节点的各个属性
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
String parameterType = context.getStringAttribute("parameterType");
String resultType = context.getStringAttribute("resultType");
String resultMap = context.getStringAttribute("resultMap");
String flushCache = context.getStringAttribute("flushCache");
String useCache = context.getStringAttribute("useCache");
// SQL 语句解析(核心)
// 使用 XNodeParser 将包含 ${}、#{}、、 的 SQL 文本
// 解析为 SqlSource 对象
SqlSource sqlSource = langDriver.createSqlSource(configuration,
context, parameterTypeClass);
// 构建 MappedStatement(一个 MappedStatement 对应一个 Mapper 方法)
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(
configuration, id, sqlSource, sqlCommandType);
// ... 设置各种属性
}
}
5.2 动态 SQL 的解析——OGNL 的魔法
MyBatis 的动态 SQL 是其最强功能之一。、、 等标签的解析通过 OGNL(Object-Graph Navigation Language) 来实现:
// 动态 SQL 的处理节点
public class IfSqlNode implements SqlNode {
private final ExpressionEvaluator evaluator;
private final String test; // OGNL 表达式
private final SqlNode contents; // 子节点
public IfSqlNode(SqlNode contents, String test) {
this.test = test;
this.contents = contents;
this.evaluator = new ExpressionEvaluator();
}
@Override
public boolean apply(DynamicContext context) {
// 评估 OGNL 表达式
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context); // 条件满足 → 拼接 SQL
return true;
}
return false;
}
}
// 标签的实现
public class WhereSqlNode implements SqlNode {
// 自动处理 WHERE 关键字和 AND/OR 前缀
// 如果子节点以 AND 或 OR 开头,自动移除
@Override
public boolean apply(DynamicContext context) {
// 1. 让子节点拼接 SQL
contents.apply(context);
// 2. 检查拼接结果:开头不能是 AND 或 OR
// 3. 如果没有子节点有内容,就不加 WHERE
// 4. 否则在前面加上 WHERE
}
}
一个动态 SQL 的解析示例:
六、参数处理——ParameterHandler
6.1 参数绑定的过程
// 参数绑定的核心——将用户传入的参数设置到 PreparedStatement
public class DefaultParameterHandler implements ParameterHandler {
private final TypeHandlerRegistry typeHandlerRegistry;
private final MappedStatement mappedStatement;
private final Object parameterObject;
private final BoundSql boundSql;
@Override
public void setParameters(PreparedStatement ps) {
// 遍历 SQL 中的参数占位符
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
// 获取参数值
Object value;
if (parameterMapping.getMode() == ParameterMode.OUT) {
// 存储过程输出参数
value = null;
} else {
// 普通参数:通过 MetaObject 获取值
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(parameterMapping.getProperty());
}
// 获取类型处理器
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
// 设置参数到 PreparedStatement
typeHandler.setParameter(ps, i + 1, value, jdbcType);
}
}
}
}
6.2 TypeHandler——类型转换的核心
// TypeHandler 接口——各种类型转换的抽象
public interface TypeHandler<T> {
// Java → JDBC(写入数据库时)
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType)
throws SQLException;
// JDBC → Java(从数据库读取时,通过列名)
T getResult(ResultSet rs, String columnName) throws SQLException;
// JDBC → Java(通过列索引)
T getResult(ResultSet rs, int columnIndex) throws SQLException;
}
// 例如:StringTypeHandler
public class StringTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter,
JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter);
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getString(columnName);
}
}
// 自定义枚举 TypeHandler
public class GenderTypeHandler extends BaseTypeHandler<Gender> {
// 将 Gender 枚举转为数据库的 VARCHAR
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Gender parameter,
JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter.getCode());
}
@Override
public Gender getNullableResult(ResultSet rs, String columnName) throws SQLException {
return Gender.fromCode(rs.getString(columnName));
}
}
七、执行器(Executor)与缓存
7.1 执行器层级
flowchart TD
subgraph Executor 层级
BASE[BaseExecutor\n抽象基类]
SIMPLE[SimpleExecutor\n每次执行创建 Statement]
REUSE[ReuseExecutor\n缓存 Statement 复用]
BATCH[BatchExecutor\n批量执行]
CACHE[CachingExecutor\n二级缓存装饰器]
end
BASE --> SIMPLE
BASE --> REUSE
BASE --> BATCH
CACHE -->|装饰| BASE
style CACHE fill:#27ae60,color:#fff
7.2 一级缓存
public abstract class BaseExecutor implements Executor {
// 一级缓存:PerpetualCache(HashMap)
protected PerpetualCache localCache;
// 查询逻辑
@Override
public <E> List<E> query(MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler,
CacheKey key, BoundSql boundSql) throws SQLException {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 清空一级缓存的时机:flushCache=true 或执行了 update 操作
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// 从一级缓存中查找
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 缓存命中
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 缓存未命中 → 从数据库查询
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
// 延迟加载的处理
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// STATEMENT 级别:查询完成后立即清除缓存
clearLocalCache();
}
}
return list;
}
// 一级缓存的生命周期:
// 1. 同一个 SqlSession 中,同一个 SQL + 参数,第二次查询直接返回缓存
// 2. 执行 UPDATE/INSERT/DELETE 后,清除缓存
// 3. SqlSession close 或 commit 后,清除缓存
// 4. 跨 SqlSession 不共享(一级缓存是 SqlSession 级别的)
}
7.3 二级缓存
// CachingExecutor 通过装饰器模式为 Executor 添加二级缓存
public class CachingExecutor implements Executor {
private final Executor delegate; // 被装饰的 Executor
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
@Override
public <E> List<E> query(MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler,
CacheKey key, BoundSql boundSql) throws SQLException {
// 获取 MappedStatement 关联的二级缓存
Cache cache = ms.getCache();
if (cache != null) {
// 先尝试从二级缓存获取
List<E> list = tcm.getObject(cache, key);
if (list == null) {
// 二级缓存未命中 → 委派给底层 Executor 查询
list = delegate.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
// 查询结果放入二级缓存
tcm.putObject(cache, key, list);
}
return list;
}
// 没有二级缓存 → 直接查询
return delegate.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
}
| 缓存 | 级别 | 生命周期 | 默认是否开启 | 适用范围 |
|---|---|---|---|---|
| 一级缓存 | SqlSession 级别 | SqlSession 生命周期内 | 开启 | 同一个 SqlSession 内的重复查询 |
| 二级缓存 | Mapper 级别(跨 SqlSession) | 整个应用运行期间 | 关闭 | 跨 SqlSession 的重复查询 |
八、结果集映射——ResultSetHandler
8.1 从 ResultSet 到 Java 对象
public class DefaultResultSetHandler implements ResultSetHandler {
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
ResultSetWrapper rsw = new ResultSetWrapper(stmt.getResultSet(), configuration);
// 处理多结果集(存储过程等场景)
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
// 核心:将 ResultSet 当前行映射为 Java 对象
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
// 处理 ResultSet 结果
return collapseSingleResultList(multipleResults);
}
// 单行映射
private Object handleRowValueForResultMap(ResultSetWrapper rsw,
ResultMap resultMap,
CacheKey combinedKey,
String columnPrefix,
Object partialObject) throws SQLException {
// 创建默认映射对象(通过无参构造器)
Object resultObject = createResultObject(rsw, resultMap, constructor, columnPrefix);
if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
// 创建 MetaObject 用于设置属性
MetaObject metaObject = configuration.newMetaObject(resultObject);
// 遍历结果映射
List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
// 处理嵌套映射(association、collection)
if (propertyMapping.getNestedResultMapId() != null) {
handleRowValuesForNestedResultMap(rsw, resultMap, ...);
} else {
// 普通列 → 属性映射
String columnName = propertyMapping.getColumn();
TypeHandler> typeHandler = propertyMapping.getTypeHandler();
Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
metaObject.setValue(propertyMapping.getProperty(), value);
}
}
}
return resultObject;
}
}
九、插件体系——拦截器的实现
MyBatis 的插件机制允许你在 Executor、StatementHandler、ParameterHandler、ResultSetHandler 四个核心对象的方法调用过程中插入自定义逻辑。
// 插件本质上是通过 JDK 动态代理实现的拦截器
@Intercepts({
@Signature(type = Executor.class,
method = "update",
args = {MappedStatement.class, Object.class})
})
public class SqlMonitorPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.currentTimeMillis();
try {
// 执行原方法
return invocation.proceed();
} finally {
long duration = System.currentTimeMillis() - start;
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
String sqlId = ms.getId();
if (duration > 1000) {
log.warn("Slow SQL [{}] took {}ms", sqlId, duration);
}
}
}
@Override
public Object plugin(Object target) {
// 使用 Plugin.wrap 创建代理
return Plugin.wrap(target, this);
}
}
十、总结
-
MyBatis 的核心是 JDBC 的优雅封装。 它将连接管理、参数设置、结果映射这些重复劳动抽象化,通过配置和注解实现了 SQL 与 Java 代码的分离。
-
动态代理是 Mapper 接口无需实现类的秘密。 MapperProxy 通过 JDK 动态代理将接口方法调用转换为 SqlSession 操作,这是整个 MyBatis-Spring 集成的基础。
-
XML 解析是 MyBatis 的心脏。 从全局配置到 SQL 映射,XMLStatementBuilder 和 SqlSource 将动态 SQL 解析为可执行的 BoundSql,OGNL 表达式为动态 SQL 注入了灵活性。
-
缓存架构体现了”先读缓存,后读数据库”的思想。 一级缓存自动生效减少重复查询,二级缓存通过装饰器模式提供跨 SqlSession 的缓存共享,但需要谨慎使用(脏数据风险)。
-
TypeHandler 是类型转换的桥梁。 MyBatis 内置了丰富的类型处理器,同时支持自定义 TypeHandler 以应对特殊枚举或复杂类型的转换需求。
-
插件体系是 MyBatis 扩展性的体现。 拦截四个核心对象的方法调用,可以无侵入地实现 SQL 监控、分页、性能统计等功能。
如何设置 MySQL 事务隔离级别
如何设置 MySQL 事务隔离级别
概述
MySQL 提供了灵活的方式来设置事务隔离级别,支持全局级别、会话级别和下一个事务级别三种作用域。掌握这些设置方法,是日常开发和管理 MySQL 的基础技能。
隔离级别种类
MySQL 支持的四种隔离级别:
| 级别名称 | 数值代号 | 描述 |
|---|---|---|
| READ UNCOMMITTED | 0 | 读未提交 |
| READ COMMITTED | 1 | 读已提交 |
| REPEATABLE READ | 2 | 可重复读(默认) |
| SERIALIZABLE | 3 | 可串行化 |
设置语法
1. 全局级别(影响所有新连接)
-- 语法
SET GLOBAL TRANSACTION ISOLATION LEVEL 级别名称;
-- 或
SET @@global.tx_isolation = '级别名称';
-- MySQL 8.0+ 新系统变量
SET @@global.transaction_isolation = '级别名称';
-- 示例
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET @@global.tx_isolation = 'READ-COMMITTED';
作用范围:设置后,新建立的连接会使用该级别。已存在的连接不受影响。
2. 会话级别(影响当前连接)
-- 语法
SET SESSION TRANSACTION ISOLATION LEVEL 级别名称;
-- 或
SET @@session.tx_isolation = '级别名称';
-- 示例
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET @@session.tx_isolation = 'REPEATABLE-READ';
作用范围:仅当前会话有效,断开连接后失效。
3. 下一个事务级别(仅影响下一个事务)
-- 语法
SET TRANSACTION ISOLATION LEVEL 级别名称;
-- 示例
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
作用范围:仅影响当前会话中的下一个事务。执行完该事务后,恢复为会话级别的隔离级别。
查看当前隔离级别
查看会话级别
SELECT @@session.tx_isolation;
-- 或(MySQL 8.0 推荐)
SELECT @@session.transaction_isolation;
-- 结果示例
-- +-------------------------------+
-- | @@session.tx_isolation |
-- +-------------------------------+
-- | REPEATABLE-READ |
-- +-------------------------------+
查看全局级别
SELECT @@global.tx_isolation;
-- 或
SELECT @@global.transaction_isolation;
通过配置文件持久化设置
my.cnf / my.ini
[mysqld]
# 设置默认隔离级别
transaction-isolation = READ-COMMITTED
# 可选值:
# READ-UNCOMMITTED
# READ-COMMITTED
# REPEATABLE-READ(默认)
# SERIALIZABLE
设置后需要重启 MySQL 服务才能生效:
# 重启 MySQL
systemctl restart mysqld
MySQL 8.0 的变化
系统变量更名
MySQL 8.0 移除了旧的 tx_isolation 变量,改用新名:
-- MySQL 5.x 兼容写法
SET @@session.tx_isolation = 'READ-COMMITTED';
SELECT @@session.tx_isolation;
-- MySQL 8.0 推荐写法
SET @@session.transaction_isolation = 'READ-COMMITTED';
SELECT @@session.transaction_isolation;
新增特性
MySQL 8.0 支持在 SET TRANSACTION 时指定作用域更加明确:
-- 这些在 8.0 中仍有效
SET PERSIST transaction_isolation = 'READ-COMMITTED'; -- 持久化到数据目录 + 运行时
SET PERSIST_ONLY transaction_isolation = 'READ-COMMITTED'; -- 仅持久化,下次重启生效
实践建议
1. 开发环境
-- 在应用连接池初始化时设置
-- Java (JDBC):
-- jdbc:mysql://localhost:3306/db?transactionIsolation=READ_COMMITTED
-- Python (SQLAlchemy):
-- engine = create_engine('mysql://...', isolation_level="READ_COMMITTED")
2. 性能调优
-- 如果业务可以接受,推荐降级到 READ COMMITTED
-- 可以减少间隙锁带来的锁冲突
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
3. 临时调试
-- 排查问题时临时提升隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- 执行怀疑有并发问题的查询
-- ... 执行完切回正常级别
面试要点
- 设置方式:全局(GLOBAL)、会话(SESSION)、下一个事务(无前缀)
- 生效范围:全局不影响已有连接;会话不影响当前正在执行的事务
- MySQL 8.0 变化:
tx_isolation→transaction_isolation - 配置持久化:my.cnf 或 SET PERSIST
- 应用端配置:连接池或 JDBC URL 中配置
四种事务隔离级别
四种事务隔离级别
什么是隔离级别
隔离级别定义了一个事务对另一个事务的可见程度。MySQL InnoDB 支持四种隔离级别,从低到高:
| 级别 | 名称 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|---|
| 0 | READ UNCOMMITTED | ✅ 可能 | ✅ 可能 | ✅ 可能 |
| 1 | READ COMMITTED | ❌ 不会 | ✅ 可能 | ✅ 可能 |
| 2 | REPEATABLE READ | ❌ 不会 | ❌ 不会 | ✅ 可能(InnoDB 解决) |
| 3 | SERIALIZABLE | ❌ 不会 | ❌ 不会 | ❌ 不会 |
✅ = 可能发生 ❌ = 不会发生
READ UNCOMMITTED(读未提交)
行为
事务可以读到其他事务未提交的数据。
-- 设置隔离级别
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
-- 事务 A
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 未提交
-- 事务 B
-- 可以读取 A 未提交的数据!
SELECT balance FROM accounts WHERE id = 1;
-- 返回:修改后的值(比如 400)
脏读
事务 B 读到了事务 A 尚未提交的修改。如果事务 A 随后 ROLLBACK,事务 B 读到的就是”脏数据”。
脏读的后果:
事务 A 扣了 100 元(未提交)
事务 B 读到余额 400 元
事务 A 回滚 → 余额回到 500 元
事务 B 基于 400 元做决策 → 业务不一致!
使用场景
几乎不用。唯一场景可能是"我只需要一个大概值"的监控查询。
但即便如此,也不推荐。
READ COMMITTED(读已提交)
行为
只能读到已提交的数据,解决了脏读问题。
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 事务 A
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 未提交
-- 事务 B
SELECT balance FROM accounts WHERE id = 1;
-- 返回:旧值 500(读不到未提交的修改)
-- 事务 A 提交
COMMIT;
-- 事务 B 再次查询
SELECT balance FROM accounts WHERE id = 1;
-- 返回:新值 400(可以读到已提交的修改)
不可重复读问题
同一个事务中,两次相同查询可能返回不同结果:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1;
-- 第一次:500
-- 其他事务此时提交了修改
-- UPDATE accounts SET balance = 400 WHERE id = 1; COMMIT;
SELECT balance FROM accounts WHERE id = 1;
-- 第二次:400(同个事务中值变了!)
COMMIT;
使用场景
- 很多数据库的默认级别(如 Oracle、PostgreSQL)
- 适合:对一致性要求不是极端严格,但需要避免脏读
- 不适合:一个事务需要多次读取同一数据并做业务判断
REPEATABLE READ(可重复读)
行为
MySQL 的默认隔离级别。同一个事务中,第一次读取后,后续相同读取始终看到同样的结果。
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1;
-- 第一次:500
-- 即使其他事务修改并提交
-- UPDATE accounts SET balance = 400 WHERE id = 1; COMMIT;
SELECT balance FROM accounts WHERE id = 1;
-- 第二次:500(不变!)
SELECT balance FROM accounts WHERE id = 1;
-- 第三次:500(始终不变)
COMMIT;
幻读问题
理论上 REPEATABLE READ 存在幻读问题:同一个事务中,两次相同条件的范围查询返回不同行数。
-- 理论上的幻读
-- 事务 A
SELECT * FROM orders WHERE amount > 100;
-- 返回:10 行
-- 其他事务插入了一行 amount=200 并提交
SELECT * FROM orders WHERE amount > 100;
-- 理论上应该返回 11 行(多了 1 行 "幻影")
-- 但 InnoDB 的 REPEATABLE READ 不会!
-- 因为 InnoDB 用间隙锁 + MVCC 解决了幻读
InnoDB 用 Next-Key Lock(临键锁) 在 REPEATABLE READ 级别下实际上解决了幻读问题。所以 MySQL 的 RR 级别比标准 SQL 的 RR 更强。
使用场景
-- 适合场景:需要事务内多次读取数据一致
-- 比如:报表生成、数据导出无需每次都重新查
-- MySQL 的默认级别
-- 大多数应用可以放心使用
SERIALIZABLE(可串行化)
行为
最强的隔离级别,所有事务串行执行,完全杜绝并发问题。
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
-- 普通的 SELECT 也会加锁
SELECT * FROM accounts WHERE id = 1;
-- 自动转为 SELECT ... LOCK IN SHARE MODE
-- 其他事务无法修改 id=1 的数据
-- 直到事务提交,锁才释放
COMMIT;
性能代价
SERIALIZABLE 对所有读操作自动加 S 锁:
- 读读:不冲突
- 读写:互斥!
- 性能下降明显(并发度极低)
几乎所有场景都不推荐,除非:
- 数据一致性高于一切
- 并发极低或没有
- 需要调试/测试时
隔离级别的设置
-- 查看当前隔离级别
SELECT @@transaction_isolation;
-- MySQL 8.0:REPEATABLE READ
-- 设置全局
SET GLOBAL transaction_isolation = 'READ-COMMITTED';
-- 设置当前会话
SET SESSION transaction_isolation = 'READ-COMMITTED';
-- 设置下一个事务
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- 配置文件(永久)
-- [mysqld]
-- transaction-isolation = READ-COMMITTED
隔离级别的实践建议
业务场景 推荐隔离级别
──────────────────────────────────────
大多数 Web 应用 REPEATABLE READ(MySQL 默认)
高并发、数据一致性要求一般 READ COMMITTED
数据导出、长事务报表查询 REPEATABLE READ
银行/金融核心系统 SERIALIZABLE(或 RR+显式锁)
几乎不会用 READ UNCOMMITTED
面试要点
- 四种隔离级别(从低到高):RU → RC → RR → Serializable
- 三个并发问题:脏读(RU)、不可重复读(RC)、幻读(RR 基本解决)
- MySQL 默认:REPEATABLE READ(与 Oracle/PostgreSQL 不同,后两者默认 RC)
- MySQL 的 RR 更强:通过间隙锁解决了幻读,实际上接近 Serializable
- 「读已提交 vs 可重复读」的核心区别:Read View 的创建时机不同
- RU 和 Serializable 很少用:一个极端不安全,一个极端慢
一句话总结:隔离级别是并发与一致的交易——RU 放弃了隔离换取性能,SERIALIZABLE 放弃性能换取绝对隔离;MySQL 的默认 RR 用 MVCC 和间隙锁达到了”读不阻塞写、写不阻塞读、还能防幻读”的较好平衡。
Spring核心特性详解:IoC控制反转与AOP面向切面编程
Spring核心特性详解:IoC控制反转与AOP面向切面编程
一、定义
Spring 框架的两大核心特性:
- IoC(Inversion of Control,控制反转):将对象的创建和依赖关系的管理交给 Spring 容器,由”程序员主动创建”变为”容器自动注入”
- AOP(Aspect Oriented Programming,面向切面编程):通过预编译和运行期动态代理实现程序功能的统一维护,将横切关注点与业务逻辑分离
二、IoC(控制反转)原理
传统方式 vs IoC 方式
flowchart TD
subgraph 传统方式
T1["UserService"] --> T2["new UserDao()"]
T2 --> T3["new JdbcTemplate()"]
T3 --> T4["new DataSource()"]
T1 -.->|"反向依赖
主动创建所有依赖"| T4
end
subgraph IoC方式
I1["UserService
@Autowired"] -- "请求" --> I2["IoC容器"]
I2 --> I3["UserDao 实例"]
I3 --> I4["JdbcTemplate 实例"]
I4 --> I5["DataSource 实例"]
I2 -.->|"正转向
容器自动注入"| I1
end
// ❌ 传统方式:对象自己创建依赖(紧耦合)
public class UserService {
private UserDao userDao = new UserDao(); // 硬编码创建
public User findUser(Long id) {
return userDao.findById(id);
}
}
// ✅ IoC 方式:容器注入依赖(松耦合)
public class UserService {
private final UserDao userDao; // 依赖由外部注入
public UserService(UserDao userDao) {
this.userDao = userDao;
}
public User findUser(Long id) {
return userDao.findById(id);
}
}
IoC 的核心优势
| 优势 | 说明 |
|---|---|
| 解耦 | 调用者不再关心被调用者的创建细节 |
| 可测试性 | 方便 Mock 替换,单元测试更简单 |
| 灵活性 | 可以在不修改代码的情况下更换实现 |
| 生命周期管理 | 容器自动管理 Bean 的创建、初始化、销毁 |
三、DI(依赖注入)三种方式
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
// 1️⃣ 字段注入(Field Injection)— ❌ 不推荐
@Autowired
private OrderDao orderDao;
// 2️⃣ 构造器注入(Constructor Injection)— ✅ 推荐
private final PaymentService paymentService;
private final InventoryService inventoryService;
@Autowired
public OrderService(PaymentService paymentService,
InventoryService inventoryService) {
this.paymentService = paymentService;
this.inventoryService = inventoryService;
}
// 3️⃣ Setter 注入(Setter Injection)— ⚠️ 可选注入时使用
private DiscountService discountService;
@Autowired
public void setDiscountService(DiscountService discountService) {
this.discountService = discountService;
}
}
三种注入方式对比
flowchart TD
SUBJ["选择注入方式"] --> Q1{"字段是否需要可变?"}
Q1 -->|"否(推荐)"| CON["构造器注入
✅ 推荐
final 不可变
保证不为null
方便测试"]
Q1 -->|"是"| Q2{"是否存在循环依赖?"}
Q2 -->|"是"| SET["Setter注入
可解决循环依赖
(但应重构避免)"]
Q2 -->|"否"| CON
SET -.->|"不推荐"| FIELD["字段注入
@Autowired on field
代码最简洁
但无法测试"]
四、AOP(面向切面编程)原理
AOP 核心概念
flowchart TD
subgraph 业务逻辑
A["方法A"] -->|"执行"| B["方法B"]
B --> C["方法C"]
end
subgraph 横切关注点-AOP
LOG["切面: 日志记录"]
TX["切面: 事务管理"]
SEC["切面: 权限校验"]
end
LOG -.->|"@Before"| A
TX -.->|"@Around"| B
SEC -.->|"@Before"| B
LOG -.->|"@AfterReturning"| C
| 概念 | 含义 | 类比 |
|---|---|---|
| Aspect(切面) | 横切关注点的模块化 | 日志模块 |
| Join Point(连接点) | 程序执行中的某个点 | 每个方法调用、异常等 |
| Pointcut(切入点) | 匹配连接点的表达式 | “哪些方法需要日志” |
| Advice(通知) | 在切入点执行的动作 | “打印日志这个动作” |
| Target(目标对象) | 被代理的对象 | UserService 实例 |
| Proxy(代理) | 生成的代理对象 | JDK 动态代理 / CGLIB 代理 |
| Weaving(织入) | 将切面应用到目标对象的过程 | 编译期/类加载期/运行期 |
五种通知类型
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
// 定义切入点(可复用)
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
// 1. @Before:方法执行前
@Before("serviceMethods()")
public void beforeAdvice() {
System.out.println("【前置通知】方法开始执行");
}
// 2. @AfterReturning:方法正常返回后(可获取返回值)
@AfterReturning(value = "serviceMethods()", returning = "result")
public void afterReturningAdvice(Object result) {
System.out.println("【返回通知】返回结果: " + result);
}
// 3. @AfterThrowing:方法抛出异常后
@AfterThrowing(value = "serviceMethods()", throwing = "ex")
public void afterThrowingAdvice(Throwable ex) {
System.out.println("【异常通知】异常: " + ex.getMessage());
}
// 4. @After:方法结束后(finally 语义,不管是否异常)
@After("serviceMethods()")
public void afterAdvice() {
System.out.println("【后置通知】方法执行结束(finally)");
}
// 5. @Around:环绕通知(最强大,可控制方法执行)
@Around("serviceMethods()")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("【环绕前置】方法: " + pjp.getSignature().getName());
Object result = pjp.proceed(); // 执行目标方法
long elapsed = System.currentTimeMillis() - start;
System.out.println("【环绕后置】耗时: " + elapsed + "ms");
return result;
}
}
五、AOP 的典型应用场景
| 场景 | 实现方式 | 常见注解 |
|---|---|---|
| 事务管理 | @Transactional 底层是 AOP | @Transactional |
| 日志记录 | @Before / @Around 记录方法调用 | @Before("execution(*)") |
| 权限校验 | @Before 校验用户权限 | @PreAuthorize, @Secured |
| 性能监控 | @Around 统计执行时间 | 自定义注解 |
| 缓存处理 | @Cacheable 等实现方法返回值缓存 | @Cacheable, @CacheEvict |
| 异常处理 | @AfterThrowing 统一异常处理 | 自定义注解 |
六、Spring 整体架构
flowchart TD
subgraph Spring Framework
CORE["Core Container
核心容器"]
AOP["AOP + Aspects
切面编程"]
DATA["Data Access
数据访问"]
WEB["Web
Web 层"]
TEST["Test
测试"]
end
subgraph Core Container
BEANS["Beans
BeanFactory"]
CORE_CORE["Core
IoC容器"]
CONTEXT["Context
ApplicationContext"]
SPEL["SpEL
表达式语言"]
end
subgraph Data Access
JDBC["JDBC
JdbcTemplate"]
ORM["ORM
JPA/MyBatis"]
TX["Transactions
事务管理"]
end
subgraph Web
WS["Web MVC
Spring MVC"]
WSOCKET["WebSocket"]
end
CORE --> AOP
CORE --> DATA
CORE --> WEB
CORE --> TEST
七、面试常见问题
Q1:IoC 和 DI 的关系是什么?
IoC 是一种设计思想——控制反转,即”别找我,我来找你”。DI(依赖注入)是 IoC 的具体实现方式。Spring 通过 DI(构造器注入、Setter 注入、字段注入)来实现 IoC 的设计思想。
Q2:AOP 有哪些实现方式?
① 编译期:AspectJ 修改字节码(编译时织入);② 类加载期:Load-time Weaving(加载时织入);③ 运行期:Spring AOP(JDK 动态代理 / CGLIB)。Spring AOP 默认使用运行期动态代理。
Q3:Spring AOP 和 AspectJ 的区别?
Spring AOP 基于动态代理(运行期),只支持方法级别的连接点;AspectJ 可以织入构造器、字段访问、静态初始化等,功能更强大但配置更复杂。Spring AOP 通常与 AspectJ 的注解配合使用。
Q4:为什么 Spring 推荐构造器注入?
① 保证依赖不可变(final 关键字);② 保证依赖不为空(NullPointerException 风险低);③ 方便单元测试(不需要反射或 Spring 容器);④ 可以避免部分循环依赖问题。
Q5:@Autowired 和 @Resource 的区别?
– @Autowired:Spring 注解,默认按类型注入
– @Resource:JSR-250 注解,默认按名称注入(name 属性指定)
>
IoC原理详解:控制反转与依赖注入的深入分析
IoC原理详解:控制反转与依赖注入的深入分析
一、定义
IoC(Inversion of Control,控制反转) 是一种设计思想,将对象的创建权和依赖关系的管理权从程序代码中转移到容器中。DI(Dependency Injection,依赖注入) 是 IoC 的具体实现方式。
好莱坞原则(Hollywood Principle):“别打电话给我,我会打给你。” ——对象不需要主动查找或创建依赖,容器会将依赖注入进来。
二、传统方式 vs IoC 方式
flowchart LR
subgraph 传统方式:正向控制
A["UserService"] -->|"主动创建"| B["UserDao"]
B -->|"主动创建"| C["DataSource"]
A -.->|"高耦合
修改需改代码"| C
end
subgraph IoC方式:控制反转
D["UserService"] -->|"申请依赖"| E["IoC容器"]
E -->|"管理并注入"| F["UserDao
(由容器创建)"]
E -->|"管理并注入"| G["DataSource
(由容器创建)"]
E -.->|"松耦合
配置化"| D
end
代码对比
// ❌ 传统方式 — 主动创建依赖
public class OrderService {
private OrderDao orderDao = new OrderDao(); // 硬编码
private PaymentService paymentService = new PaymentService(); // 硬编码
public void createOrder(Order order) {
orderDao.save(order);
paymentService.pay(order.getAmount());
}
}
// ✅ IoC 方式 — 由容器注入
@Service
public class OrderService {
private final OrderDao orderDao;
private final PaymentService paymentService;
// 构造器注入:依赖由 IoC 容器自动提供
public OrderService(OrderDao orderDao, PaymentService paymentService) {
this.orderDao = orderDao;
this.paymentService = paymentService;
}
public void createOrder(Order order) {
orderDao.save(order);
paymentService.pay(order.getAmount());
}
}
三、IoC 容器的核心接口
BeanFactory vs ApplicationContext
flowchart TD
subgraph IoC容器体系
BF["BeanFactory
顶级接口
延迟加载"]
AC["ApplicationContext
BeanFactory的子接口
预加载"]
end
BF -->|"功能:
getBean()
isSingleton()"| CORE["核心IoC功能"]
AC -->|"额外功能:
国际化(MessageSource)
事件发布
AOP集成
资源访问"| EXT["高级容器功能"]
subgraph 常用实现
XML["ClassPathXmlApplicationContext
XML配置"]
ANNOT["AnnotationConfigApplicationContext
注解配置"]
WEB["WebApplicationContext
Web应用"]
end
XML --> AC
ANNOT --> AC
WEB --> AC
| 对比维度 | BeanFactory | ApplicationContext |
|---|---|---|
| Bean 加载 | 懒加载(使用时才创建) | 预加载(启动时创建所有单例 Bean) |
| 国际化 | ❌ 不支持 | ✅ 支持 |
| 事件发布 | ❌ 不支持 | ✅ 支持 |
| AOP 集成 | 手动集成 | 自动集成 |
| 应用场景 | 资源受限环境(移动/嵌入式) | 企业应用(绝大多数场景) |
四、依赖注入的 3 种方式
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class PaymentService {
// 1️⃣ 字段注入(Field Injection)— 不推荐
@Autowired
private PaymentGateway paymentGateway;
// 2️⃣ 构造器注入(Constructor Injection)— ✅ 推荐
private final TransactionRepository transactionRepository;
private final PolicyService policyService;
public PaymentService(TransactionRepository transactionRepository,
PolicyService policyService) {
this.transactionRepository = transactionRepository;
this.policyService = policyService;
}
// 3️⃣ Setter 注入(Setter Injection)— 可选注入
private NotificationService notificationService;
@Autowired
public void setNotificationService(NotificationService notificationService) {
this.notificationService = notificationService;
}
}
| 注入方式 | 可靠性 | 可测试性 | 不可变性 | 循环依赖处理 |
|---|---|---|---|---|
| 构造器注入 | ✅ 对象创建后依赖可靠 | ✅ 无需 Spring | ✅ final 支持 | ❌ 无法解决 |
| Setter 注入 | ⚠️ 依赖可选 | ✅ 易替换 | ❌ 可修改 | ✅ 可解决 |
| 字段注入 | ❌ 依赖不明确 | ❌ 需反射/容器 | ❌ 可修改 | ❌ 不推荐 |
五、IoC 容器的工作流程
sequenceDiagram
participant APP as 应用程序
participant CTX as IoC容器<br>ApplicationContext
participant REG as BeanDefinitionRegistry
participant FACT as BeanFactory
participant BEAN as Bean实例
APP->>CTX: 初始化容器<br>new AnnotationConfigApplicationContext(...)
CTX->>CTX: 扫描包/解析配置类/XML
CTX->>REG: 注册 BeanDefinition
REG->>FACT: 开始创建 Bean
Note over FACT: 单例 Bean 预实例化
FACT->>BEAN: 实例化(反射创建对象)
BEAN->>BEAN: 依赖注入(populateBean)
BEAN->>BEAN: 初始化(InitializingBean/init-method)
BEAN-->>CTX: Bean 就绪
CTX-->>APP: 容器初始化完成
APP->>CTX: getBean("userService")
CTX-->>APP: 返回 UserService Bean
六、@Component 和 @Bean 的区别
// 方式一:@Component + 类扫描
@Component
public class MyService {
public void doSomething() { }
}
// 方式二:@Bean + 配置类
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyService(); // 方法体可包含复杂逻辑
}
}
| 对比维度 | @Component | @Bean |
|---|---|---|
| 使用位置 | 类上 | 方法上 |
| 控制力 | 较低(Spring 控制创建) | 较高(方法体控制创建) |
| 适用场景 | 自己编写的类 | 第三方库、复杂初始化 |
| 数量 | 类上有多个注解 | 一个方法创建一个 Bean |
| 扫描方式 | 组件扫描 | 配置类加载 |
七、常用注解
| 注解 | 用途 | 等效于 |
|---|---|---|
@Component |
通用组件 | |
@Service |
业务层 | @Component 语义化 |
@Repository |
数据访问层 | DAO 异常转换 |
@Controller |
Web 层 | Spring MVC 控制器 |
@Autowired |
依赖注入 | byType 注入 |
@Qualifier |
指定 Bean 名称 | byName 注入 |
@Value |
注入配置值 | ${property} |
@Scope |
指定作用域 | singleton/prototype |
八、面试常见问题
Q1:IoC 和 DI 是一回事吗?
不是。IoC 是设计思想(控制反转:谁控制谁的问题),DI 是实现手段(谁注入给谁的问题)。IoC 可以有多种实现方式:DI、DL(Dependency Lookup)、Service Locator 等。Spring 通过 DI 实现了 IoC 思想。
Q2:Spring 如何解决循环依赖?
Spring 使用三级缓存(Three-level Cache)解决构造器以外的循环依赖:
1. 一级缓存:singletonObjects — 完全初始化的单例 Bean
2. 二级缓存:earlySingletonObjects — 提前暴露的半成品 Bean(已实例化但未完成属性填充和初始化)
3. 三级缓存:singletonFactories — 创建 Bean 的工厂(用于 AOP 场景)
Setter 注入支持循环依赖;构造器注入不支持循环依赖。
Q3:为什么推荐构造器注入?
① Bean 创建时即获得所有依赖,保证运行时依赖一定不为 null;② 依赖可用 final 修饰,保证不可变;③ 单元测试时不需要 Spring 容器,直接 new 即可;④ 可发现循环依赖问题(启动即报错,而非运行时)。
Q4:ApplicationContext 的预加载有什么缺点?
启动时间较长(所有单例 Bean 在启动时创建);占用更多内存(部分可能不常用的 Bean 也被提前创建)。但优点是提前发现配置错误和运行时响应更快。
>
SpringMVC 执行流程:DispatcherServlet / HandlerMapping / HandlerAdapter 完整解析
SpringMVC 执行流程:DispatcherServlet / HandlerMapping / HandlerAdapter 完整解析
一、定义
Spring MVC 基于前端控制器模式(Front Controller Pattern),所有请求通过中央控制器(DispatcherServlet)统一分发,再委派给具体的处理器处理。理解其完整执行流程是掌握 Spring MVC 的核心。
二、核心组件总览
| 组件 | 说明 | 默认实现 |
|---|---|---|
| DispatcherServlet | 前端控制器,统一接收请求并分发 | FrameworkServlet |
| HandlerMapping | 处理器映射器,根据请求找到对应 Handler | RequestMappingHandlerMapping |
| HandlerAdapter | 处理器适配器,调用具体 Handler | RequestMappingHandlerAdapter |
| Handler(Controller) | 处理器,处理具体业务逻辑 | @Controller/@RestController |
| ModelAndView | 封装模型数据和视图信息 | — |
| ViewResolver | 视图解析器,将逻辑视图名解析为具体视图 | InternalResourceViewResolver |
| View | 视图,渲染响应结果 | JstlView, ThymeleafView |
| HandlerExceptionResolver | 异常解析器 | DefaultHandlerExceptionResolver |
三、Spring MVC 完整执行流程
sequenceDiagram
participant C as 客户端
participant DS as DispatcherServlet
participant HM as HandlerMapping
participant HI as HandlerInterceptor
participant HA as HandlerAdapter
participant CT as Controller
participant VR as ViewResolver
participant V as View
C->>DS: 发送请求
DS->>HM: 1. 查找Handler
HM->>DS: 返回HandlerExecutionChain
DS->>HA: 2. 获取HandlerAdapter
HA->>DS: 返回HandlerAdapter
HA->>HI: 3. preHandle 拦截器前置处理
HI-->>HA: 返回true继续
HA->>CT: 4. 调用Controller方法
CT->>CT: 执行业务逻辑
CT->>HA: 返回ModelAndView/响应
HA->>HI: 5. postHandle 拦截器后置处理
DS->>VR: 6. 视图解析
VR->>DS: 返回View对象
DS->>V: 7. 视图渲染
V->>C: 响应HTML/JSON
DS->>HI: 8. afterCompletion 完成处理
四、详细步骤说明
步骤 1:DispatcherServlet 接收请求
// Spring Boot 自动配置 DispatcherServlet,以 / 拦截所有请求
// 传统 web.xml 配置:
<servlet>
<servlet-name>dispatcherservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>dispatcherservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
步骤 2:HandlerMapping 查找处理器
// RequestMappingHandlerMapping 通过 @RequestMapping 匹配
@RestController
public class UserController {
@GetMapping("/api/users/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
}
步骤 3:HandlerInterceptor 前置处理
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
response.setStatus(401);
return false; // 拦截请求
}
return true; // 放行
}
}
步骤 4:HandlerAdapter 执行处理器
HandlerAdapter 的核心工作包括参数解析和返回值处理:
// 参数解析器(ArgumentResolver)处理:
// @PathVariable("id") → uriTemplateVariables.get("id")
// @RequestBody → HttpMessageConverter.read()
// @RequestParam → request.getParameter("name")
// @ModelAttribute → 对象属性绑定
// 返回值处理器(ReturnValueHandler)处理:
// @ResponseBody → RequestResponseBodyMethodProcessor → JSON
// ModelAndView → 视图解析渲染
// String → 逻辑视图名
步骤 5:Controller 执行业务逻辑
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
User saved = userService.save(user);
return ResponseEntity.status(HttpStatus.CREATED).body(saved);
}
}
步骤 6:视图解析(传统 MVC)
@Controller
public class PageController {
@GetMapping("/user/{id}")
public String userDetail(@PathVariable Long id, Model model) {
model.addAttribute("user", userService.findById(id));
return "user/detail"; // 逻辑视图名 → ViewResolver 解析
}
}
// ViewResolver 配置:
// prefix: /WEB-INF/views/
// suffix: .jsp
// 解析为:/WEB-INF/views/user/detail.jsp
步骤 7:响应输出
// REST 风格:@ResponseBody → HttpMessageConverter
// @ResponseBody → RequestResponseBodyMethodProcessor
// → 遍历 HttpMessageConverter
// → MappingJackson2HttpMessageConverter 将对象转为 JSON
// → 写入 Response
五、异常处理流程
flowchart TD
A[Controller抛出异常] --> B{HandlerExceptionResolver链}
B --> C[@ExceptionHandler匹配?]
C -->|是| D[自定义异常处理]
C -->|否| E[DefaultHandlerExceptionResolver
处理标准异常]
E --> F[ResponseStatusExceptionResolver
处理@ResponseStatus]
F --> G[SimpleMappingExceptionResolver
映射到错误视图]
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
return ResponseEntity.notFound().build();
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ResponseEntity<ErrorResponse> handleGeneric(Exception ex) {
return ResponseEntity.internalServerError().body(
new ErrorResponse("服务器内部错误"));
}
}
六、要点总结
| 阶段 | 组件 | 职责 |
|---|---|---|
| 请求入口 | DispatcherServlet | 统一接收并分发请求 |
| 路由匹配 | HandlerMapping | 根据 URL 找 Handler |
| 拦截过滤 | HandlerInterceptor | 前置/后置/完成处理 |
| 方法调用 | HandlerAdapter | 参数解析 + 方法执行 + 返回值处理 |
| 视图解析 | ViewResolver | 逻辑视图名 → 物理视图 |
| 异常处理 | HandlerExceptionResolver | 统一异常处理 |
七、面试常见问题
Q1:DispatcherServlet 在 Spring Boot 中怎么配置的?
Spring Boot 通过 WebMvcAutoConfiguration 自动配置 DispatcherServlet,以 / 拦截所有请求。不需要 web.xml。
Q2:HandlerMapping 和 HandlerAdapter 的区别?
HandlerMapping 负责找到能处理请求的 Handler(根据 URL 映射),HandlerAdapter 负责调用 Handler(适配不同调用方式,处理参数解析和返回值)。
Q3:Spring MVC 如何处理 @ResponseBody?
RequestMappingHandlerAdapter 检测方法是否有 @ResponseBody 注解,使用 RequestResponseBodyMethodProcessor 通过 HttpMessageConverter 将返回值转为 JSON/XML 写入响应。
Q4:Interceptor 和 Filter 的区别?
Filter 是 Servlet 规范的一部分,在 DispatcherServlet 之前执行;Interceptor 是 Spring MVC 组件,在 Handler 执行前后执行。Filter 作用范围更广,Interceptor 可以访问 Spring 上下文。
相关文章:Spring Boot 自动配置原理 | @SpringBootApplication 注解详解 | AOP 原理与动态代理
Spring 事务传播机制:七种传播行为详解与最佳实践
Spring 事务传播机制:七种传播行为详解与最佳实践
一、定义
事务传播机制(Transaction Propagation)定义了当多个事务方法互相调用时,事务如何在这两个方法之间传播。Spring 通过 @Transactional(propagation=...) 来指定传播行为,它解决的是事务边界的问题——即多个声明了事务的方法嵌套调用时,是共享一个事务还是各自独立事务。
二、核心原理分析
Spring 事务传播机制基于 AOP 代理实现。当调用带有 @Transactional 的方法时,Spring 通过代理对象拦截方法调用,根据 Propagation 配置决定事务的创建、挂起或加入策略。
flowchart TD
A[客户端调用] --> B{是否存在事务?}
B -->|否| C[REQUIRED/REQUIRES_NEW/NESTED]
C --> D[创建新事务]
B -->|是| E{传播行为?}
E -->|REQUIRED| F[加入当前事务]
E -->|REQUIRES_NEW| G[挂起当前事务<br>创建新事务]
E -->|NESTED| H[创建Savepoint<br>嵌套事务]
E -->|SUPPORTS| I[加入当前事务]
E -->|MANDATORY| J[加入当前事务]
E -->|NOT_SUPPORTED| K[挂起当前事务<br>非事务执行]
E -->|NEVER| L[抛出异常]
B -->|否| M{传播行为?}
M -->|MANDATORY| N[抛出异常<br>IllegalTransactionStateException]
M -->|NEVER| O[非事务方式执行]
M -->|SUPPORTS/NOT_SUPPORTED| P[非事务方式执行]
三、七种传播行为详解
1. REQUIRED(默认传播行为)
定义:如果当前存在事务,则加入该事务;如果没有,则创建新事务。这是最常用的传播行为。
@Service
public class OrderService {
@Autowired
private AccountService accountService;
@Transactional(propagation = Propagation.REQUIRED) // 默认值,可省略
public void createOrderAndPay(Order order) {
orderDao.save(order);
accountService.deduct(order.getAmount()); // 加入同一个事务
}
}
@Service
public class AccountService {
@Transactional(propagation = Propagation.REQUIRED)
public void deduct(BigDecimal amount) {
accountDao.deduct(amount);
}
}
效果:createOrderAndPay 和 deduct 在同一个事务中,任何一方抛异常都会导致全部回滚。
2. REQUIRES_NEW
定义:总是创建一个新事务。如果当前存在事务,则将当前事务挂起,等新事务执行完毕后再恢复。
@Service
public class OrderService {
@Autowired
private LogService logService;
@Transactional
public void createOrder() {
orderDao.save(order);
logService.recordLog("创建订单"); // 独立事务,即使外部回滚也不受影响
}
}
@Service
public class LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void recordLog(String message) {
logDao.save(new Log(message)); // 完全独立的事务
}
}
适用场景:日志记录、审计、异步记录操作历史——即使主事务回滚,日志也必须持久化保存。
3. NESTED
定义:如果当前存在事务,则在嵌套事务中执行(使用 JDBC Savepoint);如果没有,则按 REQUIRED 执行。
@Service
public class BatchService {
@Autowired
private ItemService itemService;
@Transactional
public void processBatch(List<Item> items) {
for (Item item : items) {
try {
itemService.processItem(item); // 每个 item 独立 Savepoint
} catch (Exception e) {
// 回滚单个 item,不影响 batch 中的其他 item
log.error("Item failed: " + item.getId());
}
}
}
}
@Service
public class ItemService {
@Transactional(propagation = Propagation.NESTED)
public void processItem(Item item) {
// 如果抛异常,只回滚到当前 Savepoint
}
}
4. SUPPORTS
支持当前事务,如果没有则以非事务方式执行。适用于查询方法——有事务就参与,没有也不强求。
@Transactional(propagation = Propagation.SUPPORTS)
public void queryData() {
// 有事务则参与,没有则以非事务方式执行
}
5. MANDATORY
必须在已有事务中调用,否则抛出 IllegalTransactionStateException。
@Transactional(propagation = Propagation.MANDATORY)
public void updateData() {
// 必须在已有事务中调用,否则抛异常
}
6. NOT_SUPPORTED
以非事务方式执行,如果当前存在事务则挂起。
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void executeNonTransactional() {
// 以非事务方式执行,如果有事务则挂起
}
7. NEVER
如果存在事务则抛出异常。
@Transactional(propagation = Propagation.NEVER)
public void execute() {
// 如果存在事务则抛异常
}
四、REQUIRES_NEW vs NESTED 要点总结
| 对比维度 | REQUIRES_NEW | NESTED |
|---|---|---|
| 原理 | 暂停当前事务,开启全新独立事务 | 基于 Savepoint,回滚到 Savepoint |
| 事务独立性 | 完全独立(互不影响) | 部分独立(外层可控制内层) |
| 提交时机 | 内层事务独立提交 | 内层事务跟随外层提交 |
| 性能 | 开销大(两阶段提交) | 开销小(Savepoint 机制) |
| JDBC 支持 | 所有数据库都支持 | 需要 JDBC 3.0 Savepoint 支持 |
| 内层回滚影响 | 不影响外层 | 仅回滚到 Savepoint |
五、使用注意事项
自调用问题——事务传播不生效
Spring 事务基于 AOP 代理,自调用时 this.method() 不经过代理,@Transactional 注解不生效:
@Service
public class OrderService {
@Transactional
public void outerMethod() {
innerMethod(); // ❌ 自调用,内部事务注解不生效
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void innerMethod() {
// REQUIRES_NEW 不会生效,会加入 outerMethod 的事务
}
}
解决方案:注入自身代理或拆分到不同的 Service。
六、面试常见问题
Q1:REQUIRED 和 REQUIRES_NEW 的区别?
REQUIRED 加入当前事务(共用一个),REQUIRES_NEW 挂起当前事务并创建全新独立事务。前者内层回滚整体回滚,后者内层回滚完全不影响外层。
Q2:REQUIRES_NEW 和 NESTED 的区别?
REQUIRES_NEW 是完全独立的事务(两阶段提交),NESTED 是基于 Savepoint 的嵌套事务。NESTED 的内层回滚只会回滚到 Savepoint,外层可以选择提交或回滚。
Q3:为什么自调用时 @Transactional 不生效?
AOP 代理只能在从外部通过代理对象调用时才起作用。类内部的 this.method() 调用直接绕过代理,事务注解不会被处理。
Q4:NESTED 需要什么条件?
需要 JDBC 3.0 的 Savepoint 支持。大多数主流数据库都支持,但某些数据库可能不支持。
七、传播机制选择指南
| 场景 | 推荐传播行为 |
|---|---|
| 大部分业务操作 | REQUIRED |
| 日志、审计、异步记录 | REQUIRES_NEW |
| 批量处理,单个失败不影响整体 | NESTED |
| 只读查询 | SUPPORTS 或 REQUIRED |
| 必须带事务调用的方法 | MANDATORY |
| 明确不允许在事务中执行 | NEVER |
相关文章:Spring 事务隔离级别详解 | Spring AOP 原理与动态代理 | Spring Bean 生命周期
MyBatis 源码解析——从 JDBC 到 ORM 的封装艺术
一、引言:从 JDBC 到 MyBatis
Java 开发者早期的数据库操作记忆可以用一个词概括:重复性劳动。每次数据库操作都需要:获取连接 → 创建 Statement → 设置参数 → 执行 SQL → 处理 ResultSet 映射为对象 → 关闭连接。这只是最基本的 CRUD 操作,就已经需要十几行样板代码。
// JDBC 的原始写法——每个方法都要重复的样板代码
public class UserJdbcDao {
public User findById(Long id) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// 1. 获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db",
"user", "password");
// 2. 创建 PreparedStatement,设置参数
ps = conn.prepareStatement("SELECT * FROM user WHERE id = ?");
ps.setLong(1, id);
// 3. 执行 SQL
rs = ps.executeQuery();
// 4. 处理结果集,手动映射为对象
User user = null;
if (rs.next()) {
user = new User();
user.setId(rs.getLong("id"));
user.setName(rs.getString("name"));
user.setEmail(rs.getString("email"));
// 如果表有 20 列,这里就要写 20 行
}
return user;
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
// 5. 关闭资源(最容易忘记的一步)
try { if (rs != null) rs.close(); } catch (Exception e) {}
try { if (ps != null) ps.close(); } catch (Exception e) {}
try { if (conn != null) conn.close(); } catch (Exception e) {}
}
}
// 每个方法都要重复上述整个流程!
}
MyBatis 解决了这个问题,但不是简单地封装了 JDBC。它的核心价值在于将 SQL 的灵活性 与 ORM 的便捷性 结合在了一起。
flowchart LR
JDBC[纯 JDBC\n灵活但代码冗长] -->|MyBatis\n封装了这层| SEMI[SQL 写在 XML\n映射交给框架]
Hibernate[全自动 ORM\nSQL 由框架生成] -->|但 SQL 调优困难| SEMI
SEMI -->|MyBatis 的定位| M[半自动 ORM\nSQL 可控\n映射自动]
本文将从 MyBatis 的源码出发,深入拆解 MyBatis 是如何封装 JDBC 的——从配置加载、SQL 解析、参数绑定、结果集映射到核心执行器。
二、MyBatis 核心架构总览
2.1 整体分层
flowchart TD
subgraph 接口层
API[SqlSession\n核心 API]
API2[SqlSessionFactory\n工厂]
end
subgraph 核心处理层
C1[Configuration\n全局配置]
MAP[MapperRegistry\n映射器注册]
SQL[SqlSource\nSQL 语句封装]
PA[ParameterHandler\n参数处理器]
RS[ResultSetHandler\n结果集处理器]
STMT[StatementHandler\nStatement 处理器]
EXEC[Executor\n执行器]
end
subgraph 基础支持层
D1[数据源模块]
D2[事务管理]
D3[缓存模块\n一级/二级]
D4[TypeHandler\n类型处理器]
D5[日志模块]
D6[反射工具箱\nMetaObject]
end
subgraph JDBC
JDBC_PKG[java.sql.*]
end
API --> EXEC
EXEC --> STMT
STMT --> PA
STMT --> RS
EXEC --> D3
STMT --> JDBC_PKG
2.2 一次 SQL 查询的流转过程
// 一个典型的 MyBatis 查询
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.findById(1L);
// 内部流转:
// 1. mapper.findById(1L) → MapperProxy.invoke()
// 2. → MapperMethod.execute()
// 3. → SqlSession.selectOne()
// 4. → Executor.query()
// 5. → StatementHandler.query()
// 6. → ParameterHandler.setParameters()
// 7. → PreparedStatement.executeQuery()
// 8. → ResultSetHandler.handleResultSets()
// 9. → 返回 User 对象
三、配置加载——SqlSessionFactoryBuilder
3.1 XMLConfigBuilder——配置文件的解析
MyBatis 启动时,SqlSessionFactoryBuilder 使用 XMLConfigBuilder 解析配置文件:
public class XMLConfigBuilder extends BaseBuilder {
// 解析 MyBatis 全局配置文件的入口
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 从 XML 根节点 开始,按顺序解析子元素:
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
// 配置元素解析顺序(必须遵循这个顺序,否则报错)
private void parseConfiguration(XNode root) {
try {
// 1. 解析 (外部属性文件)
propertiesElement(root.evalNode("properties"));
// 2. 解析 (全局配置参数)
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 3. 解析
loadCustomVfs(settings);
loadCustomLogImpl(settings);
// 4. 解析 (类型别名)
typeAliasesElement(root.evalNode("typeAliases"));
// 5. 解析 (拦截器插件)
pluginElement(root.evalNode("plugins"));
// 6. 解析
objectFactoryElement(root.evalNode("objectFactory"));
// 7. 解析
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 8. 解析
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 9. 解析 应用到 Configuration
settingsElement(settings);
// 10. 解析 (数据源 + 事务管理器)
environmentsElement(root.evalNode("environments"));
// 11. 解析
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 12. 解析 (自定义类型处理器)
typeHandlerElement(root.evalNode("typeHandlers"));
// 13. 解析 (映射器文件)
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration.", e);
}
}
}
元素解析顺序的重要性: MyBatis 要求配置文件中各元素的书写顺序必须与 parseConfiguration 方法中的解析顺序一致。比如, 必须在 之前,因为 environments 可能引用 properties 中的变量。
四、Mapper 注册与代理
4.1 MapperRegistry——注册 Mapper 接口
public class MapperRegistry {
private final Configuration config;
// Mapper 接口 → MapperProxyFactory 的映射
private final Map<Class>, MapperProxyFactory>> knownMappers = new HashMap<>();
// 注册 Mapper 接口
public <T> void addMapper(Class<T> type) {
// 检查:必须是接口
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 创建 MapperProxyFactory
knownMappers.put(type, new MapperProxyFactory<>(type));
// 解析 Mapper 接口上的注解(@Options 等)
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(
type.getResourceAsStream(type.getName().replace('.', '/') + ".xml"),
config, type.getName(), config.getSqlFragments());
xmlMapperBuilder.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
}
4.2 MapperProxy——JDK 动态代理的实现
// 为什么 Mapper 接口不需要实现类?
// 答案:通过 JDK 动态代理
public class MapperProxy<T> implements InvocationHandler, Serializable {
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 如果是 Object 类的方法,直接调用
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 处理默认方法(JDK 8+)
if (method.isDefault()) {
return invokeDefaultMethod(proxy, method, args);
}
// 核心:通过缓存获取 MapperMethodInvoker
// 每个 MapperMethod 对应一个 SQL 操作
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
动态代理的组成:
flowchart LR
subgraph 应用层
M[UserMapper 接口] -->|getMapper 返回代理对象| MP[MapperProxy]
end
subgraph MyBatis 内部
MP -->|invoke 调起| MM[MapperMethod]
MM -->|execute| SS[SqlSession]
SS -->|selectOne / selectList| EX[Executor]
end
subgraph JDBC 层
EX --> ST[StatementHandler]
ST -->|PreparedStatement| DB[(Database)]
DB --> RS[ResultSet]
RS -->|handleResultSets| MM
MM -->|返回结果| MP
MP -->|返回| M
end
五、SQL 解析——从 XML 到 SqlSource
5.1 XMLStatementBuilder——解析 Mapper XML
public class XMLStatementBuilder extends BaseBuilder {
// 解析
public void parseStatementNode() {
// 提取 SQL 节点的各个属性
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
String parameterType = context.getStringAttribute("parameterType");
String resultType = context.getStringAttribute("resultType");
String resultMap = context.getStringAttribute("resultMap");
String flushCache = context.getStringAttribute("flushCache");
String useCache = context.getStringAttribute("useCache");
// SQL 语句解析(核心)
// 使用 XNodeParser 将包含 ${}、#{}、、 的 SQL 文本
// 解析为 SqlSource 对象
SqlSource sqlSource = langDriver.createSqlSource(configuration,
context, parameterTypeClass);
// 构建 MappedStatement(一个 MappedStatement 对应一个 Mapper 方法)
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(
configuration, id, sqlSource, sqlCommandType);
// ... 设置各种属性
}
}
5.2 动态 SQL 的解析——OGNL 的魔法
MyBatis 的动态 SQL 是其最强功能之一。、、 等标签的解析通过 OGNL(Object-Graph Navigation Language) 来实现:
// 动态 SQL 的处理节点
public class IfSqlNode implements SqlNode {
private final ExpressionEvaluator evaluator;
private final String test; // OGNL 表达式
private final SqlNode contents; // 子节点
public IfSqlNode(SqlNode contents, String test) {
this.test = test;
this.contents = contents;
this.evaluator = new ExpressionEvaluator();
}
@Override
public boolean apply(DynamicContext context) {
// 评估 OGNL 表达式
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context); // 条件满足 → 拼接 SQL
return true;
}
return false;
}
}
// 标签的实现
public class WhereSqlNode implements SqlNode {
// 自动处理 WHERE 关键字和 AND/OR 前缀
// 如果子节点以 AND 或 OR 开头,自动移除
@Override
public boolean apply(DynamicContext context) {
// 1. 让子节点拼接 SQL
contents.apply(context);
// 2. 检查拼接结果:开头不能是 AND 或 OR
// 3. 如果没有子节点有内容,就不加 WHERE
// 4. 否则在前面加上 WHERE
}
}
一个动态 SQL 的解析示例:
六、参数处理——ParameterHandler
6.1 参数绑定的过程
// 参数绑定的核心——将用户传入的参数设置到 PreparedStatement
public class DefaultParameterHandler implements ParameterHandler {
private final TypeHandlerRegistry typeHandlerRegistry;
private final MappedStatement mappedStatement;
private final Object parameterObject;
private final BoundSql boundSql;
@Override
public void setParameters(PreparedStatement ps) {
// 遍历 SQL 中的参数占位符
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
// 获取参数值
Object value;
if (parameterMapping.getMode() == ParameterMode.OUT) {
// 存储过程输出参数
value = null;
} else {
// 普通参数:通过 MetaObject 获取值
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(parameterMapping.getProperty());
}
// 获取类型处理器
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
// 设置参数到 PreparedStatement
typeHandler.setParameter(ps, i + 1, value, jdbcType);
}
}
}
}
6.2 TypeHandler——类型转换的核心
// TypeHandler 接口——各种类型转换的抽象
public interface TypeHandler<T> {
// Java → JDBC(写入数据库时)
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType)
throws SQLException;
// JDBC → Java(从数据库读取时,通过列名)
T getResult(ResultSet rs, String columnName) throws SQLException;
// JDBC → Java(通过列索引)
T getResult(ResultSet rs, int columnIndex) throws SQLException;
}
// 例如:StringTypeHandler
public class StringTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter,
JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter);
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getString(columnName);
}
}
// 自定义枚举 TypeHandler
public class GenderTypeHandler extends BaseTypeHandler<Gender> {
// 将 Gender 枚举转为数据库的 VARCHAR
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Gender parameter,
JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter.getCode());
}
@Override
public Gender getNullableResult(ResultSet rs, String columnName) throws SQLException {
return Gender.fromCode(rs.getString(columnName));
}
}
七、执行器(Executor)与缓存
7.1 执行器层级
flowchart TD
subgraph Executor 层级
BASE[BaseExecutor\n抽象基类]
SIMPLE[SimpleExecutor\n每次执行创建 Statement]
REUSE[ReuseExecutor\n缓存 Statement 复用]
BATCH[BatchExecutor\n批量执行]
CACHE[CachingExecutor\n二级缓存装饰器]
end
BASE --> SIMPLE
BASE --> REUSE
BASE --> BATCH
CACHE -->|装饰| BASE
style CACHE fill:#27ae60,color:#fff
7.2 一级缓存
public abstract class BaseExecutor implements Executor {
// 一级缓存:PerpetualCache(HashMap)
protected PerpetualCache localCache;
// 查询逻辑
@Override
public <E> List<E> query(MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler,
CacheKey key, BoundSql boundSql) throws SQLException {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 清空一级缓存的时机:flushCache=true 或执行了 update 操作
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// 从一级缓存中查找
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 缓存命中
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 缓存未命中 → 从数据库查询
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
// 延迟加载的处理
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// STATEMENT 级别:查询完成后立即清除缓存
clearLocalCache();
}
}
return list;
}
// 一级缓存的生命周期:
// 1. 同一个 SqlSession 中,同一个 SQL + 参数,第二次查询直接返回缓存
// 2. 执行 UPDATE/INSERT/DELETE 后,清除缓存
// 3. SqlSession close 或 commit 后,清除缓存
// 4. 跨 SqlSession 不共享(一级缓存是 SqlSession 级别的)
}
7.3 二级缓存
// CachingExecutor 通过装饰器模式为 Executor 添加二级缓存
public class CachingExecutor implements Executor {
private final Executor delegate; // 被装饰的 Executor
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
@Override
public <E> List<E> query(MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler,
CacheKey key, BoundSql boundSql) throws SQLException {
// 获取 MappedStatement 关联的二级缓存
Cache cache = ms.getCache();
if (cache != null) {
// 先尝试从二级缓存获取
List<E> list = tcm.getObject(cache, key);
if (list == null) {
// 二级缓存未命中 → 委派给底层 Executor 查询
list = delegate.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
// 查询结果放入二级缓存
tcm.putObject(cache, key, list);
}
return list;
}
// 没有二级缓存 → 直接查询
return delegate.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
}
| 缓存 | 级别 | 生命周期 | 默认是否开启 | 适用范围 |
|---|---|---|---|---|
| 一级缓存 | SqlSession 级别 | SqlSession 生命周期内 | 开启 | 同一个 SqlSession 内的重复查询 |
| 二级缓存 | Mapper 级别(跨 SqlSession) | 整个应用运行期间 | 关闭 | 跨 SqlSession 的重复查询 |
八、结果集映射——ResultSetHandler
8.1 从 ResultSet 到 Java 对象
public class DefaultResultSetHandler implements ResultSetHandler {
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
ResultSetWrapper rsw = new ResultSetWrapper(stmt.getResultSet(), configuration);
// 处理多结果集(存储过程等场景)
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
// 核心:将 ResultSet 当前行映射为 Java 对象
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
// 处理 ResultSet 结果
return collapseSingleResultList(multipleResults);
}
// 单行映射
private Object handleRowValueForResultMap(ResultSetWrapper rsw,
ResultMap resultMap,
CacheKey combinedKey,
String columnPrefix,
Object partialObject) throws SQLException {
// 创建默认映射对象(通过无参构造器)
Object resultObject = createResultObject(rsw, resultMap, constructor, columnPrefix);
if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
// 创建 MetaObject 用于设置属性
MetaObject metaObject = configuration.newMetaObject(resultObject);
// 遍历结果映射
List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
// 处理嵌套映射(association、collection)
if (propertyMapping.getNestedResultMapId() != null) {
handleRowValuesForNestedResultMap(rsw, resultMap, ...);
} else {
// 普通列 → 属性映射
String columnName = propertyMapping.getColumn();
TypeHandler> typeHandler = propertyMapping.getTypeHandler();
Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
metaObject.setValue(propertyMapping.getProperty(), value);
}
}
}
return resultObject;
}
}
九、插件体系——拦截器的实现
MyBatis 的插件机制允许你在 Executor、StatementHandler、ParameterHandler、ResultSetHandler 四个核心对象的方法调用过程中插入自定义逻辑。
// 插件本质上是通过 JDK 动态代理实现的拦截器
@Intercepts({
@Signature(type = Executor.class,
method = "update",
args = {MappedStatement.class, Object.class})
})
public class SqlMonitorPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.currentTimeMillis();
try {
// 执行原方法
return invocation.proceed();
} finally {
long duration = System.currentTimeMillis() - start;
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
String sqlId = ms.getId();
if (duration > 1000) {
log.warn("Slow SQL [{}] took {}ms", sqlId, duration);
}
}
}
@Override
public Object plugin(Object target) {
// 使用 Plugin.wrap 创建代理
return Plugin.wrap(target, this);
}
}
十、总结
-
MyBatis 的核心是 JDBC 的优雅封装。 它将连接管理、参数设置、结果映射这些重复劳动抽象化,通过配置和注解实现了 SQL 与 Java 代码的分离。
-
动态代理是 Mapper 接口无需实现类的秘密。 MapperProxy 通过 JDK 动态代理将接口方法调用转换为 SqlSession 操作,这是整个 MyBatis-Spring 集成的基础。
-
XML 解析是 MyBatis 的心脏。 从全局配置到 SQL 映射,XMLStatementBuilder 和 SqlSource 将动态 SQL 解析为可执行的 BoundSql,OGNL 表达式为动态 SQL 注入了灵活性。
-
缓存架构体现了”先读缓存,后读数据库”的思想。 一级缓存自动生效减少重复查询,二级缓存通过装饰器模式提供跨 SqlSession 的缓存共享,但需要谨慎使用(脏数据风险)。
-
TypeHandler 是类型转换的桥梁。 MyBatis 内置了丰富的类型处理器,同时支持自定义 TypeHandler 以应对特殊枚举或复杂类型的转换需求。
-
插件体系是 MyBatis 扩展性的体现。 拦截四个核心对象的方法调用,可以无侵入地实现 SQL 监控、分页、性能统计等功能。
Spring Boot 核心机制源码级分析:IoC 容器、AOP 代理与事务管理
title: “Spring Boot 核心机制源码级分析:IoC 容器、AOP 代理与事务管理的底层实现”
一、引言
Spring Boot 的”开箱即用”背后,是一套精巧的核心机制在支撑:IoC(控制反转)容器管理着所有 Bean 的生命周期,依赖注入框架织就了组件间的协作网络,AOP 代理在运行时透明地增强方法,而事务管理则借助 AOP 实现了声明式的事务边界控制。本文将逐一切入 Spring Boot 的这些核心机制,深入源码进行剖析。
二、IoC 容器与 Bean 生命周期
2.1 容器初始化流程
Spring 容器的初始化始于 ApplicationContext 的创建。以 AnnotationConfigApplicationContext 为例:
// 入口代码
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
// 此时容器已初始化完毕
}
}
内部经历了三大步骤:
1. 扫描(Scan)
└─ 找到所有 @Component 类 → BeanDefinition 注册到 BeanFactory
2. 推断构建方法
└─ 确定每个 Bean 使用哪个构造器
3. 实例化 + 初始化(生命周期回调)
flowchart TD
A[创建 ApplicationContext] --> B[注册配置类 BeanDefinition]
B --> C[调用 refresh 方法]
C --> D[invokeBeanFactoryPostProcessors]
D --> E[扫描包路径, 注册所有 BeanDefinition]
E --> F[registerBeanPostProcessors]
F --> G[finishBeanFactoryInitialization]
G --> H[实例化所有非懒加载单例 Bean]
D --> D1[BeanFactoryPostProcessor 处理 @Configuration]
E --> E1[{
'beanDefinitions': 所有类都被扫描为 BeanDefinition
}]
H --> H1[调用 getBean → doGetBean → createBean]
2.2 Bean 生命周期完整流程
flowchart LR
A[BeanDefinition] --> B[实例化 Instantiation]
B --> C[属性赋值 Populate]
C --> D[Aware 接口回调]
D --> E[BeanPostProcessor before]
E --> F[初始化方法 Init]
F --> G[BeanPostProcessor after]
G --> H[就绪 Ready]
H --> I[销毁 Destroy]
D --> D1[BeanNameAware / BeanFactoryAware<br/>ApplicationContextAware]
E --> E1[@PostConstruct / InitializingBean]
F --> F1[@Bean(initMethod)]
G --> G1[AOP 代理创建点]
I --> I1[@PreDestroy / DisposableBean]
2.3 三级缓存与循环依赖的解决
Spring 通过三级缓存解决构造器注入之外的循环依赖:
// DefaultSingletonBeanRegistry
public class DefaultSingletonBeanRegistry {
// 一级缓存:完整初始化的单例 Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:早期暴露的 Bean(还未完全初始化)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 三级缓存:ObjectFactory 延迟生成代理
private final Map<String, ObjectFactory>> singletonFactories = new HashMap<>(16);
}
| 级别 | 缓存名称 | 内容 | 用途 |
|---|---|---|---|
| 一级 | singletonObjects | 完全初始化好的 Bean | 最终成品 |
| 二级 | earlySingletonObjects | 实例化但未初始化的 Bean | 暴露半成品 |
| 三级 | singletonFactories | 生成代理的工厂 | 延迟创建 AOP 代理 |
循环依赖解决示例:
@Component
public class A {
@Autowired
private B b;
}
@Component
public class B {
@Autowired
private A a;
}
解决过程:
1. 创建 A:实例化 A → A 放入三级缓存 → 属性注入 B
2. 创建 B:实例化 B → B 放入三级缓存 → 属性注入 A
3. B 注入 A:从三级缓存取 A → 执行 ObjectFactory → 放入二级缓存
4. B 完成初始化:从二级缓存/或者finalAOP代理→put一级
5. A 完成初始化:从一级取B→注入→A完成→put一级
注意: 构造器注入无法解决循环依赖,因为构造器调用在实例化之前,此时三级缓存还未放入工厂。会导致 BeanCurrentlyInCreationException。
三、依赖注入实现原理
3.1 @Autowired 注入处理
AutowiredAnnotationBeanPostProcessor 负责处理 @Autowired 和 @Value 注解:
class AutowiredAnnotationBeanPostProcessor {
@Override
public PropertyValues postProcessProperties(
PropertyValues pvs, Object bean, String beanName) {
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass());
metadata.inject(bean, beanName, pvs);
return pvs;
}
}
注入方式对比:
| 注入方式 | 处理时机 | 实现类 | 优缺点 |
|---|---|---|---|
| @Autowired 字段 | BeanPostProcessor 阶段 | AutowiredAnnotationBeanPostProcessor | 简洁,不可测试 |
| setter 注入 | BeanPostProcessor 阶段 | AutowiredAnnotationBeanPostProcessor | 可选依赖 |
| 构造器注入 | 实例化阶段显式初始化参数 | AnnotationConfigUtils | 推荐,不可变 |
构造器注入的实现:
// Spring 遍历所有构造器,选最优的
@Component
public class UserService {
private final UserRepository userRepository;
// Spring 自动选这个构造器(唯一或最"肥"的)
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
内部通过 ConstructorResolver.autowireConstructor() 方法解析,按参数类型和 @Qualifier 查找匹配的 Bean。
四、AOP 代理机制
4.1 代理创建流程
Spring AOP 的入口是 AbstractAutoProxyCreator(一个 BeanPostProcessor),在每个 Bean 初始化之后判断是否生成代理:
public abstract class AbstractAutoProxyCreator {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof AopInfrastructureBean) {
return bean;
}
// 检查是否有 Advisor 匹配该 Bean
Advisor[] advisors = getAdvicesAndAdvisorsForBean(
bean.getClass(), beanName, null);
if (advisors != null) {
// 创建代理对象
return createProxy(bean.getClass(), beanName,
specificInterceptors, new SingletonTargetSource(bean));
}
return bean;
}
}
4.2 JDK 动态代理 vs CGLIB
| 特性 | JDK 动态代理 | CGLIB |
|---|---|---|
| 实现方式 | 基于接口,运行时生成 Proxy 类 | 基于子类,继承增强 |
| 要求 | 目标类必须实现接口 | 无要求 |
| 代理方法调用效率 | 反射 + Method.invoke | FastClass 机制(索引调用) |
| 无法代理的场景 | – | final 类、final 方法、static 方法 |
| Spring Boot 默认 | 2.0 之前 | 2.0 之后(需设置 proxyTargetClass=true) |
Spring Boot 2.0+ 默认使用 CGLIB(即使有接口),因为:
- 现代框架普遍基于类(
@Configuration类等),而不是接口- CGLIB 的 FastClass 机制比 JDK 反射更高效
- 统一代理方式可避免接口与实现类不一致导致的诡异 BUG
4.3 拦截器链执行
sequenceDiagram
participant Client as 调用方
participant Proxy as AOP 代理对象
participant Chain as 拦截器链
participant Target as 目标方法
Client->>Proxy: userService.findUser()
Proxy->>Chain: 调用拦截器链
Note over Chain: 第一个拦截器: @Around
Chain->>Chain: 第二个拦截器: @Before
Chain->>Target: proceed() 调用目标
Chain->>Chain: 第三个拦截器: @AfterReturning
Chain-->>Proxy: 返回结果
Proxy-->>Client: 结果
Note over Chain: 责任链模式
Note over Chain: 顺序: Around → Before → 目标 → After → AfterReturning
4.4 代理自调用失效问题
@Service
public class UserService {
@Transactional
public void methodA() {
methodB(); // 自调用:不会触发事务
}
@Transactional
public void methodB() {
// 数据库操作
}
}
原因: methodA() 调用 methodB() 时,是通过 this.methodB() 调用的,this 是目标对象而不是代理对象,因此不会触发事务增强。
解决方案:
// 1. 注入自身代理(Spring 5+ 可用 @Lazy 解决循环依赖)
@Autowired
@Lazy
private UserService self;
public void methodA() {
self.methodB(); // 通过代理调用
}
// 2. 提取到另一个 Service(推荐)
public void methodA() {
auditService.methodB(); // 独立 Bean
}
五、事务管理底层
5.1 事务代理的创建
@EnableTransactionManagement 会向容器中注册 BeanFactoryTransactionAttributeSourceAdvisor,加上 TransactionInterceptor(实现了 MethodInterceptor),共同组成一个事务 Advisor。
当某个 Bean 有 @Transactional 方法时,InfrastructureAdvisorAutoProxyCreator 创建代理,事务拦截器被织入:
public class TransactionInterceptor extends TransactionAspectSupport
implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 获取事务属性
TransactionAttribute txAttr = getTransactionAttributeSource()
.getTransactionAttribute(invocation.getMethod(), targetClass);
// 获取 PlatformTransactionManager
PlatformTransactionManager tm = getTransactionManager();
// 执行带事务的调用
return invokeWithinTransaction(invocation.getMethod(), targetClass,
invocation::proceed, txAttr, tm);
}
}
5.2 事务传播行为实现
声明式事务的传播行为(Propagation)是 Spring 区别于原生 JDBC 事务的核心能力:
| 传播行为 | 含义 | 实现逻辑 |
|---|---|---|
| REQUIRED(默认) | 支持当前事务,没有则新建 | doGetTransaction() → 有则加入 |
| REQUIRES_NEW | 必须新建事务,挂起当前事务 | 挂起当前事务,创建新 Connection |
| SUPPORTS | 有就用,没有就不开 | 简单判断是否已有事务 |
| MANDATORY | 必须要有事务,否则抛异常 | 抛 NoTransactionException |
| NOT_SUPPORTED | 必须以非事务方式执行 | 挂起当前事务 |
| NESTED | 嵌套事务(JDBC Savepoint) | 创建数据库级保存点 |
REQUIRES_NEW 的实现:
@Override
protected Object doBeginTransaction(
Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject =
(DataSourceTransactionObject) transaction;
Connection con = txObject.getConnectionHolder().getConnection();
// REQUIRES_NEW:挂起当前事务,获取新连接
if (txObject.hasTransaction()) {
suspend(txObject); // 保存当前事务状态
}
// 设置自动提交关闭、隔离级别等
con.setAutoCommit(false);
// ...
}
5.3 事务隔离级别的底层实现
flowchart TD
subgraph "隔离级别与问题"
READ_UNCOMMITTED -->|脏读+不可重复读+幻读| LOW[并发高, 一致性低]
READ_COMMITTED -->|不可重复读+幻读| MID[PG/默认]
REPEATABLE_READ -->|幻读| HIGH
SERIALIZABLE -->|无并发问题| HIGHEST[并发极低]
end
实现差异:
| 数据库 | 如何实现 REPEATABLE_READ |
|---|---|
| MySQL InnoDB | MVCC + Next-Key Lock(行锁 + Gap Lock) |
| PostgreSQL | MVCC(不会幻读,但允许更新冲突) |
| Oracle | 只支持 READ_COMMITTED 和 SERIALIZABLE |
六、总结
| 机制 | 核心组件 | 关键源码入口 | 常见陷阱 |
|---|---|---|---|
| IoC 容器 | DefaultListableBeanFactory | refresh() → finishBeanFactoryInitialization() |
构造器循环依赖 |
| DI 注入 | AutowiredAnnotationBeanPostProcessor | postProcessProperties() |
字段注入无法测试 |
| AOP 代理 | AbstractAutoProxyCreator | postProcessAfterInitialization() |
自调用失效 |
| 事务管理 | TransactionInterceptor | invokeWithinTransaction() |
自调用 + REQUIRES_NEW 误解 |
掌握 Spring Boot 的底层机制不仅仅是”看源码”,而是建立对框架行为模式的预期。当你理解了 BeanPostProcessor 的链式调用、AbstractAutoProxyCreator 的触发时机,以及事务 TransactionInterceptor 在代理链中的位置之后,绝大多数 Spring Boot 的”奇怪问题”都会变得清晰可预测。


暂无评论内容