Spring 面试题笔记

Spring 面试题笔记

个人复习笔记,覆盖 Spring 核心容器、AOP、事务、MVC 的高频面试题。

目录

  • [IOC 与依赖注入](#IOC 与依赖注入)
  • [Bean 生命周期与作用域](#Bean 生命周期与作用域)
  • 循环依赖
  • [AOP 面向切面](#AOP 面向切面)
  • [Spring 事务](#Spring 事务)
  • [Spring MVC](#Spring MVC)
  • [Spring 中的设计模式](#Spring 中的设计模式)
  • 事件机制与其他

IOC 与依赖注入

什么是 IOC?什么是 DI?

  • IOC(控制反转) :对象的创建、管理、销毁的控制权由程序员手写 new 反转给 Spring 容器,是一种设计思想,DI 是它的实现方式。
  • DI(依赖注入):容器在创建 Bean 时,自动把依赖的其他 Bean 注入进来(构造器、Setter、字段)。

好处:解耦(依赖面向接口,实现可替换)、便于单测(可注入 Mock)、生命周期统一管理。

BeanFactory 和 ApplicationContext 的区别?

BeanFactory ApplicationContext
定位 最底层容器接口 功能更全的容器,继承自 BeanFactory
Bean 初始化 懒加载,getBean 时才创建 默认预实例化,容器启动即创建所有单例 Bean
附加能力 基础的 Bean 管理 事件发布、国际化、资源加载、AOP 自动代理

有哪几种依赖注入方式?推荐哪种?

  1. 构造器注入(推荐):依赖不可变(final)、不为 null、便于单测;依赖过多时构造器臃肿,能"闻出"设计问题;
  2. Setter 注入:适合可选依赖;
  3. 字段注入 (@Autowired 直接标在字段上):写法最简单,但有缺点------不能声明 final、隐藏依赖关系、脱离容器无法实例化。

Spring 官方推荐构造器注入 ;如果只有一个构造器,可以省略 @Autowired。

@Autowired、@Resource、@Qualifier 的区别?

@Autowired @Resource
来源 Spring 提供 JDK 提供(JSR-250)
匹配规则 先按类型,多个再按名称 先按名称,找不到再按类型
搭配 @Qualifier 指定名称 name 属性直接指定

同一个接口有多个实现类时:@Autowired + @Qualifier("beanName"),或用 @Resource(name = "beanName"),或字段/参数名恰好等于 Bean 名。

什么是 Bean?

由 Spring 容器实例化、组装、管理的对象。配置来源:XML、JavaConfig(@Configuration + @Bean)、注解扫描(@Component 及衍生注解)。

@Component 和 @Bean 的区别?

  • @Component:标在类上,靠类路径扫描自动注册,类是自己写的;
  • @Bean:标在方法 上(通常在 @Configuration 类中),方法返回值注册为 Bean,常用于第三方类(拿不到源码无法加 @Component)。

Bean 生命周期与作用域

Bean 的生命周期?(高频)

完整链路可以记成"实例化 → 属性 → 初始化 → 使用 → 销毁":

  1. 实例化:通过构造器/工厂方法创建对象(反射);
  2. 属性填充:依赖注入(@Autowired 等);
  3. Aware 回调 :实现 BeanNameAware、BeanFactoryAware、ApplicationContextAware 等拿到容器信息;
  4. BeanPostProcessor 前置处理 :postProcessBeforeInitialization(@PostConstruct 在这一步附近执行);
  5. 初始化 :@PostConstruct → InitializingBean.afterPropertiesSet() → 自定义 init-method;
  6. BeanPostProcessor 后置处理 :postProcessAfterInitialization,AOP 代理就是在这里生成的(AbstractAutoProxyCreator);
  7. 使用中;
  8. 销毁 :容器关闭时 @PreDestroy → DisposableBean.destroy() → 自定义 destroy-method。

Bean 有哪些作用域?

作用域 说明
singleton 默认,容器中只有一个实例
prototype 每次 getBean 都新建实例
request 每个 HTTP 请求一个实例(Web 环境)
session 每个 HTTP 会话一个实例(Web 环境)
application 整个 ServletContext 一个实例
websocket 每个 WebSocket 会话一个实例

prototype Bean 注入到 singleton Bean 时,singleton 里的那一份永远是同一个(除非用 @Lookup 或 ObjectFactory 延迟获取)。

单例 Bean 是线程安全的吗?

Spring 不保证。单例 Bean 无状态(只有方法、没有可变的成员变量)时线程安全;有可变状态时会出现并发问题。

解决:成员变量改局部变量、用 ThreadLocal、加锁、或改 prototype 作用域。这也是为什么 Service/Controller 通常写成无状态的。

@PostConstruct、init-method、afterPropertiesSet 的执行顺序?

@PostConstruct → InitializingBean.afterPropertiesSet() → init-method。销毁同理:@PreDestroy → destroy() → destroy-method。


循环依赖

什么是循环依赖?Spring 怎么解决的?(高频)

A 依赖 B,B 又依赖 A。Spring 通过三级缓存 解决 单例 + 字段/Setter 注入 的循环依赖:

缓存 名称 存什么
一级缓存 singletonObjects 完整的成品 Bean
二级缓存 earlySingletonObjects 提前曝光的半成品(已实例化、未填充属性)
三级缓存 singletonFactories 能生成早期引用的 ObjectFactory

流程:A 实例化后,先把工厂放入三级缓存再填充属性 → 发现需要 B → 创建 B → B 填充属性时需要 A → 从三级缓存拿到 A 的早期引用(升级到二级缓存)→ B 完成创建放入一级缓存 → A 拿到 B 完成初始化。

三级缓存的意义:保证正常情况下 Bean 用原始对象,只有发生循环依赖时才提前生成代理对象,避免 AOP 代理被创建两次。

哪些情况 Spring 解决不了循环依赖?

  1. 构造器注入的循环依赖:还没实例化完就要注入,无法提前曝光(会抛 BeanCurrentlyInCreationException);
  2. prototype 作用域:每次都新建,不做缓存;
  3. 无法用早期引用的场景(如构造完就完成的代理逻辑)。

解决办法:改用 Setter 注入、加 @Lazy 延迟初始化其中一方、或重新设计消除环。


AOP 面向切面

什么是 AOP?解决什么问题?

面向切面编程,把日志、事务、权限、限流等横切逻辑从业务代码中抽离成"切面",通过动态代理织入目标方法。好处:业务代码干净、横切逻辑集中可复用。

AOP 的核心术语?

  • 切面 Aspect:横切逻辑的模块化(类);
  • 连接点 JoinPoint:程序执行的某个点(Spring 里就是方法执行);
  • 切点 Pointcut:匹配连接点的表达式(哪些方法要增强);
  • 通知 Advice:增强的逻辑(前置/后置/环绕等);
  • 织入 Weaving:把通知应用到目标对象的过程(运行期动态代理实现)。

Spring AOP 的实现原理?

运行期动态代理(编译期织入 AspectJ 才是字节码增强):

  • 目标类实现了接口 → JDK 动态代理(Proxy + InvocationHandler);
  • 没有接口 → CGLIB(生成目标类的子类,方法不能是 final/static/private)。

Spring Boot 2.x 起默认无论有无接口都用 CGLIB(spring.aop.proxy-target-class=true)。

五种通知类型及执行顺序?

java 复制代码
@Around      // 环绕:包住整个方法,可控制是否执行、修改入参和返回值
@Before      // 前置:方法执行前
@After       // 后置:方法执行后(finally,无论是否异常)
@AfterReturning  // 返回后:正常返回时
@AfterThrowing   // 异常抛出后

正常流程(Spring 5.2+):@Around 前半 → @Before → 业务方法 → @AfterReturning → @After → @Around 后半。

异常流程:@Around 前半 → @Before → 抛异常 → @AfterThrowing → @After(@Around 后半不执行,除非 try 住)。

AOP 在切面里能拿到方法参数吗?

能。@Around("execution(* com.demo.service..*(..))") 配合 JoinPoint / ProceedingJoinPoint:

java 复制代码
@Around("logPointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
    Object[] args = pjp.getArgs();       // 入参
    long start = System.currentTimeMillis();
    Object result = pjp.proceed();       // 执行目标方法
    long cost = System.currentTimeMillis() - start;
    return result;                        // 也可修改后返回
}

Spring 事务

Spring 事务的传播行为有哪些?(高频)

同一个事务管理器下,方法之间调用时事务如何传递,共 7 种:

传播行为 说明
REQUIRED(默认) 有事务就加入,没有就新建
REQUIRES_NEW 挂起当前事务,新建独立事务(常用于"无论主流程成败都要记日志")
NESTED 有事务则在其中开嵌套事务(保存点),可部分回滚;没有等同 REQUIRED
SUPPORTS 有就加入,没有就非事务执行
NOT_SUPPORTED 挂起当前事务,以非事务执行
MANDATORY 必须在事务中,否则抛异常
NEVER 必须不在事务中,否则抛异常

面试重点记前三个:REQUIRED、REQUIRES_NEW、NESTED(嵌套回滚不影响外层已提交部分,外层回滚会带走内层)。

Spring 事务的隔离级别?

直接沿用数据库隔离级别,Spring 提供了 Isolation.DEFAULT(跟随数据库默认)以及 READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE。MySQL InnoDB 默认 REPEATABLE_READ,Oracle 默认 READ_COMMITTED。

@Transactional 失效场景?(超高频)

  1. 方法不是 public:代理只拦截 public 方法;
  2. 同类内部自调用 :this.methodB() 不走代理,事务注解无效------最常见坑。解法:注入自身代理、拆到另一个类、或用 AopContext.currentProxy();
  3. 异常被 catch 吞掉:事务感知不到异常,不回滚;
  4. 默认只回滚 RuntimeException 和 Error :抛受检异常不回滚,需要 @Transactional(rollbackFor = Exception.class);
  5. 类没被 Spring 管理(没加 @Service 等);
  6. 数据库引擎不支持事务(如 MyISAM);
  7. 多线程调用:事务绑定在 ThreadLocal 的连接上,子线程不在同一事务;
  8. 传播行为配置错误:如 NOT_SUPPORTED、NEVER;
  9. 事务方法内新开事务 REQUIRES_NEW 后又抛异常时的回滚范围需理清楚。

Spring 事务的实现原理?

AOP:TransactionInterceptor 环绕目标方法 → 开启事务(获取数据库连接,绑定到 ThreadLocal)→ 执行业务 → 正常提交 / 异常按规则回滚。本质是对 Connection 提交/回滚动作的代理封装。

事务什么时候会导致脏数据?------Propagation.REQUIRED 与 REQUIRES_NEW 嵌套的坑

REQUIRES_NEW 是独立事务独立连接,外层事务未提交时,内层事务已提交的数据对外层不可见(取决于隔离级别);若外层最终回滚,内层已提交的数据不会回滚------设计"必须落库的记录"(审计日志)时用它,但要知道它占两个连接,可能引发连接池耗尽。


Spring MVC

Spring MVC 的请求处理流程?(高频)

  1. 请求到达 DispatcherServlet(前端控制器,统一入口);
  2. HandlerMapping 根据 URL 找到对应的 Handler(Controller 方法)及拦截器链;
  3. HandlerAdapter 适配并执行 Handler(参数绑定、类型转换在这完成);
  4. 执行业务返回结果,@ResponseBody 时经 HttpMessageConverter 序列化(如 Jackson 转 JSON)直接写响应;传统方式返回 ModelAndView;
  5. ViewResolver 解析视图 → 渲染返回(前后端分离项目基本不走视图解析);
  6. 触发拦截器 afterCompletion、异常由 HandlerExceptionResolver 处理。

@Controller 和 @RestController 的区别?

  • @Controller:返回视图名(配合模板引擎);
  • @RestController = @Controller + @ResponseBody:所有方法的返回值直接序列化为 JSON/XML 写入响应体,前后端分离用这个。

拦截器(Interceptor)和过滤器(Filter)的区别?

Filter Interceptor
规范 Servlet 规范 Spring MVC 提供
执行时机 在 DispatcherServlet 之前 在 DispatcherServlet 内部、Handler 前后
能否拿到 Handler 信息 不能 能(preHandler 可拿到 HandlerMethod)
能否用 Spring Bean 理论可以但时机偏早 天然支持注入
典型用途 编码、跨域、全站防护 登录校验、权限、日志、性能监控

执行顺序:Filter → DispatcherServlet → Interceptor.preHandle → Controller → postHandle → afterCompletion → Filter。

@RequestParam、@RequestBody、@PathVariable 的区别?

  • @RequestParam:取 URL 查询参数或表单字段;
  • @PathVariable:取 RESTful 路径变量 /user/{id};
  • @RequestBody:把请求体 JSON 反序列化为对象(POST/PUT);
  • 响应侧对应 @ResponseBody / ResponseEntity。

全局异常怎么处理?

@RestControllerAdvice + @ExceptionHandler(XxxException.class) 统一捕获转换成标准响应结构,避免每个 Controller 重复 try-catch。


Spring 中的设计模式

Spring 框架用到了哪些设计模式?(高频)

模式 在 Spring 里的体现
工厂模式 BeanFactory / ApplicationContext 创建 Bean
单例模式 Bean 默认作用域 singleton(容器级单例,不是 JVM 饿汉式那种)
代理模式 AOP 的 JDK 动态代理、CGLIB
模板方法 JdbcTemplate、RedisTemplate、RestTemplate 封装固定流程
观察者模式 ApplicationEvent / ApplicationListener 事件机制
适配器模式 HandlerAdapter 适配不同类型的 Controller
策略模式 Resource 加载策略、实例化策略
装饰器模式 各类 Wrapper 包装类
责任链模式 MVC 拦截器链、AOP 通知链

事件机制与其他

Spring 的事件机制是什么?

基于观察者模式:发布者发布 ApplicationEvent,监听器通过 @EventListener 或实现 ApplicationListener 接收。

java 复制代码
// 定义事件
public class OrderCreatedEvent extends ApplicationEvent { ... }

// 发布
applicationEventPublisher.publishEvent(new OrderCreatedEvent(this, orderId));

// 监听
@EventListener
public void onOrderCreated(OrderCreatedEvent event) { ... }

@Async @EventListener  // 加 @Async 可异步执行、与发布者事务解耦

用途:业务解耦(下单成功后发短信、加积分),常配合 @TransactionalEventListener 做"事务提交后再执行"。

@Lazy 有什么用?

延迟初始化:Bean 不在容器启动时创建,第一次被使用时才创建。用途:加快启动、打破循环依赖(在依赖点标 @Lazy 注入一个代理,真正调用时才获取 Bean)。

BeanPostProcessor 和 BeanFactoryPostProcessor 的区别?

  • BeanFactoryPostProcessor :在 Bean 实例化之前修改 Bean 定义信息(BeanDefinition),如占位符解析 PropertySourcesPlaceholderConfigurer;
  • BeanPostProcessor :在 Bean 初始化前后加工 Bean 实例,如 AOP 代理生成、@Autowired 处理(AutowiredAnnotationBeanPostProcessor)。
相关推荐
Azure++3 天前
Spring Boot 面试题笔记
个人笔记
Azure++3 天前
MyBatis 面试题笔记
个人笔记
Azure++3 天前
JVM 面试题笔记
个人笔记
我的golang之路果然有问题9 个月前
linux 个人笔记导出之网络,防火墙,定时,权限,后台
linux·运维·服务器·网络·笔记·个人笔记
我的golang之路果然有问题9 个月前
mysql 个人笔记导出之-数据库时间戳问题以及增删改查
数据库·笔记·学习·mysql·分享·个人笔记