在业务迭代和面试准备中,你是否曾被问到“Spring Bean的生命周期是怎样的?”、“MyBatis的一级缓存和二级缓存有什么区别?”或者“Spring Boot的自动配置是如何实现的?”这类问题?面对这些框架核心原理的追问,仅停留在API使用层面往往难以给出令人信服的答案。本文将带你深入Spring生态的核心,系统剖析IOC容器、Spring MVC、MyBatis以及Spring Boot的源码实现,不仅让你知其然,更能知其所以然。无论你是希望提升技术深度、应对高阶面试,还是为了在复杂业务场景下更优雅地解决问题,这篇万字长文都将为你提供一条清晰的源码学习路径。我们将从最基础的IOC容器启动流程开始,逐步深入到Web层、持久层以及现代Spring Boot的自动化魔法。

1. Spring IOC容器核心源码剖析

Spring框架的基石是IOC(控制反转)容器,它负责管理应用中所有对象的生命周期和依赖关系。理解IOC容器,是理解整个Spring生态的前提。

1.1 BeanDefinition:容器的蓝图

在Spring中,Bean并非直接以对象实例的形式存在容器中,而是先以 BeanDefinition 的形式被定义和注册。你可以将其理解为创建Bean的“蓝图”或“配方”。

// BeanDefinition 是一个接口,定义了Bean的元数据
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
    // 设置Bean的类名
    void setBeanClassName(@Nullable String beanClassName);
    // 获取Bean的类名
    String getBeanClassName();
    // 设置作用域,如singleton、prototype
    void setScope(@Nullable String scope);
    // 设置是否懒加载
    void setLazyInit(boolean lazyInit);
    // 设置依赖的Bean名称
    void setDependsOn(@Nullable String... dependsOn);
    // ... 其他方法
}

当我们在XML中配置 <bean class="com.example.Service"/> 或在Java Config中使用 @Bean 注解时,Spring内部都会解析这些配置,并生成对应的 BeanDefinition 对象,然后将其注册到 BeanDefinitionRegistry (通常由 DefaultListableBeanFactory 实现)中。这个过程是容器启动的 第一步 ,为后续Bean的实例化做好了准备。

1.2 BeanFactory与ApplicationContext:容器的双核心

BeanFactory 是IOC容器的基础接口,提供了最基本的依赖注入支持。而 ApplicationContext 是其子接口,在BeanFactory的基础上,增加了企业级功能,如国际化、事件发布、AOP集成等。

核心启动流程(以基于注解的 AnnotationConfigApplicationContext 为例):

// 1. 实例化容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

// 2. 注册配置类(相当于注册了该配置类中所有@Bean方法对应的BeanDefinition)
context.register(AppConfig.class);

// 3. 刷新容器 —— 这是最核心的一步!
context.refresh();

refresh() 方法是整个IOC容器启动的 灵魂 ,它触发了完整的Bean生命周期。其内部主要调用链如下:

  1. prepareRefresh() : 准备刷新上下文,设置启动时间、活动标志等。
  2. obtainFreshBeanFactory() : 获取或创建新的 BeanFactory (对于 GenericApplicationContext ,就是初始化内部的 DefaultListableBeanFactory )。
  3. prepareBeanFactory(beanFactory) : 对BeanFactory进行标准初始化,配置类加载器、后置处理器等。
  4. postProcessBeanFactory(beanFactory) : 空方法,子类可覆盖以在BeanFactory标准初始化后对其进行定制。
  5. invokeBeanFactoryPostProcessors(beanFactory) : 关键步骤! 调用所有 BeanFactoryPostProcessor 。这是处理 BeanDefinition 的扩展点。例如, ConfigurationClassPostProcessor 会在此阶段扫描 @Component 、 @Bean 等注解,并注册新的 BeanDefinition 。
  6. registerBeanPostProcessors(beanFactory) : 注册所有 BeanPostProcessor ,这些处理器将在Bean实例化前后介入。
  7. initMessageSource() : 初始化国际化相关。
  8. initApplicationEventMulticaster() : 初始化事件广播器。
  9. onRefresh() : 空方法,子类可覆盖以添加特定上下文刷新逻辑。
  10. registerListeners() : 注册监听器。
  11. finishBeanFactoryInitialization(beanFactory) : 另一关键步骤! 初始化所有剩余的单例Bean(非懒加载的)。Bean的实例化、属性填充、初始化都在这里发生。
  12. finishRefresh() : 完成刷新,发布 ContextRefreshedEvent 事件。

1.3 Bean的生命周期:从诞生到销毁

一个单例Bean在容器中的完整生命周期,主要体现在 finishBeanFactoryInitialization 阶段的 getBean() -> doGetBean() -> createBean() -> doCreateBean() 调用链中。

// 简化版的Bean创建流程(AbstractAutowireCapableBeanFactory.doCreateBean)
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
    // 1. 实例化(Instantiation)
    BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
    Object bean = instanceWrapper.getWrappedInstance();

    // 2. 属性填充(Populate)—— 依赖注入发生在这里
    populateBean(beanName, mbd, instanceWrapper);

    // 3. 初始化(Initialization)
    Object exposedObject = initializeBean(beanName, exposedObject, mbd);
    
    // ... 其他处理(如循环依赖处理)
    return exposedObject;
}

具体到细节,一个Bean的生命周期包含以下关键节点(按顺序):

  1. 实例化 :通过构造器或工厂方法创建Bean的原始对象。
  2. 属性赋值(依赖注入) : populateBean() 方法,为Bean的属性注入值或其他Bean的引用。
  3. BeanNameAware.setBeanName :如果Bean实现了该接口,则调用。
  4. BeanClassLoaderAware.setBeanClassLoader :如果Bean实现了该接口,则调用。
  5. BeanFactoryAware.setBeanFactory :如果Bean实现了该接口,则调用。
  6. BeanPostProcessor.postProcessBeforeInitialization :所有 BeanPostProcessor 的 前置处理 。
  7. @PostConstruct / InitializingBean.afterPropertiesSet :执行自定义的初始化方法。
  8. BeanPostProcessor.postProcessAfterInitialization :所有 BeanPostProcessor 的 后置处理 (AOP代理通常在此处生成!)。
  9. Bean就绪 :放入单例池,可以被其他Bean依赖和使用。
  10. 容器关闭 :如果Bean实现了 DisposableBean 接口或指定了 destroy-method ,则调用销毁方法。

1.4 循环依赖与三级缓存

循环依赖是面试高频考点。Spring通过 三级缓存 机制解决了单例Bean的Setter注入和字段注入的循环依赖问题。

// DefaultSingletonBeanRegistry 中定义的三级缓存
public class DefaultSingletonBeanRegistry ... {
    // 一级缓存:存放完整的单例Bean(成品)
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    // 二级缓存:存放早期的Bean(半成品),用于解决循环依赖
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
    // 三级缓存:存放单例工厂,用于生成AOP代理对象
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
    
    // 正在创建中的Bean名称集合
    private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
}

解决Setter注入循环依赖(A依赖B,B依赖A)的流程:

  1. 开始创建A,标记A为“创建中”,并放入 singletonsCurrentlyInCreation 。
  2. 实例化A(调用构造器),得到一个原始对象。此时将能生成A的 ObjectFactory 放入 三级缓存 。
  3. 为A进行属性填充( populateBean ),发现需要注入B。
  4. 开始创建B,标记B为“创建中”。
  5. 实例化B,将B的 ObjectFactory 放入三级缓存。
  6. 为B进行属性填充,发现需要注入A。
  7. 关键点 :此时去获取A。在一级缓存 singletonObjects 中未找到A,但发现A在 singletonsCurrentlyInCreation 中(说明A正在创建)。于是,从 三级缓存 中取出A的 ObjectFactory ,调用 getObject() 方法。这个方法可能会返回A的原始对象,也可能返回一个A的早期代理对象(如果A需要AOP)。将得到的对象放入 二级缓存 earlySingletonObjects ,并从三级缓存中移除A的工厂。最后,将这个(早期)A对象注入给B。
  8. B完成属性填充、初始化,成为一个完整Bean,放入一级缓存。
  9. 回到A的创建流程,此时能顺利从一级缓存拿到B,完成A的属性填充和初始化。
  10. A创建完成,放入一级缓存,并从二级、三级缓存中清理掉A的相关条目。

为什么需要三级缓存,而不是两级? 二级缓存( earlySingletonObjects )似乎就够了?关键在于 AOP 。如果A需要被AOP代理,那么在B注入A的时候,注入的应该是A的 代理对象 ,而不是原始对象。三级缓存中的 ObjectFactory 就是负责在发生循环依赖时,判断并返回原始对象或代理对象的工厂。如果只有二级缓存,那么提前暴露的就只能是原始对象,无法处理需要AOP代理的Bean的循环依赖。

2. Spring MVC 请求处理流程源码解析

Spring MVC是Spring框架的Web模块,其核心是 DispatcherServlet ,它作为前端控制器,统一处理所有请求。

2.1 DispatcherServlet 的初始化与请求入口

DispatcherServlet 本身也是一个Servlet。在Web容器(如Tomcat)启动时,会调用其 init() 方法,进而触发其父类 FrameworkServlet 和 HttpServletBean 的初始化,最终调用到 DispatcherServlet 的 initStrategies() 方法,初始化了九大组件,如 HandlerMapping 、 HandlerAdapter 、 ViewResolver 等。

当一个HTTP请求到达时,Servlet容器会调用 DispatcherServlet 的 service() 方法,最终路由到 doDispatch() 方法,这是整个请求处理的核心。

// DispatcherServlet.doDispatch() 方法核心流程(简化)
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    ModelAndView mv = null;

    // 1. 检查是否为文件上传请求,并进行包装
    processedRequest = checkMultipart(request);

    // 2. 根据请求,找到对应的处理器执行链(HandlerExecutionChain)
    //    其中包含目标Handler(Controller方法)和一系列拦截器(Interceptor)
    mappedHandler = getHandler(processedRequest);
    if (mappedHandler == null) {
        noHandlerFound(processedRequest, response);
        return;
    }

    // 3. 根据Handler,找到对应的处理器适配器(HandlerAdapter)
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

    // 4. 执行拦截器的 preHandle 方法
    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
        return; // 如果某个拦截器返回false,则中断流程
    }

    // 5. 【核心】通过适配器,实际执行Handler(即我们的Controller方法),并返回ModelAndView
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

    // 6. 设置默认视图名(如果返回的ModelAndView没有视图信息)
    applyDefaultViewName(processedRequest, mv);

    // 7. 执行拦截器的 postHandle 方法
    mappedHandler.applyPostHandle(processedRequest, response, mv);

    // 8. 处理结果(渲染视图或处理@ResponseBody)
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}

2.2 九大组件协同工作

DispatcherServlet 的 initStrategies() 方法初始化的九大组件,各司其职:

  • HandlerMapping :将请求映射到处理器(Handler)及其拦截器链。常用实现有 RequestMappingHandlerMapping (处理 @RequestMapping )。
  • HandlerAdapter :用于实际执行处理器。因为处理器有多种形式(如 @Controller 、 HttpRequestHandler ),适配器模式统一了调用方式。 RequestMappingHandlerAdapter 是处理 @Controller 的核心。
  • HandlerExceptionResolver :解析请求处理过程中抛出的异常,将其转换为统一的错误响应(如ModelAndView或ResponseEntity)。
  • ViewResolver :根据视图名解析出具体的 View 对象,用于渲染。
  • LocaleResolver :区域解析器,用于国际化。
  • ThemeResolver :主题解析器。
  • MultipartResolver :用于处理文件上传请求。
  • FlashMapManager :管理 FlashMap ,用于重定向时传递参数。
  • RequestToViewNameTranslator :当处理器未返回视图名时,根据请求URL生成默认视图名。

2.3 参数绑定与返回值处理

RequestMappingHandlerAdapter 在调用Controller方法前,需要解决两个核心问题: 如何将HTTP请求中的数据绑定到方法参数上 ,以及 如何处理方法的返回值 。

参数绑定 :依赖于 HandlerMethodArgumentResolver (参数解析器)接口。Spring MVC内置了数十个实现,例如:

  • RequestParamMethodArgumentResolver :解析 @RequestParam 注解的参数。
  • PathVariableMethodArgumentResolver :解析 @PathVariable 注解的参数。
  • RequestResponseBodyMethodProcessor :解析 @RequestBody 注解的参数,并使用 HttpMessageConverter 将请求体转换为对象。
  • ModelAttributeMethodProcessor :解析 @ModelAttribute 注解的参数或非简单类型的参数。

返回值处理 :依赖于 HandlerMethodReturnValueHandler (返回值处理器)接口。例如:

  • ModelAndViewMethodReturnValueHandler :处理返回 ModelAndView 类型。
  • RequestResponseBodyMethodProcessor :处理 @ResponseBody 注解的返回值,使用 HttpMessageConverter 将对象转换为响应体(如JSON)。
  • ViewNameMethodReturnValueHandler :处理返回字符串(视图名)的情况。

RequestMappingHandlerAdapter 内部维护了这两个组件的列表。在调用Controller方法时,会遍历解析器列表,找到第一个支持当前参数的解析器来解析参数;方法执行后,会遍历返回值处理器列表,找到第一个支持当前返回值的处理器来处理结果。

2.4 视图渲染流程

如果Controller方法返回的是视图名或 ModelAndView ,最终会进入视图渲染流程,即 processDispatchResult() 方法中的 render() 方法。

// View.render() 方法的核心
public void render(@Nullable Map<String, ?> model,
                   HttpServletRequest request,
                   HttpServletResponse response) throws Exception {
    // 合并模型数据(ModelMap)和请求属性
    Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
    prepareResponse(request, response);
    // 调用子类的实际渲染方法,如JSP的renderMergedOutputModel
    renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}

对于JSP视图( InternalResourceView ), renderMergedOutputModel 方法最终会将请求转发到指定的JSP页面,并将模型数据设置到请求属性中,供JSP页面使用。

3. MyBatis 核心源码与工作机制

MyBatis是一个优秀的持久层框架,它封装了JDBC操作,通过XML或注解配置映射关系,实现了SQL与Java代码的解耦。

3.1 核心架构与主要组件

MyBatis的核心组件构成了其执行SQL的完整链路:

  • SqlSessionFactoryBuilder :用于构建 SqlSessionFactory ,读取配置文件(mybatis-config.xml)和映射文件。
  • SqlSessionFactory :工厂模式,用于创建 SqlSession 实例。它是单例的,生命周期与应用相同。
  • SqlSession :代表一次数据库会话。它提供了执行SQL、获取Mapper、管理事务的方法。 它不是线程安全的 ,每次请求都应该获取一个新的实例,用完后关闭。
  • Executor :SQL执行器,是MyBatis的核心。它负责SQL语句的生成、缓存维护以及数据库操作。有几种类型: SimpleExecutor (简单执行,每次执行都创建新的Statement)、 ReuseExecutor (重用Statement)、 BatchExecutor (批处理)。
  • MappedStatement :封装了一条SQL语句的所有信息,包括SQL源码、参数映射、结果映射、缓存配置等。它存储在 Configuration 对象中。
  • StatementHandler :负责操作JDBC的 Statement 对象,包括参数设置、SQL执行、结果集处理。 PreparedStatementHandler 是其常用实现。
  • ParameterHandler :负责将Java参数转换为JDBC参数( PreparedStatement.setXXX )。
  • ResultSetHandler :负责将JDBC返回的 ResultSet 转换为Java对象。

3.2 配置文件加载与Mapper解析

MyBatis的启动始于 SqlSessionFactoryBuilder.build() 方法。它会读取XML配置文件,构建一个 Configuration 对象,这个对象是MyBatis的“全局配置中心”。

<!-- mybatis-config.xml 示例 -->
<configuration>
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <typeAliases>
        <typeAlias alias="User" type="com.example.model.User"/>
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/example/mapper/UserMapper.xml"/>
    </mappers>
</configuration>

XMLConfigBuilder 负责解析这个文件,将 <settings> 、 <environments> 等信息填充到 Configuration 对象。当解析到 <mappers> 时,会调用 XMLMapperBuilder 去解析具体的Mapper XML文件。

Mapper接口与XML的绑定 是MyBatis的魔法所在。它通过动态代理实现:

  1. 解析Mapper XML时,会将每个 <select|insert|update|delete> 标签解析为一个 MappedStatement 对象,其 id 为 namespace.methodName ,并注册到 Configuration 中。
  2. 当我们通过 SqlSession.getMapper(UserMapper.class) 获取Mapper接口的代理对象时,MyBatis会使用 MapperProxyFactory 创建一个实现了该接口的 动态代理对象 ( MapperProxy )。
  3. 当我们调用代理对象的方法(如 userMapper.selectById(1) )时,会触发 MapperProxy.invoke() 方法。该方法根据接口全限定名和方法名,拼接出 MappedStatement 的id(如 com.example.mapper.UserMapper.selectById ),然后从 Configuration 中获取对应的 MappedStatement ,最后委托给 Executor 去执行。

3.3 SQL执行完整流程

一次Mapper方法调用的背后,是多个组件的精密协作:

// 简化版的SQL执行调用链
MapperProxy.invoke() // 动态代理入口
    -> MapperMethod.execute(SqlSession sqlSession, Object[] args) // 根据SQL类型(SELECT/INSERT等)决定调用SqlSession的哪个方法
        -> DefaultSqlSession.selectOne(String statement, Object parameter) // 以selectOne为例
            -> CachingExecutor.query(...) // 如果开启了二级缓存,先经过缓存执行器
                -> BaseExecutor.query(...) // 基础执行器(如SimpleExecutor)
                    -> queryFromDatabase(...) // 从数据库查询
                        -> SimpleExecutor.doQuery(...) // 简单执行器执行查询
                            -> new StatementHandler (PreparedStatementHandler) // 创建StatementHandler
                            -> prepareStatement(StatementHandler handler) // 预处理语句
                                -> ParameterHandler.setParameters(ps) // 设置参数
                            -> handler.query(stmt, resultHandler) // 执行查询,并由ResultSetHandler处理结果集

一级缓存与二级缓存 :

  • 一级缓存(本地缓存) :默认开启,作用域为 SqlSession 。在同一个 SqlSession 中,执行相同的SQL和参数,会直接从缓存返回结果。当执行了 insert/update/delete 或调用了 sqlSession.clearCache() 时,缓存会被清空。其实现位于 BaseExecutor 的 localCache 属性(一个 PerpetualCache 对象)。
  • 二级缓存 :需要手动在Mapper XML中配置 <cache/> ,作用域为 namespace (Mapper级别),跨 SqlSession 共享。其实现依赖于 CachingExecutor 装饰器。当数据被修改时,同一个 namespace 下的所有缓存都会被清空。二级缓存容易引起脏读问题,在分布式或读写分离场景下需谨慎使用。

3.4 插件(Interceptor)机制

MyBatis提供了强大的插件机制,允许用户拦截四大核心组件的执行:

  1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  2. ParameterHandler (getParameterObject, setParameters)
  3. ResultSetHandler (handleResultSets, handleOutputParameters)
  4. StatementHandler (prepare, parameterize, batch, update, query)

插件通过实现 Interceptor 接口,并标注 @Intercepts 和 @Signature 注解来定义。

@Intercepts({
    @Signature(type = StatementHandler.class,
               method = "prepare",
               args = {Connection.class, Integer.class})
})
public class MySqlExplainPlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler handler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = handler.getBoundSql();
        String sql = boundSql.getSql();
        // 例如:可以在这里为SELECT语句加上EXPLAIN
        if (sql.trim().toUpperCase().startsWith("SELECT")) {
            sql = "EXPLAIN " + sql;
            // 通过反射修改BoundSql中的sql字段(注意:BoundSql的sql字段是final的,需要特殊处理)
            Field field = BoundSql.class.getDeclaredField("sql");
            field.setAccessible(true);
            field.set(boundSql, sql);
        }
        return invocation.proceed();
    }
}

插件通过 InterceptorChain 以责任链模式包装目标对象。在 Configuration 初始化四大对象时,会调用 interceptorChain.pluginAll() 方法,为它们创建代理。这也是PageHelper等分页插件实现的基础。

4. Spring Boot 自动化配置与启动源码

Spring Boot的核心目标是简化Spring应用的初始搭建和开发过程,其“约定大于配置”的理念和自动配置能力是其灵魂。

4.1 启动类与 @SpringBootApplication

一切始于一个标注了 @SpringBootApplication 的启动类。

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

@SpringBootApplication 是一个复合注解,它核心包含三个注解:

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

4.2 自动配置原理:@EnableAutoConfiguration

@EnableAutoConfiguration 注解通过 @Import(AutoConfigurationImportSelector.class) 导入了自动配置选择器。

AutoConfigurationImportSelector 的 selectImports() 方法是关键。它会:

  1. 从 META-INF/spring.factories 文件中读取 org.springframework.boot.autoconfigure.EnableAutoConfiguration 键对应的所有自动配置类的全限定名(Spring Boot内置了上百个)。
  2. 根据当前项目的类路径、已存在的Bean、配置文件属性( @Conditional 系列注解)等条件,对这些自动配置类进行过滤,只将符合条件的配置类加载到容器中。

例如, spring-boot-autoconfigure jar包下的 META-INF/spring.factories 文件中有如下条目:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
...

**条件注解(@Conditional)**是自动配置的开关。常见的条件注解有:

  • @ConditionalOnClass :当类路径下存在指定的类时,配置生效。
  • @ConditionalOnMissingBean :当容器中不存在指定类型的Bean时,配置生效。
  • @ConditionalOnProperty :当指定的配置属性满足条件时,配置生效。
  • @ConditionalOnWebApplication :当应用是Web应用时生效。

以 DataSourceAutoConfiguration 为例,它上面标注了 @ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }) ,意味着只有当类路径下存在 DataSource 和 EmbeddedDatabaseType 类(即引入了数据库驱动相关的jar包)时,这个自动配置类才会被加载,进而尝试创建 DataSource Bean。

4.3 SpringApplication.run() 启动流程

SpringApplication.run() 方法启动了Spring Boot应用,它封装了传统Spring应用需要手动完成的很多步骤。

// SpringApplication.run() 核心流程
public ConfigurableApplicationContext run(String... args) {
    // 1. 创建并启动计时监听器
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    
    // 2. 准备环境(读取application.properties/yml,命令行参数,系统属性等)
    ConfigurableApplicationContext context = null;
    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        
        // 3. 打印Banner
        Banner printedBanner = printBanner(environment);
        
        // 4. 创建应用上下文(根据应用类型创建AnnotationConfigServletWebServerApplicationContext等)
        context = createApplicationContext();
        
        // 5. 准备上下文(注册启动参数Bean,触发初始化器等)
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        
        // 6. 【核心】刷新上下文 —— 调用AbstractApplicationContext.refresh(),加载Bean,启动内嵌Web服务器等
        refreshContext(context);
        
        // 7. 刷新后置处理(空方法,子类可扩展)
        afterRefresh(context, applicationArguments);
        
        // 8. 触发已启动的监听器
        listeners.started(context);
        
        // 9. 调用ApplicationRunner和CommandLineRunner
        callRunners(context, applicationArguments);
    } catch (Throwable ex) {
        handleRunFailure(context, ex, listeners);
        throw new IllegalStateException(ex);
    }
    listeners.running(context);
    return context;
}

内嵌Web服务器 :对于Web应用,在 refreshContext() 阶段,会触发 onRefresh() 方法。对于 ServletWebServerApplicationContext ,该方法会调用 createWebServer() 来创建并启动一个内嵌的Web服务器(如Tomcat、Jetty或Undertow)。这是Spring Boot“开箱即用”的关键之一。

4.4 外部化配置与Profile

Spring Boot允许通过多种方式(properties文件、YAML文件、环境变量、命令行参数等)进行配置,并提供了强大的 @ConfigurationProperties 注解将配置绑定到Bean上。

Profile 机制允许我们为不同的环境(如dev, test, prod)定义不同的配置。通过 spring.profiles.active 属性来激活特定的Profile。配置文件中可以使用 --- 分隔符定义多段配置,并用 spring.profiles 指定该段配置所属的Profile。

# application.yml
server:
  port: 8080
---
spring:
  profiles: dev
server:
  port: 8081
logging:
  level:
    root: debug
---
spring:
  profiles: prod
server:
  port: 80
logging:
  level:
    root: warn

5. 常见问题与深度排查思路

在深入源码后,很多日常开发中的问题可以迎刃而解。以下是一些高频问题的深度排查思路。

5.1 Spring 相关

问题现象 可能原因(源码层面) 排查思路与解决方案
@Autowired 注入失败,报 NoSuchBeanDefinitionException 1. Bean未被扫描到(不在 @ComponentScan 路径下)。
2. Bean的创建条件不满足( @Conditional )。
3. 存在多个同类型Bean,未使用 @Qualifier 指定。
1. 检查启动类位置,确保目标Bean所在包在其子包下,或使用 @ComponentScan 手动指定。
2. 开启Spring的 debug 日志( logging.level.root=debug ),查看自动配置报告,确认目标配置类是否被加载,条件是否匹配。
3. 检查是否有多个实现类,使用 @Primary 或 @Qualifier 明确指定。
循环依赖报错: Requested bean is currently in creation 1. 构造器注入的循环依赖(Spring无法解决)。
2. Prototype作用域的Bean循环依赖。
3. @Async 代理导致的循环依赖。
1. 避免构造器循环依赖 ,改用Setter或字段注入。
2. 检查Bean的作用域,Prototype Bean的循环依赖需要重新设计。
3. 对于 @Async 或 @Transactional 等AOP代理,尝试使用 @Lazy 注解延迟注入。
@Transactional 注解不生效 1. 方法非 public 。
2. 异常类型未被捕获或非 RuntimeException / Error 。
3. 在同一个类内部方法调用(自调用问题)。
1. 确保方法为 public 。
2. 检查抛出的异常类型,或使用 @Transactional(rollbackFor=Exception.class) 。
3. 自调用问题 :事务由AOP代理实现,自调用不走代理。可通过注入自身代理( @Autowired private MyService self; )或使用AspectJ模式解决。

5.2 MyBatis 相关

问题现象 可能原因(源码层面) 排查思路与解决方案
查询结果映射失败,属性为null 1. 数据库字段名与Java属性名未遵循命名约定(下划线转驼峰未开启)。
2. resultMap 配置错误或未指定。
3. 类型处理器(TypeHandler)缺失或错误。
1. 在配置中设置 mapUnderscoreToCamelCase=true ,或使用 @Result 注解手动映射。
2. 检查XML中的 resultMap 的 property 和 column 是否正确对应。
3. 检查复杂类型(如枚举、LocalDateTime)是否有对应的TypeHandler。
一级/二级缓存未生效 1. 一级缓存:在同一个 SqlSession 中,但中间执行了更新操作或调用了 clearCache() 。
2. 二级缓存:未在Mapper XML中配置 <cache/> ;或实体类未实现 Serializable 接口;或发生了跨 namespace 的更新操作。
1. 理解一级缓存作用域,更新操作会清空当前 SqlSession 的缓存。
2. 确认二级缓存已配置,实体类可序列化。注意:二级缓存是跨 SqlSession 的,但作用域是 namespace ,关联表的更新可能需使用 <cache-ref> 或放弃二级缓存。
动态SQL拼接错误 if , choose , foreach 等OGNL表达式写错; #{} 和 ${} 误用。 1. 开启MyBatis的SQL日志( logging.level.com.example.mapper=debug )查看最终生成的SQL。
2. 理解 #{} 是预编译占位符(防SQL注入), ${} 是字符串替换(有注入风险,慎用)。
3. 检查OGNL表达式,如 test=\"name != null and name != ''\" 。

5.3 Spring Boot 相关

问题现象 可能原因(源码层面) 排查思路与解决方案
自动配置未生效 1. 依赖未引入( @ConditionalOnClass 不满足)。
2. 自定义配置覆盖了自动配置( @ConditionalOnMissingBean 不满足)。
3. 配置属性不正确。
1. 检查 pom.xml / build.gradle ,确认相关starter依赖已引入。
2. 检查是否自定义了同类型的Bean,导致自动配置的Bean未被创建。
3. 运行应用时添加 --debug 参数,查看自动配置报告,确认哪些配置类生效、哪些未生效及原因。
配置文件( application.yml )属性不生效 1. 配置文件位置不正确或未加载。
2. 属性名拼写错误或层级错误。
3. 多Profile下,激活的Profile不对。
1. 了解Spring Boot配置文件加载顺序(classpath根目录、config目录、当前目录等)。
2. 使用 @ConfigurationProperties 或 @Value 注入时,确保前缀和属性名正确。
3. 检查 spring.profiles.active 配置,或启动命令中指定的Profile。
内嵌Tomcat端口冲突或启动失败 1. 端口被占用。
2. SSL配置错误。
3. Web服务器依赖冲突。
1. 使用 netstat -ano (Windows)或 lsof -i:8080 (Linux/Mac)检查端口占用,或修改 server.port 。
2. 检查 server.ssl.* 配置,确保证书路径和密码正确。
3. 排除冲突的依赖,如项目中同时存在Tomcat和Jetty的starter。

6. 最佳实践与工程化建议

深入源码不仅是为了面试,更是为了写出更健壮、高效、可维护的代码。以下是一些基于源码理解的工程实践。

6.1 Spring 应用设计

  1. 合理规划包结构与组件扫描 :将启动类放在根包下,利用默认的 @ComponentScan 规则。按功能模块划分子包(如 controller , service , repository , config ),避免循环依赖。对于第三方库的配置类,可考虑使用 @Import 显式导入。
  2. 善用 @Configuration 与 @Bean 进行显式配置 :对于复杂的Bean依赖或需要条件初始化的Bean,使用Java Config进行显式配置,比XML和纯注解更清晰、更灵活。在 @Configuration 类中,可以使用 @Conditional 系列注解进行条件化装配。
  3. 理解并管理Bean的作用域与生命周期 :默认单例Bean需注意线程安全问题。对于有状态的Bean,考虑使用 prototype 作用域或 request 、 session 作用域(Web环境)。在Bean中实现 InitializingBean 、 DisposableBean 接口或使用 @PostConstruct 、 @PreDestroy 注解来管理初始化与销毁逻辑。
  4. 谨慎处理循环依赖 :优先通过设计避免循环依赖。如果不可避免,使用Setter/字段注入而非构造器注入。对于因AOP代理(如 @Async , @Transactional )导致的循环依赖,可尝试使用 @Lazy 注解。

6.2 MyBatis 使用优化

  1. SQL写在XML中,复杂查询使用动态SQL :坚持SQL与Java代码分离的原则。对于多条件查询,使用 <where> , <if> , <choose> 等动态SQL标签,避免在Java中拼接SQL字符串。使用 <sql> 片段复用公共SQL部分。
  2. 使用ResultMap进行精细化的结果映射 :对于复杂的联表查询结果,定义明确的 <resultMap> ,指定每一列的映射关系。开启 mapUnderscoreToCamelCase 可以简化简单的映射。
  3. 批处理操作 :对于大量数据的插入或更新,使用MyBatis的批处理模式。可以通过在获取 SqlSession 时指定执行器类型为 ExecutorType.BATCH ,或者在Service方法中手动调用 sqlSession.flushStatements() 来提高性能。
  4. 插件用于通用逻辑 :将分页、数据权限过滤、SQL执行时间监控、SQL改写(如加解密)等通用逻辑,通过自定义插件实现,避免在每一个Mapper方法中重复编码。

6.3 Spring Boot 生产就绪

  1. 多环境配置管理 :严格区分 application-dev.yml , application-test.yml , application-prod.yml 。使用 spring.profiles.active 激活配置。敏感信息(如数据库密码)切勿提交到代码仓库,应使用环境变量、配置中心(如Nacos, Apollo)或Vault管理。
  2. 健康检查与监控 :引入 spring-boot-starter-actuator 依赖,暴露 /actuator/health , /actuator/metrics , /actuator/info 等端点,方便运维监控。结合Prometheus和Grafana搭建可视化监控。
  3. 统一的异常处理与响应封装 :使用 @ControllerAdvice 和 @ExceptionHandler 定义全局异常处理器,将系统异常转换为友好的、统一的API错误响应格式。
  4. API文档生成 :集成Swagger( springfox 或 springdoc-openapi )或Knife4j,自动生成和可视化API文档,减少前后端沟通成本。
  5. 内嵌服务器调优 :根据实际并发量,在 application.yml 中调整内嵌Tomcat(或其他服务器)的连接池、线程池等参数,例如 server.tomcat.max-connections , server.tomcat.threads.max 。

源码阅读是一条从“会用”到“精通”的必经之路。通过对Spring IOC容器生命周期的剖析,你能在Bean创建异常时快速定位;通过对Spring MVC请求流程的理解,你能自定义拦截器、参数解析器来满足特定业务需求;通过对MyBatis执行流程的掌握,你能优化SQL性能、编写高效插件;通过对Spring Boot自动配置原理的探究,你能更好地定制和排除配置问题。建议你 clone Spring Framework、MyBatis、Spring Boot 的官方仓库,结合本文梳理的主线,从 main 方法或一个测试用例开始,用IDE的调试功能一步步跟踪,亲自感受框架的设计之美。遇到问题多翻源码,多查官方文档,你的技术视野和解决问题的能力必将提升到一个新的层次。

更多推荐