Spring生态核心源码深度解析:IOC容器、MVC、MyBatis与Boot自动配置
在业务迭代和面试准备中,你是否曾被问到“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生命周期。其内部主要调用链如下:
-
prepareRefresh(): 准备刷新上下文,设置启动时间、活动标志等。 -
obtainFreshBeanFactory(): 获取或创建新的BeanFactory(对于GenericApplicationContext,就是初始化内部的DefaultListableBeanFactory)。 -
prepareBeanFactory(beanFactory): 对BeanFactory进行标准初始化,配置类加载器、后置处理器等。 -
postProcessBeanFactory(beanFactory): 空方法,子类可覆盖以在BeanFactory标准初始化后对其进行定制。 -
invokeBeanFactoryPostProcessors(beanFactory): 关键步骤! 调用所有BeanFactoryPostProcessor。这是处理BeanDefinition的扩展点。例如,ConfigurationClassPostProcessor会在此阶段扫描@Component、@Bean等注解,并注册新的BeanDefinition。 -
registerBeanPostProcessors(beanFactory): 注册所有BeanPostProcessor,这些处理器将在Bean实例化前后介入。 -
initMessageSource(): 初始化国际化相关。 -
initApplicationEventMulticaster(): 初始化事件广播器。 -
onRefresh(): 空方法,子类可覆盖以添加特定上下文刷新逻辑。 -
registerListeners(): 注册监听器。 -
finishBeanFactoryInitialization(beanFactory): 另一关键步骤! 初始化所有剩余的单例Bean(非懒加载的)。Bean的实例化、属性填充、初始化都在这里发生。 -
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的生命周期包含以下关键节点(按顺序):
- 实例化 :通过构造器或工厂方法创建Bean的原始对象。
-
属性赋值(依赖注入)
:
populateBean()方法,为Bean的属性注入值或其他Bean的引用。 - BeanNameAware.setBeanName :如果Bean实现了该接口,则调用。
- BeanClassLoaderAware.setBeanClassLoader :如果Bean实现了该接口,则调用。
- BeanFactoryAware.setBeanFactory :如果Bean实现了该接口,则调用。
-
BeanPostProcessor.postProcessBeforeInitialization
:所有
BeanPostProcessor的 前置处理 。 - @PostConstruct / InitializingBean.afterPropertiesSet :执行自定义的初始化方法。
-
BeanPostProcessor.postProcessAfterInitialization
:所有
BeanPostProcessor的 后置处理 (AOP代理通常在此处生成!)。 - Bean就绪 :放入单例池,可以被其他Bean依赖和使用。
-
容器关闭
:如果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)的流程:
-
开始创建A,标记A为“创建中”,并放入
singletonsCurrentlyInCreation。 -
实例化A(调用构造器),得到一个原始对象。此时将能生成A的
ObjectFactory放入 三级缓存 。 -
为A进行属性填充(
populateBean),发现需要注入B。 - 开始创建B,标记B为“创建中”。
-
实例化B,将B的
ObjectFactory放入三级缓存。 - 为B进行属性填充,发现需要注入A。
-
关键点
:此时去获取A。在一级缓存
singletonObjects中未找到A,但发现A在singletonsCurrentlyInCreation中(说明A正在创建)。于是,从 三级缓存 中取出A的ObjectFactory,调用getObject()方法。这个方法可能会返回A的原始对象,也可能返回一个A的早期代理对象(如果A需要AOP)。将得到的对象放入 二级缓存earlySingletonObjects,并从三级缓存中移除A的工厂。最后,将这个(早期)A对象注入给B。 - B完成属性填充、初始化,成为一个完整Bean,放入一级缓存。
- 回到A的创建流程,此时能顺利从一级缓存拿到B,完成A的属性填充和初始化。
- 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的魔法所在。它通过动态代理实现:
-
解析Mapper XML时,会将每个
<select|insert|update|delete>标签解析为一个MappedStatement对象,其id为namespace.methodName,并注册到Configuration中。 -
当我们通过
SqlSession.getMapper(UserMapper.class)获取Mapper接口的代理对象时,MyBatis会使用MapperProxyFactory创建一个实现了该接口的 动态代理对象 (MapperProxy)。 -
当我们调用代理对象的方法(如
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提供了强大的插件机制,允许用户拦截四大核心组件的执行:
-
Executor(update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) -
ParameterHandler(getParameterObject, setParameters) -
ResultSetHandler(handleResultSets, handleOutputParameters) -
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()
方法是关键。它会:
-
从
META-INF/spring.factories文件中读取org.springframework.boot.autoconfigure.EnableAutoConfiguration键对应的所有自动配置类的全限定名(Spring Boot内置了上百个)。 -
根据当前项目的类路径、已存在的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 应用设计
-
合理规划包结构与组件扫描
:将启动类放在根包下,利用默认的
@ComponentScan规则。按功能模块划分子包(如controller,service,repository,config),避免循环依赖。对于第三方库的配置类,可考虑使用@Import显式导入。 -
善用
@Configuration与@Bean进行显式配置 :对于复杂的Bean依赖或需要条件初始化的Bean,使用Java Config进行显式配置,比XML和纯注解更清晰、更灵活。在@Configuration类中,可以使用@Conditional系列注解进行条件化装配。 -
理解并管理Bean的作用域与生命周期
:默认单例Bean需注意线程安全问题。对于有状态的Bean,考虑使用
prototype作用域或request、session作用域(Web环境)。在Bean中实现InitializingBean、DisposableBean接口或使用@PostConstruct、@PreDestroy注解来管理初始化与销毁逻辑。 -
谨慎处理循环依赖
:优先通过设计避免循环依赖。如果不可避免,使用Setter/字段注入而非构造器注入。对于因AOP代理(如
@Async,@Transactional)导致的循环依赖,可尝试使用@Lazy注解。
6.2 MyBatis 使用优化
-
SQL写在XML中,复杂查询使用动态SQL
:坚持SQL与Java代码分离的原则。对于多条件查询,使用
<where>,<if>,<choose>等动态SQL标签,避免在Java中拼接SQL字符串。使用<sql>片段复用公共SQL部分。 -
使用ResultMap进行精细化的结果映射
:对于复杂的联表查询结果,定义明确的
<resultMap>,指定每一列的映射关系。开启mapUnderscoreToCamelCase可以简化简单的映射。 -
批处理操作
:对于大量数据的插入或更新,使用MyBatis的批处理模式。可以通过在获取
SqlSession时指定执行器类型为ExecutorType.BATCH,或者在Service方法中手动调用sqlSession.flushStatements()来提高性能。 - 插件用于通用逻辑 :将分页、数据权限过滤、SQL执行时间监控、SQL改写(如加解密)等通用逻辑,通过自定义插件实现,避免在每一个Mapper方法中重复编码。
6.3 Spring Boot 生产就绪
-
多环境配置管理
:严格区分
application-dev.yml,application-test.yml,application-prod.yml。使用spring.profiles.active激活配置。敏感信息(如数据库密码)切勿提交到代码仓库,应使用环境变量、配置中心(如Nacos, Apollo)或Vault管理。 -
健康检查与监控
:引入
spring-boot-starter-actuator依赖,暴露/actuator/health,/actuator/metrics,/actuator/info等端点,方便运维监控。结合Prometheus和Grafana搭建可视化监控。 -
统一的异常处理与响应封装
:使用
@ControllerAdvice和@ExceptionHandler定义全局异常处理器,将系统异常转换为友好的、统一的API错误响应格式。 -
API文档生成
:集成Swagger(
springfox或springdoc-openapi)或Knife4j,自动生成和可视化API文档,减少前后端沟通成本。 -
内嵌服务器调优
:根据实际并发量,在
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的调试功能一步步跟踪,亲自感受框架的设计之美。遇到问题多翻源码,多查官方文档,你的技术视野和解决问题的能力必将提升到一个新的层次。
更多推荐

所有评论(0)