Spring源码深度剖析:IOC容器、MVC流程与MyBatis执行机制
很多Java开发者都有过这样的困惑:为什么我的Spring项目启动越来越慢?为什么一个简单的依赖注入,在并发场景下偶尔会报错?为什么MyBatis的Mapper接口明明没有实现类,却能执行SQL?这些看似“魔法”的背后,其实是Spring生态核心源码在默默工作。
如果你只是停留在“会用”Spring Boot,那么当线上出现一个诡异的循环依赖问题,或者MyBatis缓存导致的数据不一致时,你很可能束手无策。源码阅读不是炫技,而是解决问题、深入理解框架设计思想、写出更健壮代码的必经之路。然而,面对Spring庞大的代码库,很多人不知从何入手,最终浅尝辄止。
本文不打算平铺直叙地罗列每个类的继承关系,而是聚焦于 三个最核心、也最能解决实际问题的源码剖析场景 :IOC容器的启动与Bean生命周期(解决依赖注入与循环依赖之谜)、Spring MVC的请求处理流程(理解Web层核心)、以及MyBatis的SQL执行引擎(揭秘接口代理与缓存机制)。我们会结合高频面试题和真实开发中的“坑”,带你穿透表面API,直抵设计精髓。读完本文,你将能清晰地回答上述问题,并具备独立分析和调试其他Spring模块源码的能力。
1. 为什么要读Spring源码?从“会用”到“精通”的关键一跃
在开始深入代码之前,我们必须先统一认知:读源码的目标是什么?对于大多数开发者,目标不是成为Spring Committer,而是为了:
- 高效排查问题 :当遇到
BeanCurrentlyInCreationException(循环依赖)、NoSuchBeanDefinitionException或事务不生效等复杂问题时,能通过日志和堆栈,快速定位到框架层面的根源,而不是盲目地重启和祈祷。 - 深度性能优化 :理解Bean的创建过程、代理机制、缓存策略,才能在设计应用时避免性能陷阱。例如,为什么
@Scope("prototype")的Bean不适合注入到单例Bean中?MyBatis一级缓存为什么在Spring整合后容易引发问题? - 做出更好的架构设计 :Spring本身是一个优秀的设计范本。学习它的设计模式(工厂、模板方法、代理、观察者等)、模块划分、扩展点设计(
BeanPostProcessor,BeanFactoryPostProcessor),能极大提升自己的软件设计能力。 - 应对高级面试 :关于Spring的面试问题早已超越“说说AOP原理”的层面,深入到三级缓存解决循环依赖的细节、Spring MVC九大组件协作流程、MyBatis插件(Interceptor)的执行顺序等。
因此,本文的剖析将始终围绕“问题驱动”和“场景驱动”展开。我们不会孤立地看一个类,而是看它在整个流程中扮演的角色,以及它如何与其他组件协作来解决一个具体的工程问题。
2. 核心基石:IOC容器启动与Bean生命周期全解析
IOC(控制反转)是Spring的基石。它的核心是 BeanFactory 和 ApplicationContext 。我们常说的“Spring容器启动”,本质上就是 ApplicationContext 的初始化过程,这个过程完成了配置解析、Bean定义加载、Bean创建、依赖注入、初始化等一系列复杂操作。
2.1 容器启动的核心流程: AbstractApplicationContext.refresh()
这是整个Spring IOC容器启动的“总指挥”方法。它定义了清晰且可扩展的12个步骤:
// 简化版流程,位于 org.springframework.context.support.AbstractApplicationContext
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 1. 准备刷新上下文,设置启动时间、激活状态,初始化属性源(PropertySources)
prepareRefresh();
// 2. 获取刷新后的内部Bean工厂(ConfigurableListableBeanFactory)。
// 对于GenericApplicationContext,这里会创建并加载Bean定义。
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 3. 配置Bean工厂的标准特性,如类加载器、EL表达式解析器、属性编辑器等。
prepareBeanFactory(beanFactory);
try {
// 4. 允许子上下文对Bean工厂进行后置处理。这是第一个重要的扩展点。
// 例如,Web应用上下文会在这里注册Servlet相关的Scope和Bean。
postProcessBeanFactory(beanFactory);
// 5. 实例化并调用所有注册的BeanFactoryPostProcessor。
// 这是第二个关键扩展点!用于修改Bean定义(BeanDefinition)。
// 例如:PropertySourcesPlaceholderConfigurer 就是在这里解析 `${}` 占位符的。
invokeBeanFactoryPostProcessors(beanFactory);
// 6. 注册BeanPostProcessor。这些处理器将在Bean的初始化前后被调用。
// 这是第三个关键扩展点!AOP、事务等功能的代理对象创建都依赖它。
registerBeanPostProcessors(beanFactory);
// 7. 初始化消息源(MessageSource),用于国际化。
initMessageSource();
// 8. 初始化应用事件广播器(ApplicationEventMulticaster)。
initApplicationEventMulticaster();
// 9. 留给子类的特殊刷新逻辑。例如,Spring Boot 的 EmbeddedWebApplicationContext 会在这里创建内嵌Servlet容器。
onRefresh();
// 10. 注册监听器(ApplicationListener),并发布早期事件。
registerListeners();
// 11. 核心步骤:实例化所有剩余的非懒加载单例Bean。
finishBeanFactoryInitialization(beanFactory);
// 12. 完成刷新,发布ContextRefreshedEvent事件。
finishRefresh();
} catch (BeansException ex) {
// ... 异常处理和销毁已创建的单例Bean
destroyBeans();
cancelRefresh(ex);
throw ex;
} finally {
// 重置Spring核心的公共缓存,例如反射元数据缓存。
resetCommonCaches();
}
}
}
关键洞察 : refresh() 方法是一个典型的 模板方法模式 。它定义了容器启动的固定骨架,但将许多具体步骤(如 postProcessBeanFactory , onRefresh )留给子类去实现,提供了极强的扩展性。理解这个流程,你就掌握了Spring容器启动的“地图”。
2.2 Bean的生命周期:从定义到销毁的十九个步骤
一个Bean从被定义到完全就绪,经历了远比我们想象中复杂的旅程。下图概括了单例Bean的核心生命周期(以注解驱动为例):
Bean定义加载 (BeanDefinition)
↓
BeanFactoryPostProcessor 干预 (修改BeanDefinition)
↓
实例化 (Instantiation) - 调用构造器
↓
属性填充 (Population) - 依赖注入 (@Autowired, @Value等)
↓
Aware接口回调 (BeanNameAware, BeanFactoryAware, ApplicationContextAware等)
↓
BeanPostProcessor.postProcessBeforeInitialization
↓
初始化 (Initialization) - @PostConstruct, InitializingBean.afterPropertiesSet(), init-method
↓
BeanPostProcessor.postProcessAfterInitialization (AOP代理在此创建!)
↓
Bean就绪,放入单例池
↓
使用中...
↓
容器关闭
↓
@PreDestroy, DisposableBean.destroy(), destroy-method
代码示例:一个Bean的生命周期演示
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class LifecycleDemoBean implements BeanNameAware, BeanFactoryAware,
ApplicationContextAware, InitializingBean, DisposableBean {
private String name;
public LifecycleDemoBean() {
System.out.println("1. 构造器调用 - 实例化");
}
public void setName(String name) {
this.name = name;
System.out.println("2. 属性设置 - " + name);
}
@Override
public void setBeanName(String name) {
System.out.println("3. BeanNameAware.setBeanName - " + name);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("4. BeanFactoryAware.setBeanFactory");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("5. ApplicationContextAware.setApplicationContext");
}
@PostConstruct
public void postConstruct() {
System.out.println("6. @PostConstruct 方法调用");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("7. InitializingBean.afterPropertiesSet");
}
public void customInit() {
System.out.println("8. 自定义 init-method");
}
// 这个方法通常由BeanPostProcessor调用,例如AOP创建代理
public void doBusiness() {
System.out.println("9. 业务方法执行 - Bean已完全就绪");
}
@PreDestroy
public void preDestroy() {
System.out.println("10. @PreDestroy 方法调用");
}
@Override
public void destroy() throws Exception {
System.out.println("11. DisposableBean.destroy");
}
public void customDestroy() {
System.out.println("12. 自定义 destroy-method");
}
}
在配置中指定初始化/销毁方法:
<bean id="lifecycleDemo" class="com.example.LifecycleDemoBean"
init-method="customInit" destroy-method="customDestroy">
<property name="name" value="TestBean"/>
</bean>
运行后,控制台输出将清晰地展示这个顺序。理解这个顺序对于解决 @PostConstruct 中依赖注入未完成、AOP代理未生效等问题至关重要。
2.3 循环依赖与三级缓存:Spring的经典解决方案
循环依赖是面试高频考点,也是实际开发中容易踩坑的地方。Spring通过 三级缓存 巧妙地解决了单例Bean的Setter注入和字段注入( @Autowired )的循环依赖问题。
三级缓存定义(在 DefaultSingletonBeanRegistry 中):
- 一级缓存
singletonObjects:存放完全初始化好的单例Bean。ConcurrentHashMap。我们getBean()最终就是从这里取。 - 二级缓存
earlySingletonObjects:存放早期暴露的Bean引用(已实例化,但未完成属性填充和初始化)。用于解决循环依赖。 - 三级缓存
singletonFactories:存放创建Bean的工厂对象(ObjectFactory)。用于在循环依赖时,决定是否返回原始对象还是代理对象。
解决循环依赖的核心流程(以A依赖B,B依赖A为例):
- 开始创建A,实例化A(调用构造器),得到一个 原始对象 。
- 将A的
ObjectFactory放入 三级缓存 。 - 开始为A进行属性填充(
populateBean),发现需要注入B。 - 转去创建B,实例化B,将B的
ObjectFactory放入三级缓存。 - 为B进行属性填充,发现需要注入A。
- 此时,B去获取A:
- 从一级缓存未找到。
- 从二级缓存未找到。
- 从三级缓存找到A的ObjectFactory ,调用
getObject()。这个方法可能会返回A的原始对象,也可能返回一个A的 早期代理对象 (如果A需要被AOP代理)。将返回的对象放入 二级缓存 ,并 删除三级缓存中A的工厂 。 - B成功获得A的引用(可能是代理),完成属性填充和初始化,成为一个完整的Bean,放入 一级缓存 。
- B创建完毕,返回给正在创建A的流程。
- A成功注入B,继续完成自己的属性填充和初始化。
- A初始化完成后,将自己放入 一级缓存 ,并 删除二级和三级缓存中关于A的记录 。
为什么需要三级缓存,而不是两级? 关键在于 AOP代理 。如果A需要被AOP增强(例如有 @Transactional ),那么在它完全初始化之前,就需要为其创建一个代理对象。这个代理对象的创建时机非常微妙。三级缓存中的 ObjectFactory 就是一个“决策器”,它能在发现循环依赖时,判断是直接返回原始对象,还是需要先创建一个早期代理对象返回。如果只有二级缓存,那么提前放入的只能是原始对象或最终代理对象中的一个,无法应对这种动态决策的场景。
重要限制 :Spring 无法解决构造器注入(Constructor Injection)的循环依赖 。因为构造器注入发生在实例化阶段,此时Bean的实例还未创建,更无法提前暴露引用到缓存中。因此,良好的实践是 优先使用Setter注入或字段注入 ,并在设计时尽量避免循环依赖。
3. Spring MVC请求处理流程:从Servlet到Controller的旅程
当我们在Controller中写一个 @GetMapping 方法时,一个HTTP请求是如何精准路由到这个方法并执行的?理解这个流程,对于定制拦截器、处理全局异常、实现参数解析等高级功能必不可少。
Spring MVC的核心是 DispatcherServlet ,它作为前端控制器,统一处理请求。其核心处理方法是 doDispatch() 。
3.1 doDispatch() 主流程剖析
// 简化版 org.springframework.web.servlet.DispatcherServlet.doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
ModelAndView mv = null;
// 1. 检查是否是文件上传请求,并进行包装
processedRequest = checkMultipart(request);
try {
// 2. 为当前请求确定一个处理器执行链 (Handler + Interceptors)
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
// 没有找到处理器,返回404
noHandlerFound(processedRequest, response);
return;
}
// 3. 获取适配器,用于执行处理器(适配不同类型的Handler,如@Controller方法、HttpRequestHandler等)
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 4. 执行处理器拦截器的 preHandle 方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
// 如果某个拦截器返回false,则中断执行
return;
}
// 5. 核心:实际执行处理器方法,返回ModelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 6. 如果返回的视图名需要默认前缀,则应用默认视图名
applyDefaultViewName(processedRequest, mv);
// 7. 执行处理器拦截器的 postHandle 方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception ex) {
// 处理执行过程中抛出的异常
dispatchException = ex;
}
// 8. 处理分发结果,渲染视图或处理异常
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
3.2 九大组件详解
DispatcherServlet 的顺利工作依赖于其持有的九大组件(在 initStrategies 方法中初始化)。理解它们,就掌握了Spring MVC的可扩展架构。
| 组件名 | 接口 | 作用 | 常见实现类 |
|---|---|---|---|
| HandlerMapping | HandlerMapping |
将请求映射到处理器(Handler)及其拦截器链。 | RequestMappingHandlerMapping (用于 @Controller ) |
| HandlerAdapter | HandlerAdapter |
用于执行处理器。屏蔽处理器类型的差异。 | RequestMappingHandlerAdapter (用于 @Controller 方法) |
| HandlerExceptionResolver | HandlerExceptionResolver |
解析请求处理过程中抛出的异常,将其转换为ModelAndView或错误响应。 | ExceptionHandlerExceptionResolver (用于 @ExceptionHandler ) |
| ViewResolver | ViewResolver |
将逻辑视图名解析为实际的 View 对象。 |
InternalResourceViewResolver (JSP), ThymeleafViewResolver |
| LocaleResolver | LocaleResolver |
解析客户端区域信息,用于国际化。 | AcceptHeaderLocaleResolver , CookieLocaleResolver |
| ThemeResolver | ThemeResolver |
解析主题,用于应用多套UI风格。 | FixedThemeResolver |
| MultipartResolver | MultipartResolver |
处理文件上传请求。 | CommonsMultipartResolver , StandardServletMultipartResolver |
| FlashMapManager | FlashMapManager |
管理 FlashMap ,用于重定向时传递参数。 |
SessionFlashMapManager |
| RequestToViewNameTranslator | RequestToViewNameTranslator |
当处理器未返回视图名时,根据请求URL生成默认视图名。 | DefaultRequestToViewNameTranslator |
关键洞察 :Spring MVC通过 HandlerAdapter 模式,优雅地支持了多种处理器类型(如基于注解的Controller、旧的 Controller 接口、 HttpRequestHandler 等),这是一种典型的 适配器模式 应用。当你需要支持一种新的处理器类型时,只需实现对应的 HandlerAdapter 即可。
3.3 参数解析与返回值处理的奥秘
RequestMappingHandlerAdapter 内部使用了一组 HandlerMethodArgumentResolver (参数解析器)和 HandlerMethodReturnValueHandler (返回值处理器)来工作。
- 参数解析 :当调用Controller方法时,如何将HTTP请求中的参数(Query Param, Path Variable, Header, Body等)转换成方法入参(String, Integer,
@RequestBodyPOJO,HttpServletRequest等)?这就是参数解析器的职责。例如:@RequestParam->RequestParamMethodArgumentResolver@PathVariable->PathVariableMethodArgumentResolver@RequestBody->RequestResponseBodyMethodProcessorHttpServletRequest->ServletRequestMethodArgumentResolver
- 返回值处理 :Controller方法执行后,返回一个
String、ModelAndView、ResponseEntity或普通对象,如何将其转换成HTTP响应?这是返回值处理器的职责。例如:@ResponseBody->RequestResponseBodyMethodProcessor- 返回
String(视图名) ->ViewNameMethodReturnValueHandler - 返回
ModelAndView->ModelAndViewMethodReturnValueHandler
自定义扩展示例:实现一个解析Token的注解
// 1. 自定义注解
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUser {
}
// 2. 实现参数解析器
public class CurrentUserMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 支持带有 @CurrentUser 注解的参数
return parameter.hasParameterAnnotation(CurrentUser.class);
}
@Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
String token = request.getHeader("Authorization");
// 根据token查询用户信息... (此处简化)
User user = userService.findUserByToken(token);
if (user == null) {
throw new UnauthorizedException("Invalid token");
}
return user;
}
}
// 3. 注册解析器 (通过Java Config)
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new CurrentUserMethodArgumentResolver());
}
}
// 4. 在Controller中使用
@RestController
public class UserController {
@GetMapping("/profile")
public UserProfile getProfile(@CurrentUser User currentUser) {
// 可以直接使用注入的当前用户对象
return userService.getProfile(currentUser.getId());
}
}
通过这个例子,你可以看到Spring MVC强大的可扩展性。理解这套机制,你就能轻松实现各种自定义的参数绑定逻辑。
4. MyBatis源码核心:SQL会话、执行器与缓存机制
MyBatis的核心是 SqlSession ,但真正执行SQL的“发动机”是 Executor 。理解 Executor 及其内部的 StatementHandler 、 ParameterHandler 、 ResultSetHandler ,是掌握MyBatis工作原理的关键。
4.1 一次SQL执行的完整链路
当我们调用 sqlSession.selectList(“com.example.mapper.UserMapper.selectAll”) 时,背后发生了什么?
- 获取Mapper代理对象 :通过
SqlSession.getMapper()获取的是一个动态代理对象(MapperProxy)。 - 代理对象调用 :调用Mapper接口方法时,会被
MapperProxy.invoke()拦截。 - 转换为SqlSession调用 :
MapperProxy根据方法名和所属Mapper接口,找到对应的MappedStatement(保存了SQL、参数映射、结果映射等所有信息),然后调用SqlSession的对应方法(如selectList)。 - Executor执行 :
SqlSession将请求委托给Executor。Executor首先查询 二级缓存 (如果启用且该语句配置了缓存)。- 缓存未命中,则调用
StatementHandler。
- StatementHandler处理 :
StatementHandler(例如PreparedStatementHandler)负责:- 从
Connection中创建PreparedStatement。 - 使用
ParameterHandler将Java参数设置到SQL占位符(?)中。 - 执行SQL,得到
ResultSet。
- 从
- 结果映射 :
ResultSetHandler使用ResultSet和MappedStatement中定义的ResultMap,将结果集转换为Java对象(List或单个对象)。 - 返回与缓存 :结果返回给
Executor,Executor将其放入 一级缓存 (SqlSession级别),如果配置了二级缓存,也会同步到二级缓存。最终结果返回给调用者。
4.2 一级缓存与二级缓存深度解析
一级缓存(本地缓存)
- 范围 :
SqlSession级别。同一个SqlSession多次执行相同的查询,只会查询一次数据库。 - 实现 :
BaseExecutor中的localCache(一个PerpetualCache,本质是HashMap)。 - 失效条件 :
- 执行了INSERT/UPDATE/DELETE操作(任何写操作)。
- 调用了
sqlSession.clearCache()。 - 执行了
sqlSession.commit()或rollback()(除非配置了本地缓存作用于事务生命周期)。 - 在配置中显式关闭了一级缓存(几乎不会这么做)。
- 坑点 :在Spring整合中,默认的
SqlSessionTemplate为每个DAO方法调用都会创建/关闭一个SqlSession(基于SqlSessionInterceptor)。这意味着 在Spring管理的事务中,一级缓存几乎无效 ,因为每次方法调用都是新的SqlSession。这是很多开发者误解的地方。
二级缓存(全局缓存)
- 范围 :
Mapper(Namespace)级别。多个SqlSession共享。 - 开启 :在MyBatis配置文件中设置
<setting name="cacheEnabled" value="true"/>,并在具体的Mapper XML中使用<cache/>标签。 - 实现 :底层使用装饰器模式(
Cache接口),默认是PerpetualCache,但可以通过<cache>标签的type属性指定其他实现(如Ehcache, Redis)。 - 工作流程 :
- 查询时,先看二级缓存。
- 二级缓存命中,直接返回。
- 未命中,查询数据库,结果放入一级缓存。
-
SqlSession关闭或提交时 ,一级缓存的内容才会被刷入二级缓存。
- 重要提醒 :二级缓存存储的是 数据对象 ,而不是结果集。如果多个
SqlSession修改了同一数据,可能导致缓存数据与数据库不一致。因此, 在读写频繁、对一致性要求高的场景下,慎用二级缓存 。使用时,需要仔细配置flushInterval、size、readOnly等属性,并可能需要在关联查询的Mapper上配置<cache-ref>。
4.3 插件(Interceptor)原理与实战
MyBatis插件是其扩展性的集中体现,允许你拦截四大核心对象的方法调用:
Executor(update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)StatementHandler(prepare, parameterize, batch, update, query)ParameterHandler(getParameterObject, setParameters)ResultSetHandler(handleResultSets, handleOutputParameters)
插件原理 :基于 动态代理 和 责任链模式 。MyBatis在创建上述对象时,会检查是否有配置的插件,如果有,则使用 Plugin.wrap() 方法为目标对象创建代理。当调用代理对象的方法时,会按插件配置顺序依次执行插件的 intercept 方法。
实战:实现一个慢SQL日志插件
// 1. 实现Interceptor接口
@Intercepts({
@Signature(type = StatementHandler.class,
method = "query",
args = {Statement.class, ResultHandler.class}),
@Signature(type = StatementHandler.class,
method = "update",
args = {Statement.class})
})
public class SlowSqlInterceptor implements Interceptor {
private static final Logger logger = LoggerFactory.getLogger(SlowSqlInterceptor.class);
private long threshold = 1000; // 阈值,单位毫秒,默认1秒
public void setThreshold(long threshold) {
this.threshold = threshold;
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.currentTimeMillis();
try {
// 继续执行原方法
return invocation.proceed();
} finally {
long end = System.currentTimeMillis();
long cost = end - start;
if (cost > threshold) {
// 获取SQL信息
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
BoundSql boundSql = statementHandler.getBoundSql();
String sql = boundSql.getSql();
// 简化处理,实际可格式化SQL、记录参数等
logger.warn("慢SQL执行耗时: {} ms, SQL: {}", cost, sql);
}
}
}
@Override
public Object plugin(Object target) {
// 使用Plugin工具类创建代理
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 从配置中读取阈值
String thresholdStr = properties.getProperty("threshold");
if (thresholdStr != null) {
this.threshold = Long.parseLong(thresholdStr);
}
}
}
配置插件 :
<!-- mybatis-config.xml -->
<plugins>
<plugin interceptor="com.example.plugin.SlowSqlInterceptor">
<property name="threshold" value="500"/> <!-- 超过500ms记录 -->
</plugin>
</plugins>
这个插件会拦截所有查询和更新操作,记录执行时间超过阈值的SQL,对于线上性能监控非常有用。
5. Spring Boot自动配置:约定大于配置的魔法
Spring Boot的“开箱即用”体验,核心在于 自动配置(Auto-Configuration) 。它基于类路径、已存在的Bean、属性配置等条件,自动为你配置Spring应用。
5.1 @SpringBootApplication 揭秘
这个注解是三个注解的合成注解:
@SpringBootConfiguration:标记该类为配置类(本质是@Configuration)。@EnableAutoConfiguration: 启用自动配置的核心 。@ComponentScan:启用组件扫描。
其中, @EnableAutoConfiguration 是关键,它通过 @Import(AutoConfigurationImportSelector.class) 导入选择器。
5.2 AutoConfigurationImportSelector 工作流程
- 加载候选配置 :
AutoConfigurationImportSelector会从META-INF/spring.factories文件中读取org.springframework.boot.autoconfigure.EnableAutoConfiguration键下的所有自动配置类全限定名(如org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration)。 - 条件过滤 :这些候选配置类并不会全部生效。每个配置类上都标有大量的
@ConditionalOnXxx注解(条件注解),如:@ConditionalOnClass:类路径下存在某个类时才生效。@ConditionalOnMissingBean:容器中不存在某个Bean时才生效。@ConditionalOnProperty:配置文件中存在特定属性且值为特定值时生效。
- 按序加载 :经过条件过滤后,剩下的配置类才会被真正加载到Spring容器中,定义所需的Bean。
示例: DataSourceAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@Conditional(EmbeddedDatabaseCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import(EmbeddedDataSourceConfiguration.class)
protected static class EmbeddedDatabaseConfiguration { }
@Configuration(proxyBeanMethods = false)
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Hikari.class, // 默认使用HikariCP
DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Dbcp2.class,
DataSourceConfiguration.Generic.class })
protected static class PooledDataSourceConfiguration { }
// ... 其他内部配置类
}
这个配置类告诉我们:
- 当类路径下存在
DataSource和EmbeddedDatabaseType类时,该配置才生效。 - 如果用户没有自己定义
DataSource或XADataSource类型的Bean,才会自动配置。 - 它导入了其他配置类,并根据条件选择是配置内嵌数据库(H2, HSQL, Derby)还是连接池数据库(默认HikariCP)。
5.3 如何自定义Starter与自动配置
理解自动配置后,我们可以创建自己的Starter,为团队或社区提供“开箱即用”的组件。
步骤1:创建自动配置模块
- 创建一个普通的Spring Boot项目,只包含
spring-boot-autoconfigure和spring-boot-configuration-processor依赖。 - 定义配置属性类(
@ConfigurationProperties)。 - 定义核心功能类。
- 定义自动配置类(
@Configuration),使用@Conditional系列注解控制生效条件,并使用@EnableConfigurationProperties导入属性类。 - 在
src/main/resources/META-INF/下创建spring.factories文件,添加:org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.yourcompany.autoconfigure.YourAutoConfiguration
步骤2:创建Starter模块
- 创建一个空项目,只包含一个
pom.xml。 - 在
pom.xml中,将自动配置模块作为依赖引入。 - 也可以引入其他该Starter运行所必需的第三方库。
- 这个Starter模块本身不写代码,只是一个“依赖包集合”。
最佳实践 :将自动配置模块和Starter模块分离,遵循Spring Boot官方Starter的命名约定: yourmodule-spring-boot-starter (第三方)或 spring-boot-starter-yourmodule (官方内部)。
6. 常见问题与深度排查思路
基于上述源码分析,我们可以系统地回答和排查一些常见难题。
| 问题现象 | 可能根源(源码层面) | 排查思路 |
|---|---|---|
BeanCurrentlyInCreationException |
构造器注入的循环依赖,或原型Bean注入单例Bean等复杂循环。 | 1. 检查是否存在构造器注入的循环。2. 使用 @Lazy 注解延迟加载。3. 重构设计,打破循环。 |
NoSuchBeanDefinitionException |
Bean未定义、扫描路径不对、条件注解未满足、Bean名称不匹配。 | 1. 检查 @ComponentScan 包路径。2. 检查自动配置类条件(如 @ConditionalOnClass )。3. 检查Bean名称(尤其是 @Bean 方法名)。4. 开启 debug=true 查看自动配置报告。 |
@Transactional 注解不生效 |
代理未创建、方法非public、自调用、异常类型未回滚。 | 1. 确认Bean是否被AOP代理(可通过 bean.getClass() 打印)。2. 方法是否为 public 。3. 是否在同一个类中自调用(绕过代理)。4. 抛出的异常是否为 RuntimeException 或配置的回滚异常。 |
| MyBatis查询结果与预期不符 | 一级/二级缓存脏数据、结果映射错误、SQL拼接问题。 | 1. 检查是否因缓存导致数据陈旧,尝试 sqlSession.clearCache() 。2. 开启MyBatis日志( logging.level.com.example.mapper=DEBUG )查看实际执行的SQL和参数。3. 检查 ResultMap 定义是否正确。 |
| Spring Boot应用启动慢 | 类路径扫描过多、自动配置类太多、 @SpringBootApplication 扫描范围过大。 |
1. 使用 @SpringBootApplication(scanBasePackages=“com.your.app”) 限定扫描范围。2. 使用 spring.autoconfigure.exclude 排除不必要的自动配置。3. 使用Spring Boot Actuator的 /startup 端点分析启动过程。 |
@Autowired 注入失败,但Bean存在 |
存在多个同类型Bean、 @Primary 未设置、 @Qualifier 名称不匹配。 |
1. 检查容器中该类型Bean的数量。2. 使用 @Qualifier 指定Bean名称。3. 在其中一个 @Bean 方法上标记 @Primary 。 |
7. 最佳实践与源码学习建议
- 带着问题去读 :不要漫无目的地看。先遇到一个实际问题或疑惑,然后带着这个问题去源码中寻找答案。例如:“为什么我的
@Async方法不异步执行?”就去跟踪@EnableAsync和相关的AsyncAnnotationBeanPostProcessor。 - 善用调试工具 :在IDE中,对关键入口(如
AbstractApplicationContext.refresh,DispatcherServlet.doDispatch)打上断点,以Debug模式启动你的应用,观察调用栈和变量状态。这是理解运行时行为最直观的方式。 - 关注设计模式 :Spring大量使用了设计模式。在读源码时,有意识地识别这些模式(工厂、模板方法、策略、代理、观察者、装饰器等),能帮助你更快理解模块结构和设计意图。
- 阅读官方文档与源码注释 :Spring和MyBatis的官方文档质量很高,源码中的注释也非常详细。在深入代码前,先通读相关章节的文档,能建立整体认知。
- 从简化版开始 :如果觉得Spring Framework太庞大,可以从一些简化版的IOC/AOP实现(如“手写Spring”系列教程)入手,理解核心原理后再回头看官方源码,会清晰很多。
- 总结与输出 :将你的理解通过博客、笔记或图表的形式总结出来。费曼技巧(尝试向别人讲解)是检验你是否真正理解的最佳方法。
源码阅读是一条陡峭但回报丰厚的路径。它不会让你立刻成为更好的“API调用者”,但会让你在遇到复杂问题时,拥有抽丝剥茧、直击根源的能力。从IOC容器的生命周期,到Spring MVC的请求流转,再到MyBatis的SQL执行引擎,每一个环节的深入理解,都在加固你作为后端开发者的核心能力栈。希望本文提供的这些切入点和分析思路,能成为你探索Spring生态源码宇宙的一张可靠星图。
更多推荐
所有评论(0)