1. Spring 核心基础

IoC(控制反转)与 DI(依赖注入)

基础概念深度解析

  • IoC (Inversion of Control):一种设计思想。传统开发中,对象由程序员手动 new;在 Spring 中,对象的创建、管理、配置权力交给了容器。

  • DI (Dependency Injection):IoC 的具体实现手段。容器在运行期将依赖对象通过配置(如注解)注入到组件中。

  • IoC 容器的作用:解耦。它充当了对象之间的“粘合剂”,负责管理对象的全生命周期。

Bean 的创建方式

  1. XML 方式:已逐渐退出历史舞台,主要用于老旧项目维护。

  2. 注解方式@Component 及其衍生注解(@Service, @Repository, @Controller)。底层由 ClassPathBeanDefinitionScanner 扫描。

  3. Java Config@Configuration + @Bean。这种方式最灵活,常用于配置第三方类库的 Bean(因为你没法在人家的源码上加 @Component)。

依赖注入方式对比

  • 构造器注入(推荐):保证了依赖不可变(final),确保 Bean 在初始化时就处于就绪状态,且能避免循环依赖(在某些特定场景下)。

  • Setter 注入:灵活性高,允许在对象创建后修改依赖。

  • 字段注入(@Autowired 在字段上)

    • 缺点:与容器强耦合,无法脱离容器进行单元测试;不能使用 final;可能导致 NullPointerException。

Spring 为什么不推荐字段注入?

Spring 不推荐字段注入,主要是因为它违反了单一职责原则(容易导致依赖过多而不自知),破坏了对象的不可变性(无法使用 final 关键字),并且深度耦合 IoC 容器,导致在不启动容器的情况下难以进行单元测试;相比之下,官方推荐的构造器注入能确保对象在实例化时就处于完全就绪的状态,依赖关系透明且易于维护。

Bean 作用域 (Scope)

  • singleton:全局唯一,Spring 默认。存储在单例池(singletonObjects)中。

  • prototype:每次获取都创建一个新实例。Spring 不负责其后续生命周期的销毁。

  • Web 作用域request (请求级), session (会话级), application (ServletContext 级)。

生命周期

Bean 的生命周期可以高度概括为:实例化 -> 属性赋值 -> 初始化 -> 销毁

  1. 实例化 (Instantiate):通过反射创建对象。

  2. 属性赋值 (Populate):注入依赖。

  3. 初始化阶段 (Initialization)

    • Aware 接口:注入容器基础设施(如 BeanNameAware, ApplicationContextAware)。

    • BeanPostProcessor.postProcessBeforeInitialization:前置处理器。

    • InitializingBean / @PostConstruct:执行自定义初始化逻辑。

    • BeanPostProcessor.postProcessAfterInitialization:后置处理器(AOP 代理通常在此处产生)。

  4. 销毁 (Destruction):执行 DisposableBean@PreDestroy 方法。

AOP(面向切面编程)

核心术语

  • Aspect (切面):通知(Advice)和切点(Pointcut)的结合。

  • Join Point (连接点):程序执行的某个特定位置(Spring 仅支持方法执行)。

  • Pointcut (切点):匹配连接点的断言(决定哪些方法被拦截)。

  • Advice (通知):在切点处执行的代码逻辑(@Around 功能最强,可控制方法执行前、后、异常、返回值)。

动态代理深度对比

特性

JDK 动态代理

CGLIB 代理

原理

基于 接口 实现,生成接口的实现类

基于 继承 实现,生成目标类的子类

要求

目标类必须实现至少一个接口

目标类不能是 final,方法不能是 final

性能

早期较慢,JDK 8+ 性能已非常优秀

字节码生成,执行效率高

Spring AOP 默认用哪个?

  • 如果目标对象实现了接口,默认用 JDK

  • 如果没有实现接口,强制用 CGLIB

  • SpringBoot 2.x 后,为了减少转换带来的问题,默认倾向于使用 CGLIB(可以通过配置修改)。

为什么 final 方法不能被 AOP?

  • CGLIB 通过生成子类重写方法来实现代理,final 方法无法被子类重写,因此无法增强。

Spring 核心容器

BeanFactory vs ApplicationContext

  • BeanFactory:Spring 框架的核心接口。提供基础配置和 Bean 管理。采用懒加载(第一次调用 getBean 时才实例化)。

  • ApplicationContext:BeanFactory 的子接口。提供更多高级功能:国际化、事件传播、资源加载。采用预加载(容器启动时实例化所有单例 Bean),能及早发现配置错误。

容器启动流程

Spring 容器启动的核心方法是 AbstractApplicationContext.refresh(),其主要步骤如下:

  1. prepareRefresh:激活容器状态,初始化属性源。

  2. obtainFreshBeanFactory:解析 XML 或扫描注解,将 Bean 信息封装成 BeanDefinition 注册到 BeanDefinitionMap。

  3. prepareBeanFactory:配置标准上下文特征(如类加载器、PostProcessor)。

  4. invokeBeanFactoryPostProcessors:执行 BeanFactoryPostProcessor(可以在实例化前修改 BeanDefinition)。

  5. registerBeanPostProcessors:注册用于拦截 Bean 创建过程的处理器。

  6. finishBeanFactoryInitialization核心步骤。实例化所有剩余的非懒加载单例 Bean(完成依赖注入、初始化)。

  7. finishRefresh:发布容器刷新完成事件。

Spring 容器启动做了什么?

  • 资源定位与加载:找到配置。

  • 元数据解析:把配置转换成 BeanDefinition

  • 注册:存入 BeanDefinitionRegistry

  • 实例化与增强:利用 BeanFactoryPostProcessorBeanPostProcessor 对 Bean 进行加工和实例化。

2. Spring 核心原理

Bean 生命周期

Spring Bean 的生命周期并非简单的创建与销毁,而是一场由AbstractAutowireCapableBeanFactory 主导的“精密组装”。

  1. 实例化 (Instantiation):通过反射调用构造函数,在堆内存中开辟空间。此时对象仅是个“空壳”。

  2. 属性填充 (Populate):注入依赖(@Autowired / @Value)。这是解决循环依赖的关键点。

  3. Aware 回调:Spring 填充容器基础设施信息。

    • BeanNameAware -> BeanFactoryAware -> ApplicationContextAware

  4. BeanPostProcessor(前置):执行 postProcessBeforeInitialization

    • @PostConstruct 就在这一步被 InitDestroyAnnotationBeanPostProcessor 执行。

  5. 初始化 (Initialization)

    • 执行 InitializingBean.afterPropertiesSet()

    • 执行 XML 中定义的 init-method

  6. BeanPostProcessor(后置):执行 postProcessAfterInitialization

    • 注意:Spring AOP 的代理对象通常就在这一步创建并返回。

  7. 使用 (Ready to Use):Bean 进入单例池,供业务调用。

  8. 销毁 (Destruction):容器关闭时执行 @PreDestroyDisposableBean 接口方法。

循环依赖与三级缓存

什么是循环依赖

A 类注入 B,B 类注入 A,形成闭环。Spring 默认支持单例、Setter 注入模式下的循环依赖。

三级缓存机制

Spring 在 DefaultSingletonBeanRegistry 中维护了三个 Map:

缓存层级

名称

存储内容

作用

第一级

singletonObjects

完整的单例 Bean

供外部直接获取使用

第二级

earlySingletonObjects

半成品 Bean(已实例化未初始化)

预防多次获取同一个半成品 Bean

第三级

singletonFactories

ObjectFactory (Lambda 表达式)

解决 AOP 代理对象 的循环依赖

为什么是三级缓存,不是二级?

  • 核心理由是 AOP。如果只有二级缓存,意味着所有 Bean 在实例化后必须立刻创建代理对象。但 Spring 规范要求代理应在“初始化后”创建。三级缓存通过工厂模式,实现了“只有发生循环依赖时,才提前创建代理对象”,否则仍按常规生命周期执行。

构造器注入为什么解决不了?

  • 因为构造器注入发生在“实例化”阶段。A 实例化时发现需要 B,此时 A 连“半成品”都还没产生(没进缓存),B 也就无法拿到 A 的引用,导致死锁。

事务管理 (Spring Transaction)

核心配置

  • 隔离级别 (Isolation):与数据库标准对应。Spring 默认使用底层数据库的选择(MySQL 默认为 REPEATABLE_READ)。

  • 传播行为 (Propagation):最常考。

    • REQUIRED (默认):有事务就加入,没事务就新建。

    • REQUIRES_NEW:无论有没有,都新建事务。原来的事务挂起。

    • NESTED:嵌套事务,利用数据库 Savepoint。父事务回滚,子事务必回滚;子事务回滚,父事务可不回滚。

底层原理

Spring 事务本质是 AOP + 数据库连接管理

  1. 通过 TransactionInterceptor 拦截目标方法。

  2. 获取当前数据库连接,并设置 autoCommit = false

  3. 执行业务逻辑。

  4. 根据执行结果调用 commitrollback

@Transactional 失效场景

  1. 内部调用:类内部 a() 方法调用 b()b 上的事务失效。因为此时走的是 this 而不是 proxy 代理对象。

  2. 非 public 方法:Spring 要求事务方法必须是公有的(底层 CGLIB 限制或 AOP 设计选择)。

  3. 异常被“吞”了:代码里写了 try-catch 但没手动抛出 RuntimeException,Spring 感知不到异常。

  4. 数据库不支持:比如 MySQL 使用了 MyISAM 引擎。

3. Spring MVC

Spring MVC 是基于 Servlet API 构建的原始 Web 框架。其核心思想是 Front Controller(前端控制器)模式,即通过一个中央调度器统一处理所有请求。

核心五大组件

组件名称

职责描述

DispatcherServlet

核心大脑。负责拦截所有请求并分发到各组件,充当调度中心。

HandlerMapping

导航员。根据 URL 找到对应的 Handler(通常是 Controller 的方法)。

HandlerAdapter

适配器。因为 Handler 类型多样,Adapter 负责按照特定规则去执行它。

HandlerExceptionResolver

急救员。处理执行过程中的异常(如 @ExceptionHandler)。

ViewResolver

翻译官。将逻辑视图名(如 "index")解析为物理视图(如 "/WEB-INF/jsp/index.jsp")。

执行流程

当一个 HTTP 请求到达服务器时,Spring MVC 的处理步骤如下:

  1. 请求拦截:所有请求首先到达 DispatcherServlet

  2. 寻找 HandlerDispatcherServlet 调用 HandlerMapping,根据 URL 匹配返回一个 HandlerExecutionChain(包含 Handler 和一系列 Interceptor 拦截器)。

  3. 获取适配器DispatcherServlet 为该 Handler 寻找合适的 HandlerAdapter

  4. 执行拦截器 (Pre):执行所有拦截器的 preHandle 方法。

  5. 执行逻辑HandlerAdapter 执行真正的 Controller 业务逻辑。

  6. 返回结果:Controller 返回 ModelAndView 对象(包含模型数据和逻辑视图名)。

  7. 执行拦截器 (Post):执行拦截器的 postHandle 方法。

  8. 视图解析ViewResolver 将逻辑视图名解析为真正的 View 对象。

  9. 渲染输出DispatcherServlet 将 Model 数据填充到 View 中,响应给客户端。

注意:在现代前后端分离(RESTful)开发中,第 6-9 步通常被简化。如果标记了 @ResponseBody,则通过 HttpMessageConverter 直接写回 JSON 字符串。

参数绑定与注解

Spring MVC 通过 HandlerMethodArgumentResolver 实现了极其灵活的参数绑定。

  • @RequestParam:绑定 URL 查询参数(Query Param)或表单数据。

    • 例如:/user?id=1 -> @RequestParam("id") Long id

  • @PathVariable:绑定 URL 占位符(路径变量)。

    • 例如:/user/{id} -> @PathVariable("id") Long id

  • @RequestBody:用于接收 JSON 格式的请求体。

    • 底层通过 JacksonGson 等消息转换器将 JSON 转为 Java 对象。

返回值处理与 REST 控制器

@ResponseBody:告诉 Spring,方法的返回值不是视图名,而是直接写入 HTTP 响应体。如果是对象,默认转为 JSON。

@RestController

  • 这是一个组合注解,等同于 @Controller + @ResponseBody

  • 它代表该类下的所有方法都直接返回数据,不走视图解析器,是目前主流的 API 开发方式。

DispatcherServlet 做了什么?

  • 它不直接处理业务,而是起调度作用。它解耦了 HTTP 请求与具体业务代码,统一了文件上传、异常处理、主题解析等跨切面逻辑。

为什么说 Spring MVC 是前端控制器模式?

  • 因为它只有一个单一的入口(Servlet)处理所有类型的请求。相比传统的一个 Servlet 处理一个 URL,这种模式极大减少了配置复杂度,并允许在 DispatcherServlet 中进行统一的安全、日志或性能统计。

4. Spring Boot 核心

Spring Boot 的核心宗旨是 “约定优于配置” (Convention Over Configuration)

自动配置 (Auto-Configuration)

核心注解:@SpringBootApplication

它是一个复合注解,主要由以下三个组成:

  • @SpringBootConfiguration:本质是 @Configuration,允许在当前类中注册 Bean。

  • @ComponentScan:扫描当前包及其子包下的组件。

  • @EnableAutoConfiguration自动配置的关键

自动配置原理

  1. 启动加载:Spring Boot 启动时寻找 META-INF/spring.factories (旧版) 或 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports (新版)。

  2. 获取类名:文件中列出了大量备选的 XxxAutoConfiguration 类。

  3. 条件过滤:利用 条件注解 (@Conditional) 进行筛选。

    • @ConditionalOnClass:当路径下有某个类(如 Druid.class)时才生效。

    • @ConditionalOnMissingBean:当用户没手动配置该 Bean 时,Spring 才自动配置一份。

    • @ConditionalOnProperty:配置文件中某个属性为特定值时才生效。

  4. 注入容器:符合条件的配置类会被解析,将其内部定义的 Bean 注入 IoC 容器。

Starter 机制

  • 本质:Starter 是一组依赖的组合(pom.xml)+ 自动配置类。

  • 与普通依赖的区别:普通依赖只提供代码;Starter 不仅提供代码,还通过 AutoConfiguration 帮你把代码里的核心对象全配好了,实现“开箱即用”。

配置体系

  • @Value:适合注入单个简单属性,不支持松散绑定(Relaxed Binding)。

  • @ConfigurationProperties:支持批量注入、支持嵌套对象、支持松散绑定(如 user_name 自动匹配 userName),生产环境更推荐。

内嵌服务器

  • 原理:Spring Boot 将 Tomcat/Jetty 以 Java 对象的形式启动,而不是传统的将 war 包部署到外部 Tomcat。

  • 为什么可以直接运行:Spring Boot 打包成 Fat Jar,包含所有依赖和内置服务器,通过 java -jar 调用 JarLauncher 启动。

监控与运维 (Actuator)

Actuator 是 Spring Boot 提供的准生产级特性,用于监控和管理应用。

  • 核心功能

    • 健康检查 (/actuator/health):展示应用的运行状态(UP/DOWN),可集成数据库、Redis、磁盘空间等检查。

    • 指标监控 (/actuator/metrics):收集 JVM 堆内存、线程池、HTTP 请求频率等数据。通常配合 Micrometer 暴露给 Prometheus。

    • 环境信息 (/actuator/env):查看当前应用加载的所有配置属性。

    • 堆栈转储 (/actuator/threaddump / /heapdump):在线生成线程快照或堆内存快照,用于排查死锁或内存泄漏。

  • 安全性:默认除 /health 外大都被隐藏,需配置 management.endpoints.web.exposure.include=* 开启。

日志体系

Spring Boot 默认采用 SLF4J + Logback 的组合。

  • SLF4J (Simple Logging Facade for Java):日志门面(接口)。它不负责记录日志,只负责制定规范,解耦业务代码与具体日志框架。

  • Logback:日志实现。由 Log4j 的作者开发,性能和功能均优于 Log4j。

  • 配置:推荐使用 logback-spring.xml 而非 logback.xml,因为前者支持 Spring Profile 特性(如 <springProfile name="dev">)。

全局异常处理

利用 AOP 思想,将异常处理逻辑从 Controller 中剥离。

  • @ControllerAdvice / @RestControllerAdvice:定义全局异常处理类,通过切面拦截 Controller 抛出的异常。

  • @ExceptionHandler:标注在方法上,指定拦截的异常类型(如 RuntimeException.class)。

  • 优势

    1. 统一响应格式:无论什么错误,都返回固定的 JSON 结构。

    2. 代码解耦:Controller 逻辑更纯粹,不再充斥 try-catch

启动流程

Spring Boot 启动可以拆分为两个阶段:

阶段一:初始化 SpringApplication 实例

  • 推断 Web 容器类型。

  • 加载所有 ApplicationContextInitializer

  • 加载所有 ApplicationListener

阶段二:执行 run 方法(核心逻辑)

  1. 启动计时器

  2. 准备环境 (Environment):读取配置文件。

  3. 打印 Banner

  4. 创建上下文 (ApplicationContext)

  5. 预处理上下文:执行 Initializer,注册 BeanDefinition。

  6. 刷新上下文 (refreshContext)最关键一步,会触发 IoC 容器初始化,并启动内嵌服务器(如 Tomcat)

  7. 发布完成事件

  8. 执行 Runner:调用 CommandLineRunnerApplicationRunner

Spring 常见设计模式

  1. 工厂模式BeanFactory 是创建 Bean 的工厂;ObjectFactory 是三级缓存中的对象工厂。

  2. 单例模式:Spring Bean 默认作用域即为单例(底层通过 Map 维护单例池)。

  3. 代理模式:AOP 的核心。JDK 动态代理与 CGLIB。

  4. 模板方法模式:以 Template 结尾的类(JdbcTemplate, RestTemplate, RedisTemplate)。父类定义执行骨架(如连接、关闭),子类(或回调)提供 SQL 逻辑。

  5. 观察者模式:Spring 的事件机制(ApplicationEvent, ApplicationListener)。

  6. 策略模式HandlerMapping 的多种实现(如 RequestMappingHandlerMapping);或者 DefaultResourceLoader 根据前缀(classpath:, http:)选择不同的加载策略。

5. 高频问题总结

Spring 如何解决循环依赖?

  • 核心: 三级缓存 + 提前暴露对象。

  • 流程: A 实例化后将其 ObjectFactory 存入三级缓存;注入 B 时发现 B 不在,去创建 B;B 注入 A 时通过三级缓存拿到 A 的代理对象(或原始对象)并升入二级缓存。B 完成初始化后,A 也能顺利完成。

Bean 生命周期: 记住“四步走”:实例化 -> 属性赋值 -> 初始化(Aware/PostProcessor) -> 销毁。

AOP 原理: 基于动态代理。目标类有接口用 JDK,无接口用 CGLIB。通过 AdvisorAdapter 将通知封装为拦截器链。

事务实现原理: AOP 增强。在目标方法前后开启和提交事务,通过 ThreadLocal 结合 TransactionSynchronizationManager 确保同一个线程使用同一个数据库连接。

自动配置原理: @EnableAutoConfiguration 通过 ImportSelector 扫描类路径下的 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,配合 @Conditional 条件注解实现按需加载。

启动流程: 重点是 run() 方法。涉及环境准备、容器创建、**刷新容器(invokeBeanFactoryPostProcessors)**以及内置 Tomcat 启动。

Starter 原理: “依赖管理” + “自动配置”。利用 SPI 机制让 Spring Boot 发现并加载特定的配置类。

如何自定义 Starter

  1. 创建一个模块 xxx-spring-boot-starter

  2. 编写配置属性类 @ConfigurationProperties

  3. 编写自动配置类 @AutoConfiguration 并注册所需的 Bean。

  4. resources 下创建 AutoConfiguration.imports 文件指向该类。

@Transactional 失效: 重点排查“自调用”(不走代理)、“异常捕获未抛出”、“非 public 方法”。

全局异常处理: 使用 @RestControllerAdvice + @ExceptionHandler 捕获特定异常并返回统一数据格式。

6. 其他

源码级核心类

  1. AbstractApplicationContext:核心中的核心,尤其是 refresh() 方法,它是整个 Spring 容器的“生命之泉”。

  2. DefaultListableBeanFactory:Spring 默认的 IoC 容器实现,所有的 BeanDefinition 最终都注册在这里。

  3. BeanPostProcessor:这是 Spring 最强大的扩展点。@Autowired@Async@Value 甚至 AOP 代理,全是靠它的子类实现的。

性能与优化

  • Bean 懒加载 (@Lazy)

    • 作用: 减少启动时的内存占用和启动时间。

    • 注意: 仅对非关键业务 Bean 使用,关键 Bean 建议预加载以提前发现配置错误。

  • 循环依赖优化

    • 虽然 Spring 能解决,但构造器循环依赖是无解的。可以通过 @Lazy 注入依赖来缓解,或者重构代码消除闭环。

  • AOP 性能影响

    • AOP 涉及动态代理调用和拦截器链遍历,会有轻微损耗。在高频调用的核心算法逻辑中,应尽量避免过多 AOP 嵌套。