GQ Spring

什么是循环依赖

循环依赖指的是两个或多个模块、类或组件之间相互依赖,从而形成一个闭环
简单来说:

  • 模块 A 依赖模块 B,
  • 模块 B 又依赖模块 A,
  • 这样就会导致系统在加载或初始化时,无法确定先创建哪个对象。
    循环依赖本身不是语法错误,但在依赖注入或对象构建时会导致程序启动失败或逻辑异常。

和死锁(Deadlock)的区别

对比项 循环依赖 死锁
定义 代码层面或模块层面的相互依赖关系 运行时线程相互等待资源,谁也不释放
发生阶段 编译期或启动期(比如 Spring Bean 创建阶段) 运行期(线程执行过程中)
结果表现 程序无法完成依赖注入或初始化 程序卡死、不再响应但不报错
解决方式 拆分依赖、使用懒加载(@Lazy)、构造改为 setter 注入 通过加锁顺序、超时锁或死锁检测机制避免

循环依赖是代码设计上的问题,而死锁是运行时线程竞争资源的问题。
前者在启动就会出错,后者是运行中卡死。

小结:
循环依赖就是两个或多个类互相依赖,导致系统在初始化时无法确定先创建谁,比如 A 依赖 B,B 又依赖 A它常见于依赖注入框架(比如 Spring)中,可能导致启动失败。

而死锁是运行时两个线程互相占用资源不释放,导致程序卡死。
简单说:循环依赖是‘启动时互等’,死锁是‘运行时互等’。

Spring 如何解决循环依赖?

在 Spring 中,循环依赖指的是:

Bean A 依赖 Bean B,而 Bean B 又依赖 Bean A。

如果两者都在实例化阶段互相等待,就会导致创建失败。
Spring 为了解决这个问题,引入了 三级缓存机制(Three-Level Cache),来打破这种“互等”局面。

Spring 的三级缓存机制

Spring 的三级缓存位于 DefaultSingletonBeanRegistry 中,分别是:

缓存层级 名称 作用
一级缓存 singletonObjects 存放完全初始化好的 Bean(成品)
二级缓存 earlySingletonObjects 存放实例化但未完成依赖注入的 Bean(半成品)
三级缓存 singletonFactories 存放可以创建早期 Bean 引用的工厂对象(ObjectFactory)

img

可以把它想成一个生产车间:

  • 一级区是‘成品区’,
  • 二级区是‘半成品区’,
  • 三级区是‘工厂区’,能提前生产半成品出来救急。

Spring 解决循环依赖的核心流程(以 A ↔ B 为例)

① 创建 A
Spring 发现 A 还没创建,于是调用 createBean(A)。

把 “A 正在创建中” 标记进三级缓存(工厂区)。

实例化 A(这时只是空壳,没有注入属性)。

② A 注入属性时发现依赖 B
Spring 去创建 B,同样走 createBean(B) 流程。

实例化 B 时,发现它依赖 A。

此时 Spring 检查缓存:

一级缓存中没有(成品还没完成);

二级缓存也没有;

但在 三级缓存中找到了 A 的 ObjectFactory。

Spring 调用 getObject() 获取 A 的早期引用,放进二级缓存(半成品区)。
这样 B 可以顺利完成依赖注入。

③ B 初始化完成后放入一级缓存
此时 B 已经是成品。

④ 回到 A
A 继续完成剩余属性注入和初始化。
此时 B 已经是成品,因此注入成功。
最终,A 也放入一级缓存,循环依赖解除。

通俗来说:Spring 会先查成品区,找不到再查半成品区,还找不到才动用工厂区提前生产一个早期对象引用。

img

img

Spring 不能解决的循环依赖

Spring 的三级缓存机制只对 单例(Singleton)Bean + Setter 注入 有效。
但以下场景无法解决:

  • 构造器注入的循环依赖:
    • 因为在实例化阶段就需要依赖对方,此时还没放入缓存。
  • 原型(Prototype)作用域的 Bean:
    • 每次创建新实例,不共享缓存,无法打破循环。

Setter 注入可以救,但构造器注入救不了。因为你还没造出对象,就已经要拿它用。

小结:

Spring 是通过三级缓存来解决大部分循环依赖问题的。
它维护了三个 Map:一级缓存放成品 Bean,二级缓存放半成品 Bean,三级缓存放可以创建早期对象的工厂。

当 Bean A 依赖 Bean B,而 B 又依赖 A 时,Spring 会从三级缓存提前拿出 A 的早期引用注入到 B 中,等 B 创建完后再回头把 A 初始化完成,这样就打破了互相等待。

但注意,这种机制只适用于单例 + Setter 注入,构造器注入的循环依赖仍然会报错。

为什么 Spring 循环依赖需要三级缓存,二级不够吗?

Spring 之所以要设计三级缓存,是为了同时解决两类问题:
1️ 循环依赖问题(让 Bean 能互相注入),
2️ AOP 代理的提前暴露问题(保证注入的是代理对象而不是原始对象)。
简单说:

二级缓存能解决“互相依赖”,
但只有三级缓存才能让“代理对象”参与循环依赖,
保证最终 Bean 是对的那个代理版本。

为什么二级缓存不够用?

如果只有二级缓存(成品 + 半成品),确实可以解决大部分循环依赖问题。
但问题出在 AOP 代理场景

假如 A 被代理(比如加了事务、切面):

  1. Spring 在创建 A 的时候,A 还没完成初始化;
  2. 但此时 B 又依赖 A,于是去缓存里找:
  • 一级缓存:没有;
  • 二级缓存:A 还没放进去;
  1. 如果直接放原始 A(未代理)进二级缓存,
  • 那 B 注入的就是原始对象,而不是代理对象 。

所以 Spring 必须提前暴露一个能生成代理的“工厂”,
即放进三级缓存的 ObjectFactory。
当 B 要 A 时,就能通过这个工厂拿到最终的代理对象 。

三级缓存解决流程

1️ 创建 A → 放入“正在创建集合”
2️ 提前将 A 的 ObjectFactory 放入三级缓存
3️ 创建 B,发现依赖 A
4️ 从三级缓存中拿到 getObject() 工厂 → 生成 A 的代理对象 → 放入二级缓存
5️ B 成功注入 A → B 完成 → 放入一级缓存
6️ A 也完成属性注入 → 从二级缓存移到一级缓存
此时两个 Bean 都是正常可用的,且代理生效。

小结

Spring 用三级缓存来解决循环依赖。
一级放成品 Bean,二级放半成品 Bean,三级放能生成 Bean 的工厂对象。
如果只用二级缓存,在有 AOP 时 B 会拿到未代理的原始对象。
有了三级缓存,Spring 就能在注入时通过工厂提前生成代理对象,保证注入的永远是最终版本的 Bean。

看过源码吗?说下 Spring 由哪些重要的模块组成?

Spring 整体是一个分层、模块化设计的框架,核心是 IoCAOP
Spring 整体可以分为五大核心模块:Core Container、AOP、Data Access、Web、以及扩展模块
各模块之间松耦合、可独立使用,也能协同组合,这就是 Spring 灵活和可扩展的关键。

核心模块

1️. Core Container(核心容器层)

这是整个 Spring 的基础,主要负责 依赖注入(DI) 和 Bean 管理。

模块 作用
Spring Core 提供 IoC(控制反转)功能,实现 Bean 的创建与依赖注入,是最底层的核心。
Spring Beans 定义 Bean、依赖关系和生命周期的基础类。
Spring Context 提供上下文容器(ApplicationContext),支持事件、国际化、资源加载等。
Spring Expression Language(SpEL) 表达式语言,用于动态注入属性或条件配置。

核心容器相当于 Spring 的‘心脏’,它负责 Bean 的创建、注入和管理,所有功能模块都依赖它。

2️ AOP(面向切面编程)

用来在不修改业务代码的情况下,实现通用功能复用,比如日志、事务、安全控制等。

模块 作用
Spring AOP 提供切面编程能力,用代理机制动态地在方法前后插入逻辑。

AOP 就像在方法外面包了一层壳,能在执行前后加上日志、安全校验或事务控制。

3️ Data Access(数据访问层)

用于整合各种数据访问技术,对外提供一致的事务与数据访问接口。

模块 作用
Spring JDBC 简化原生 JDBC 操作,自动处理连接与资源释放。
Spring ORM 集成 Hibernate、MyBatis、JPA 等 ORM 框架。
Spring Transaction 提供声明式事务管理机制。

Spring 对数据层做了统一封装,不管你用 JDBC 还是 MyBatis,事务都能用同一套机制管理。

4️ Web(Web 层)

提供 Web 开发的基础支持,包括 MVC 架构、REST API、响应式编程等。

模块 作用
Spring Web 封装基础的 Web 功能,如多层架构和上下文集成。
Spring MVC 实现 MVC 模式,是构建 Web 应用的核心模块。
Spring WebFlux 基于 Reactor 的响应式编程框架,适合高并发、异步场景。

Web 层就是 Spring 做前后端交互的地方,MVC 适合传统请求,WebFlux 适合异步流式请求。

5️ 扩展模块(Advanced Modules)

模块 作用
Spring Batch 支持批量任务处理。
Spring Integration 实现企业集成模式(EAI),支持消息、服务编排。
Spring Cloud 面向分布式系统的服务治理和微服务支持。

这些模块是为企业级或分布式场景准备的,比如批处理、消息通信、微服务等。

模块化设计的灵活性

Spring 模块化设计的核心思想是按需引入、低耦合高复用
比如如果我只想用 IoC 功能,可以只引入 spring-core 和 spring-beans,不需要整个 Web 模块,这保证了 Spring 的轻量化和扩展性。

Spring Boot 与 Spring Framework 的关系

Spring Boot 是对 Spring Framework 的进一步封装,它帮我们做了自动配置和模块整合。开发者不再需要手动写 XML 或 JavaConfig,大量默认配置让 Spring 应用启动更快、更简单。
Spring 是引擎,Spring Boot 是启动器。

小结

Spring 框架主要由五大模块组成:
核心容器(Core、Beans、Context、SpEL)负责 IoC;
AOP 模块 负责切面编程;
数据访问模块(JDBC、ORM、Transaction)管理数据操作和事务;
Web 模块(MVC、WebFlux)支撑 Web 应用;
还有扩展模块(Batch、Integration、Cloud)提供企业级支持。
模块之间松耦合、按需加载,这也是 Spring 灵活和强大的原因。

什么是 Spring IOC?

“Spring IOC,也叫控制反转(Inversion of Control),是 Spring 框架的核心思想之一。

  • 控制反转(IoC):
    原本对象之间的依赖关系是由我们手动控制的,比如在代码里 A a = new A();。
    而现在这种“控制权”被反转给 Spring 容器 —— 它负责创建对象,并把需要的依赖注入进去。
    所以叫 “控制反转”。

  • 依赖注入(DI):
    实现控制反转的具体方式。Spring 会通过 构造器注入 或 Setter 注入
    把对象需要的依赖传进去,而不是让对象自己去 new。

以前我自己去超市买材料做饭(手动 new 对象);
现在我点了外卖,Spring 容器帮我准备好并送上门(IoC 容器负责创建和注入)。
这就是控制反转的本质。

Spring IOC 的核心原理

Spring IOC 底层是基于 工厂模式(Factory Pattern) 实现的:

  • Spring 容器其实就是一个 工厂BeanFactoryApplicationContext)。
  • 它在启动时会解析配置文件或注解(如 @Component, @Bean),然后根据配置创建 Bean 实例,并维护它们的依赖关系。
  • 当我们需要一个 Bean 时,直接从容器里拿(getBean()),而不是自己去创建。

Spring 容器其实就像一个智能工厂,我们只需要告诉它要什么对象,
它会自动帮我们创建好、装配好依赖。
所以我们在用的时候不需要关心 new 的过程,这就是 IoC 的思想。

IOC 带来的好处

IOC 的核心价值就是解耦,程序员不用再管理对象生命周期,Spring 帮我们管一切。

小结

Spring IOC,也就是控制反转,是通过依赖注入实现的。
它把对象的创建和依赖管理交给 Spring 容器完成,而不是由代码自己去 new。
容器通过工厂模式创建 Bean,并自动注入依赖。
这样让程序更解耦、更灵活、更好维护。
简单来说,IOC 就是 Spring 在帮我们管理对象之间的依赖关系。

Spring 中的 DI 是什么?

DI是实现控制反转的具体方式。Spring 会通过 构造器注入 或 Setter 注入 把对象需要的依赖传进去,而不是让对象自己去 new。

什么是 Spring Bean?

在 Spring 中,Bean 就是由 Spring 容器创建、管理和装配的对象
换句话说,只要一个 Java 对象被交给 Spring 管理,它就是一个 Bean
它的整个生命周期(创建、初始化、依赖注入、销毁)都由容器负责。

Spring Bean 的生命周期大致可以分为四个阶段

阶段 说明
1️ 实例化(Instantiation) 容器启动时,根据配置或注解解析,Spring 会先创建 Bean 实例。
2️ 依赖注入(Dependency Injection) 实例化后,Spring 会通过构造器、setter 方法等把所需依赖注入进去。
3️ 初始化(Initialization) 如果 Bean 实现了 InitializingBean 接口,或使用了 @PostConstruct 注解,会在依赖注入完成后调用初始化方法。
4️ 销毁(Destruction) 容器关闭时,如果实现了 DisposableBean 接口,或使用了 @PreDestroy 注解,会调用销毁逻辑。

可以把 Spring Bean 想象成‘由容器养大的对象’,
它从出生(实例化)到长大(初始化)再到被销毁,全程都由容器照顾。

Spring Bean 的定义方式

定义方式 示例 特点
XML 配置 <bean id="userService" class="com.xxx.UserService"/> 早期常用方式,现在较少用。
注解方式 @Component, @Service, @Repository, @Controller Spring 会自动扫描并注册为 Bean,主流做法。
Java 配置方式 @Configuration + @Bean 代码方式定义,类型安全、灵活度高。

以前我们在 XML 里写 ,现在更常用 @Component 或 @Bean 注解来声明 Bean,Spring 会自动识别并注册。

Bean 与 IoC 的关系

Spring Bean 是 IoC 容器管理的最小单位
IOC 解决的是谁来创建对象、谁来管理依赖的问题,而 Bean 就是被管理的那些对象。
所以说:Spring 管容器,容器管 Bean

小结

Spring Bean 就是被 Spring 容器创建和管理的对象。
它的生命周期包括实例化、依赖注入、初始化和销毁四个阶段。
我们可以通过注解、XML 或 Java 配置来定义 Bean。
简单说,Spring Bean 是 IOC 容器中最基本的管理单元,容器负责帮我们创建和维护它。

Spring 中的 BeanFactory 是什么?

BeanFactory 是 Spring IoC 容器的底层接口,负责 Bean 的创建、依赖注入和生命周期管理。
它采用延迟加载机制,只有在第一次请求 Bean 时才会创建对象。
它有多个实现类,比如 DefaultListableBeanFactory。
平时我们更常用的 ApplicationContext 实际上是对 BeanFactory 的增强版,提供了更丰富的功能,比如国际化、事件发布等。

  • 简单说,BeanFactory 就是 Spring 管 Bean 的‘工厂’,它负责生产和管理所有的 Bean。

Spring 中的 FactoryBean 是什么?

FactoryBean 是 Spring 中一种特殊的 Bean,用来创建其他 Bean 的工厂
它允许我们通过实现 getObject() 方法自定义 Bean 的创建逻辑。
当我们通过 getBean() 获取时,拿到的是它生产的对象,而不是 FactoryBean 自身

  • 就像你点外卖,你拿到的不是厨师(FactoryBean 本身),
    而是厨师做出来的菜(它生成的 Bean 对象)。

如果要获取工厂本身,需要在 Bean 名称前加上 &。
它常用于像 AOP、MyBatis 这种需要动态生成代理对象的场景

Spring 中的 ObjectFactory 是什么?

ObjectFactory 是 Spring 提供的一个轻量级工厂接口,用来延迟获取 Bean 实例。
它只有一个 getObject() 方法,调用时才真正从容器中取出 Bean。
这样可以避免容器启动时就创建所有 Bean,提高性能。
Spring 内部也大量使用 ObjectFactory,比如在解决循环依赖时,它就是二级缓存的核心组件之一。

  • 简单说,ObjectFactory 是一个懒加载的小工厂,用来在需要时再取出 Bean。

一句话串讲记忆

BeanFactory 是“仓库”,
FactoryBean 是“定制工厂”,
ObjectFactory 是“取货券”。

口语化表达:

“Spring 的世界里,BeanFactory 管理所有 Bean;

Spring 中的 ApplicationContext 是什么?

ApplicationContext 是 Spring 容器的高级接口,它是 BeanFactory 的增强版
它不仅能管理 Bean 的创建和生命周期,还提供了事件机制、国际化、资源加载等高级功能。
简单来说,BeanFactory 是基础仓库,而 ApplicationContext 是功能更强的‘智能仓库’。

BeanFactory 更偏底层,而我们日常开发中几乎都用 ApplicationContext因为它不仅能帮我们管 Bean,还能帮我们做配置、国际化、事件广播,甚至支持 AOP

ApplicationContext 的五大核心功能

  1. Bean 管理:负责所有 Bean 的创建、配置和生命周期。
  2. 资源加载:可以方便地加载文件、XML、属性文件等。
  3. 国际化支持:根据不同语言环境加载不同资源,实现多语言切换。
  4. 事件机制:可以发布、监听和处理事件,实现模块间解耦。
  5. AOP 支持:与 Spring AOP 深度集成,支持切面逻辑织入。

ApplicationContext 不只是管 Bean 的,它其实是整个应用的‘上下文环境’,负责帮我们管理配置文件、监听事件、加载资源,甚至支持 AOP

ApplicationContext常见实现类

实现类 适用场景
ClassPathXmlApplicationContext 从类路径加载 XML 配置文件(常见于本地项目)
FileSystemXmlApplicationContext 从文件系统加载配置文件(适用于外部配置)
AnnotationConfigApplicationContext 基于注解配置(现代 Spring Boot 常用)
WebApplicationContext 专为 Web 应用设计的上下文环境

不同的 ApplicationContext 实现类就像不同版本的容器,
你在 Web 环境、命令行工具或 Spring Boot 项目里都能选到合适的那一个

和 BeanFactory 的区别

BeanFactory 只是‘仓库’,
ApplicationContext 是‘智能仓库 + 管理中心’。

对比项 BeanFactory ApplicationContext
加载时机 懒加载(第一次 getBean 才创建) 启动时立即创建所有单例 Bean
功能性 仅负责 Bean 管理 附带国际化、事件机制、AOP、资源加载等
使用场景 底层或轻量级容器 实际开发主流选择

小结

  • ApplicationContext 是 Spring 的高级容器接口,是 BeanFactory 的增强版本。
  • 它除了负责 Bean 的创建、配置和生命周期管理外,还提供国际化、资源加载、事件发布、AOP 支持等高级功能。
  • 它是我们在 Spring 应用中最常用的上下文环境接口,比如常见的 AnnotationConfigApplicationContext、ClassPathXmlApplicationContext 等实现类

Spring Bean 一共有几种作用域?

Spring Bean 一共有 6 种作用域(scope),其中前两种是 常规的(几乎所有应用都有),后四种是 Web 环境专属(Spring Web 应用中才有效)

  1. singleton(单例)
    singleton 就是全局只造一个对象,整个容器里共用这一个。

  2. prototype(原型 / 多实例)
    prototype 就是每次都新建一个,谁来要我就给谁造一个新的。

  3. request(请求级)
    request 就是一次请求一个新 Bean,请完就销毁。

  4. session(会话级)
    session 就是每个用户一份 Bean,自己用自己的。

  5. application(应用级)
    application 就是整个 Web 应用共享一个 Bean。

  6. websocket(WebSocket 级)
    websocket 就是一个连接一个 Bean,断了就销毁。

作用域 是否默认 适用环境 生命周期说明
singleton ✅ 是 所有 Spring 应用 全局单例
prototype ❌ 否 所有 Spring 应用 每次创建新实例
request ❌ 否 Spring Web 每个请求一个实例
session ❌ 否 Spring Web 每个会话一个实例
application ❌ 否 Spring Web 整个应用一个实例
websocket ❌ 否 Spring WebSocket 每个 WebSocket 一个实例

小结

Spring 一共有 6 种作用域。最常用的是 singleton(默认单例)和 prototype(多实例)。
另外四种——request、session、application、websocket——是 Web 环境下特有的,分别对应一次请求、一次会话、整个应用和一次 WebSocket 连接的生命周期。

Spring 一共有几种注入方式?

Spring 一共有 四种常见的依赖注入方式:
构造器注入、Setter 注入、字段/方法注入、接口回调注入
其中官方推荐使用构造器注入(Constructor-based Injection)

构造器注入(Constructor-based Injection)

原理:通过类的构造函数传入依赖对象。
优点:依赖是“强制性的”,在对象创建时就必须提供;
保证了 Bean 的完整性与不可变性。
实现方式:

  • 使用 @Autowired 标注构造器,或
  • 使用 Lombok 的 @RequiredArgsConstructor 简化。

Spring 推荐的就是构造器注入,依赖是强制性的,对象一创建就完整,不可能少依赖。

Setter 注入(Setter-based Injection)

  • 原理:通过 setter 方法注入依赖对象。
  • 优点:依赖可选,可在运行时动态替换。
  • 适用场景:部分依赖在某些情况下才需要时。

Setter 注入适合可选依赖,比如某个组件有就用,没有也能运行。

字段/方法注入(Field Injection)

  • 原理:直接在字段/方法上用 @Autowired 注解,由 Spring 反射注入依赖。
  • 优点:写法最简单,代码量最少。
  • 缺点:不利于单元测试,不符合依赖倒置原则。

字段注入虽然方便,但不推荐用在复杂项目里,测试不好写。

接口回调注入(接口注入 / Aware 接口)

  • 原理:实现 Spring 的某些接口(如 BeanFactoryAware、ApplicationContextAware),容器会在初始化时回调注入容器对象。
  • 用途:通常用于框架级开发或工具类,不常用于业务代码。

Aware 系列接口属于 Spring 内部机制,开发者一般少用。

小结

Spring 的依赖注入主要有四种方式:构造器注入、Setter 注入、字段/方法注入和接口回调注入。
官方推荐构造器注入,因为它能保证依赖完整、易测试;Setter 适合可选依赖;
字段注入虽然简单,但不建议在正式项目中使用;而接口回调主要用于框架内部。

什么是 AOP?

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程思想。
它的目标是把程序中与业务无关但又经常重复出现的逻辑(比如日志、安全检查、事务管理等),从业务代码里分离出来,用‘切面(Aspect)’统一管理
简单来说,就是把共性逻辑抽取出来,动态地织入到业务流程中。

AOP 就像是在程序运行时‘偷偷加点料’。
不改动原来的业务逻辑,就能在某些方法前后加上日志、事务、校验这些通用操作。

AOP 的核心思想与组成

名称 说明 示例
切面(Aspect) 切面是一个模块,封装了一组通用功能,比如日志、事务等。 @Aspect public class LogAspect { ... }
连接点(Join Point) 程序执行的具体位置,比如方法调用或异常抛出。 方法执行前、执行后等位置
通知(Advice) 定义在连接点要执行的操作。 @Before, @After, @Around
切入点(Pointcut) 定义要拦截哪些方法或类。 @Pointcut("execution(* com.demo.service.*.*(..))")
织入(Weaving) 把切面逻辑“织入”到目标对象的过程。 通过代理机制在运行时完成织入

AOP 的核心就是:
我先声明我要在哪个地方(Pointcut)加什么逻辑(Advice),
然后由 Spring 帮我在运行时自动把这段逻辑织进去(Weaving)。

AOP 的底层原理

AOP 的实现其实就是代理模式的应用。Spring AOP 主要用两种代理机制:

  • JDK 动态代理:基于接口的代理。
  • CGLIB 代理:基于类继承的代理。

你可以把它理解成 Spring 在运行时帮你‘包了一层壳’,
在你执行目标方法前后,自动执行切面逻辑。

AOP 常见的应用场景

场景 说明 示例
日志记录 在方法执行前后记录日志,避免每个类都手写日志代码。 打印调用方法名、参数等
事务管理 在方法执行前开启事务,执行后提交或回滚。 @Transactional
安全检查 在方法执行前检查权限,未授权则阻止访问。 权限验证逻辑
性能监控 统计方法执行耗时,方便系统调优。 @Around 环绕通知

像日志、事务、安全、性能这些功能,其实都属于‘横切逻辑’,有了 AOP,我们就能一处定义,到处生效。

小结

  • AOP 是一种面向切面编程思想,它把像日志、事务、安全检查这些通用逻辑从业务代码里抽取出来,用‘切面’统一管理。
  • 它通过代理机制,在方法执行前后动态地插入额外逻辑,实现了业务逻辑与系统服务的解耦。
  • 常见的应用场景包括日志记录、事务管理、权限验证和性能监控。

你的项目中哪里用到了AOP?

在 xxx 这个项目里,我确实把 AOP 用在了两类典型的横切场景。

  • 第一类是“登录校验”的全局拦截。我做了一个自定义注解 @GlobalInterceptor,控制器方法只要打上这个注解并把 checkLogin=true,切面 GlobalOperationAspect 就会在方法执行前先跑一遍登录校验:从请求头里拿 token,到 Redis 里校验用户信息,不通过就直接抛业务异常。这样业务方法完全不用重复写登录判断,后期如果登录策略调整,只改切面一处就能全局生效。

  • 第二类是“把用户行为自动转成站内消息”。我定义了 @RecordUserMessage 注解,像收藏、评论、审核这类会触发通知的行为,只要在方法上打注解,UserMessageOperationAspect 会用 @Around 在方法成功返回后读取方法入参(比如 videoIdactionTypereplyCommentIdcontent),归一化成对应的 MessageType,然后调用消息服务落一条站内消息。失败或抛异常的情况不会记消息,保证幂等和一致性。管理端那边也有同名切面,做的事是一致的,只是调用方不同(通过 InteractClient 触发消息)。

这两处 AOP 的好处非常直观:

  • 一是把“登录校验”“行为转消息”这种跟业务强相关、但又应该统一收口的共性逻辑,从每个接口里抽出来集中管理,业务代码更干净;
  • 二是灵活性高,新增/调整规则都不需要改散落在各处的控制器;
  • 三是天然适合做“只在成功时记录”的事情,用 @Around 能拿到返回值与异常,更好地保证边界与一致性。

为什么用 AOP 而不是过滤器/拦截器:

  • 我会强调登录这类是“业务前置规则”,需要在方法级拿到注解、参数等上下文,AOP 在方法颗粒度更合适;
  • 网关 Filter/Servlet Filter/HandlerInterceptor 更偏协议层或路由层,做限流、跨域、鉴权粗粒度拦截很好,但像“成功后落一条站内消息”这种强业务语义,用切面在目标方法周围处理最自然。

最后,我也预留了扩展位:以后要做“操作审计”“性能埋点”“权限细粒度校验”,直接再加注解和切面就行,跟现有两套模式完全一致。

能说说 Spring 拦截链的实现吗?

Spring 的拦截链本质上是一系列拦截器按顺序执行的机制,
主要包括 Filter(过滤器)、HandlerInterceptor(MVC 拦截器) 和 AOP 切面拦截 三种,
它们从外到内一层层包裹,实现请求前后和方法调用前后的统一处理。

  • Filter(过滤器) 是最外层的守门员,拦得最早,一般负责安全验证、跨域这些全局性的事。
  • MVC 拦截器像一个环环相扣的链条,请求进来先经过 preHandle,再到 Controller,然后按顺序调用 postHandle、afterCompletion。
  • AOP 拦截器更细粒度,是方法级别的拦截。Spring 会把所有切面按顺序放进一个集合,然后递归调用,就形成了完整的拦截链。

小结

Spring 拦截链分三层:Filter、HandlerInterceptor 和 AOP。
Filter 是基于 Servlet 的全局拦截;
HandlerInterceptor 是 MVC 层的请求拦截,控制请求的前后处理;
AOP 是方法级别的动态拦截,通过切面实现。
它们组合起来形成一条完整的请求链,从外到内层层进入,再按反方向返回,
就像一个洋葱模型一样,既灵活又强大。

说下 Spring Bean 的生命周期?

一句话总结

“Spring Bean 从创建到销毁,大致会经历 实例化 → 属性注入 → 初始化 → 使用 → 销毁 这五个阶段,


二、详细生命周期阶段

1️⃣ 实例化(Instantiation)

  • Spring 根据配置文件或注解,创建 Bean 实例对象(相当于执行 new)。
  • 此时 Bean 还没被依赖注入。

“Spring 先把 Bean 给造出来,这时还只是个空壳对象。”


2️⃣ 属性注入(Populate Properties)

  • Spring 通过 setter 方法或反射,把依赖对象注入到 Bean 中。
  • 相当于给这个“空壳对象”装上它需要的组件。

“接下来 Spring 会往 Bean 里面塞依赖,比如注入 Service 或 Dao。”


3️⃣ Aware 接口回调(可选)

  • 如果 Bean 实现了类似 BeanNameAwareBeanFactoryAware 等接口,
    Spring 会在这里回调这些方法,让 Bean 获取容器信息。

“如果 Bean 想知道自己叫什么、在哪个容器里,这一步 Spring 会告诉它。”


4️⃣ BeanPostProcessor:初始化前(Before Initialization)

  • 在 Bean 初始化前,会调用所有注册的 BeanPostProcessorpostProcessBeforeInitialization() 方法。
  • 常用于在初始化前修改 Bean 的属性或增强功能。

“初始化前,Spring 会让一些后置处理器先动手,比如 AOP 或注解增强。”


5️⃣ 初始化(Initialization)

  • Bean 如果实现了 InitializingBean 接口,会执行 afterPropertiesSet() 方法;
  • 或者配置了 init-method,Spring 会调用它。
  • 这一步 Bean 已经准备好,可以开始使用。

“这一步是真正的‘激活’,Spring 会执行 Bean 的初始化逻辑,比如加载缓存、开启任务。”


6️⃣ BeanPostProcessor:初始化后(After Initialization)

  • 调用 postProcessAfterInitialization()
    通常在这里完成 AOP 代理对象的生成。

“初始化完,Spring 再让后置处理器做最后加工,比如创建代理对象。”


7️⃣ Bean 使用阶段(In Use)

  • Bean 被正常使用,可能被别的 Bean 引用,也可能参与业务逻辑。

“到这一步 Bean 就‘上线’了,正式参与业务。”


8️⃣ 销毁阶段(Destroy)

  • 当 Spring 容器关闭时:

    • 如果 Bean 实现了 DisposableBean 接口,会执行 destroy()
    • 或者定义了 destroy-method,Spring 会调用该方法。

“最后容器关闭,Spring 会优雅地销毁 Bean,比如释放资源、关闭连接。”


📊 三、完整生命周期流程图(逻辑顺序)

1
2
3
4
实例化 → 属性注入 → Aware接口回调
BeanPostProcessor(前) → 初始化(init)
BeanPostProcessor(后) → 使用
→ 销毁(destroy)

四、小结

“Spring Bean 的生命周期可以分为五个阶段:实例化、属性注入、初始化、使用、销毁。
初始化前后会执行 BeanPostProcessor,
如果实现了 Aware、InitializingBean、DisposableBean 等接口,也会有对应回调。
整个流程由 Spring 容器全程托管,开发者只需关注初始化和销毁逻辑。”

说下对 Spring MVC 的理解?

一、一句话总结

“Spring MVC 是 Spring 框架对经典 MVC 模式的扩展实现,它通过前端控制器 DispatcherServlet 统一接收和分发请求,把传统的 Servlet + JSP 模式彻底解耦,让 Web 开发更清晰、更高效。”


二、核心理解

1️ 本质是什么

Spring MVC 就是一个 基于 MVC 模式的 Web 框架
MVC 分为:

  • Model(模型层):负责业务逻辑和数据处理
  • View(视图层):负责页面展示
  • Controller(控制层):负责接收请求、调用业务、返回结果

SpringMVC 让这三层之间职责清晰、解耦合。

“Spring MVC 就是帮你把 Controller、业务逻辑、视图展示这几部分分开管理的 Web 框架。”


2️ Spring MVC 的核心组件

Spring MVC 的核心是 DispatcherServlet(前端控制器)。
它就像整个 Web 请求的“总调度员”,负责:

  • 接收 HTTP 请求
  • 调用合适的 Controller
  • 处理业务、封装数据
  • 再把结果交给 View 层渲染返回

DispatcherServlet 就像快递分拣中心,所有请求先送到它手上,再由它分发到对应的 Controller。”


3️ 相比传统 MVC 的改进

传统 Servlet + JSP 模式要自己写大量 Servlet、手动解析参数,代码冗余。
Spring MVC 把这些工作都交给框架自动处理,比如:

  • 请求映射(@RequestMapping
  • 参数绑定
  • 异常处理
  • 视图解析

极大简化了开发工作。

“以前写 Web 要自己写一堆 Servlet,现在有了 Spring MVC,只要写 Controller 方法,框架帮你搞定剩下的。”


4️ Spring MVC 中的分层思想

虽然传统 MVC 是三层(Model、View、Controller),
但在 Spring 体系中,Model 层又被进一步细分:

  • Service 层:负责业务逻辑
  • Repository 层:负责数据库访问
  • Controller 层:负责 Web 层请求

这种分层更贴合实际项目架构,也让职责更清晰。

“Spring 把 MVC 又细化了,Model 层里再分业务层(Service)和数据层(Repository),真正做到清晰分工。”


三、面试回答

“Spring MVC 是 Spring 框架基于经典 MVC 模式实现的 Web 开发框架。
它通过前端控制器 DispatcherServlet 统一接收和分发请求,把传统 Servlet+JSP 模式中繁琐的请求处理、参数绑定、视图解析都交给框架完成。
在 Spring 体系中,MVC 被进一步细化成 Controller、Service 和 Repository 三层,职责更清晰、开发更高效。”

Spring MVC 具体的工作原理?

简要流程

img

1.“所有请求先到 DispatcherServlet(前端控制器),它就像一个总调度员,统一安排后续处理。
2.DispatcherServlet 会问 HandlerMapping:这次请求该哪个 Controller 来管?
3.找到对应的处理器(Handler,也就是 Controller 方法)后,Spring 会生成一个 HandlerExecutionChain(执行链)
4.根据执行链会得到一个适配器HandlerAdapter(处理器适配器),什么样的请求处理得到什么样的适配器
5.在执行 目标方法 之前,会先经过拦截器链的前置流程 然后执行目标方法,执行完目标方法之后再执行拦截器的后置。
6.执行完会返回目标方法的返回结果
7.

  • 如果是页面跳转(比如返回 ModelAndView),就交给 ViewResolver(视图解析器)(8),得到视图进行页面渲染(9)
  • 如果是前后端分离接口(带 @ResponseBody),就交给 HttpMessageConverter(消息转换器),把对象转成 JSON 返回(8)。

10,11:如果执行过程中抛异常,会交给HandlerExceptionResolver 来捕获处理,
比如跳到错误页或者返回统一的异常 JSON。

小结:

浏览器一进来,请求发过来,由 DispatcherServlet 处理,它会去 HandlerMapping 里边,根据每一个请求路径的这个 map 去找这个请求由谁处理,找到由谁处理以后会返回执行链,根据执行链会得到一个适配器,什么样的请求处理得到什么样的适配器,适配器就是大型反射工具,适配器在执行目标方法之前,其实会有一个拦截器的流程(前置)然后再执行目标方法,目标方法执行完后再执行拦截器的后置,执行完这个流程后得到目标方法的返回结果,如果你是页面跳转就会有 ModelAndView 这一套,这一套就交给视图解析器,得到视图进行页面渲染。如果是前后端分离交给消息转换器,消息转换器把这个 JSON 写出去。如果在这期间出现任何异常,异常解析器就会捕获处理期间的所有异常,得到错误的异常内容,再返回

img

完整流程

img

DispatcherServlet 进来先是文件上传解析器,判定是不是文件上传请求,在这里做处理,然后再获取处理器,从 HandlerMapping 里边挨个找映射,如果找到了就会拿到目标方法的执行链,找不到响应 404,最终把找到还是找不到封装到 mappedHandler,如果它是 Null 就代表没找到,即 404,找到了就再找到它的适配器,适配器最终处理目标方法,处理目标方法之前它还会判断是否有请求缓存,如果请求缓存就直接结束,如果请求不缓存就接着往下走,看拦截器是不是 preHandle,但凡有一个返回 false,代表拦截器炸了,炸了之后从中断位置执行 afterCompletion 然后结束,如果全部返回 true,就往下走,执行目标方法,目标方法里边参数处理会有参数解析器,返回值处理会有返回值解析器,这两个目标方法执行完如果目标方法执行完出现了异常咋办,有异常会把异常封装起来,没异常逆序执行拦截器 postHandler。无论有无异常最终都会执行最终处理 processDispatchResult,有异常是封装异常,没异常执行 postHandler,最后都会来到 processDispatchResult,最终处理的话有异常处理异常,有页面渲染页面,在以上所有步骤中,但凡有异常,拦截器逆序执行 afterCompletion

Spring 事务有几个隔离级别?

Spring 提供了五种事务隔离级别:

DEFAULT(默认):使用底层数据库的默认隔离级别。如果数据库没有特定的设置,通常默认为 READ_COMMITTED。
READ_UNCOMMITTED(读未提交):最低的隔离级别,允许事务读取尚未提交的数据,可能会导致脏读、不可重复读和幻读。
READ_COMMITTED(读已提交):仅允许读取已经提交的数据,避免了脏读,但可能会出现不可重复读和幻读问题。
REPEATABLE_READ(可重复读):确保在同一个事务内的多次读取结果一致,避免脏读和不可重复读,但可能会有幻读问题。
SERIALIZABLE(可串行化):最高的隔离级别,通过强制事务按顺序执行,完全避免脏读、不可重复读和幻读,代价是性能显著下降。

spring 事务隔离级别 和 数据库事务隔离级别不一致时以 spring中设定的为准,数据库connection链接可以设置数据库隔离级别

Spring 有哪几种事务传播行为?

Spring的事务传播机制用于控制事务方法在相互调用时如何共享事务或创建新的事务。在复杂的业务逻辑中,事务传播机制能够确保事务的一致性和完整性,避免出现数据丢失或重复提交等问题

Spring 提供了7种事务传播行为,默认的传播方式是 REQUIRED

  1. REQUIRED(默认):如果当前存在事务,方法就在该事务中执行;如果没有事务,则创建新事务。
  2. REQUIRES_NEW:每次执行都会开启新事务,若已有事务则挂起当前事务,独立执行。
  3. SUPPORTS:如果有事务,方法加入该事务;如果没有事务,方法就不在事务中执行。
  4. NOT_SUPPORTED:如果当前存在事务,则暂停事务,若没有事务,则方法在没有事务的环境中执行。
  5. MANDATORY:强制当前有事务,若没有事务则会抛出异常。
  6. NEVER:如果当前存在事务,则抛出异常;如果没有事务,则执行。
  7. NESTED:如果当前有事务,则创建嵌套事务,嵌套事务回滚时不影响父事务,反之,父事务回滚时会影响嵌套事务。

使用场景:

  • REQUIRED:常用在方法需要共享事务时,确保多个方法在同一事务中执行。
  • REQUIRES_NEW:当你希望某个方法执行时不受外部事务影响时使用。
  • SUPPORTSNOT_SUPPORTEDMANDATORYNEVER:适用于特定业务逻辑需要控制事务行为的情况。
  • NESTED:用于复杂的业务逻辑中,特别是当一个方法的失败不应该影响外部事务时,使用嵌套事务来实现更细粒度的回滚控制。

示例:

假设有两个方法A和B,方法A执行数据库更新操作,方法B在A之后被调用。如果发生异常,需要确保两个方法的操作一起回滚。你可以在这两个方法上都使用 REQUIRED 传播行为,确保它们在同一事务中执行。如果方法A或B中的任一方法失败,整个事务都会回滚。

Spring IOC 容器初始化过程?

Spring 容器启动其实就是在做一件事:创建并管理 Bean 的生命周期。整个过程可以简单分为 12 步

第一阶段:准备和工厂创建

  1. prepareRefresh() —— 做准备,比如环境变量检查。
  2. obtainFreshBeanFactory() —— 创建一个新的 BeanFactory。
  3. prepareBeanFactory() —— 给 BeanFactory 加一些基础组件。
  4. postProcessBeanFactory() —— 让子类可以进一步定制 BeanFactory。

第二阶段:处理和注册 Bean 定义

  1. invokeBeanFactoryPostProcessors() —— 执行 Bean 工厂的后置处理器,比如修改 Bean 定义。
  2. registerBeanPostProcessors() —— 注册 Bean 的后置处理器,负责 Bean 创建前后的增强逻辑(比如 AOP)。

第三阶段:国际化、事件与监听器

  1. initMessageSource() —— 初始化国际化组件。
  2. initApplicationEventMulticaster() —— 初始化事件广播器。
  3. onRefresh() —— 留给子类扩展,比如 Web 容器会在这步创建 Web 环境。
  4. registerListeners() —— 注册事件监听器。

第四阶段:Bean 创建与刷新完成

  1. finishBeanFactoryInitialization() —— 实例化所有单例 Bean,调用 getBean()。
  2. finishRefresh() —— 发布事件,标志容器启动完成。

img

img

@Bean和@Component有什么区别?

  • @Bean作用在方法上,在configuration类中;
  • @Component作用在类上,作用于自动扫描

@Component、@Controller、@Repository和@Service 的区别?

本质上没区别,他们都是@Component的衍生注解,名字不同是为了区分不同业务场景

  • @Component:通用注解,用于将任何类标记为 Spring Bean。
  • @Controller:特定于 Spring MVC,处理 Web 层请求
  • @Service:特定于业务逻辑层,用于处理服务逻辑
  • @Repository:特定于持久层,通常用于数据访问对象(DAO)。

Spring中的@Primary注解的作用是什么?

@Primary 注解的作用是:在多个候选Bean存在时,标记一个Bean为默认Bean,Spring 会优先选择它进行注入。

Spring中的@Value注解的作用是什么?

@value的主要作用是用于属性注入
1: 为当前注解的字段 设定默认值
2: 获取配置文件的配置
3、将系统变量和环境变量中的值注入到bean中

Spring 中的 @Profile 注解的作用是什么?

@Profile 用于定义一组 Bean 的配置文件所属的环境,比如 dev,prod

Spring 中的 @RequestBody 和 @ResponseBody 注解的作用是什么?

@RequestBody是接受Http的请求,将前端JSON对象转化为Java对象
@ResponseBody是将Java对象转化为前端的JSON格式,返回给前端

Spring 中的 @PathVariable 注解的作用是什么?

@PathVariable:获取URL路径中的参数,如 /users/123。
@RequestParam:获取URL地址栏中的参数 ,通常用于处理表单数据或查询参数,如 /users?id=123。

Spring MVC 中如何处理异常?

Spring MVC 主要有两种核心异常处理机制:

  1. 局部异常处理(@ExceptionHandler)
    在某个控制器里,用 @ExceptionHandler 注解定义方法来捕获特定异常。
    它只作用在当前 Controller,可以返回自定义错误信息或 JSON 响应。

  2. 全局异常处理(@ControllerAdvice + @ExceptionHandler)
    如果想统一处理所有 Controller 的异常,可以定义一个带 @ControllerAdvice 的类,在里面写带 @ExceptionHandler 的方法。
    这样就不用在每个控制器重复写异常处理逻辑。

3.自定义异常
一般我们还会结合四步最佳实践:

  1. 定义异常枚举类(统一异常码和提示信息)
  2. 定义自定义异常类(继承 RuntimeException)接收枚举参数,记录异常码
  3. 业务中按需抛出自定义异常
  4. 统一用全局异常处理器捕获并返回标准格式的错误响应