【Java面试】Spring

By | 2026 年 3 月 17 日

Spring/Spring MVC

为什么要使用 spring?

  • spring 提供 ioc 技术,容器会帮你管理依赖的对象,从而不需要自己创建和管理依赖对象了,更轻松的实现了程序的解耦。
  • spring 提供了事务支持,使得事务操作变的更加方便。
  • spring 提供了面向切片编程,这样可以更方便的处理某一类的问题。
  • 更方便的框架集成,spring 可以很方便的集成其他框架,比如 MyBatis、hibernate 等。

解释一下AOP?意义是什么?实现机制是什么

  1. 什么是AOP–面向切面编程
    • 用于将与业务无关的但却对多个对象产生影响公共行为和逻辑,使用动态代理的方式封装为一个统一的可重用的模块这个模块被命名为“切面”(Aspect),具有无侵入性的特点,可以减少重复代码.如用于鉴权、日志、事务、运行监控等
  2. 实现机制
    • Spring AOP基于动态代理实现.使用JDK动态代理CGLIB.JDK动态代理支持接口的代理,不支持类的代理,当无法使用JDK动态代理时,Spring AOP才会使用CGLIB

静态代理和动态代理的区别是什么

代理主要分为静态代理动态代理。静态代理的代表为AspectJ;动态代理的代表为Spring AOP

生成代理对象的时机不同

  1. 静态代理:由程序员主动创建或由特定工具自动生成源代码,再对其编译,在程序运行前代理类的.class文件就已经存在了。
  2. 动态代理:在程序运行时用反射机制在内存中临时生成代理对象,该代理对象包含了目标对象的全部方法,并且在特定的切点做了增强处理并回调原对象的方法

AspectJ的静态代理具有更好的性能,但AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理

解释一下 ioc?

ioc:Inversionof Control(中文:控制反转)是 spring 的核心,对于 spring 框架来说,就是由 spring 来负责控制对象的生命周期和对象间的关系。

简单来说,控制指的是当前对象对内部成员的控制权;控制反转指的是,这种控制权不由当前对象管理了,由其他(类,第三方容器)来管理。

spring 有哪些主要模块?

  • spring core:框架的最基础部分,提供 ioc 和依赖注入特性。
  • spring context:构建于 core 封装包基础上的 context 封装包,提供了一种框架式的对象访问方法。
  • spring dao:Data Access Object 提供了JDBC的抽象层。
  • spring aop:提供了面向切面的编程实现,让你可以自定义拦截器、切点等。
  • spring Web:提供了针对 Web 开发的集成特性,例如文件上传,利用 servlet listeners 进行 ioc 容器初始化和针对 Web 的 ApplicationContext。
  • spring Web mvc:spring 中的 mvc 封装包提供了 Web 应用的 Model-View-Controller(MVC)的实现。

spring 常用的注入方式有哪些?

  • setter 属性注入
  • 构造方法注入
  • 注解方式注入

spring 中的 bean 是线程安全的吗?

spring 中的 bean 默认是单例模式,spring 框架并没有对单例 bean 进行多线程的封装处理。

实际上大部分时候 spring bean 无状态的(比如 dao 类),所有某种程度上来说 bean 也是安全的,但如果 bean 有状态的话(比如 view model 对象),那就要开发者自己去保证线程安全了,最简单的就是改变 bean 的作用域,把“singleton”变更为“prototype”,这样请求 bean 相当于 new Bean()了,所以就可以保证线程安全了。

  • 有状态就是有数据存储功能。
  • 无状态就是不会保存数据。

spring 支持几种 bean 的作用域?

spring 支持 5 种作用域,如下:

  • singleton:spring ioc 容器中只存在一个 bean 实例,bean 以单例模式存在,是系统默认值;
  • prototype:每次从容器调用 bean 时都会创建一个新的示例,既每次 getBean()相当于执行 new Bean()操作;
  • Web 环境下的作用域:
  • request:每次 http 请求都会创建一个 bean;
  • session:同一个 http session 共享一个 bean 实例;
  • global-session:用于 portlet 容器,因为每个 portlet 有单独的 session,globalsession 提供一个全局性的 http session。

「注意:」 使用 prototype 作用域需要慎重的思考,因为频繁创建和销毁 bean 会带来很大的性能开销。

spring 自动装配 bean 有哪些方式?

  • no:默认值,表示没有自动装配,应使用显式 bean 引用进行装配。
  • byName:它根据 bean 的名称注入对象依赖项。
  • byType:它根据类型注入对象依赖项。
  • 构造函数:通过构造函数来注入依赖项,需要设置大量的参数。
  • autodetect:容器首先通过构造函数使用 autowire 装配,如果不能,则通过 byType 自动装配。

spring 事务实现方式有哪些?

  • 声明式事务:声明式事务也有两种实现方式,基于 xml 配置文件的方式和注解方式(在类上添加 @Transaction 注解)。
  • 编码方式:提供编码的形式管理和维护事务。

spring 的事务隔离?

spring 有五大隔离级别,默认值为 ISOLATION_DEFAULT(使用数据库的设置),其他四个隔离级别和数据库的隔离级别一致:

  • ISOLATION_DEFAULT:用底层数据库的设置隔离级别,数据库设置的是什么我就用什么;
  • ISOLATIONREADUNCOMMITTED:未提交读,最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读);
  • ISOLATIONREADCOMMITTED:提交读,一个事务提交后才能被其他事务读取到(会造成幻读、不可重复读),SQL server 的默认级别;
  • ISOLATIONREPEATABLEREAD:可重复读,保证多次读取同一个数据时,其值都和事务开始时候的内容是一致,禁止读取到别的事务未提交的数据(会造成幻读),MySQL 的默认级别;
  • ISOLATION_SERIALIZABLE:序列化,代价最高最可靠的隔离级别,该隔离级别能防止脏读、不可重复读、幻读。
  • 「脏读」 :表示一个事务能够读取另一个事务中还未提交的数据。比如,某个事务尝试插入记录 A,此时该事务还未提交,然后另一个事务尝试读取到了记录 A。
  • 「不可重复读」 :是指在一个事务内,多次读同一数据。
  • 「幻读」 :指同一个事务内多次查询返回的结果集不一样。比如同一个事务 A 第一次查询时候有 n 条记录,但是第二次同等条件下查询却有 n+1 条记录,这就好像产生了幻觉。发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据,同一个记录的数据内容被修改了,所有数据行的记录就变多或者变少了。

spring事务的传播方式

事务传播行为(Propagation)

决定调用另一个带事务的方法时,是加入当前事务还是新建事务。

传播行为含义
REQUIRED加入当前事务,若无则新建(最常用
REQUIRES_NEW总是新建事务,若已存在则挂起当前事务
SUPPORTS有事务就加入,没有就以非事务运行
NOT_SUPPORTED以非事务运行,如有事务则挂起
MANDATORY必须存在事务,否则抛异常
NEVER必须非事务运行,否则抛异常
NESTED嵌套事务,底层需支持 savepoint(JDBC 可)

常见误区REQUIRES_NEW 会让内部事务与外部事务完全独立,内部回滚不影响外部,外部回滚也不影响已提交的内部事务。

spring mvc 运行流程?

  • spring mvc 先将请求发送给 DispatcherServlet。
  • DispatcherServlet 查询一个或多个 HandlerMapping,找到处理请求的 Controller。
  • DispatcherServlet 再把请求提交到对应的 Controller。
  • Controller 进行业务逻辑处理后,会返回一个ModelAndView。
  • Dispathcher 查询一个或多个 ViewResolver 视图解析器,找到 ModelAndView 对象指定的视图对象。
  • 视图对象负责渲染返回给客户端。

spring mvc 有哪些组件?

  • 前置控制器 DispatcherServlet。
  • 映射控制器 HandlerMapping。
  • 处理器 Controller。
  • 模型和视图 ModelAndView。
  • 视图解析器 ViewResolver。

@RequestMapping 的作用是什么?

将 http 请求映射到相应的类/方法上。

@Autowired 的作用是什么?

@Autowired 它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作,通过@Autowired 的使用来消除 set/get 方法。

@Autowired和@Resource注解的区别

@Autowired与@Resource都可以用来装配bean,都可以写在属性set方法上,@Autowired还可以写在构造方法上,@Resource不可以

1、@Autowired

由Spring提供,只按照类型自动注入

2、@Resource

由J2EE提供,默认按照名称自动注入

@Resource有两个属性:name和type

@Resource装配顺序:

  1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
  2. 如果指定了name,则从Spring上下文中查找名称匹配的bean进行装配,找不到则抛出异常
  3. 如果指定了type,则从Spring上下文中找到类型匹配的bean进行装配,找不到或找到多个,都抛出异常

Spring Boot/Spring Cloud

什么是 spring boot?

spring boot 是为 spring 服务的,是用来简化新 spring 应用的初始搭建以及开发过程的。

为什么要用 spring boot?

  • 配置简单
  • 独立运行
  • 自动装配
  • 无代码生成和 xml 配置
  • 提供应用监控
  • 易上手
  • 提升开发效率

spring boot 核心配置文件是什么?

spring boot 核心的两个配置文件:

  • bootstrap (. yml 或者 . properties):boostrap 由父 ApplicationContext 加载的,比 applicaton 优先加载,且 boostrap 里面的属性不能被覆盖;
  • application (. yml 或者 . properties):用于 spring boot 项目的自动化配置。

spring boot 配置文件有哪几种类型?它们有什么区别?

配置文件有 . properties 格式和 . yml 格式,它们主要的区别是书法风格不同。

. properties 配置如下:

spring.RabbitMQ.port=5672

. yml 配置如下:

spring:
    RabbitMQ:
        port: 5672

yml 格式不支持 @PropertySource 注解导入。

spring boot 有哪些方式可以实现热部署?

  • 使用 devtools 启动热部署,添加 devtools 库,在配置文件中把 spring. devtools. restart. enabled 设置为 true;
  • 使用 Intellij Idea 编辑器,勾上自动编译或手动重新编译。

jpa 和 hibernate 有什么区别?

jpa 全称 Java Persistence API,是 Java 持久化接口规范,hibernate 属于 jpa 的具体实现。

什么是 spring cloud?

spring cloud 是一系列框架的有序集合。它利用 spring boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 spring boot 的开发风格做到一键启动和部署。

spring cloud 断路器的作用是什么?

在分布式架构中,断路器模式的作用也是类似的,当某个服务单元发生故障(类似用电器发生短路)之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个错误响应,而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。

spring cloud 的核心组件有哪些?

  • Eureka:服务注册于发现。
  • Feign:基于动态代理机制,根据注解和选择的机器,拼接请求 url 地址,发起请求。
  • Ribbon:实现负载均衡,从一个服务的多台机器中选择一台。
  • Hystrix:提供线程池,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题。
  • Zuul:网关管理,由 Zuul 网关转发请求给对应的服务。

Spring循环依赖怎么解决的?

Spring通过三级缓存解决单例Bean的循环依赖(主要是构造器注入无法解决):

  • 一级缓存:singletonObjects,存放完全初始化好的Bean。
  • 二级缓存:earlySingletonObjects,存放提前暴露的早期对象(尚未填充属性)。
  • 三级缓存:singletonFactories,存放Bean工厂,用于生成早期对象的引用。
  • 过程:A依赖B,B依赖A。创建A时,将A的工厂放入三级缓存,然后填充属性时发现依赖B,去创建B;B创建时依赖A,从三级缓存获取A的工厂生成早期A(放入二级缓存),B完成注入后创建完毕,然后A从二级缓存拿到B的引用,完成后续初始化。最终A放入一级缓存。

详细说明:

Spring 框架为了解决单例作用域下的循环依赖问题,设计了一套基于 三级缓存 的机制。下面将详细解释其原理、步骤以及为什么需要三级缓存。


1. 什么是循环依赖

循环依赖是指两个或多个 Bean 之间互相持有对方的引用,形成一个闭环。例如:

  • A 依赖 B,B 依赖 A(最简单的循环依赖)。
  • A 依赖 B,B 依赖 C,C 依赖 A(更复杂的循环)。

在创建 Bean 时,如果容器无法打破这个环,就会陷入无限递归,最终导致 BeanCurrentlyInCreationException

2. Spring 能解决哪些循环依赖

Spring 仅能解决 以下条件的循环依赖:

  • Bean 的作用域是 单例(singleton)
  • 依赖注入的方式为 setter 注入 或 字段注入(@Autowired)
    (因为这两种方式可以在对象实例化后,再设置属性)
  • 不能解决 构造函数注入 的循环依赖(因为构造函数必须在实例化时完成所有参数的注入,此时对象尚未创建,无法提前暴露)。

3. 三级缓存的内部结构

Spring 容器内部维护了三个缓存,用于存放不同阶段的 Bean 实例:

缓存名称作用
一级缓存 singletonObjects存放完全初始化好的单例 Bean(成品)
二级缓存 earlySingletonObjects存放提前暴露的 Bean 实例(半成品),即对象已经创建,但尚未完成属性填充和初始化
三级缓存 singletonFactories存放生成 Bean 实例的 ObjectFactory 工厂对象(用于生成代理对象等)

这三个缓存对应 Spring 的 DefaultSingletonBeanRegistry 类中的三个成员变量:

// 一级
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);          
// 二级
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);     
// 三级
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);        

4. 解决循环依赖的流程(以 A 依赖 B,B 依赖 A 为例)

假设两个 Bean 都是通过 setter 注入,且作用域为单例。

步骤 1:开始创建 A

  • getBean("a") 发现一级缓存中没有 A。
  • 实例化 A(调用构造函数),得到一个原始对象 aInstance
  • 提前暴露 A:将 aInstance 封装为一个 ObjectFactory 放入三级缓存singletonFactories 中(key = “a”)。
    • 这一步实际上执行的是 addSingletonFactory("a", () -> getEarlyBeanReference("a", mbd, aInstance)),其中 getEarlyBeanReference 可能会返回原始对象或代理对象(如果有 AOP 增强)。

步骤 2:填充 A 的属性

  • 发现 A 需要注入 B,因此调用 getBean("b") 去创建或获取 B。

步骤 3:创建 B(同样的流程)

  • getBean("b") 一级缓存无 B。
  • 实例化 B,得到原始对象 bInstance
  • 将 B 的工厂放入三级缓存(key = “b”)。

步骤 4:填充 B 的属性

  • 发现 B 需要注入 A,调用 getBean("a")

步骤 5:获取 A 的“提前引用”

  • 此时 A 正在创建中,一级缓存没有 A。
  • 查看二级缓存 earlySingletonObjects,也没有 A。
  • 查看三级缓存 singletonFactories,发现有 A 的工厂。
  • 执行工厂,得到提前暴露的 A 的引用(可能是原始对象,也可能是 AOP 代理对象)。
  • 将得到的对象放入二级缓存 earlySingletonObjects(key = “a”),并从三级缓存中移除该工厂。

步骤 6:B 获得 A 的引用

  • B 成功拿到了 A 的引用(此时 A 尚未完成属性填充,但对象已存在)。
  • 继续完成 B 的剩余属性填充和初始化。
  • B 初始化完成后,将自己放入一级缓存 singletonObjects(此时 B 是成品),并清理二、三级缓存中的 B。

步骤 7:回到 A

  • A 从 getBean("b") 调用返回,拿到了初始化好的 B 的完整引用。
  • 将 B 注入到 A 中(此时 A 的属性填充完成)。
  • 执行 A 的初始化方法(如 afterPropertiesSet、init-method)。
  • 将 A 放入一级缓存 singletonObjects,并清理二、三级缓存中的 A。

至此,循环依赖被完美解决,两个 Bean 都已完成创建并存于一级缓存。


5. 为什么需要三级缓存,而不是两级?

有些人可能会问:二级缓存(提前暴露的对象)加上一级缓存(成品)不就够了吗?为什么还需要第三级缓存(工厂)?

关键在于 AOP 代理对象的创建时机

  • 如果 Bean 不需要 AOP 代理,那么提前暴露的对象就是原始对象,没有区别。
  • 如果 Bean 需要 AOP 代理(例如被 @Transactional 注解),那么代理对象的创建通常是在 Bean 初始化之后(通过 BeanPostProcessor 的 postProcessAfterInitialization)。
    但是,在解决循环依赖时,其他 Bean 可能在初始化之前就需要引用这个 Bean。如果等到初始化完成才创建代理,那么其他 Bean 拿到的就是原始对象,而非代理对象,这会导致 AOP 功能失效(例如事务不起作用)。

因此,Spring 通过三级缓存中的 ObjectFactory 来 延迟创建代理对象

  • 工厂方法 getEarlyBeanReference 允许在提前暴露阶段就生成代理对象(如果 Bean 需要被代理)。
  • 这样,当 B 通过工厂获取 A 的引用时,拿到的可能是一个尚未完全初始化的 A 的代理对象。之后 A 继续完成初始化,但 B 持有的已经是代理对象,保证了 AOP 的正确性。

如果只有二级缓存(提前暴露原始对象),就无法在必要的时候提前创建代理,导致 AOP 失效。

举例说明:

  • A 和 B 互相依赖,且 A 需要被事务代理。
  • 如果没有三级缓存:
    • A 提前暴露原始对象,B 拿到原始 A 并完成创建。
    • 之后 A 初始化完成,生成代理对象。
    • 但 B 中注入的仍是原始 A,事务注解无效。
  • 有了三级缓存:
    • B 通过工厂获取 A 时,工厂返回一个代理对象(尚未完全初始化,但已经是代理)。
    • B 持有代理 A。
    • A 后续初始化完成后,容器中保存的也是同一个代理对象(或者代理对象内部包装了原始对象),因此 B 使用的代理正确。

6. 哪些循环依赖 Spring 无法解决?

  • 构造函数注入:因为构造函数必须在实例化时完成,无法提前暴露对象,无法打破循环。
  • 非单例作用域(prototype):Spring 不缓存 prototype 的 Bean,每次请求都新建,因此无法提前暴露,也无法解决循环依赖。
  • 多例混合:比如 singleton 依赖 prototype,但反过来则无法解决。

对于构造函数注入的循环依赖,可以改用 setter 注入,或者使用 @Lazy 注解延迟加载其中一个依赖。


7. 源码简要追溯

主要逻辑集中在 AbstractBeanFactory.doGetBean() 和 DefaultSingletonBeanRegistry.getSingleton() 方法中。

getSingleton(String beanName, boolean allowEarlyReference) 方法体现了三级缓存的查找顺序:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

当创建一个 Bean 时,在 doCreateBean 中实例化后,会调用 addSingletonFactory 将工厂放入三级缓存。

总结

Spring 通过 三级缓存 巧妙地解决了单例 setter 注入的循环依赖问题:

  • 一级缓存存放完整 Bean。
  • 二级缓存存放提前暴露的 Bean(半成品)。
  • 三级缓存存放生产 Bean 的工厂,用于在必要时提前生成代理对象,避免 AOP 失效。

这种设计既保证了循环依赖的解决,又兼顾了 AOP 等增强功能的正确性。

@Lazy延迟加载

@Lazy 是 Spring 提供的一个灵活控制 Bean 生命周期的注解,主要用途包括:

  • 性能优化:延迟非必要 Bean 的初始化,加快启动速度。
  • 打破循环依赖:特别是解决构造器注入导致的循环依赖。
  • 按需加载:让资源消耗较大的 Bean 在真正需要时才创建。

工作原理

Spring 容器在启动时,对于延迟加载的 Bean,并不会立即调用其构造方法和初始化逻辑,而是:

  • 对于 @Lazy 标注的类或 @Bean 方法:容器会将这些 Bean 的创建推迟到第一次调用 getBean() 获取它们时。但在容器启动阶段,它们会被记录下来,但不会实例化。
  • 对于注入点上使用 @Lazy:Spring 会生成一个代理对象(基于 CGLIB 或 JDK 动态代理)并注入到目标 Bean 中。该代理对象会拦截所有方法调用,在首次调用时触发目标 Bean 的创建和初始化,然后将调用委派给真实的 Bean 实例。

正是通过这种代理机制,@Lazy 才能在不破坏依赖关系的情况下实现延迟加载。

CGLIB 与 JDK 动态代理

  • JDK 动态代理 是 Java 原生提供的,基于接口的代理,适合接口设计良好的场景。
  • CGLIB 代理 通过生成子类实现代理,更加灵活,适合没有接口的类或需要更细粒度控制的场景。
  • Spring 根据目标类是否有接口自动选择,也允许强制使用 CGLIB。
  • 在实际开发中,如果使用 Spring Boot 2.x,默认 CGLIB 模式可以避免接口定义,使代码更简洁。

SpringBoot 的核心注解是哪个?

核心注解是 @SpringBootApplication 。通常标注在项目的主启动类上。

SpringBootApplication注解包含哪几个注解?

它是一个组合注解,主要包含以下三个核心注解 

  1. @SpringBootConfiguration:继承自 @Configuration,表示该类是一个配置类。
  2. @EnableAutoConfiguration:开启自动配置功能。
  3. @ComponentScan:启用组件扫描,默认扫描主启动类所在包及其子包。

SpringBoot最核心的注解有哪些?

除了上面的 @SpringBootApplication 组合注解外,还有:

  • @EnableAutoConfiguration:自动配置的核心。
  • @Conditional 系列注解:如 @ConditionalOnClass@ConditionalOnMissingBean,用于控制自动配置在满足特定条件时才生效 
  • @ConfigurationProperties:用于将配置文件(如 application.yml)中的属性绑定到 Java Bean 上 

SpringBoot自动配置原理是什么?

原理主要基于 @EnableAutoConfiguration 注解 。它会通过 SpringFactoriesLoader 机制,从 classpath 下所有 JAR 包中的 META-INF/spring.factories 文件里,加载 EnableAutoConfiguration 键对应的自动配置类(如 XxxAutoConfiguration。这些配置类上通常标有 @Conditional 条件注解,只有满足特定条件(如存在相关类、配置了相关属性)时,这些自动配置类才会生效,实例化相应的 Bean。

SpringBoot开启自动配置的原理是什么?

只需要在配置类上使用 @EnableAutoConfiguration 注解即可。由于 @SpringBootApplication 已经组合了该注解,所以只要在启动类上标记 @SpringBootApplication,就相当于开启了自动配置 

SpringBoot自动配置的类型在哪注册?

自动配置类(类型)的注册信息存放在所有 JAR 包中的 META-INF/spring.factories 文件中 。Spring Boot 启动时会扫描该文件,获取所有注册的自动配置类的全限定名。

SpringBoot自动配置报告怎么查看?

开启 debug 模式。在配置文件(application.properties 或 application.yml)中设置 debug: true。启动应用后,控制台会打印 Positive matches(生效的自动配置)和 Negative matches(未生效的自动配置)报告,方便开发者了解哪些自动配置类被应用了。

SpringBoot怎么排除某些自动配置?

有多种方式可以排除自动配置类 

  1. 在 @SpringBootApplication 或 @EnableAutoConfiguration 注解中使用 exclude 属性:@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
  2. 在配置文件中使用 spring.autoconfigure.exclude 属性:spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

SpringBoot怎么开启和关闭自动配置?

  • 开启:使用 @EnableAutoConfiguration 注解(通常通过 @SpringBootApplication 间接使用)
  • 关闭:一般不建议完全关闭自动配置。如果想禁用特定的自动配置,可以使用排除(exclude)机制。如果不想使用整个 Spring Boot 的自动配置特性,可以不使用 @EnableAutoConfiguration 注解,但这会丧失 Spring Boot 的核心优势。

SpringBoot的目录结构是怎样的?

一个典型的 Spring Boot 项目目录结构遵循一定的约定 

com.example.project
├── Application.java (主启动类,位于根包下)
├── domain/ (或 model) - 实体类
├── repository/ (或 dao) - 数据访问层
├── service/ - 业务逻辑层
│   └── impl/ - 业务实现类
└── web/ (或 controller) - 控制器层

此外,资源文件位于 src/main/resources 目录下,包含:

  • application.properties 或 application.yml:配置文件。
  • static/:存放静态资源(如 CSS, JS, 图片)。
  • templates/:存放模板文件(如 Thymeleaf)。

SpringBoot中的Starters是什么?

Starters 是一组方便的依赖描述符 。它相当于一个“一站式”的依赖集合。例如,引入 spring-boot-starter-web 依赖,它就会自动传递引入构建 Web 应用所需的所有依赖(如 Spring MVC、内嵌 Tomcat、Jackson 等),避免了开发者手动寻找和配置一个个兼容版本的依赖,极大地简化了项目的依赖管理 

IOC容器与Bean生命周期

Spring的三级缓存是如何解决循环依赖的?为什么二级缓存不够?

  • 三级缓存定义
    • 一级缓存 singletonObjects:存放完全初始化的单例Bean(成品池)
    • 二级缓存 earlySingletonObjects:存放提前暴露的Bean实例(半成品,属性未完全填充)
    • 三级缓存 singletonFactories:存放 ObjectFactory 工厂,用于生成Bean的早期引用
  • 解决流程(以A依赖B、B依赖A为例) :
    1. 实例化A,将A的ObjectFactory放入三级缓存
    2. 填充A属性时发现需要B,去获取B
    3. 实例化B,填充B属性时发现需要A,从三级缓存取出A的ObjectFactory生成A的引用放入二级缓存
    4. B完成初始化放入一级缓存
    5. A获取B引用完成初始化
  • 为什么必须三级缓存
    • 二级缓存不行,因为Spring需要在“提前暴露Bean”时兼容AOP代理的创建时机。若只有二级缓存,代理对象无法在实例化后、依赖注入前被正确创建。三级缓存的ObjectFactory允许在获取早期引用时动态决定返回原始对象还是代理对象。
  • 无法解决的循环依赖
    • 构造器注入的循环依赖(实例化阶段即死锁)
    • prototype作用域的循环依赖(每次getBean都创建新实例,无限递归导致OOM)

请完整描述Spring Bean的生命周期,并列举核心扩展点。

  • 四大核心阶段(非死记硬背的10步)
阶段核心动作关键扩展点
实例化通过反射调用构造方法创建Bean实例(仅分配内存)InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation
属性填充注入@Autowired依赖、@Value配置等InstantiationAwareBeanPostProcessor.postProcessProperties
初始化执行自定义初始化逻辑BeanPostProcessor.postProcessBeforeInitialization → @PostConstruct / InitializingBean → postProcessAfterInitialization
销毁容器关闭时释放资源@PreDestroy / DisposableBean
  • 面试加分扩展点
    • BeanFactoryPostProcessor:在Bean定义加载后、实例化前执行,可修改BeanDefinition(如修改作用域、属性值)
    • BeanPostProcessor:在Bean初始化前后执行,Spring AOP就是通过它创建代理对象的
    • Aware接口BeanNameAwareBeanFactoryAwareApplicationContextAware等,让Bean感知容器

BeanFactory和FactoryBean的区别是什么?

  • BeanFactory:Spring IOC容器的根接口,负责管理和提供Bean实例,采用懒加载策略
  • FactoryBean:是一个特殊的Bean,用于自定义复杂对象的创建逻辑
  • 关键区别
    • 通过 getBean("beanName") 获取的是 FactoryBean生产的对象,而非FactoryBean本身
    • 若要获取FactoryBean实例本身,需使用 &beanName 前缀
    • 典型应用:MyBatis的 SqlSessionFactoryBean、Spring的 ProxyFactoryBean

AOP面向切面编程

Spring AOP的底层实现原理是什么?JDK动态代理和CGLIB如何选择?

Spring AOP基于动态代理在运行时生成代理对象,将切面逻辑织入。代理创建需要三个核心信息:拦截器数组、目标对象接口数组、目标对象。

对比项JDK动态代理CGLIB动态代理
实现基础基于接口 + Java反射(InvocationHandler基于继承 + ASM字节码生成(MethodInterceptor
目标要求必须实现至少一个接口无接口要求,但不能代理final类/方法
性能反射调用,略逊直接生成字节码,接近原生方法
Spring默认策略目标实现接口时默认使用无接口时自动使用;可配置proxyTargetClass=true强制使用
  • Spring选择逻辑DefaultAopProxyFactory 根据目标类是否实现接口决定
  • AOP与AspectJ的区别:Spring AOP是运行时增强(仅方法级),AspectJ是编译时/类加载时增强(支持字段、构造器等更细粒度)

多个切面是如何形成责任链的?

Spring AOP通过 Advisor(通知器) 将Advice和Pointcut封装,多个切面按 @Order 排序后,通过责任链模式ReflectiveMethodInvocation)依次执行。环绕通知(@Around)通过 ProceedingJoinPoint.proceed() 控制是否继续执行下一个拦截器或目标方法


为什么类内部方法调用时AOP不生效?

根本原因:内部调用绕过了代理对象。Spring AOP基于代理,只有通过代理对象调用方法时切面才生效。

解决方案

  1. 将方法拆分到独立的Bean中
  2. 通过 ApplicationContext.getBean() 获取代理对象再调用
  3. 使用 AopContext.currentProxy() 获取当前代理
  4. 使用AspectJ的编译时织入(<mode>aspectj</mode>

事务管理

@Transactional在哪些情况下会失效?请列举全部场景。

失效场景原理源码依据
非public方法代理仅拦截public方法computeTransactionAttribute 方法校验 Modifier.isPublic
同类自调用绕过代理对象代理机制天然限制
异常被catch吞掉事务管理器感知不到异常只有异常抛出才能触发回滚
抛出检查型异常默认只对RuntimeException回滚需配置 rollbackFor=Exception.class
数据库引擎不支持事务如MySQL MyISAM引擎需改用InnoDB
传播机制不支持事务如 Propagation.NOT_SUPPORTED事务被挂起
方法被final修饰CGLIB无法继承无法生成代理子类

Spring事务的传播机制有哪些?REQUIRED和REQUIRES_NEW的区别?

Spring定义了7种传播行为

传播行为含义
REQUIRED(默认)当前有事务则加入,无则新建
REQUIRES_NEW始终新建事务,挂起当前事务
SUPPORTS当前有事务则加入,无则以非事务方式执行
NOT_SUPPORTED以非事务方式执行,挂起当前事务
MANDATORY必须已有事务,否则抛异常
NEVER必须没有事务,否则抛异常
NESTED嵌套事务(Savepoint机制)

核心区别

  • REQUIRED:内外方法共享同一个事务,内方法异常会导致全部回滚
  • REQUIRES_NEW:内外方法各自独立事务,内方法异常不影响外方法(但需注意:内方法异常必须被外方法catch,否则异常传播出去仍会导致外方法回滚)

Spring事务的底层原理是什么?

声明式事务的本质是AOP。核心执行链路

  1. TransactionInterceptor 拦截事务方法(AOP环绕通知)
  2. 通过 PlatformTransactionManager 获取事务
    • getTransaction():根据传播行为决定新建/加入事务
    • 常见实现:DataSourceTransactionManager(JDBC/MyBatis)、JpaTransactionManager(JPA/Hibernate)
  3. 执行目标方法
  4. 异常时 rollback(),正常时 commit()

Spring事务本质是数据库事务的封装,依赖数据库的 redo log 和 undo log 实现提交与回滚。Spring只提供统一管理接口,具体由数据库实现

Spring Boot与自动装配

Spring Boot的自动装配原理是什么?

核心入口是 @SpringBootApplication,它是一个三合一注解

  • @SpringBootConfiguration:标识配置类
  • @ComponentScan:扫描启动类所在包及其子包
  • @EnableAutoConfiguration核心,负责加载自动配置类

执行流程

  1. @EnableAutoConfiguration 通过 @Import 导入 AutoConfigurationImportSelector
  2. AutoConfigurationImportSelector.selectImports() 加载候选配置类

配置文件加载(版本差异) 

  • Spring Boot 2.x:读取 META-INF/spring.factories
  • Spring Boot 3.x:读取 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(纯文本行,启动更快)

条件过滤:加载的配置类通过 @Conditional系列注解 按需生效。例如 RedisAutoConfiguration 上有 @ConditionalOnClass(RedisOperations.class),只有引入Redis依赖时才会生效。


@Conditional系列注解有哪些?各自的作用是什么?

@Conditional是条件装配的元注解,满足条件才注入Bean。

注解作用典型场景
@ConditionalOnClass类路径存在指定类时才生效Redis自动配置需RedisOperations存在
@ConditionalOnMissingBean容器中无指定Bean时才生效保证用户自定义Bean优先
@ConditionalOnProperty配置文件中存在指定属性才生效数据库配置根据 spring.datasource.url 决定
@ConditionalOnBean容器中存在指定Bean时才生效依赖其他组件存在
@ConditionalOnWebApplication当前是Web应用时才生效Web相关自动配置

Spring扩展机制与进阶

Spring的事件监听机制是如何实现的?

基于观察者模式,三个核心角色:

  • 事件:继承 ApplicationEvent
  • 事件发布者ApplicationEventPublisher.publishEvent()
  • 事件监听器:实现 ApplicationListener 接口 或使用 @EventListener 注解

注意:默认是同步单线程执行。若需异步,需配置 @Async + 线程池。


Spring中Bean的作用域有哪些?

作用域说明
singleton(默认)每个容器仅有一个实例
prototype每次获取都创建新实例
request每个HTTP请求一个实例(仅Web)
session每个HTTP会话一个实例(仅Web)
application每个ServletContext一个实例(仅Web)
websocket每个WebSocket会话一个实例(仅Web)

Spring MVC的请求处理流程是怎样的?

  1. 客户端发起HTTP请求
  2. DispatcherServlet(前端控制器) 接收请求
  3. HandlerMapping 根据请求URL找到对应的Controller和方法
  4. HandlerAdapter 执行Controller方法
  5. Controller返回ModelAndView(或JSON数据)
  6. ViewResolver 解析视图并渲染响应

核心九大组件包括:HandlerMappingHandlerAdapterViewResolverHandlerExceptionResolver 等。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注