Appearance
源码解读
为什么需要阅读源码
在日常开发中,我们通过文档和教程能够快速上手一个框架,但这只是"知其然"。当遇到以下场景时,源码阅读就变得不可或缺:
- 排查诡异 Bug:框架表现不符合预期,日志信息不足,文档没有覆盖边缘场景。此时只有深入源码,才能理解框架的真实行为逻辑。
- 性能调优:框架的默认配置不一定适合你的业务场景。理解内部实现后,才能精准调整参数、替换组件,而不是盲目"调参"。
- 学习设计模式:优秀框架是设计模式的最佳实践场。Spring 中随处可见模板方法、策略、观察者等模式,阅读源码比看任何设计模式教材都更深刻。
- 技术面试:高级岗位面试中,"你看过哪些框架源码"几乎是必问题。能讲清楚一个框架的核心流程,远比背诵八股文更有说服力。
- 提升编码能力:阅读顶级开源项目的代码,学习命名规范、异常处理、并发控制、扩展性设计,潜移默化地提升自己的代码品味。
| 阅读层级 | 目标 | 典型产出 |
|---|---|---|
| 第一层:会用 | 通过文档和示例完成功能开发 | 功能实现 |
| 第二层:理解 | 看懂核心流程和关键设计 | 技术分享、问题排查 |
| 第三层:改造 | 能扩展、裁剪、优化框架 | 定制化组件、性能优化 |
| 第四层:创造 | 借鉴思想设计自己的框架 | 自研框架/中间件 |
如何高效阅读源码
1. 明确目标,带着问题去读
不要试图从头到尾读完整份源码——那只会让你迷失。每次阅读应该有明确的问题驱动,例如:
- "Spring 是如何解决循环依赖的?"
- "MyBatis 的 Mapper 接口为什么不需要实现类?"
- "Spring Boot 自动配置是如何发现和加载的?"
2. 从入口开始,沿调用链追踪
以调试模式运行一个最简单的 Demo,在关键位置打断点,然后一步步跟进。这是最直观的方式:
1. 写一个最简单的测试用例
2. 在框架入口处打断点(如 Spring 的 ApplicationContext 构造)
3. Step Into 进入核心方法
4. 观察调用栈,画出调用链
5. 重点关注:关键数据结构、分支逻辑、扩展点3. 善用 IDE 的源码导航能力
- Ctrl+Alt+B (IDEA):查看接口/抽象方法的所有实现
- Ctrl+H:查看类的继承层次结构
- Ctrl+Alt+F7:查找方法/字段的所有使用位置
- Diagrams 插件:生成类图、调用图
- Bookmarks:标记关键类和方法,方便反复跳转
4. 画图辅助理解
复杂的调用链和类关系,用文字描述远不如一张图清晰。推荐做法:
- 时序图:记录关键方法的调用顺序(谁调用了谁,传了什么参数)
- 类图:记录核心类之间的依赖和继承关系
- 流程图:记录某个核心流程的分支逻辑
5. 抓住主干,忽略细节
框架源码通常有大量边界处理、日志、异常处理代码。第一次阅读时大胆跳过:
java
// 第一次阅读时可以直接跳过这类代码
if (logger.isTraceEnabled()) {
logger.trace("...");
}
// 以及各种 null check、参数校验等重点关注:核心数据结构 + 核心流程 + 扩展点接口。
6. 版本选择策略
| 策略 | 优点 | 缺点 |
|---|---|---|
| 读最新版 | 学到最新设计,面试加分 | 代码量大,复杂度高 |
| 读早期版本(如 Spring 1.x) | 代码量小,核心思想清晰 | 部分设计已过时 |
| 对比阅读 | 理解演进原因 | 耗时较长 |
建议:先读早期版本理解核心思想,再对比最新版本看演进。
Spring IoC 容器源码解读
Spring IoC 是整个 Spring 生态的基石,理解它等于掌握了 Spring 的"任督二脉"。
核心类层次结构
BeanFactory (接口)
└── HierarchicalBeanFactory
└── ListableBeanFactory
└── AutowireCapableBeanFactory
└── ConfigurableBeanFactory
└── AbstractBeanFactory
└── AbstractAutowireCapableBeanFactory
└── DefaultListableBeanFactory
└── XmlBeanFactory (已废弃)
ApplicationContext (接口)
└── ConfigurableApplicationContext
└── AbstractApplicationContext
└── AbstractRefreshableApplicationContext
└── AbstractRefreshableConfigApplicationContext
└── AbstractXmlApplicationContext
└── ClassPathXmlApplicationContext
└── GenericApplicationContext
└── AnnotationConfigApplicationContextrefresh() 方法——Spring 容器启动的核心
AbstractApplicationContext.refresh() 是整个 Spring 容器初始化的入口,它定义了容器启动的 12 个步骤(模板方法模式):
java
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 1. 准备刷新:记录启动时间,初始化属性源
prepareRefresh();
// 2. 获取 BeanFactory:如果是可刷新容器则刷新,否则新建
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 3. 准备 BeanFactory:设置类加载器、添加后置处理器等
prepareBeanFactory(beanFactory);
try {
// 4. 允许子类对 BeanFactory 进行后置处理(模板方法)
postProcessBeanFactory(beanFactory);
// 5. 调用 BeanFactoryPostProcessor(核心扩展点)
invokeBeanFactoryPostProcessors(beanFactory);
// 6. 注册 BeanPostProcessor
registerBeanPostProcessors(beanFactory);
// 7. 初始化 MessageSource(国际化)
initMessageSource();
// 8. 初始化事件广播器
initApplicationEventMulticaster();
// 9. 模板方法:留给子类初始化其他特殊 Bean
onRefresh();
// 10. 注册事件监听器
registerListeners();
// 11. 实例化所有非懒加载的单例 Bean
finishBeanFactoryInitialization(beanFactory);
// 12. 完成刷新:发布 ContextRefreshedEvent 事件
finishRefresh();
} catch (BeansException ex) {
// 销毁已创建的 Bean
destroyBeans();
cancelRefresh(ex);
throw ex;
}
}
}BeanDefinition 加载流程
以 XML 配置为例,Bean 定义的加载过程如下:
1. XmlBeanDefinitionReader 读取 XML 配置文件
2. 委托给 DefaultBeanDefinitionDocumentReader 解析 <beans> 元素
3. 遍历每个 <bean> 元素,委托给 BeanDefinitionParserDelegate 解析
4. 生成 BeanDefinitionHolder(包含 BeanDefinition + beanName + aliases)
5. 注册到 BeanDefinitionRegistry(即 DefaultListableBeanFactory 的 beanDefinitionMap)java
// DefaultListableBeanFactory 中 BeanDefinition 的存储结构
private final Map<String, BeanDefinition> beanDefinitionMap =
new ConcurrentHashMap<>(256);依赖注入流程
当调用 getBean() 时,Spring 的依赖注入流程如下:
java
// AbstractBeanFactory.doGetBean() 简化版
protected <T> T doGetBean(String name, Class<T> requiredType,
Object[] args, boolean typeCheckOnly) {
// 1. 转换 beanName(处理别名、FactoryBean 的 & 前缀)
String beanName = transformedBeanName(name);
// 2. 尝试从单例缓存中获取(处理循环依赖的关键)
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
return (T) getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
// 3. 检查是否存在原型循环依赖
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// 4. 如果当前容器没有,去父容器查找
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
return parentBeanFactory.getBean(name, requiredType);
}
// 5. 合并 BeanDefinition(处理继承)
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// 6. 先实例化依赖的 Bean(depends-on)
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
registerDependentBean(dep, beanName);
getBean(dep); // 递归创建依赖的 Bean
}
}
// 7. 创建 Bean 实例
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
return createBean(beanName, mbd, args);
});
} else if (mbd.isPrototype()) {
// 原型模式,每次新建
sharedInstance = createBean(beanName, mbd, args);
} else {
// 其他 Scope(request、session 等)
// ...
}
return (T) getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}循环依赖解决——三级缓存机制
Spring 通过三级缓存解决了单例 Bean 的构造器注入循环依赖问题:
java
// DefaultSingletonBeanRegistry 中的三级缓存
public class DefaultSingletonBeanRegistry {
// 一级缓存:存放完全初始化好的 Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:存放提前暴露的早期 Bean 引用(未完成属性注入)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 三级缓存:存放 Bean 的 ObjectFactory(用于生成代理对象)
private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>(16);
}循环依赖解决流程:
A 创建 → 实例化 A → 将 A 的 ObjectFactory 放入三级缓存
→ A 依赖 B → 创建 B → 实例化 B → 将 B 的 ObjectFactory 放入三级缓存
→ B 依赖 A → 从三级缓存获取 A 的 ObjectFactory → 生成 A 的早期引用(可能是代理)
→ 将 A 的早期引用放入二级缓存 → B 完成注入 A → B 初始化完成
→ B 放入一级缓存 → A 完成注入 B → A 初始化完成 → A 放入一级缓存注意:Spring 只能解决单例 setter 注入的循环依赖,无法解决构造器注入的循环依赖和原型 Bean 的循环依赖。
Spring AOP 源码解读
核心概念与类关系
Spring AOP 基于动态代理实现,核心类如下:
AopProxy (接口)
├── JdkDynamicAopProxy → 基于 JDK 动态代理(要求实现接口)
└── CglibAopProxy → 基于 CGLIB 字节码增强(无接口时使用)
ProxyFactory → 创建代理对象的工厂
AdvisedSupport → 封装 AOP 配置(目标对象、拦截器链等)
Advisor → 切面 = Advice + Pointcut
└── PointcutAdvisor
└── AspectJPointcutAdvisorAOP 代理创建流程
java
// ProxyFactory.getProxy() 简化逻辑
public Object getProxy() {
// 根据配置选择代理方式
AopProxyFactory aopProxyFactory = new DefaultAopProxyFactory();
return aopProxyFactory.createAopProxy(this).getProxy();
}
// DefaultAopProxyFactory.createAopProxy()
public AopProxy createAopProxy(AdvisedSupport config) {
if (config.isOptimize() || config.isProxyTargetClass()
|| hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new CglibAopProxy(config);
} else {
return new JdkDynamicAopProxy(config);
}
}JdkDynamicAopProxy 核心逻辑
java
// JdkDynamicAopProxy.invoke() 简化版
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 获取目标方法的拦截器链(Advisor 链)
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(
method, targetClass);
if (chain.isEmpty()) {
// 没有匹配的切面,直接调用目标方法
return AopUtils.invokeJoinpointUsingReflection(target, method, args);
}
// 2. 创建 MethodInvocation,执行拦截器链
MethodInvocation invocation = new ReflectiveMethodInvocation(
proxy, target, method, args, targetClass, chain);
return invocation.proceed();
}
// ReflectiveMethodInvocation.proceed() 责任链模式
public Object proceed() throws Throwable {
// 当拦截器全部执行完毕,调用目标方法
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
// 依次执行拦截器链中的每个拦截器
Object interceptor = this.interceptorsAndDynamicMethodMatchers.get(
++this.currentInterceptorIndex);
return ((MethodInterceptor) interceptor).invoke(this);
}CglibAopProxy 核心逻辑
CGLIB 通过生成目标类的子类来实现代理,核心是 DynamicAdvisedInterceptor:
java
// CglibAopProxy.DynamicAdvisedInterceptor.intercept() 简化版
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
// 获取拦截器链
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(
method, targetClass);
if (chain.isEmpty()) {
// 直接调用目标方法(通过 CGLIB 的 MethodProxy)
return methodProxy.invoke(target, args);
}
// 创建 CglibMethodInvocation 执行拦截器链
CglibMethodInvocation invocation = new CglibMethodInvocation(
proxy, target, method, args, targetClass, chain, methodProxy);
return invocation.proceed();
}Advisor 链的构建过程
java
// DefaultAdvisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice()
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
Advised config, Method method, Class<?> targetClass) {
List<Object> interceptorList = new ArrayList<>(config.getAdvisors().length);
for (Advisor advisor : config.getAdvisors()) {
if (advisor instanceof PointcutAdvisor) {
PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
// 检查切点是否匹配当前方法和类
if (pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)
&& pointcutAdvisor.getPointcut().getMethodMatcher().matches(method, targetClass)) {
// 将 Advisor 转换为 MethodInterceptor
MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));
}
}
}
return interceptorList;
}JDK 动态代理 vs CGLIB 代理
| 对比维度 | JDK 动态代理 | CGLIB 代理 |
|---|---|---|
| 实现原理 | 基于接口,通过 Proxy + InvocationHandler 生成代理类 | 基于继承,通过 ASM 生成目标类的子类 |
| 要求 | 目标类必须实现接口 | 目标类不能是 final,方法不能是 final |
| 性能 | 创建代理快,执行稍慢(反射) | 创建代理慢(字节码生成),执行快 |
| 默认策略 | Spring 中目标类实现了接口则默认使用 JDK 代理 | Spring Boot 2.x 开始默认使用 CGLIB |
MyBatis 源码解读
整体架构
MyBatis 的核心架构分为三层:
┌─────────────────────────────────────────────┐
│ 接口层 (SqlSession) │
│ 提供对外的 CRUD API,供应用程序调用 │
├─────────────────────────────────────────────┤
│ 核心处理层 │
│ 参数映射 → SQL 解析 → SQL 执行 → 结果映射 │
│ (Executor, StatementHandler, ResultSetHandler)│
├─────────────────────────────────────────────┤
│ 基础支撑层 │
│ 连接管理、事务管理、配置加载、缓存 │
└─────────────────────────────────────────────┘核心组件与执行流程
SqlSessionFactoryBuilder
→ build(InputStream) → XMLConfigBuilder.parse()
→ Configuration (全局配置对象)
→ SqlSessionFactory.openSession()
→ SqlSession (会话,默认 DefaultSqlSession)
→ Executor (执行器)
→ StatementHandler (语句处理器)
→ ParameterHandler (参数处理器)
→ ResultSetHandler (结果集处理器)1. SqlSessionFactory 的构建
java
// SqlSessionFactoryBuilder.build() 简化版
public SqlSessionFactory build(InputStream inputStream) {
// 创建 XMLConfigBuilder 解析 mybatis-config.xml
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, null, null);
// 解析生成 Configuration 对象
return build(parser.parse());
}
// XMLConfigBuilder.parse() 核心逻辑
public Configuration parse() {
// 解析 <configuration> 节点下的各个子节点
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
// 解析 properties、settings、typeAliases、plugins
// environments、mappers 等配置
propertiesElement(root.evalNode("properties"));
settingsElement(root.evalNode("settings"));
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
environmentsElement(root.evalNode("environments"));
mapperElement(root.evalNode("mappers")); // 关键:解析 Mapper 映射文件
}2. Mapper 接口的动态代理
MyBatis 最巧妙的设计:Mapper 接口不需要实现类,通过 JDK 动态代理在运行时生成实现。
java
// MapperProxyFactory.newInstance()
public T newInstance(SqlSession sqlSession) {
// MapperProxy 实现了 InvocationHandler
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return (T) Proxy.newProxyInstance(
mapperInterface.getClassLoader(),
new Class[] { mapperInterface },
mapperProxy
);
}
// MapperProxy.invoke() 核心逻辑
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 如果是 Object 的方法(toString、hashCode 等),直接执行
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 获取 MapperMethod 并执行
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
// MapperMethod.execute() 根据 SQL 类型分派
public Object execute(SqlSession sqlSession, Object[] args) {
switch (command.getType()) {
case INSERT:
return sqlSession.insert(command.getName(), param);
case UPDATE:
return sqlSession.update(command.getName(), param);
case DELETE:
return sqlSession.delete(command.getName(), param);
case SELECT:
if (method.returnsVoid()) {
sqlSession.select(command.getName(), param, resultHandler);
return null;
} else if (method.returnsMany()) {
return sqlSession.selectList(command.getName(), param);
} else if (method.returnsMap()) {
return sqlSession.selectMap(command.getName(), param, ...);
} else {
return sqlSession.selectOne(command.getName(), param);
}
default:
throw new BindingException("Unknown execution method");
}
}3. Executor 执行器——二级缓存与执行链
java
// Executor 接口的继承层次
Executor (接口)
└── BaseExecutor (抽象类,模板方法模式)
├── SimpleExecutor → 简单执行器,每次执行都创建新的 Statement
├── ReuseExecutor → 可重用执行器,复用 Statement
└── BatchExecutor → 批量执行器,用于批量操作
└── CachingExecutor (装饰器模式) → 包装 BaseExecutor,增加二级缓存
// Configuration.newExecutor()
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
// 如果开启了二级缓存,用 CachingExecutor 装饰
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 应用插件拦截(责任链模式)
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}4. StatementHandler——SQL 执行的核心
java
// StatementHandler 的继承层次
StatementHandler (接口)
└── BaseStatementHandler (抽象类)
├── SimpleStatementHandler → 处理普通 Statement
├── PreparedStatementHandler → 处理 PreparedStatement(最常用)
└── CallableStatementHandler → 处理 CallableStatement(存储过程)
// RoutingStatementHandler(路由,根据 MappedStatement 选择具体实现)5. MyBatis 插件机制——责任链模式
java
// InterceptorChain 维护所有插件
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
// 对目标对象应用所有插件,生成代理
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
}
// 自定义插件示例:SQL 执行时间统计
@Intercepts({
@Signature(type = StatementHandler.class, method = "query",
args = {Statement.class, ResultHandler.class})
})
public class SqlCostInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.currentTimeMillis();
try {
return invocation.proceed();
} finally {
long cost = System.currentTimeMillis() - start;
System.out.println("SQL 执行耗时: " + cost + "ms");
}
}
}一级缓存与二级缓存
| 对比维度 | 一级缓存 | 二级缓存 |
|---|---|---|
| 作用范围 | SqlSession 级别 | Mapper 级别(跨 SqlSession) |
| 默认开启 | 是 | 否 |
| 实现类 | PerpetualCache(HashMap) | PerpetualCache + 装饰器 |
| 生命周期 | 随 SqlSession 关闭而清除 | 随应用生命周期(可配置驱逐策略) |
| 清除时机 | update/delete/insert 后自动清除 | commit/close 时写入,update 后清除 |
源码阅读实战建议
推荐阅读顺序
- 先读 Spring IoC:IoC 是 Spring 的核心,AOP、事务、MVC 都依赖它
- 再读 Spring AOP:理解代理机制和拦截器链,这是声明式事务的基础
- 然后读 MyBatis:相对独立,核心流程清晰,适合练手
- 最后读 Spring Boot 自动配置:理解
@EnableAutoConfiguration和spring.factories机制
搭建源码阅读环境
bash
# 1. Clone 源码
git clone https://github.com/spring-projects/spring-framework.git
cd spring-framework
# 2. 切换到合适的版本标签
git checkout v5.3.30
# 3. 导入 IDEA,等待 Gradle 构建完成
# 4. 在各自模块下编写测试用例,打断点调试调试技巧
java
// 在测试用例中打断点,观察调用栈
@Test
public void testIoC() {
// 在这里打断点,Step Into 进入 refresh()
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 在这里打断点,观察 getBean() 的完整流程
UserService userService = context.getBean(UserService.class);
userService.doSomething();
}面试常见问题
1. Spring 的 refresh() 方法做了哪些事?
答:refresh() 是 Spring 容器启动的核心方法,分为 12 个步骤:准备刷新上下文、获取 BeanFactory、准备 BeanFactory、BeanFactory 后置处理、调用 BeanFactoryPostProcessor、注册 BeanPostProcessor、初始化 MessageSource、初始化事件广播器、onRefresh 模板方法、注册监听器、实例化单例 Bean、完成刷新。核心是第 5 步(调用 BeanFactoryPostProcessor)和第 11 步(实例化单例 Bean)。
2. Spring 如何解决循环依赖?
答:Spring 通过三级缓存解决单例 Bean 的 setter 注入循环依赖。一级缓存 singletonObjects 存放完全初始化的 Bean,二级缓存 earlySingletonObjects 存放提前暴露的早期引用,三级缓存 singletonFactories 存放 ObjectFactory。当 A 和 B 循环依赖时,A 先实例化后将自己的 ObjectFactory 放入三级缓存,然后注入 B,B 创建时发现依赖 A,从三级缓存获取 A 的早期引用并升级到二级缓存,B 完成后 A 再从二级缓存获取 B 完成注入。构造器注入的循环依赖无法解决。
3. MyBatis 的 Mapper 接口为什么不需要实现类?
答:MyBatis 使用 JDK 动态代理,在运行时通过 MapperProxy(实现了 InvocationHandler)为 Mapper 接口生成代理对象。当调用 Mapper 接口方法时,MapperProxy.invoke() 拦截调用,根据方法名找到对应的 MappedStatement(SQL 映射),然后通过 SqlSession 执行 SQL 并返回结果。
4. Spring AOP 和 AspectJ 有什么区别?
答:Spring AOP 是运行时动态代理(JDK 动态代理或 CGLIB),只能对 Spring 管理的 Bean 进行方法级别的增强;AspectJ 是编译时/类加载时织入,通过修改字节码实现,支持字段、构造器等更细粒度的增强,性能更好但使用更复杂。
5. MyBatis 的一级缓存和二级缓存会带来什么问题?
答:一级缓存是 SqlSession 级别的,在同一个 SqlSession 中多次查询同一数据会返回缓存结果,可能导致读到脏数据(另一个 SqlSession 已修改)。二级缓存是 Mapper 级别的,多个 SqlSession 共享,在分布式环境下容易出现数据不一致。建议在分布式环境中关闭二级缓存,使用 Redis 等集中式缓存替代。
