学习笔记

SpringBoot框架

28 分钟阅读

Spring Bean 基础

一、Spring Bean 基础


1.1 Bean 是单例的吗?

核心要点

  • 默认是单例(singleton):Spring IOC 容器中只有一个实例,全局共享
  • 多例(prototype):每次请求都创建新实例
  • 其他作用域:request(每次HTTP请求)、session(每次HTTP会话)、application(ServletContext生命周期)

详细解析

Spring Bean 的作用域通过 @Scope 注解配置:

作用域说明使用场景
singleton默认值,容器只有一个实例无状态的服务类、配置类
prototype每次获取都创建新实例有状态的Bean
request每个HTTP请求创建一个Web应用中请求级别的数据
session每个HTTP会话创建一个用户登录信息等会话数据
applicationServletContext生命周期内一个全局应用配置

单例 Bean 的特点

  • 容器启动时创建(默认懒加载除外)
  • 所有注入该 Bean 的地方引用的是同一个对象
  • 节省内存,但需要注意线程安全问题

面试口头回答

Spring Bean 默认是单例的,也就是 singleton 作用域。在 Spring IOC 容器中,一个 Bean 只有一个实例,全局共享,所有注入这个 Bean 的地方引用的都是同一个对象。

除了单例,还有 prototype 多例模式,每次向容器请求的时候都会创建一个新的实例。另外还有 request、session、application 这些 Web 相关的作用域。

单例 Bean 的好处是节省内存,不需要频繁创建销毁对象。但如果 Bean 里有可变的成员变量,多个线程同时访问时可能会有线程安全问题,这一点需要注意。


1.2 Bean 是线程安全的吗?

核心要点

  • 单例 Bean 默认不是线程安全的(多个线程共享同一个实例)
  • 无状态 Bean 是线程安全的:不存储可变数据,只执行业务逻辑
  • 解决方案:使用多例 Bean、设计为无状态、ThreadLocal、加锁

详细解析

为什么单例 Bean 可能线程不安全?

  • Spring 默认的 Bean 作用域是 singleton,IOC 容器中只有一个实例,被多个线程共享
  • 如果 Bean 维护了可变的成员变量(如计数器、状态标志等),并发访问时可能引发数据不一致

线程安全的情况

  • 无状态 Bean:常见的 Service 或 DAO 层 Bean,不存储可变数据,仅执行业务逻辑,方法内变量都是局部变量,不存在线程安全问题

解决方案

方案实现方式适用场景
多例 Bean@Scope("prototype")Bean 必须维护状态
无状态设计避免定义可变成员变量大多数 Service/DAO
ThreadLocal让每个线程拥有独立变量副本线程上下文数据
加锁synchronized 或 ReentrantLock必须共享可变状态

最佳实践

  • 尽量将 Bean 设计为无状态
  • 需要状态时用 ThreadLocal 或方法参数传递
  • 避免在单例 Bean 中使用可变成员变量

面试口头回答

Spring 的单例 Bean 默认不是线程安全的。因为单例 Bean 在容器中只有一个实例,被多个线程共享,如果这个 Bean 维护了可变的成员变量,并发访问时就会引发数据不一致的问题。

但是无状态的 Bean 是线程安全的。比如我们常见的 Service 层或 DAO 层的 Bean,它们通常不存储可变数据,只执行业务逻辑,方法内部都是局部变量,所以不会受并发影响。

如果确实需要保证线程安全,有几种解决方案:

  • 使用多例 Bean,每次请求都创建新实例,互不干扰;
  • 设计为无状态 Bean,尽量避免定义可变成员变量;
  • 使用 ThreadLocal,让每个线程拥有独立的变量副本;
  • 加锁,用 synchronized 或 ReentrantLock 保证互斥访问。

实际开发中,我最常用的做法是把 Service 层设计为无状态,需要保存的上下文信息通过方法参数或 ThreadLocal 传递,避免在 Bean 中维护可变状态。


1.3 Bean 的生命周期

核心要点

  • 四个主要阶段:实例化 → 属性赋值 → 初始化 → 销毁
  • 详细七步:扫描定义 → 实例化 → 属性赋值 → Aware回调 → 前置处理 & 初始化 → 使用 → 销毁
  • BeanPostProcessor 在初始化前后提供扩展点,AOP 代理在此生成

详细解析

简化版四阶段

  1. 创建 Bean 实例:Spring 根据配置使用反射实例化 Bean
  2. 属性赋值和依赖注入:解析 @Autowired@Value 等注解,通过构造方法或 setter 注入依赖
  3. 初始化 Bean
    • 调用 Aware 接口的 set 方法
    • 执行 BeanPostProcessor 前置处理
    • 调用初始化方法(InitializingBean、@PostConstruct、init-method)
    • 执行 BeanPostProcessor 后置处理(可能产生 AOP 代理对象)
  4. 销毁 Bean:容器关闭时调用 destroy() 方法

详细版七阶段

  1. 扫描与定义阶段(BeanDefinition):Spring 扫描配置文件、注解等,获取 Bean 定义信息,封装成 BeanDefinition。其中记录了类名、作用域、依赖信息以及初始化/销毁方法等元数据。

  2. 实例化阶段(Instantiation):Spring 根据 BeanDefinition,通过反射创建 Bean 实例。此时属性还未填充。

  3. 属性赋值阶段(Populate Properties / DI):将 Bean 的依赖属性注入,例如通过构造器或 setter 注入。

  4. Aware 接口回调(Aware Callbacks):如果 Bean 实现了 BeanNameAwareBeanFactoryAwareApplicationContextAware 等接口,Spring 会在此阶段回调相应方法,让 Bean 获取容器相关信息。

  5. 前置处理 & 初始化阶段(BeanPostProcessor + Initializing)

    • 前置处理器:调用 BeanPostProcessor.postProcessBeforeInitialization(),可对 Bean 做增强处理
    • 初始化:执行 InitializingBean.afterPropertiesSet()@PostConstruct 或 XML 配置的 init-method
    • 后置处理器:调用 BeanPostProcessor.postProcessAfterInitialization(),对 Bean 做最终增强(例如生成 AOP 代理)
  6. 使用阶段(Ready to Use):Bean 已经完成初始化,可以被应用程序正常使用。

  7. 销毁阶段(Destruction):当容器关闭时,Spring 调用 Bean 的销毁方法,例如 DisposableBean.destroy()@PreDestroy 或 XML 配置的 destroy-method。

面试口头回答

Bean 的生命周期主要包括四个阶段:实例化、属性赋值、初始化和销毁。

更详细来说有七个步骤:

  • 第一步是扫描与定义:Spring 扫描配置文件和注解,把 Bean 的定义信息封装成 BeanDefinition;
  • 第二步是实例化:通过反射创建 Bean 的对象实例;
  • 第三步是属性赋值:解析 Autowired、Value 这些注解,把依赖注入到 Bean 中;
  • 第四步是 Aware 回调:如果 Bean 实现了 BeanNameAware、ApplicationContextAware 这些接口,Spring 会回调相应方法,让 Bean 拿到容器的信息;
  • 第五步是前置处理和初始化:先执行 BeanPostProcessor 的前置处理,然后执行初始化方法,比如 @PostConstruct 或 afterPropertiesSet,再执行 BeanPostProcessor 的后置处理,这里可能会生成 AOP 代理对象;
  • 第六步是使用:Bean 初始化完成,可以被正常使用;
  • 第七步是销毁:容器关闭时调用销毁方法,比如 @PreDestroy。

其中 BeanPostProcessor 是很重要的扩展点,Spring 的 AOP 就是在后置处理器中生成代理对象的。


二、Spring 循环依赖


2.1 Spring 中的循环依赖?

核心要点

  • 循环依赖:两个或多个 Bean 互相依赖,形成闭环
  • Spring 通过三级缓存解决单例 Bean 的循环依赖(构造器注入除外)
  • 三级缓存:一级(成品Bean)、二级(半成品Bean)、三级(ObjectFactory)
  • 只有单例 Bean 支持循环依赖,prototype 不支持

详细解析

发生场景

  • A 依赖 B,B 依赖 A
  • 或者 A → B → C → A 的更复杂循环

三级缓存机制

缓存级别名称存储内容作用
一级缓存singletonObjects最终形态的 Bean(已实例化、属性填充、初始化)单例池,获取 Bean 的主要来源
二级缓存earlySingletonObjects过渡 Bean(半成品,尚未属性填充)存储三级缓存中 ObjectFactory 产生的对象,防止 AOP 时多次创建代理
三级缓存singletonFactoriesObjectFactory 的 getObject() 方法可以生成原始 Bean 或代理对象,只对单例 Bean 生效

解决循环依赖的过程

  1. Spring 创建 A 之后,发现 A 依赖了 B,又去创建 B
  2. B 依赖了 A,又去创建 A
  3. 在 B 创建 A 的时候,A 发生了循环依赖。由于 A 此时还没有初始化完成,在一二级缓存中肯定没有 A
  4. 此时去三级缓存中调用 getObject() 方法获取 A 的前期暴露对象,也就是调用 getEarlyBeanReference() 方法,生成一个 A 的早期引用
  5. 将这个 ObjectFactory 从三级缓存中移除,并将早期暴露对象放入二级缓存
  6. B 将早期暴露对象注入到依赖中,完成 B 的创建
  7. B 创建完成后,A 继续完成属性填充和初始化

为什么需要三级缓存?如果只有两级会发生什么?

在没有 AOP 的情况下,确实可以只使用一级和二级缓存来解决循环依赖。但当涉及到 AOP 时,三级缓存就非常重要了:

  • 三级缓存中存储的是 ObjectFactory,可以生成代理对象
  • 它确保了即使在 Bean 的创建过程中有多次对早期引用的请求,也始终只返回同一个代理对象
  • 避免了同一个 Bean 有多个代理对象的问题

如果不通过三级缓存,怎么解决循环依赖?

方案说明
重构代码最根本的办法,将相互依赖的功能提取到新类中
Setter 注入替代构造器注入构造器注入在创建 Bean 时就解析依赖,有循环依赖会报错;Setter 注入允许先创建实例再设置依赖
@Lazy 注解延迟加载 Bean,真正需要时才初始化

面试口头回答

循环依赖是指两个或多个 Bean 互相依赖持有对方,形成闭环。比如 A 依赖 B,B 又依赖 A。

Spring 主要通过三级缓存来解决单例 Bean 的循环依赖问题。

一级缓存存放最终的成品 Bean,是单例池,我们正常获取 Bean 都是从这里拿。二级缓存存放半成品 Bean,也就是还没完成属性填充和初始化的对象。三级缓存存放的是 ObjectFactory,调用它的 getObject 方法可以生成原始 Bean 或者代理对象。

解决过程是这样的:Spring 创建 A 之后发现 A 依赖 B,就去创建 B;B 又发现依赖 A,再去创建 A。这时候 A 还在创建中,一二级缓存里都没有 A,于是去三级缓存中调用 getObject 获取 A 的早期暴露对象,把这个早期引用注入到 B 中,B 就能完成创建。B 创建完成后,A 继续完成属性填充和初始化。

这里三级缓存比二级缓存多的那一级,主要是为了解决 AOP 代理的问题。如果只有两级缓存,每次获取早期引用都可能创建一个新的代理对象,导致同一个 Bean 有多个代理。三级缓存中的 ObjectFactory 确保多次请求始终返回同一个代理对象。

如果不通过三级缓存解决,也可以用 Setter 注入替代构造器注入,或者用 @Lazy 注解延迟加载,但最根本的解决办法还是重构代码,打破循环依赖。


三、IOC、AOP 与动态代理


3.1 IOC 控制反转

核心要点

  • 核心思想:将对象的管理权从应用程序代码转移到 Spring 容器
  • DI 依赖注入是 IOC 的具体实现方式
  • 传统方式:类 A 依赖类 B,A 自己创建 B 的实例;IOC 方式:Spring 负责实例化和注入 B
  • 容器负责 Bean 的实例化、依赖注入、生命周期管理和单例维护

详细解析

控制反转的含义

  • 控制:对象创建、依赖注入、生命周期管理的控制权
  • 反转:控制权从开发者手中反转给 Spring 容器

依赖注入的三种方式

注入方式说明示例
构造器注入通过构造方法注入依赖,推荐方式@Autowired 放在构造方法上
Setter 注入通过 setter 方法注入依赖@Autowired 放在 setter 方法上
字段注入直接注入到字段上,最简洁但不推荐@Autowired 放在字段上

注入 Bean 的注解

  • @Autowired:默认按照类型注入,是 Spring 提供的注解
  • @Resource:默认按照名称注入,是 JSR-250 标准注解

IOC 流程

  1. IOC 容器初始化:解析 XML 配置或注解,获取所有 Bean 的定义信息,生成 BeanDefinition
  2. Bean 实例化及依赖注入:通过反射实例化 Bean,根据 BeanDefinition 解析依赖关系,通过构造方法、setter 方法完成注入
  3. Bean 的使用:通过 @Autowired 获取当前 Bean

面试口头回答

IOC 控制反转是 Spring 的核心特性。它的核心思想是将对象创建和管理的控制权从开发者手中交给 Spring 容器,实现控制反转。

具体来说,传统方式下,如果类 A 依赖类 B,A 需要自己创建 B 的实例。而在 IOC 模式下,Spring 容器负责实例化 B 并注入到 A 中,A 只需要声明依赖即可。

依赖注入是控制反转的具体实现方式,主要有三种:构造器注入、Setter 注入和字段注入。Spring 推荐用构造器注入,因为依赖明确,而且方便单元测试。

注入 Bean 常用的注解有 @Autowired 和 @Resource。@Autowired 默认按类型注入,是 Spring 提供的;@Resource 默认按名称注入,是 Java 标准注解。

IOC 的流程大致是:容器启动时解析配置和注解,生成 BeanDefinition;然后通过反射实例化 Bean,同时完成依赖注入;最后 Bean 就可以被使用了。

我在项目中主要是配合 Spring Boot 使用 IOC,通过 @Service、@Repository 这些注解声明 Bean,通过 @Autowired 注入依赖,完全不需要手动 new 对象。


3.2 AOP 面向切面编程

核心要点

  • 将公共行为和逻辑抽取为可重用模块,减少重复代码
  • 底层实现是动态代理,允许在不修改原始代码的情况下扩展方法执行过程
  • 核心概念:切面(Aspect)、切点(Pointcut)、通知(Advice)、连接点(JoinPoint)
  • Spring 根据目标对象是否实现接口选择代理方式:有接口用 JDK 动态代理,无接口用 CGLIB

详细解析

AOP 核心概念

概念说明
切面(Aspect)横切关注点的模块化,包含通知和切点
切点(Pointcut)定义哪些方法需要被拦截,通过表达式匹配
通知(Advice)拦截后要执行的逻辑,分为 Before、After、Around 等
连接点(JoinPoint)程序执行过程中的某个点,如方法调用
目标对象(Target)被代理的原始对象

AOP 执行流程

  1. 创建代理对象:Spring 使用 JDK/CGLIB 动态代理,通过 Proxy.newProxyInstance 或 Enhancer 生成代理对象
  2. 拦截切点表达式的方法:Spring 根据目标对象是否实现接口选择代理方式:
    • JDK 动态代理:目标对象实现接口 → 生成接口代理
    • CGLIB 代理:目标对象没有接口 → 生成子类代理
  3. 执行增强逻辑:根据通知类型(@Before、@After、@Around),在方法执行前后执行对应的 AOP 逻辑
  4. 执行目标方法:调用目标对象的方法,完成实际业务逻辑

通知类型

注解执行时机
@Before目标方法执行前
@After目标方法执行后(无论是否异常)
@AfterReturning目标方法成功返回后
@AfterThrowing目标方法抛出异常后
@Around环绕通知,可以控制目标方法是否执行

连接点 JoinPoint

  • JoinPoint 抽象了连接点,可以获取方法执行时的相关信息
  • 如目标类名、方法名、方法参数等

应用场景

  • 日志记录:在方法执行前后自动记录日志
  • 事务管理:自动开启、提交或回滚事务(如 @Transactional
  • 异常处理:统一捕获和处理异常
  • 缓存机制:拦截方法调用,判断是否需要从缓存获取数据
  • 统计方法执行时长:性能监控

面试口头回答

AOP 是面向切面编程,它的核心思想是把公共行为和逻辑抽取出来,封装成一个可重用的模块,减少系统中的重复代码。它通过代理机制实现方法增强,允许在不修改原始代码的情况下,对方法的执行过程进行扩展。

AOP 的核心概念有:切面是横切关注点的模块化;切点定义哪些方法需要被拦截;通知是拦截后要执行的逻辑;连接点是程序执行过程中的某个点。

AOP 的执行流程是:首先 Spring 创建代理对象,根据目标对象是否实现接口选择代理方式——有接口用 JDK 动态代理,没接口用 CGLIB 生成子类代理。然后拦截匹配切点表达式的方法,根据通知类型在方法执行前后执行增强逻辑,比如日志记录、事务提交,最后执行目标方法完成实际业务。

通知类型主要有 @Before 前置通知、@After 后置通知、@AfterReturning 返回通知、@AfterThrowing 异常通知、@Around 环绕通知。

我在项目中主要用 AOP 做日志记录和接口耗时统计。定义了一个切面,用 @Around 环绕通知拦截所有 Controller 方法,在方法执行前记录开始时间,执行后计算耗时并打印日志。也用过 @Transactional 做声明式事务管理,这也是 AOP 的典型应用。


3.3 动态代理

核心要点

  • 运行时动态生成代理对象,增强目标对象方法
  • JDK 动态代理:基于 InvocationHandler 和 Proxy,要求目标类必须实现接口
  • CGLIB 动态代理:基于 MethodInterceptor 和 Enhancer,生成目标类的子类,不依赖接口
  • Spring AOP 中:有接口默认用 JDK,无接口用 CGLIB

详细解析

动态代理的概念

  • 一种在运行时动态生成代理对象,并在代理对象中增强目标对象方法的技术
  • 从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中
  • 广泛应用于 AOP、事务管理、日志记录等场景

JDK 动态代理

核心类:

  • InvocationHandler 接口:重写 invoke() 方法,在方法调用时执行自定义逻辑
  • Proxy 类:通过 newProxyInstance() 创建代理对象

使用步骤:

  1. 自定义类实现 InvocationHandler 接口,重写 invoke 方法
  2. invoke 中调用原生方法并自定义处理逻辑
  3. 通过 Proxy.newProxyInstance() 创建代理对象
public class MyInvocationHandler implements InvocationHandler {
    private Object target;
    public MyInvocationHandler(Object target) { this.target = target; }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("方法执行前");
        Object result = method.invoke(target, args);
        System.out.println("方法执行后");
        return result;
    }
}

CGLIB 动态代理: 核心类:

  • MethodInterceptor 接口:重写 intercept() 方法拦截增强
  • Enhancer 类:通过 create() 创建代理类

使用步骤:

  1. 自定义 MethodInterceptor 并重写 intercept 方法
  2. 通过 Enhancer 类的 create() 创建代理类

JDK vs CGLIB 对比

特性JDK 动态代理CGLIB 动态代理
代理方式实现目标类的接口生成目标类的子类
接口要求必须实现接口不依赖接口
final 限制不能代理 final 类和方法
性能JDK 版本升级后更优略逊于 JDK(新版本)
底层技术反射 + 接口ASM 字节码增强
Spring 默认目标有接口时默认使用目标无接口时使用

面试口头回答

动态代理是在运行时动态创建代理对象,对目标对象的方法进行增强。

它的核心原理是基于反射机制:通过反射获取目标类的方法信息,然后在方法执行前后插入自定义逻辑,比如日志、权限验证、事务管理。

Java 中主要有两种动态代理方式:

JDK 动态代理:基于 InvocationHandler 接口和 Proxy 类实现。要求目标类必须实现接口。使用步骤是先实现 InvocationHandler 接口并重写 invoke 方法,在里面调用原生方法并添加自定义逻辑,然后通过 Proxy.newProxyInstance 创建代理对象。代理对象调用方法时,实际会调用 InvocationHandler 的 invoke 方法。

CGLIB 动态代理:通过生成目标类的子类来实现代理,不依赖接口。核心是 MethodInterceptor 接口和 Enhancer 类。它底层使用 ASM 字节码增强技术。

两者的对比:JDK 动态代理只能代理实现了接口的类;CGLIB 可以代理未实现接口的类,但不能代理 final 类型的类和方法。性能方面,随着 JDK 版本升级,JDK 动态代理的性能优势越来越明显。

在 Spring AOP 中,如果 Bean 实现了接口,默认用 JDK 动态代理;如果没有接口,就使用 CGLIB。这个可以通过配置修改。

我在项目中使用动态代理主要是配合 Spring AOP 做日志和事务,没有自己手动实现过动态代理,但理解它的原理。


四、Spring 注解详解


4.1 Spring、SpringMVC、SpringBoot 常用注解

核心要点

  • 声明 Bean:@Component、@Controller、@Service、@Repository
  • 依赖注入:@Autowired、@Resource
  • 作用域:@Scope
  • 配置:@Configuration、@Bean
  • AOP:@Aspect、@Before、@After、@Around、@Pointcut
  • SpringMVC:@RequestMapping、@GetMapping、@PostMapping、@RequestBody、@ResponseBody、@PathVariable
  • SpringBoot:@SpringBootApplication、@RestController

详细解析

Spring 注解

注解作用
@Component通用标记,声明一个类是 Spring 的 Bean
@Controller标记控制器层的 Bean
@Service标记服务层的 Bean
@Repository标记数据访问层的 Bean(有异常转换功能)
@Autowired按类型注入 Bean
@Resource按名称注入 Bean(JSR-250 标准)
@Scope设置 Bean 作用域(singleton、prototype)
@Configuration标记为配置类
@Bean在配置类中定义 Bean(常用于第三方库)
@Aspect标记为切面类
@Pointcut定义切点表达式

SpringMVC 注解

注解作用
@RequestMapping映射 HTTP 请求的 URL 路径和方法
@GetMapping@RequestMapping(method = GET) 的简化
@PostMapping@RequestMapping(method = POST) 的简化
@RequestBody接收 HTTP 请求的 JSON 数据转为 Java 对象
@ResponseBody将 Controller 返回的 Java 对象转化为 JSON
@PathVariable从 URL 路径中获取参数值
@RequestHeader从请求头中获取指定信息

SpringBoot 注解

注解作用
@SpringBootApplication核心组合注解,包含 @Configuration + @EnableAutoConfiguration + @ComponentScan
@RestController@Controller + @ResponseBody 的组合

@SpringBootApplication 的组成

  • @SpringBootConfiguration:本质上是 @Configuration,表明该类是配置类
  • @EnableAutoConfiguration:开启 Spring Boot 的自动配置功能,读取 META-INF/spring.factories 文件中的类名自动加载 Bean
  • @ComponentScan:启用组件扫描,自动扫描指定包下的 @Component@Service@Repository@Controller 等注解的类

面试口头回答

Spring 相关的注解我分三类来说:

Spring 核心注解

  • 声明 Bean 的有 @Component 通用标记、@Controller 控制器、@Service 服务层、@Repository 数据层;
  • 依赖注入的有 @Autowired 按类型注入、@Resource 按名称注入;
  • 配置相关的有 @Configuration 标记配置类、@Bean 在配置类中定义 Bean;
  • AOP 相关的有 @Aspect 标记切面类、@Pointcut 定义切点、@Before @After @Around 通知类型。

SpringMVC 注解

  • @RequestMapping 映射 URL 路径和请求方法;
  • @GetMapping、@PostMapping 是它的简化形式;
  • @RequestBody 把请求的 JSON 转为 Java 对象;
  • @ResponseBody 把返回的 Java 对象转为 JSON;
  • @PathVariable 从 URL 路径中取参数;
  • @RequestHeader 从请求头中取信息。

SpringBoot 注解

  • @SpringBootApplication 是核心组合注解,包含了 @Configuration 表明是配置类、@EnableAutoConfiguration 开启自动配置、@ComponentScan 开启组件扫描;
  • @RestController 是 @Controller 加 @ResponseBody 的组合,标记 RESTful API 的控制器。

最常用的就是 @SpringBootApplication 放在启动类上,@RestController 放在 Controller 类上,@GetMapping/@PostMapping 放在方法上映射接口。


五、SpringBoot 自动配置


5.1 SpringBoot 自动配置原理

核心要点

  • 基于 @SpringBootApplication 中的 @EnableAutoConfiguration 触发
  • 通过 @Import 导入配置选择器,读取 META-INF/spring.factories 文件
  • 根据 @Conditional 系列注解过滤配置类
  • 最后由自动配置类创建和配置相应的 Bean

详细解析

自动配置的核心流程

  1. @EnableAutoConfiguration 注解触发自动配置机制

  2. @Import 导入 AutoConfigurationImportSelector

    • 通过 SpringFactoriesLoader.loadFactoryNames() 方法
    • 读取所有 jar 包中 META-INF/spring.factories 文件
    • 文件中以 EnableAutoConfiguration 为 key 的配置类列表
  3. @Conditional 条件过滤

条件注解作用
@ConditionalOnClass类路径存在指定类时生效
@ConditionalOnMissingClass类路径不存在指定类时生效
@ConditionalOnBean容器中存在指定 Bean 时生效
@ConditionalOnMissingBean容器中不存在指定 Bean 时生效
@ConditionalOnProperty指定属性满足条件时生效
  1. 自动配置类创建 Bean
    • 过滤后的配置类被加载到 Spring 容器
    • 配置类中的 @Bean 方法创建相应的组件
    • 同时可以通过 application.properties 进行属性定制

自动配置的本质

  • 开发者无需手动编写大量的配置代码
  • Spring Boot 根据项目的依赖自动完成配置
  • 可以通过配置文件覆盖自动配置的默认值

面试口头回答

SpringBoot 自动配置的原理主要基于 @SpringBootApplication 中的 @EnableAutoConfiguration 注解。

具体流程是:

  • 首先,@EnableAutoConfiguration 通过 @Import 导入 AutoConfigurationImportSelector 配置选择器;
  • 然后,这个选择器会读取所有 jar 包中 META-INF/spring.factories 文件,获取里面以 EnableAutoConfiguration 为 key 的自动配置类列表;
  • 接着,通过 @Conditional 系列条件注解对这些配置类进行过滤,比如 @ConditionalOnClass 判断类路径中是否存在某个类,@ConditionalOnProperty 判断配置文件中是否启用了某个属性;
  • 最后,满足条件的自动配置类会被加载到 Spring 容器,创建和配置相应的 Bean。

这样开发者就不需要手动写大量的配置代码,SpringBoot 会根据项目中引入的依赖自动完成配置。比如引入了 spring-boot-starter-web,就会自动配置 Tomcat、SpringMVC 等组件。同时我们也可以通过 application.properties 或 application.yml 来覆盖自动配置的默认值。


六、Spring 事务


6.1 Spring 事务原理与失效场景

核心要点

  • @Transactional 本质是通过 AOP 生成代理对象增强原方法
  • 方法执行前开启事务,成功执行完毕提交,出现异常回滚
  • 失效场景:异常被吞、抛出编译异常、非 public 方法、内部 this 调用、数据源不归 Spring 管

详细解析

@Transactional 的本质

  • 将被事务注解标注的方法交给 Spring 进行事务管理
  • 本质是通过 AOP 生成代理对象增强原方法
  • 方法执行前开启事务;成功执行完毕提交事务;出现异常回滚事务

Spring 事务失效的常见场景

失效场景原因解决方案
异常被 try-catch 捕获事务通知需要捕获抛出的异常才能回滚在 catch 块手动抛出 RuntimeException
抛出编译时异常Spring 默认只回滚 RuntimeException配置 rollbackFor = Exception.class
非 public 方法Spring 事务为方法创建代理的前提是 public将方法改为 public
内部 this 调用通过 this.method() 不走代理对象注入自身代理对象或拆分到另一个类
数据源不归 Spring 管事务管理器无法管理非 Spring 管理的数据源确保使用 Spring 管理的 DataSource

事务传播行为(Propagation)

传播行为说明
REQUIRED(默认)当前有事务则加入,没有则新建
SUPPORTS当前有事务则加入,没有则以非事务执行
REQUIRES_NEW新建事务,挂起当前事务
NOT_SUPPORTED以非事务执行,挂起当前事务
NEVER以非事务执行,当前有事务则抛异常
MANDATORY当前必须有事务,否则抛异常
NESTED在当前事务中创建嵌套事务

事务隔离级别

隔离级别问题说明
DEFAULT-使用数据库默认隔离级别
READ_UNCOMMITTED脏读、不可重复读、幻读最低级别
READ_COMMITTED不可重复读、幻读Oracle 默认
REPEATABLE_READ幻读MySQL 默认
SERIALIZABLE最高级别,性能最差

事务 ID 的生成时机

  • 在可重复读隔离级别下,事务 ID 是在目标方法执行之前生成的
  • Spring 通过 AOP 代理拦截方法调用,事务管理器在方法执行前创建新事务并分配唯一事务 ID
  • 事务 ID 绑定到当前线程,贯穿整个方法执行过程

面试口头回答

Spring 事务主要通过 @Transactional 注解实现声明式事务管理。

本质是通过 AOP 生成代理对象来增强原方法。方法执行前开启事务,成功执行完提交,如果出现异常就回滚。Spring 支持事务传播行为和隔离级别,能灵活控制事务嵌套和并发访问。

事务失效的场景我总结为几个:

  • 异常被自己 try-catch 吞掉了。事务通知只有捕捉到抛出的异常才能回滚,如果自己在方法里 catch 了没抛出去,事务就感知不到异常。解决方式是在 catch 块里手动抛出 RuntimeException。
  • 抛出的是编译时异常。Spring 默认只回滚 RuntimeException,如果抛出的是 IOException 这类编译异常,事务不会回滚。可以通过配置 rollbackFor = Exception.class 来解决。
  • 方法不是 public 的。Spring 事务为方法创建代理的前提是方法必须是 public,private 或 protected 方法事务不生效。
  • 内部 this 调用。比如类内部方法 A 调用方法 B,B 上有 @Transactional,但通过 this.methodB() 调用不会走代理对象,事务就失效了。解决方式是注入自身的代理对象,或者把方法拆到另一个类里。

我在项目中遇到最多的是异常被吞和内部调用导致的事务失效问题,排查时一般会先检查方法是不是 public,再看异常有没有正确抛出,最后确认调用是否通过代理对象。


七、Spring、SpringMVC、SpringBoot 的关系


7.1 三者的区别与关系

核心要点

  • Spring:轻量级 IoC 和 AOP 容器,解决对象管理和解耦问题
  • SpringMVC:Spring 的 Web 层框架,实现 MVC 模式
  • SpringBoot:基于 Spring + SpringMVC 的快速开发框架,提供自动配置、内嵌服务器、starter 依赖
  • 关系:Spring 是核心,SpringMVC 是 Web 模块,SpringBoot 是对全家桶的整合和简化

详细解析

Spring

  • 轻量级的 IoC 和 AOP 容器
  • 主要解决对象管理和解耦问题
  • 提供事务管理、数据访问、消息传递等企业级功能

SpringMVC

  • Spring 提供的 Web 层框架
  • 实现了 MVC(Model-View-Controller)模式
  • Model:模型数据,通常在控制器方法中填充
  • View:视图,用于生成用户界面
  • Controller:控制器,接收用户请求,调用业务逻辑,返回处理结果

SpringBoot

  • 基于 Spring 和 SpringMVC 的快速开发框架
  • 提供自动配置、内嵌服务器(Tomcat/Jetty/Undertow)和 starter 依赖
  • 极大简化了项目搭建和配置
  • 约定大于配置的理念

三者关系

Spring(核心 IoC + AOP)
    └── SpringMVC(Web 层模块)
            └── SpringBoot(快速开发框架,整合简化)

面试口头回答

Spring、SpringMVC 和 SpringBoot 是层层递进的关系。

Spring 是核心框架,它是一个轻量级的 IoC 和 AOP 容器,主要解决对象管理和解耦问题。提供了依赖注入、事务管理、AOP 等企业级功能。

SpringMVC 是 Spring 的 Web 层框架,实现了 MVC 模式。Model 是模型数据,View 是视图用于展示,Controller 是控制器接收请求、调用业务逻辑、返回结果。

SpringBoot 是基于 Spring 和 SpringMVC 的快速开发框架,它提供了自动配置、内嵌服务器和 starter 依赖管理。开发者不需要写大量的 XML 配置,引入 starter 就能自动集成相关组件,极大简化了项目搭建。

三者的关系是:Spring 是核心,SpringMVC 属于 Spring 家族的 Web 模块,SpringBoot 是对 Spring 全家桶的进一步整合和简化。我在项目中直接使用 SpringBoot,它底层已经包含了 Spring 和 SpringMVC,只需要引入对应的 starter 就可以快速搭建 Web 应用。


八、SpringMVC 执行流程


8.1 SpringMVC 执行流程

核心要点

  • 核心组件:DispatcherServlet(前端控制器)、HandlerMapping(处理器映射器)、HandlerAdapter(处理器适配器)、Controller(后端处理器)、ViewResolver(视图解析器)
  • 流程:请求 → DispatcherServlet → HandlerMapping → HandlerAdapter → Controller → ModelAndView → ViewResolver → View → 响应

详细解析

核心组件说明

组件作用
DispatcherServlet前端控制器,接收所有请求并分发
HandlerMapping处理器映射器,根据 URL 找到对应的 Controller 方法
HandlerAdapter处理器适配器,执行 Controller 方法
Controller后端处理器,处理业务逻辑
ViewResolver视图解析器,将逻辑视图名解析为具体视图
View视图,负责渲染页面

执行流程

  1. 用户发送请求 → 请求到达 DispatcherServlet(前端控制器)
  2. DispatcherServlet 调用 HandlerMapping(处理器映射器),根据请求 URL 找到对应的 Controller 方法
  3. HandlerMapping 返回 处理器对象及拦截器给 DispatcherServlet
  4. DispatcherServlet 调用 HandlerAdapter(处理器适配器)
  5. HandlerAdapter 调用后端处理器 Controller,执行业务逻辑
  6. Controller 返回 ModelAndView 对象(包含模型数据和逻辑视图名)
  7. HandlerAdapter 将 ModelAndView 返回给 DispatcherServlet
  8. DispatcherServlet 传给 ViewResolver(视图解析器)进行视图解析
  9. ViewResolver 解析返回视图 View 给 DispatcherServlet
  10. DispatcherServlet 渲染视图 View 并响应用户

RESTful API 场景简化

  • 如果 Controller 使用了 @ResponseBody@RestController
  • Controller 直接返回数据对象(如 JSON)
  • 跳过 ViewResolver 和 View 渲染步骤
  • 由 HttpMessageConverter 将对象转换为响应体

面试口头回答

SpringMVC 的执行流程我按步骤来说:

第一步,用户发送请求,请求首先到达 DispatcherServlet,它是前端控制器,所有请求都经过它。

第二步,DispatcherServlet 调用 HandlerMapping 处理器映射器,根据请求的 URL 找到对应的 Controller 方法。

第三步,HandlerMapping 返回处理器对象和拦截器给 DispatcherServlet。

第四步,DispatcherServlet 调用 HandlerAdapter 处理器适配器。

第五步,HandlerAdapter 调用后端处理器 Controller 执行业务逻辑。

第六步,Controller 返回 ModelAndView 对象,包含模型数据和逻辑视图名。

第七步,HandlerAdapter 把 ModelAndView 返回给 DispatcherServlet。

第八步,DispatcherServlet 传给 ViewResolver 视图解析器,把逻辑视图名转换为具体的视图。

第九步,ViewResolver 返回视图给 DispatcherServlet。

第十步,DispatcherServlet 渲染视图,把数据填充进去,生成最终响应返回给用户。

如果是开发 RESTful API,使用了 @RestController 或 @ResponseBody,Controller 直接返回数据对象,Spring 会自动用 HttpMessageConverter 转成 JSON,就不需要视图解析和渲染这些步骤了。


九、Spring 设计模式与实战


9.1 Spring 中用到了哪些设计模式?

核心要点

  • 工厂模式:BeanFactory、ApplicationContext 创建 Bean
  • 单例模式:Spring 默认 Bean 作用域
  • 代理模式:AOP 中的 JDK 动态代理和 CGLIB
  • 模板方法模式:JdbcTemplate、RestTemplate
  • 观察者模式:ApplicationListener 事件监听
  • 适配器模式:HandlerAdapter 适配不同类型的处理器
  • 策略模式:Resource 接口的不同资源加载策略
  • 装饰器模式:BeanWrapper、BufferedReader

面试口头回答

Spring 中用到的设计模式非常多,我说几个主要的:

工厂模式:Spring 的 BeanFactory 和 ApplicationContext 就是工厂模式的应用,负责创建和管理 Bean 实例。

单例模式:Spring 默认的 Bean 作用域就是单例,容器中只有一个实例,这是单例模式的典型应用。

代理模式:Spring AOP 的实现就用了代理模式,包括 JDK 动态代理和 CGLIB 代理。

模板方法模式:JdbcTemplate、RestTemplate、JpaTemplate 这些都是模板方法模式,定义了算法的骨架,具体的实现由子类或回调完成。

观察者模式:Spring 的事件机制就是观察者模式,ApplicationEvent 发布事件,ApplicationListener 监听处理。

适配器模式:SpringMVC 中的 HandlerAdapter 就是适配器模式,把不同类型的处理器适配成统一的接口。

策略模式:Resource 接口对不同类型的资源(URL、File、Classpath)采用不同的加载策略。

这些设计模式让 Spring 具有很高的扩展性和灵活性。


9.2 如何用 Spring Boot 快速实现一个 HTTP 服务?

核心要点

  • 创建 Spring Boot 项目,引入 spring-boot-starter-web
  • 主启动类使用 @SpringBootApplication
  • 编写 REST 控制器,使用 @RestController 和 @GetMapping
  • 启动应用后通过浏览器或 curl 访问

面试口头回答

用 Spring Boot 快速实现一个 HTTP 服务很简单:

第一步,创建一个 Spring Boot 项目,引入 spring-boot-starter-web 依赖。

第二步,编写主启动类,加上 @SpringBootApplication 注解,在 main 方法里调用 SpringApplication.run 启动应用。

第三步,编写 REST 控制器类,加上 @RestController 注解,在里面写一个方法,加上 @GetMapping("/hello") 映射接口路径,方法返回 “Hello World”。

第四步,启动应用,Spring Boot 会自动内嵌启动 Tomcat。

第五步,通过浏览器访问 http://localhost:8080/hello,或者用 curl 测试,就能看到 Hello World 的响应。

整个过程不需要配置 web.xml,不需要部署到外部 Tomcat,Spring Boot 的自动配置和约定大于配置的理念让开发非常高效。


9.3 如何设计并编写一个 Spring Boot 的 Controller?

核心要点

  • 使用 @RestController 定义控制器
  • 使用 @RequestMapping、@GetMapping、@PostMapping 指定接口路径和方法类型
  • 使用 @PathVariable、@RequestParam、@RequestBody 获取请求参数
  • Controller 只负责请求/响应转换,业务逻辑委托给 Service 层
  • 返回对象或 ResponseEntity,Spring Boot 自动序列化为 JSON
  • 结合 @Valid 参数校验、@ControllerAdvice 全局异常处理提高健壮性

面试口头回答

设计 Spring Boot 的 Controller 我遵循这几个原则:

第一,使用 @RestController 定义控制器类,它是 @Controller 和 @ResponseBody 的组合,这样类里所有方法的返回值都会自动转成 JSON。

第二,使用 @GetMapping、@PostMapping 这些注解来定义接口路径和请求方法类型。比如 @GetMapping("/users/{id}") 处理 GET 请求,@PostMapping("/users") 处理 POST 请求。

第三,参数获取根据场景选择不同注解:

  • @PathVariable 从 URL 路径中取参数,比如 /users/123 中的 123;
  • @RequestParam 从 URL 查询参数中取,比如 ?name=xxx;
  • @RequestBody 接收请求体中的 JSON 数据,转为 Java 对象。

第四,Controller 只负责请求和响应的转换,业务逻辑全部委托给 Service 层处理,保持 Controller 的简洁。

第五,返回数据可以直接返回对象,Spring Boot 会自动用 Jackson 序列化为 JSON。如果需要控制 HTTP 状态码,可以用 ResponseEntity 包装返回结果。

第六,健壮性方面,可以结合 @Valid 做参数校验,用 @ControllerAdvice 做全局异常处理,统一返回错误响应。

我在项目中的典型 Controller 结构是:类上加 @RestController 和 @RequestMapping("/api/users"),方法上用具体的请求映射注解,参数用 DTO 对象配合 @RequestBody 和 @Valid,方法体内调用 Service 处理业务,最后返回统一封装的 Result 对象。

评论区