news 2026/8/20 15:24:49

Spring、MyBatis、Spring Boot核心原理深度解析:从源码到实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring、MyBatis、Spring Boot核心原理深度解析:从源码到实战

如果你已经使用 Spring 框架开发了多个项目,却依然对@Autowired注解背后发生了什么感到困惑,或者在面试中被问到“Spring 如何解决循环依赖”时只能回答“三级缓存”,却说不清其具体流程和设计意图,那么这篇文章就是为你准备的。

很多开发者对 Spring 的理解停留在“会用”的层面:知道如何配置 Bean、使用注解、集成 MyBatis。但当线上出现诡异的 Bean 创建失败、事务不生效、或者 MyBatis 动态 SQL 拼接出错时,往往只能靠搜索引擎和试错来解决问题,缺乏从源码层面进行系统性排查的能力。这种“黑盒”状态,不仅影响问题解决效率,更限制了技术深度的提升和架构设计能力。

本文将带你穿透表面,直击 Spring 生态中几个最核心、也最常被问及的源码实现:IOC 容器的启动与 Bean 生命周期Spring MVC 的请求处理链路MyBatis 的 SQL 执行引擎,以及Spring Boot 的自动化配置魔法。我们的目标不是罗列每一行代码,而是提炼出那些真正影响你日常开发、面试和系统设计的关键流程与设计思想。读完本文,你将能清晰地回答:一个 HTTP 请求是如何被 Spring MVC 一步步处理的?MyBatis 是如何把一句#{name}转换成?并设置参数的?Spring Boot 的spring-boot-autoconfigure到底做了什么?

1. 这篇文章真正要解决的问题:从“会用”到“懂为什么”

在开始深入源码之前,我们必须明确目标:阅读源码不是为了炫技,而是为了解决实际问题。对于 Spring 生态,最常见的痛点集中在以下几个方面:

  1. 配置生效之谜:为什么我的@Transactional注解有时不生效?@Bean@Component在容器初始化顺序上有什么区别?自定义的BeanPostProcessor该如何编写才能影响特定 Bean?
  2. 运行时诡异问题:循环依赖在什么情况下 Spring 也无法解决?为什么MyBatis#{}${}在动态表名场景下会有不同的表现?Spring MVCHandlerInterceptorFilter谁先执行?
  3. 面试深度瓶颈:当被问到“Spring 如何管理 Bean 的生命周期”、“MyBatis 的一级缓存和二级缓存有何区别”、“Spring Boot Starter 原理”时,能否给出有层次、有细节的回答,而不是几个干巴巴的名词?

本文将围绕这些真实、高频的痛点,通过剖析核心源码流程,为你建立一幅清晰的“Spring 生态核心原理地图”。你会发现,很多复杂的现象,其根源都藏在那些精妙的设计模式(如模板方法、工厂模式、代理模式)和核心类(如BeanFactoryApplicationContextSqlSessionDispatcherServlet)的交互之中。

2. 核心基石:IOC 容器的启动与 Bean 生命周期全景

IOC(控制反转)是 Spring 的基石。理解它,就理解了 Spring 如何管理你的对象。

2.1 容器启动的入口:不仅仅是 new ClassPathXmlApplicationContext()

对于传统的 XML 配置方式,入口是ClassPathXmlApplicationContext。但更重要的是它的父类AbstractApplicationContext中的refresh()方法。这个方法是一个模板方法,定义了容器刷新的完整流程。

// 简化版 refresh() 方法流程 (位于 AbstractApplicationContext) public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // 1. 准备刷新:设置启动时间、激活状态、初始化属性源等 prepareRefresh(); // 2. 获取刷新后的 BeanFactory:这一步会解析 XML/注解,生成 BeanDefinition,但尚未创建 Bean 实例。 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 3. 准备 BeanFactory:配置 BeanFactory 的标准上下文特征,如 ClassLoader、后置处理器等。 prepareBeanFactory(beanFactory); try { // 4. 后置处理 BeanFactory(空方法,子类可扩展) postProcessBeanFactory(beanFactory); // 5. 调用 BeanFactory 后置处理器:这是关键!@Configuration, @Component 等注解的解析在此发生。 invokeBeanFactoryPostProcessors(beanFactory); // 6. 注册 Bean 后置处理器:为后续 Bean 实例化、初始化做准备。 registerBeanPostProcessors(beanFactory); // 7. 初始化消息源(国际化) initMessageSource(); // 8. 初始化事件广播器 initApplicationEventMulticaster(); // 9. 空方法,子类可扩展(如 Spring Boot 的 Web 环境启动) onRefresh(); // 10. 注册监听器 registerListeners(); // 11. 实例化所有非懒加载的单例 Bean:核心中的核心! finishBeanFactoryInitialization(beanFactory); // 12. 完成刷新:发布容器刷新完成事件 finishRefresh(); } catch (BeansException ex) { // ... 异常处理,销毁已创建的 Bean destroyBeans(); cancelRefresh(ex); throw ex; } finally { // 重置缓存 resetCommonCaches(); } } }

关键洞察refresh()方法清晰地划分了阶段。Bean 的定义(BeanDefinition)加载Bean 的实例化是分开的。第 2 步就完成了所有 Bean 定义的注册,而真正的对象创建在第 11 步。这解释了为什么在BeanPostProcessor中不能依赖还未完全初始化的 Bean。

2.2 Bean 生命周期的详细拆解:从定义到销毁

第 11 步finishBeanFactoryInitialization()最终会调用DefaultListableBeanFactorypreInstantiateSingletons()方法,开始实例化 Bean。单个 Bean 的创建流程如下:

  1. 实例化(Instantiation):调用构造函数创建原始对象。如果配置了lookup-method@Lookup,则会使用 CGLIB 生成子类。
  2. 属性填充(Population):为对象的属性赋值。这是@Autowired@Value@Resource等注解生效的地方。AutowiredAnnotationBeanPostProcessor在此阶段工作。
  3. Aware 接口回调:如果 Bean 实现了BeanNameAwareBeanFactoryAware等接口,会在此刻注入相关信息。
  4. BeanPostProcessor.postProcessBeforeInitialization:所有BeanPostProcessorbefore方法被调用。例如,@PostConstruct注解的处理器就在此阶段执行标注的方法。
  5. 初始化(Initialization):如果 Bean 实现了InitializingBean接口,调用afterPropertiesSet()方法。如果配置了init-method,也在此处调用。
  6. BeanPostProcessor.postProcessAfterInitialization:所有BeanPostProcessorafter方法被调用。这是AOP 代理对象生成的关键时机。如果 Bean 需要被代理(如事务、异步方法),AbstractAutoProxyCreator会在这里返回一个代理对象,而不是原始对象。
  7. 销毁(Destruction):容器关闭时,如果 Bean 实现了DisposableBean接口,调用destroy()方法,或执行配置的destroy-method

一个常见的误区:很多人认为 AOP 代理是在 Bean 初始化“之后”才创建的。实际上,代理是在postProcessAfterInitialization中创建的,它包装了已经完成属性填充和初始化的原始对象。这意味着,如果你在@PostConstruct方法里调用自己的另一个@Transactional方法,事务可能不生效,因为此时this引用的是原始对象,而不是代理对象。

2.3 循环依赖与三级缓存:为什么是三级,不是两级?

这是面试必考题。Spring 只能解决单例模式、Setter 注入(或字段注入)的循环依赖。构造函数注入的循环依赖无法解决。

三级缓存的定义(在DefaultSingletonBeanRegistry中):

/** 一级缓存:存放完整的单例 Bean */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** 二级缓存:存放早期的 Bean(已实例化,但未完成属性填充和初始化),用于解决循环依赖 */ private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); /** 三级缓存:存放 ObjectFactory,用于生成早期引用(可能被 AOP 代理) */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

解决循环依赖的流程(以 A 依赖 B,B 依赖 A 为例):

  1. 开始创建 A,实例化 A(调用构造函数),得到一个原始对象。此时,将 A 的ObjectFactory放入三级缓存
  2. 为 A 进行属性填充,发现需要 B。于是去获取 B。
  3. 开始创建 B,实例化 B,将 B 的ObjectFactory放入三级缓存。
  4. 为 B 进行属性填充,发现需要 A。于是去获取 A。
  5. 关键步骤:获取 A。首先从一级缓存找,没有。从二级缓存找,也没有。从三级缓存找,找到了 A 的 ObjectFactory。调用getObject()方法。这个方法会执行SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference()。如果 A 需要被 AOP 代理,这里就会提前生成代理对象;如果不需要,则返回原始对象。将这个早期引用放入二级缓存,并从三级缓存删除 A 的工厂。将获取到的 A(可能是代理)注入给 B。
  6. B 完成属性填充、初始化,成为一个完整的 Bean,放入一级缓存。同时清理二、三级缓存中关于 B 的条目。
  7. B 创建完毕,返回给正在创建 A 的流程。
  8. A 拿到了 B,完成自己的属性填充、初始化。最后,A 也要放入一级缓存。但在放入之前,它会检查二级缓存中是否已经有自己的早期引用(即第5步放入的那个)。如果有,说明发生了循环依赖,并且可能已经生成了代理。此时,Spring 会确保最终放入一级缓存的对象,与之前暴露给其他 Bean 的早期引用是同一个对象(即那个可能被代理的对象)。

为什么需要三级缓存?

  • 一级缓存:存放最终可用的 Bean。
  • 二级缓存:存放“不完整但已暴露”的 Bean,避免重复创建早期引用。
  • 三级缓存ObjectFactory):核心在于延迟决策。它封装了生成早期引用的逻辑。如果 A 需要代理,在 B 依赖 A 时(第5步),通过ObjectFactory可以生成代理对象;如果 A 不需要代理,则返回原始对象。如果将生成早期引用的逻辑直接放在二级缓存,那么所有 Bean 在实例化后都必须立即判断是否需要代理并生成,这不符合 Spring 的设计(代理通常在postProcessAfterInitialization中生成)。三级缓存将“是否提前生成代理”的决策推迟到真正发生循环依赖、需要暴露引用的时候。

3. Spring MVC:一个 HTTP 请求的完整旅程

理解了 IOC,我们再看 Web 层。DispatcherServlet是 Spring MVC 的心脏,它继承了HttpServlet

3.1 DispatcherServlet 的核心处理流程

其核心方法是doDispatch()。一个请求的大致流程如下:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { // ... 变量定义 try { // 1. 确定处理当前请求的 Handler(Controller 中的方法) mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { // 没有找到 Handler,返回 404 noHandlerFound(processedRequest, response); return; } // 2. 确定处理该 Handler 的 HandlerAdapter(适配器,如 RequestMappingHandlerAdapter) HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // 3. 执行拦截器的 preHandle 方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; // 如果 preHandle 返回 false,直接返回 } // 4. 真正的处理器执行:调用 Controller 方法,并返回 ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // 5. 应用默认视图名(如果返回的是 String 视图名) applyDefaultViewName(processedRequest, mv); // 6. 执行拦截器的 postHandle 方法 mappedHandler.applyPostHandle(processedRequest, response, mv); // 7. 处理结果:渲染视图,或处理 @ResponseBody processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { // ... 异常处理 } finally { // 8. 执行拦截器的 afterCompletion 方法(无论成功失败都会执行) if (mappedHandler != null) { mappedHandler.triggerAfterCompletion(processedRequest, response, dispatchException); } // ... 其他清理 } }

3.2 关键组件深度解析

  • HandlerMapping:负责将请求 URL 映射到具体的 Handler(通常是HandlerMethod对象,封装了 Controller 类和方法信息)。RequestMappingHandlerMapping是处理@RequestMapping及其衍生注解(@GetMapping等)的默认实现。
  • HandlerAdapter:负责实际调用 Handler。因为 Handler 可能是多种类型(如基于@Controller的类、HttpRequestHandler等),适配器模式统一了调用接口。RequestMappingHandlerAdapter是最常用的,它负责:
    1. 利用HandlerMethodArgumentResolver解析方法参数(将HttpServletRequest@RequestBody@PathVariable等转换成 Java 对象)。
    2. 调用目标方法。
    3. 利用HandlerMethodReturnValueHandler处理返回值(将返回的 Object 转换为ModelAndView或直接写入HttpServletResponse)。
  • ViewResolver:将逻辑视图名(如"home")解析为具体的View对象(如JstlViewThymeleafView)。
  • HandlerExceptionResolver:处理 Controller 层抛出的异常,将其转换为特定的错误响应(如 ModelAndView 或 JSON)。

一个常见问题的根源:当你使用@ResponseBody@RestController时,返回值是如何变成 JSON 的?关键在于RequestMappingHandlerAdapter会在初始化时注册一系列HandlerMethodReturnValueHandler,其中RequestResponseBodyMethodProcessor负责处理@ResponseBody。它内部会使用HttpMessageConverter(如MappingJackson2HttpMessageConverter)将对象序列化为 JSON 并写入 Response。

3.3 拦截器(Interceptor) vs 过滤器(Filter)

  • 执行位置Filter是 Servlet 规范,在DispatcherServlet之前之后执行。Interceptor是 Spring MVC 的机制,在DispatcherServlet内部,Handler执行前后执行(见上面doDispatch流程的第3、6、8步)。
  • 依赖关系Interceptor是 Spring Bean,可以享受 Spring 的依赖注入和 AOP 等功能。Filter不是 Spring Bean(除非使用DelegatingFilterProxy),通常无法直接注入 Spring 管理的服务。
  • 使用场景:权限校验、日志记录既可以用Filter也可以用Interceptor。但如果需要访问 Spring 的HandlerMethod信息(如判断是否有@RequiresPermissions注解),则必须用Interceptor

4. MyBatis 源码核心:SQL 是如何被执行

MyBatis 的核心是SqlSession。但更底层的是ExecutorStatementHandlerParameterHandlerResultSetHandler这四个组件。

4.1 一次查询的完整链路

// 简化版查询流程 (以 DefaultSqlSession.selectOne 为例) public <T> T selectOne(String statement, Object parameter) { // 1. 根据 statement id (如 com.xxx.UserMapper.selectById) 获取 MappedStatement MappedStatement ms = configuration.getMappedStatement(statement); // 2. 执行查询,底层委托给 Executor return executor.query(ms, wrapCollection(parameter), RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER); }

Executor是执行器,负责整个 SQL 执行过程的调度。以SimpleExecutor为例:

  1. 创建 StatementHandler:根据MappedStatement中的statementType(STATEMENT, PREPARED, CALLABLE)创建对应的StatementHandler(如PreparedStatementHandler)。
  2. 创建 ParameterHandler:用于对 PreparedStatement 设置参数。
  3. 创建 ResultSetHandler:用于将 ResultSet 转换为 Java 对象。
  4. 执行:调用StatementHandler.prepare()创建StatementParameterHandler.setParameters()设置参数,StatementHandler.execute()执行 SQL,最后ResultSetHandler.handleResultSets()处理结果集。

4.2 #{} 和 ${} 的本质区别

这是 MyBatis 中最容易混淆的点,也直接关系到 SQL 注入安全。

  • #{}:会被解析为 JDBC 的PreparedStatement的占位符?。参数值会经过ParameterHandler进行类型处理和安全地设置。能有效防止 SQL 注入

    <!-- XML 中 --> SELECT * FROM user WHERE name = #{name} <!-- 最终执行的 SQL: SELECT * FROM user WHERE name = ? --> <!-- 参数 'Alice' 会被安全地设置进去 -->
  • ${}:是纯粹的字符串替换。MyBatis 会将${}中的内容直接替换到 SQL 语句中,不会进行转义或参数化

    <!-- XML 中 --> SELECT * FROM ${tableName} WHERE id = #{id} <!-- 如果 tableName 的值为 `user`,则最终 SQL 为: SELECT * FROM user WHERE id = ? --> <!-- 这里 `${tableName}` 是直接替换,`#{id}` 是参数化。 -->

关键源码位置:在SqlSourceBuilderparse()方法中,会对 SQL 进行解析。#{}被识别为ParameterMappingTokenHandler处理,生成?和参数映射。${}被识别为TextTokenHandler处理,直接替换文本。

安全建议永远不要用${}来拼接用户输入的、用于 WHERE 条件或 VALUES 的值。它只应用于动态指定表名、列名等数据库元数据,且这些值必须是可信的(如来自枚举或配置)。

4.3 一级缓存与二级缓存

  • 一级缓存(本地缓存)

    • 范围SqlSession级别。同一个SqlSession执行相同的查询,第二次会直接从缓存返回。
    • 生命周期:与SqlSession相同。SqlSession关闭,缓存清空。执行insertupdatedelete或调用sqlSession.clearCache()也会清空该SqlSession的一级缓存。
    • 实现:在BaseExecutor中有一个localCache字段(PerpetualCache)。
  • 二级缓存

    • 范围Mapper命名空间级别(可跨SqlSession)。多个SqlSession操作同一个 Mapper,可以共享缓存。
    • 开启:需要在 MyBatis 全局配置中开启<setting name="cacheEnabled" value="true"/>,并在具体的 Mapper XML 中添加<cache/>标签。
    • 生命周期:整个应用生命周期。缓存数据会序列化/反序列化存储。执行对应 Mapper 的insertupdatedelete操作会清空该命名空间的二级缓存。
    • 实现CachingExecutor装饰了基本的Executor。查询时先查二级缓存,再查一级缓存,最后查数据库。

一个常见的坑:在分布式或微服务环境下,默认的二级缓存(PerpetualCache)是单机内存缓存,会导致数据不一致。生产环境通常需要集成 Redis 等分布式缓存来实现二级缓存。

5. Spring Boot 的魔法:自动配置与 Starter 原理

Spring Boot 的核心目标是“约定大于配置”。它的魔法主要来自@SpringBootApplication注解和spring-boot-autoconfigure模块。

5.1 @SpringBootApplication 的三合一

这个注解是三个注解的合成:

  • @SpringBootConfiguration:本质是@Configuration,标记该类为配置类。
  • @EnableAutoConfiguration开启自动配置的核心
  • @ComponentScan:扫描当前包及其子包下的组件。

5.2 自动配置的触发机制

@EnableAutoConfiguration的关键是@Import(AutoConfigurationImportSelector.class)

  1. AutoConfigurationImportSelector会调用SpringFactoriesLoader.loadFactoryNames()方法。
  2. 这个方法会从所有依赖 jar 包的META-INF/spring.factories文件中,读取org.springframework.boot.autoconfigure.EnableAutoConfiguration键对应的配置类全限定名列表。
  3. Spring Boot 内置的spring-boot-autoconfigure-x.x.x.jar中就有一个spring.factories文件,里面定义了上百个自动配置类,如DataSourceAutoConfigurationJacksonAutoConfigurationWebMvcAutoConfiguration等。

5.3 条件化配置:@Conditional 家族

自动配置类不会无条件生效。它们大量使用了@ConditionalOnXxx注解。

  • @ConditionalOnClass:当类路径下存在指定的类时才生效。例如,DataSourceAutoConfiguration上有@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }),意味着只有你的项目中引入了数据库相关的 jar(包含了这些类),这个自动配置才会被加载。
  • @ConditionalOnMissingBean:当 Spring 容器中不存在指定类型的 Bean 时才生效。这是自动配置的“退让”机制。它允许你通过自定义@Bean来覆盖自动配置提供的默认 Bean。
  • @ConditionalOnProperty:当指定的配置属性满足条件时才生效。
  • @ConditionalOnWebApplication/@ConditionalOnNotWebApplication:根据应用类型决定。

这就是为什么你引入spring-boot-starter-web后,Tomcat 会自动启动,DispatcherServlet会自动配置的原因。相应的自动配置类(如ServletWebServerFactoryAutoConfigurationDispatcherServletAutoConfiguration)满足了条件。

5.4 理解 Starter:一个“懒人包”

Starter 本身通常不包含代码,它只是一个pom.xml文件,定义了该功能所需的一组依赖。例如spring-boot-starter-web引入了 Spring MVC、Tomcat、Jackson 等。

最佳实践:当你需要为团队封装一个通用功能(如短信服务、分布式锁)时,可以创建一个自定义 Starter。它的核心是:

  1. 一个autoconfigure模块:包含你的自动配置类(使用@Configuration@Conditional)和核心业务代码。
  2. 一个starter模块:只包含一个pom.xml,依赖autoconfigure模块和其他必要库。然后在autoconfigure模块的META-INF/spring.factories中声明你的自动配置类。

6. 实战:从源码角度解决一个典型问题——@Transactional 失效

理论结合实践。我们分析一个高频问题:在同一个类中,一个非事务方法 A 调用事务方法 B,事务不生效。

@Service public class UserService { public void updateUser() { // 一些非数据库操作... this.insertLog(); // 调用本类的事务方法 } @Transactional public void insertLog() { // 插入日志到数据库 logMapper.insert(...); } }

原因分析: Spring 的声明式事务是基于 AOP 代理实现的。当调用updateUser()时,实际上是在调用代理对象的方法。代理对象的方法内部会先开启事务,再调用目标对象(原始UserService对象)的方法。但是,在updateUser()方法内部,this.insertLog()中的this指的是目标对象本身,而不是代理对象。因此,这个调用绕过了代理,事务切面无法介入,@Transactional注解自然失效。

源码佐证:事务的 AOP 代理是在BeanPostProcessor.postProcessAfterInitialization阶段由AbstractAutoProxyCreator创建的。代理对象只拦截从外部进入的调用。内部调用发生在目标对象内部,代理机制无法感知。

解决方案

  1. (推荐)将事务方法抽取到另一个 Service:这是最清晰的方式,符合单一职责。
  2. 在类中注入自身的代理:通过@AutowiredApplicationContext.getBean()获取代理对象,然后调用。
    @Service public class UserService { @Autowired private UserService selfProxy; // 注入代理 public void updateUser() { selfProxy.insertLog(); // 通过代理调用,事务生效 } @Transactional public void insertLog() { ... } }
  3. 使用 AspectJ 的编译时/加载时织入(LTW):这种方式可以直接修改字节码,使得内部调用也能被拦截,但配置复杂,一般不推荐。

7. 常见问题与排查思路

问题现象可能原因排查方式解决方案
Bean 创建失败,提示NoSuchBeanDefinitionException1. Bean 未被扫描到(包路径不对)
2. Bean 的依赖 Bean 不存在
3.@Conditional条件不满足
1. 检查@ComponentScan或 Spring Boot 主类位置
2. 检查依赖 Bean 的配置和条件
3. 开启debug=true查看自动配置报告
调整包路径,检查依赖,查看条件注解
@Autowired注入失败,提示No qualifying bean1. 存在多个同类型 Bean,未指定@Qualifier
2. Bean 的作用域不是 Singleton,且未在正确的上下文中获取
1. 检查是否有多个实现类
2. 检查 Bean 的作用域(如@Scope("prototype")
使用@Qualifier@Primary,检查作用域
MyBatis 查询结果映射失败,属性为 null1. 数据库列名与 Java 属性名不匹配(下划线转驼峰)
2.resultMap配置错误
3. 类型处理器(TypeHandler)缺失
1. 检查 SQL 查询返回的列名
2. 开启 MyBatis 的日志,查看实际执行的 SQL 和返回结果
3. 检查mybatis.configuration.map-underscore-to-camel-case配置
配置列别名,使用resultMap,配置全局驼峰映射或自定义 TypeHandler
Spring MVC 接口返回 4041.@RequestMapping路径错误
2. Controller 未被扫描到(不是@RestController/@Controller
3. 静态资源路径冲突
1. 检查控制台启动日志,看 HandlerMapping 的注册信息
2. 检查是否在@ComponentScan范围内
检查注解和路径,查看DispatcherServlet映射路径(默认/
事务不生效1. 方法非public
2. 异常未被正确抛出(被 catch 吞没)
3. 数据库引擎不支持事务(如 MyISAM)
4. 同 Bean 内方法调用(见第6节)
1. 检查方法修饰符
2. 检查异常类型(默认只回滚RuntimeExceptionError
3. 检查数据库引擎和连接池配置
确保方法为public,抛出RuntimeException,检查@Transactional(rollbackFor=Exception.class),避免自调用

8. 最佳实践与工程建议

  1. 理解而非记忆:不要死记硬背源码的类名和方法名。理解设计模式(工厂、模板方法、代理、装饰器)在 Spring 中的应用,理解核心流程(容器启动、Bean 生命周期、请求处理、SQL 执行),这比记住所有细节更重要。
  2. 善用调试工具:在遇到复杂问题时,在关键类(如AbstractAutowireCapableBeanFactory.doCreateBeanDispatcherServlet.doDispatchSimpleExecutor.doQuery)上打上断点,跟着调试一遍,是理解源码最直接的方式。
  3. 关注官方文档和版本更新:Spring 和 MyBatis 的官方文档质量很高。每个大版本(如 Spring 5.x 到 6.x)都可能引入重要变化(如 Spring 6 的 HTTP 接口客户端、MyBatis 3.5 的新特性),关注更新日志可以提前规避兼容性问题。
  4. 生产环境谨慎使用高级特性:对于循环依赖,尽管 Spring 能解决一部分,但在设计上应尽量避免。对于 MyBatis 的二级缓存,在分布式环境下要使用集中式缓存实现。对于 Spring 的@Async@Transactional等基于代理的 AOP 功能,要清楚其局限性(如自调用问题)。
  5. 自定义扩展点:在理解源码的基础上,可以合理使用 Spring 提供的扩展点,如BeanPostProcessorBeanFactoryPostProcessorHandlerMethodArgumentResolverHandlerMethodReturnValueHandler、MyBatis 的Interceptor(插件)等,来实现定制化需求,而不是粗暴地修改框架代码。

9. 总结与后续方向

通过本文的梳理,我们不再将 Spring、MyBatis、Spring Boot 视为黑盒。我们从 IOC 容器的refresh()方法出发,看到了 Bean 从定义到销毁的完整旅程;我们跟随一个 HTTP 请求穿越了DispatcherServlet的层层关卡;我们拆解了 MyBatis 将#{}转换为?的精密过程;我们也揭开了 Spring Boot Starter 自动配置的神秘面纱。

源码阅读的价值,在于当系统出现“反直觉”的行为时,你能拥有直指问题根源的洞察力,而不是盲目地尝试和搜索。它让你在技术选型、架构设计和代码评审时,能做出更合理的决策。

如果你希望继续深入:

  • Spring 方面:可以研究Spring AOPAspectJ的集成、Spring Transaction的传播机制源码、Spring Security的过滤器链和投票器机制。
  • MyBatis 方面:可以深入研究插件(Interceptor)的开发,如何实现分页、慢SQL日志、数据加解密等通用功能。
  • Spring Boot 方面:可以学习Actuator端点原理、Spring Boot Admin的集成、以及如何编写一个高质量的自定义 Starter。

技术的深度,决定了你解决问题的能力边界。从今天起,尝试带着问题去阅读源码,你收获的将不仅仅是面试题的答案,更是成为一名优秀架构师的坚实基础。建议将本文作为一份原理地图收藏,在未来的开发实践中反复对照和印证。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/20 15:22:04

一条IN子查询拖垮整个集群,AI揪出“Late Semijoin“缺失的隐秘角落!

一、案发现场&#xff1a;一条人畜无害的 EXISTS&#xff0c;差点让我卷铺盖走人 核心业务做信创替换&#xff0c;把一套跑了5年的核心交易流水系统&#xff0c;从 MySQL 8.0 迁移到某款主打“HTAP、高并发”的国产分布式数据库&#xff08;具体名字我就不点了&#xff0c;圈子…

作者头像 李华
网站建设 2026/8/20 15:19:37

B/S端界面控件DevExtreme中文使用指南——如何自定义图标

DevExtreme拥有高性能的HTML5 / JavaScript小部件集合&#xff0c;使您可以利用现代Web开发堆栈&#xff08;包括React&#xff0c;Angular&#xff0c;ASP.NET Core&#xff0c;jQuery&#xff0c;Knockout等&#xff09;构建交互式的Web应用程序&#xff0c;该套件附带功能齐…

作者头像 李华
网站建设 2026/8/20 15:18:26

并发服务故障复盘的排查路径

并发服务故障复盘的排查路径 “日常巡检怎样少走弯路”首先要落到可观察、可回滚的工程动作上。本文从配置、调用链和运行指标三个层面梳理判断方法&#xff0c;重点说明应先收集什么证据、怎样做小范围验证&#xff0c;以及何时应停止扩张改动。 本文围绕“并发服务故障复盘的…

作者头像 李华
网站建设 2026/8/20 15:17:35

企业文档如何自动摘要与翻译:流程与质量控制

所属分类&#xff1a;AI/模型 产品案例页&#xff1a;企业文档摘要翻译台 | 产品案例 | GuGuData Engineering 产品定位与截图范围 企业文档摘要翻译台属于AI/模型场景&#xff0c;面向企业跨语言资料处理的双语摘要翻译台&#xff0c;截图重点是文档队列、源语言与目标语言选择…

作者头像 李华
网站建设 2026/8/20 15:16:57

智能体协作服务变慢时先查哪里

智能体协作服务变慢时先查哪里分类&#xff1a;[工程技术]在 AI Agent 架构设计与多 Agent 协作系统搭建中&#xff0c;当系统在并发增加时出现响应延迟陡增&#xff08;如从 200ms 飙升至数秒&#xff09;且 CPU 无法打满时&#xff0c;底层原因往往并非大模型 API 响应变慢&a…

作者头像 李华