很多Java开发者都有过这样的困惑:为什么我的Spring项目启动越来越慢?为什么一个简单的依赖注入,在并发场景下偶尔会报错?为什么MyBatis的Mapper接口明明没有实现类,却能执行SQL?这些看似“魔法”的背后,其实是Spring生态核心源码在默默工作。

如果你只是停留在“会用”Spring Boot,那么当线上出现一个诡异的循环依赖问题,或者MyBatis缓存导致的数据不一致时,你很可能束手无策。源码阅读不是炫技,而是解决问题、深入理解框架设计思想、写出更健壮代码的必经之路。然而,面对Spring庞大的代码库,很多人不知从何入手,最终浅尝辄止。

本文不打算平铺直叙地罗列每个类的继承关系,而是聚焦于 三个最核心、也最能解决实际问题的源码剖析场景 :IOC容器的启动与Bean生命周期(解决依赖注入与循环依赖之谜)、Spring MVC的请求处理流程(理解Web层核心)、以及MyBatis的SQL执行引擎(揭秘接口代理与缓存机制)。我们会结合高频面试题和真实开发中的“坑”,带你穿透表面API,直抵设计精髓。读完本文,你将能清晰地回答上述问题,并具备独立分析和调试其他Spring模块源码的能力。

1. 为什么要读Spring源码?从“会用”到“精通”的关键一跃

在开始深入代码之前,我们必须先统一认知:读源码的目标是什么?对于大多数开发者,目标不是成为Spring Committer,而是为了:

  1. 高效排查问题 :当遇到 BeanCurrentlyInCreationException (循环依赖)、 NoSuchBeanDefinitionException 或事务不生效等复杂问题时,能通过日志和堆栈,快速定位到框架层面的根源,而不是盲目地重启和祈祷。
  2. 深度性能优化 :理解Bean的创建过程、代理机制、缓存策略,才能在设计应用时避免性能陷阱。例如,为什么 @Scope("prototype") 的Bean不适合注入到单例Bean中?MyBatis一级缓存为什么在Spring整合后容易引发问题?
  3. 做出更好的架构设计 :Spring本身是一个优秀的设计范本。学习它的设计模式(工厂、模板方法、代理、观察者等)、模块划分、扩展点设计( BeanPostProcessor , BeanFactoryPostProcessor ),能极大提升自己的软件设计能力。
  4. 应对高级面试 :关于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为例):

  1. 开始创建A,实例化A(调用构造器),得到一个 原始对象
  2. 将A的 ObjectFactory 放入 三级缓存
  3. 开始为A进行属性填充( populateBean ),发现需要注入B。
  4. 转去创建B,实例化B,将B的 ObjectFactory 放入三级缓存。
  5. 为B进行属性填充,发现需要注入A。
  6. 此时,B去获取A:
    • 从一级缓存未找到。
    • 从二级缓存未找到。
    • 从三级缓存找到A的ObjectFactory ,调用 getObject() 。这个方法可能会返回A的原始对象,也可能返回一个A的 早期代理对象 (如果A需要被AOP代理)。将返回的对象放入 二级缓存 ,并 删除三级缓存中A的工厂
    • B成功获得A的引用(可能是代理),完成属性填充和初始化,成为一个完整的Bean,放入 一级缓存
  7. B创建完毕,返回给正在创建A的流程。
  8. A成功注入B,继续完成自己的属性填充和初始化。
  9. 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, @RequestBody POJO, HttpServletRequest 等)?这就是参数解析器的职责。例如:
    • @RequestParam -> RequestParamMethodArgumentResolver
    • @PathVariable -> PathVariableMethodArgumentResolver
    • @RequestBody -> RequestResponseBodyMethodProcessor
    • HttpServletRequest -> 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”) 时,背后发生了什么?

  1. 获取Mapper代理对象 :通过 SqlSession.getMapper() 获取的是一个动态代理对象( MapperProxy )。
  2. 代理对象调用 :调用Mapper接口方法时,会被 MapperProxy.invoke() 拦截。
  3. 转换为SqlSession调用 MapperProxy 根据方法名和所属Mapper接口,找到对应的MappedStatement(保存了SQL、参数映射、结果映射等所有信息),然后调用 SqlSession 的对应方法(如 selectList )。
  4. Executor执行 SqlSession 将请求委托给 Executor
    • Executor 首先查询 二级缓存 (如果启用且该语句配置了缓存)。
    • 缓存未命中,则调用 StatementHandler
  5. StatementHandler处理 StatementHandler (例如 PreparedStatementHandler )负责:
    • Connection 中创建 PreparedStatement
    • 使用 ParameterHandler 将Java参数设置到SQL占位符( ? )中。
    • 执行SQL,得到 ResultSet
  6. 结果映射 ResultSetHandler 使用 ResultSet MappedStatement 中定义的 ResultMap ,将结果集转换为Java对象(List或单个对象)。
  7. 返回与缓存 :结果返回给 Executor Executor 将其放入 一级缓存 (SqlSession级别),如果配置了二级缓存,也会同步到二级缓存。最终结果返回给调用者。

4.2 一级缓存与二级缓存深度解析

一级缓存(本地缓存)

  • 范围 SqlSession 级别。同一个 SqlSession 多次执行相同的查询,只会查询一次数据库。
  • 实现 BaseExecutor 中的 localCache (一个 PerpetualCache ,本质是HashMap)。
  • 失效条件
    1. 执行了INSERT/UPDATE/DELETE操作(任何写操作)。
    2. 调用了 sqlSession.clearCache()
    3. 执行了 sqlSession.commit() rollback() (除非配置了本地缓存作用于事务生命周期)。
    4. 在配置中显式关闭了一级缓存(几乎不会这么做)。
  • 坑点 :在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)。
  • 工作流程
    1. 查询时,先看二级缓存。
    2. 二级缓存命中,直接返回。
    3. 未命中,查询数据库,结果放入一级缓存。
    4. 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 工作流程

  1. 加载候选配置 AutoConfigurationImportSelector 会从 META-INF/spring.factories 文件中读取 org.springframework.boot.autoconfigure.EnableAutoConfiguration 键下的所有自动配置类全限定名(如 org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration )。
  2. 条件过滤 :这些候选配置类并不会全部生效。每个配置类上都标有大量的 @ConditionalOnXxx 注解(条件注解),如:
    • @ConditionalOnClass :类路径下存在某个类时才生效。
    • @ConditionalOnMissingBean :容器中不存在某个Bean时才生效。
    • @ConditionalOnProperty :配置文件中存在特定属性且值为特定值时生效。
  3. 按序加载 :经过条件过滤后,剩下的配置类才会被真正加载到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:创建自动配置模块

  1. 创建一个普通的Spring Boot项目,只包含 spring-boot-autoconfigure spring-boot-configuration-processor 依赖。
  2. 定义配置属性类( @ConfigurationProperties )。
  3. 定义核心功能类。
  4. 定义自动配置类( @Configuration ),使用 @Conditional 系列注解控制生效条件,并使用 @EnableConfigurationProperties 导入属性类。
  5. src/main/resources/META-INF/ 下创建 spring.factories 文件,添加:
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.yourcompany.autoconfigure.YourAutoConfiguration
    

步骤2:创建Starter模块

  1. 创建一个空项目,只包含一个 pom.xml
  2. pom.xml 中,将自动配置模块作为依赖引入。
  3. 也可以引入其他该Starter运行所必需的第三方库。
  4. 这个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. 最佳实践与源码学习建议

  1. 带着问题去读 :不要漫无目的地看。先遇到一个实际问题或疑惑,然后带着这个问题去源码中寻找答案。例如:“为什么我的 @Async 方法不异步执行?”就去跟踪 @EnableAsync 和相关的 AsyncAnnotationBeanPostProcessor
  2. 善用调试工具 :在IDE中,对关键入口(如 AbstractApplicationContext.refresh , DispatcherServlet.doDispatch )打上断点,以Debug模式启动你的应用,观察调用栈和变量状态。这是理解运行时行为最直观的方式。
  3. 关注设计模式 :Spring大量使用了设计模式。在读源码时,有意识地识别这些模式(工厂、模板方法、策略、代理、观察者、装饰器等),能帮助你更快理解模块结构和设计意图。
  4. 阅读官方文档与源码注释 :Spring和MyBatis的官方文档质量很高,源码中的注释也非常详细。在深入代码前,先通读相关章节的文档,能建立整体认知。
  5. 从简化版开始 :如果觉得Spring Framework太庞大,可以从一些简化版的IOC/AOP实现(如“手写Spring”系列教程)入手,理解核心原理后再回头看官方源码,会清晰很多。
  6. 总结与输出 :将你的理解通过博客、笔记或图表的形式总结出来。费曼技巧(尝试向别人讲解)是检验你是否真正理解的最佳方法。

源码阅读是一条陡峭但回报丰厚的路径。它不会让你立刻成为更好的“API调用者”,但会让你在遇到复杂问题时,拥有抽丝剥茧、直击根源的能力。从IOC容器的生命周期,到Spring MVC的请求流转,再到MyBatis的SQL执行引擎,每一个环节的深入理解,都在加固你作为后端开发者的核心能力栈。希望本文提供的这些切入点和分析思路,能成为你探索Spring生态源码宇宙的一张可靠星图。

更多推荐