Spring面试题
本文《Spring面试题》由小宇宙从原站整理搬运,详细讲解相关技术要点。
大家好,我是小宇宙。 Spring 这块在 Java 后端面试里的地位毋庸置疑,几乎是必考内容。不管是校招还是社招,IOC、AOP、事务、Bean 生命周期这些主题,基本每次都逃不掉。很多人平时天天用 Spring 写代码,@Autowired 一打、@Transactional 一加就完事了,但真被问起"IOC 底层怎么实现的""循环依赖是怎么解决的""事务为什么会失效",就开始语焉不详了。 这篇文章整理了 Spring 全家桶面试中最常被问到的知识点,涵盖 Spring 核心(IOC/AOP/事务/Bean)、SpringMVC、SpringBoot 自动装配、MyBatis,以及 SpringCloud 微服务组件,内容覆盖比较全面。 几个在面试里出现频率特别高的方向,建议重点花时间: IOC 和 AOP :IOC 的底层实现原理、三种依赖注入方式的区别,AOP 的动态代理机制(JDK 动态代理 vs CGLIB),以及 AOP 的实际应用场景,这两块是 Spring 面试的绝对核心。 循环依赖和三级缓存 :为什么需要三级缓存、二级缓存为什么不够,这个问题在面试里被问得越来越频繁,而且很容易被追问到细节。 Spring 事务 :事务什么时候会失效(用 this 调用、非 public 方法、异常类型不对),这是一道经典坑题,答起来很容易只说表面。 SpringBoot 自动装配原理 : 背后的逻辑、 是怎么工作的,这块是 SpringBoot 的核心,面试里问的很多。 Bean 的生命周期 :从实例化到销毁经历了哪些步骤、各个扩展点在哪里介入,这块答好了能体现对 Spring 底层的理解深度。 如果你是第一次系统准备 Spring,建议先搞清楚 IOC 和 AOP 的原理,再去看 Bean 生命周期和循环依赖,顺序理清楚之后,后面的 SpringBoot 自动装配理解起来会顺很多。 Spring 说一下你对 Spring 的理解 Spring框架核心特性包括: IoC容器 :Spring通过控制反转实现了对象的创建和对象间的依赖关系管理。开发者只需要定义好Bean及其依赖关系,Spring容器负责创建和组装这些对象。 AOP :面向切面编程,允许开发者定义横切关注点,例如事务管理、安全控制等,独立于业务逻辑的代码。通过AOP,可以将这些关注点模块化,提高代码的可维护性和可重用性。 事务管理 :Spring提供了一致的事务管理接口,支持声明式和编程式事务。开发者可以轻松地进行事务管理,而无需关心具体的事务API。 MVC框架 :Spring MVC是一个基于Servlet API构建的Web框架,采用了模型 视图 控制器(MVC)架构。它支持灵活的URL到页面控制器的映射,以及多种视图技术。 spring的核心思想说说你的理解? | 核心思想 | 解决的问题 | 实现手段 | 典型应用场景 | | | | | | | IOC | 对象创建与依赖管理的高耦合 | 容器管理Bean生命周期 | 动态替换数据库实现、服务组装 | | DI | 依赖关系的硬编码问题 | Setter/构造器/注解注入 | 注入数据源、服务层依赖DAO层 | | AOP | 横切逻辑分散在业务代码中 | 动态代理与切面配置 | 日志、事务、权限校验统一处理 | Spring通过这IOC、DI、AOP三大核心思想,实现了轻量级、高内聚低耦合的企业级应用开发框架,成为Java生态中不可或缺的基石。 Spring IoC和AOP 介绍一下 Spring IoC和AOP 区别: IoC :即控制反转的意思,它是一种创建和获取对象的技术思想,依赖注入(DI)是实现这种技术的一种方式。传统开发过程中,我们需要通过new关键字来创建对象。使用IoC思想开发方式的话,我们不通过new关键字创建对象,而是通过IoC容器来帮我们实例化对象。 通过IoC的方式,可以大大降低对象之间的耦合度。 AOP :是面向切面编程,能够将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,以减少系统的重复代码,降低模块间的耦合度。Spring AOP 基于动态代理:如果被代理对象实现了接口,Spring AOP 默认会使用 JDK Proxy 创建代理对象;如果没有实现接口,则会使用 CGLIB 生成被代理对象的子类作为代理。 注意:自 Spring Boot 2.0 起,默认配置 ,即无论是否实现接口都优先使用 CGLIB,如需切回 JDK 动态代理需手动设为 false 。 在 Spring 框架中,IOC 和 AOP 结合使用,可以更好地实现代码的模块化和分层管理。例如: 通过 IOC 容器管理对象的依赖关系,然后通过 AOP 将横切关注点统一切入到需要的业务逻辑中。 使用 IOC 容器管理 Service 层和 DAO 层的依赖关系,然后通过 AOP 在 Service 层实现事务管理、日志记录等横切功能,使得业务逻辑更加清晰和可维护。 Spring的aop介绍一下 Spring AOP是Spring框架中的一个重要模块,用于实现面向切面编程。 我们知道,Java 就是一门面向对象编程的语言,在 OOP 中最小的单元就是“Class 对象”,但是在 AOP 中最小的单元是“切面”。一个“切面”可以包含很多种类型和对象,对它们进行模块化管理,例如事务管理。 在面向切面编程的思想里面,把功能分为两种 核心业务 :登陆、注册、增、删、改、查、都叫核心业务 周边功能 :日志、事务管理这些次要的为周边业务 在面向切面编程中,核心业务功能和周边功能是分别独立进行开发,两者不是耦合的,然后把切面功能和核心业务功能 "编织" 在一起,这就叫AOP。 AOP能够将那些与业务无关, 却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来 ,便于 减少系统的重复代码 , 降低模块间的耦合度 ,并 有利于未来的可拓展性和可维护性 。 在 AOP 中有以下几个概念: Aspect :切面,只是一个概念,没有具体的接口或类与之对应,是 Join point,Advice 和 Pointcut 的一个统称。需要注意的是,Aspect 指"切面"这一概念本身,而 AspectJ 是一个独立的 AOP 框架,Spring AOP 只是借用了它的注解(如 )。 Join point :连接点,指程序执行过程中的一个点,例如方法调用、异常处理等。在 Spring AOP 中,仅支持方法级别的连接点。 Advice :通知,即我们定义的一个切面中的横切逻辑,包含 、 、 、 、 五种类型。在很多的 AOP 实现框架中,Advice 通常作为一个拦截器,也可以包含许多个拦截器作为一条链路围绕着 Join point 进行处理。 Pointcut :切点,用于匹配连接点,一个 AspectJ 中包含哪些 Join point 需要由 Pointcut 进行筛选。 Introduction :引介,让一个切面可以声明被通知的对象实现任何他们没有真正实现的额外的接口。例如可以让一个代理对象代理两个目标类。 Weaving :织入,在有了连接点、切点、通知以及切面,如何将它们应用到程序中呢?没错,就是织入,在切点的引导下,将通知逻辑插入到目标方法上,使得我们的通知逻辑在方法调用时得以执行。 AOP proxy :AOP 代理,指在 AOP 实现框架中实现切面协议的对象。在 Spring AOP 中有两种代理,分别是 JDK 动态代理和 CGLIB 动态代理。 Target object :目标对象,就是被代理的对象。 Spring AOP 是基于 JDK 动态代理和 CGLIB 字节码增强实现的,两种代理方式都属于 运行期织入 ,不需要专用编译器(与 AspectJ 的编译期 / 类加载期织入相对),因此 Spring AOP 完全可以在普通 Java 项目中使用。 IOC和AOP是通过什么机制来实现的? Spring IOC 实现机制 反射 :Spring IOC容器利用Java的反射机制动态地加载类、创建对象实例及调用对象方法,反射允许在运行时检查类、方法、属性等信息,从而实现灵活的对象实例化和管理。 依赖注入 :IOC的核心概念是依赖注入,即容器负责管理应用程序组件之间的依赖关系。Spring通过构造函数注入、属性注入或方法注入,将组件之间的依赖关系描述在配置文件中或使用注解。 设计模式 工厂模式 :Spring IOC容器通常采用工厂模式来管理对象的创建和生命周期。容器作为工厂负责实例化Bean并管理它们的生命周期,将Bean的实例化过程交给容器来管理。 容器实现 :Spring IOC容器是实现IOC的核心,通常使用BeanFactory或ApplicationContext来管理Bean。BeanFactory是IOC容器的基本形式,提供基本的IOC功能;ApplicationContext是BeanFactory的扩展,并提供更多企业级功能。 Spring AOP 实现机制 Spring AOP的实现依赖于 动态代理技术 。动态代理是在运行时动态生成代理对象,而不是在编译时。它允许开发者在运行时指定要代理的接口和行为,从而实现在不修改源码的情况下增强方法的功能。 Spring AOP支持两种动态代理: 基于JDK的动态代理 :使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现。这种方式需要代理的类实现一个或多个接口。 基于CGLIB的动态代理 :当被代理的类没有实现接口时,Spring会使用CGLIB库生成一个被代理类的子类作为代理。CGLIB(Code Generation Library)是一个第三方代码生成库,通过继承方式实现代理。 怎么理解SpringIoc? IOC :Inversion Of Control,即控制反转,是一种设计思想。在传统的 Java SE 程序设计中,我们直接在对象内部通过 new 的方式来创建对象,是程序主动创建依赖对象; 而在Spring程序设计中,IOC 是有专门的容器去控制对象。 所谓控制 就是对象的创建、初始化、销毁。 创建对象:原来是 new 一个,现在是由 Spring 容器创建。 初始化对象:原来是对象自己通过构造器或者 setter 方法给依赖的对象赋值,现在是由 Spring 容器自动注入。 销毁对象:原来是直接给对象赋值 null 或做一些销毁操作,现在是 Spring 容器管理生命周期负责销毁对象。 总结:IOC 解决了繁琐的对象生命周期的操作,解耦了我们的代码。 所谓反转 :其实是反转的控制权,前面提到是由 Spring 来控制对象的生命周期,那么对象的控制就完全脱离了我们的控制,控制权交给了 Spring 。这个反转是指:我们由对象的控制者变成了 IOC 的被动控制者。 依赖倒置,依赖注入,控制反转分别是什么? 控制反转:“控制”指的是对程序执行流程的控制,而“反转”指的是在没有使用框架之前,程序员自己控制整个程序的执行。在使用框架之后,整个程序的执行流程通过框架来控制。流程的控制权从程序员“反转”给了框架。 依赖注入:依赖注入和控制反转恰恰相反,它是一种具体的编码技巧。我们不通过 new 的方式在类内部创建依赖类的对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类来使用。 依赖倒置:这条原则跟控制反转有点类似,主要用来指导框架层面的设计。高层模块不依赖低层模块,它们共同依赖同一个抽象。抽象不要依赖具体实现细节,具体实现细节依赖抽象。 依赖注入了解吗?怎么实现依赖注入的? 在传统编程中,当一个类需要使用另一个类的对象时,通常会在该类内部通过 关键字来创建依赖对象,这使得类与类之间的耦合度较高。 而依赖注入则是将对象的创建和依赖关系的管理交给 Spring 容器来完成,类只需要声明自己所依赖的对象,容器会在运行时将这些依赖对象注入到类中,从而降低了类与类之间的耦合度,提高了代码的可维护性和可测试性。 具体到Spring中,常见的依赖注入的实现方式,比如构造器注入、Setter方法注入,还有字段注入。 \ \ 构造器注入:\ \ 通过构造函数传递依赖对象,保证对象初始化时依赖已就绪。 \ \ Setter 方法注入:\ \ 通过 Setter 方法设置依赖,灵活性高,但依赖可能未完全初始化。 \ \ 字段注入:\ \ 直接通过 注解字段,代码简洁但隐藏依赖关系,不推荐生产代码。 如果让你设计一个SpringIoc,你觉得会从哪些方面考虑这个设计? Bean的生命周期管理:需要设计Bean的创建、初始化、销毁等生命周期管理机制,可以考虑使用工厂模式和单例模式来实现。 依赖注入:需要实现依赖注入的功能,包括属性注入、构造函数注入、方法注入等,可以考虑使用反射机制和XML配置文件来实现。 Bean的作用域:需要支持多种Bean作用域,比如单例、原型、会话、请求等,可以考虑使用Map来存储不同作用域的Bean实例。 AOP功能的支持:需要支持AOP功能,可以考虑使用动态代理机制和切面编程来实现。 异常处理:需要考虑异常处理机制,包括Bean创建异常、依赖注入异常等,可以考虑使用try catch机制来处理异常。 配置文件加载:需要支持从不同的配置文件中加载Bean的相关信息,可以考虑使用XML、注解或者Java配置类来实现。 SpringAOP主要想解决什么问题 它的目的是对于面向对象思维的一种补充,而不是像引入命令式、函数式编程思维让他顺应另一种开发场景。在我个人的理解下AOP更像是一种对于不支持多继承的弥补,除开对象的主要特征(我更喜欢叫“强共性”)被抽象为了一条继承链路,对于一些“弱共性”,AOP可以统一对他们进行抽象和集中处理。 举一个简单的例子,打印日志。需要打印日志可能是许多对象的一个共性,这在企业级开发中十分常见,但是日志的打印并不反应这个对象的主要共性。而日志的打印又是一个具体的内容,它并不抽象,所以它的工作也不可以用接口来完成。而如果利用继承,打印日志的工作又横跨继承树下面的多个同级子节点,强行侵入到继承树内进行归纳会干扰这些强共性的区分。 这时候,我们就需要AOP了。AOP首先在一个Aspect(切面)里定义了一些Advice(增强),其中包含具体实现的代码,同时整理了切入点,切入点的粒度是方法。最后,我们将这些Advice织入到对象的方法上,形成了最后执行方法时面对的完整方法。 AOP在spring中的应用,你知道哪些? 在 Spring 中,AOP(面向切面编程)是核心特性之一,它通过分离横切关注点(如日志、事务、安全等)与业务逻辑,让代码更清晰、可维护。实际应用中,Spring AOP 的典型场景有这么几个: 最常见的是 事务管理 ,Spring 的声明式事务就是基于 AOP 实现的。我们只需要在方法上标注 ,Spring 就会通过 AOP 在方法执行前开启事务,执行后根据是否有异常决定提交或回滚,不用手动写事务控制代码,大大简化了事务管理。 然后是 日志记录 ,比如对接口的调用参数、返回结果、执行时间做日志,用 AOP 可以把这些逻辑抽离出来,定义一个切面,通过 获取入参, 获取返回值, 统计执行时间,这样业务方法里就不用掺杂日志代码,既干净又方便统一修改。 还有 权限校验 ,比如某些接口需要登录后才能访问,或者需要特定角色权限。可以定义一个切面,在方法执行前( )检查用户的登录状态或权限,如果不满足就直接抛出异常阻止方法执行,避免在每个接口方法里重复写权限判断。 Spring AOP 的这些应用,本质上都是通过 “切面” 封装横切逻辑,通过 “切入点” 指定要作用的方法,然后在方法执行的不同阶段(前、后、异常时等)插入这些逻辑,实现了 “无侵入” 地增强业务功能,这也是 AOP 在 Spring 中如此重要的原因。 谈谈你对 Spring AOP 的了解程度? 我对 Spring AOP 的理解,核心就是它是一种 不侵入业务代码的横切逻辑解决方案 。比如日志、事务、权限校验这些,如果全写在业务方法里会非常乱,AOP 的作用就是把这些和业务无关、但又必须要做的事情抽出来,统一处理。 在 Spring 里,AOP 主要是通过 代理机制 来实现的。默认情况下,如果目标类实现了接口,Spring 用的是 JDK 动态代理;如果没有接口,就用 CGLIB 生成子类代理。所以本质上,调用的并不是原始对象,而是代理对象,方法执行前后会被织入额外逻辑。 从使用角度来看,Spring AOP 是 基于运行时代理的 AOP ,不是像 AspectJ 那种编译期或类加载期织入。我们平时写 、 、 、 这些注解,其实就是在定义切面、切点和通知,Spring 在容器启动的时候会解析这些配置,最后帮我们把代理对象准备好。 真正执行的时候,方法调用会走一条拦截器链。以 为例,本质上就是在方法执行前后包一层逻辑,是否放行、是否执行原方法,都是通过 来控制的。事务、日志这些底层实现,其实就是一组 MethodInterceptor 在按顺序执行。 在实际项目里,我主要用 AOP 来做几类事情,比如声明式事务、接口日志、统一异常处理、参数校验或者一些轻量级的权限控制。这些场景的共同点是,它们都不属于核心业务逻辑,但又需要在很多地方重复出现,用 AOP 抽出来会非常干净。 当然 Spring AOP 也有它的边界。第一是它 只能作用在 Spring 容器管理的 Bean 上 ,自己 new 出来的对象是不会生效的。第二是 同类内部方法调用不会触发 AOP ,因为走的是 ,绕过了代理。第三是它只能拦截方法级别的调用,做不到字段级别或者构造方法级别的增强。 整体来说,我会把 Spring AOP 当成一种 工程层面的工具 ,用来解决通用、可复用的横切问题,而不是拿它去写复杂业务逻辑,超过这个边界我一般会考虑别的设计方式。 SpringAOP的原理了解吗 Spring AOP的实现依赖于 动态代理技术 。动态代理是在运行时动态生成代理对象,而不是在编译时。它允许开发者在运行时指定要代理的接口和行为,从而实现在不修改源码的情况下增强方法的功能。 Spring AOP支持两种动态代理: 基于JDK的动态代理 :使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现。这种方式需要代理的类实现一个或多个接口。 基于CGLIB的动态代理 :当被代理的类没有实现接口时,Spring会使用CGLIB库生成一个被代理类的子类作为代理。CGLIB(Code Generation Library)是一个第三方代码生成库,通过继承方式实现代理。 动态代理是什么? Java的动态代理是一种在运行时动态创建代理对象的机制,主要用于在不修改原始类的情况下对方法调用进行拦截和增强。 Java动态代理主要分为两种类型: 基于接口的代理 (JDK动态代理): 这种类型的代理要求目标对象必须实现至少一个接口。Java动态代理会创建一个实现了相同接口的代理类,然后在运行时动态生成该类的实例。这种代理的实现核心是 类和 接口。每一个动态代理类都必须实现 接口,并且每个代理类的实例都关联到一个 。当通过代理对象调用一个方法时,这个方法的调用会被转发为由 接口的 方法来进行调用。 基于类的代理 (CGLIB动态代理): CGLIB(Code Generation Library)是一个强大的高性能的代码生成库,它可以在运行时动态生成一个目标类的子类。CGLIB代理不需要目标类实现接口,而是通过继承的方式创建代理类。因此,如果目标对象没有实现任何接口,可以使用CGLIB来创建动态代理。 动态代理和静态代理的区别 代理是一种常用的设计模式,目的是:为其他对象提供一个代理以控制对某个对象的访问,将两个类的关系解耦。代理类和委托类都要实现相同的接口,因为代理真正调用的是委托类的方法。 区别: 静态代理:由程序员创建或者是由特定工具创建,在代码编译时就确定了被代理的类是一个静态代理。静态代理通常只代理一个类; 动态代理:在代码运行期间,由 JVM 根据目标对象自动生成代理类,不需要为每个被代理类手写代理类。JDK 动态代理基于接口(要求目标对象实现接口),CGLIB 动态代理基于继承(通过生成目标类的子类作为代理,不要求目标对象实现接口)。 能使用静态代理的方式实现AOP吗? 当然 可以 用静态代理实现 AOP 的基本功能,比如你在代码里手动写个代理类,在目标方法前后加日志或者事务控制,技术上完全可行。但问题是,静态代理在实际生产中基本没人用,因为它有三大硬伤: 第一是代码爆炸 。比如你有 100 个 Service 类需要加事务,就得写 100 个对应的静态代理类,里面全是重复的 提交回滚代码,维护起来简直是灾难。 第二是僵化 。一旦业务接口改了个方法名,所有相关的代理类都得跟着改,而动态代理通过反射调用目标方法,根本不怕这种变动。 第三是无法动态筛选 。比如你想只给带 注解的方法加事务,静态代理只能写死逻辑,而 Spring AOP 可以在运行时通过切点表达式精准匹配需要增强的方法。 所以 Spring 才选了 JDK 动态代理和 CGLIB:它们能在 运行时动态生成代理类 ,一个切面配置就能覆盖成百上千个方法。像事务管理这种全局需求,用静态代理手动绑定根本不可行。 AOP实现有哪些注解? 常用的注解包括: @Aspect:用于定义切面,标注在切面类上。 @Pointcut:定义切点,标注在方法上,用于指定连接点。 @Before:在方法执行之前执行通知。 @After:在方法执行之后执行通知(无论方法正常返回还是抛出异常)。 @Around:在方法执行前后都执行通知,是功能最强的一种。 @AfterReturning:在方法正常返回结果后执行通知。 @AfterThrowing:在方法抛出异常后执行通知。 什么是反射?有哪些使用场景? 反射机制是指程序在运行状态下,对于任意一个类,都能够获取这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意属性和方法。也就是说,Java 反射允许在运行时获取类的信息并动态操作对象,即使在编译时不知道具体的类也能实现。 反射具有以下特性: 1. 运行时类信息访问 :反射机制允许程序在运行时获取类的完整结构信息,包括类名、包名、父类、实现的接口、构造函数、方法和字段等。 2. 动态对象创建 :可以使用反射API动态地创建对象实例,即使在编译时不知道具体的类名。这通常通过 方法实现( 自 Java 9 起已被标记为 ,推荐使用 )。 3. 动态方法调用 :可以在运行时动态地调用对象的方法,包括私有方法。这通过Method类的invoke()方法实现,允许你传入对象实例和参数值来执行方法。 4. 访问和修改字段值 :反射还允许程序在运行时访问和修改对象的字段值,即使是私有的。这是通过Field类的get()和set()方法完成的。 Java反射机制在spring框架中,很多地方都用到了反射,让我们来看看Spring的IoC和AOP是如何使用反射技术的。 1、Spring框架的依赖注入(DI)和控制反转(IoC) Spring 使用反射来实现其核心特性:依赖注入。 在Spring中,开发者可以通过XML配置文件或者基于注解的方式声明组件之间的依赖关系。当应用程序启动时,Spring容器会扫描这些配置或注解,然后利用反射来实例化Bean(即Java对象),并根据配置自动装配它们的依赖。 例如,当一个Service类需要依赖另一个DAO类时,开发者可以在Service类中使用@Autowired注解,而无需自己编写创建DAO实例的代码。Spring容器会在运行时解析这个注解,通过反射找到对应的DAO类,实例化它,并将其注入到Service类中。这样不仅降低了组件之间的耦合度,也极大地增强了代码的可维护性和可测试性。 2、动态代理的实现 在需要对现有类的方法调用进行拦截、记录日志、权限控制或是事务管理等场景中,反射结合动态代理技术被广泛应用。 一个典型的例子是Spring AOP(面向切面编程)的实现。Spring AOP允许开发者定义切面(Aspect),这些切面可以横切关注点(如日志记录、事务管理),并将其插入到业务逻辑中,而不需要修改业务逻辑代码。 例如,为了给所有的服务层方法添加日志记录功能,可以定义一个切面,在这个切面中,Spring会使用JDK动态代理或CGLIB(如果目标类没有实现接口)来创建目标类的代理对象。这个代理对象在调用任何方法前或后,都会执行切面中定义的代码逻辑(如记录日志),而这一切都是在运行时通过反射来动态构建和执行的,无需硬编码到每个方法调用中。 这两个例子展示了反射机制如何在实际工程中促进 松耦合、高内聚 的设计,以及如何提供动态、灵活的编程能力,特别是在框架层面和解决跨切面问题时。 spring是如何解决循环依赖的? 循环依赖指的是两个类中的属性相互依赖对方:例如 A 类中有 B 属性,B 类中有 A属性,从而形成了一个依赖闭环,如下图。 循环依赖问题在Spring中主要有三种情况: 第一种:通过构造方法进行依赖注入时产生的循环依赖问题。 第二种:通过setter方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题。 第三种:通过 setter 方法或字段(Field)进行依赖注入且是在单例模式下产生的循环依赖问题。 只有【第三种方式】的循环依赖问题被 Spring 解决了,其他两种方式在遇到循环依赖问题时,Spring都会产生异常。 Spring 在 类中维护了三个重要的缓存 (Map),称为“三级缓存”: (一级缓存):存放的是完全初始化好的、可用的 Bean 实例, 方法最终返回的就是这里面的 Bean。此时 Bean 已实例化、属性已填充、初始化方法已执行、AOP 代理(如果需要)也已生成。 (二级缓存):存放的是提前暴露的 Bean 的原始对象引用 或 早期代理对象引用,专门用来处理循环依赖。当一个 Bean 还在创建过程中(尚未完成属性填充和初始化),但它的引用需要被注入到另一个 Bean 时,就暂时放在这里。此时 Bean 已实例化(调用了构造函数),但属性尚未填充,初始化方法尚未执行,它可能是一个原始对象,也可能是一个为了解决 AOP 代理问题而提前生成的代理对象。 (三级缓存):存放的是 Bean 的 工厂对象。,这是解决循环依赖和 AOP 代理协同工作的关键。当 Bean 被实例化后(刚调完构造函数),Spring 会创建一个 并将其放入三级缓存。这个工厂的 方法负责返回该 Bean 的早期引用(可能是原始对象,也可能是提前生成的代理对象),当检测到循环依赖需要注入一个尚未完全初始化的 Bean 时,就会调用这个工厂来获取早期引用。 Spring 通过 三级缓存 和 提前暴露未完全初始化的对象引用 的机制来解决单例作用域 Bean 的 setter 注入方式的循环依赖问题。 假设存在两个相互依赖的单例Bean: 依赖 ,同时 也依赖 。当Spring容器启动时,它会按照以下流程处理: 第一步:创建 的实例并提前暴露工厂。 Spring首先调用 的构造函数进行实例化,此时得到一个原始对象(尚未填充属性)。紧接着,Spring会将一个特殊的 工厂对象存入第三级缓存( )。这个工厂的使命是:当其他Bean需要引用 时,它能动态返回当前这个半成品的 (可能是原始对象,也可能是为应对AOP而提前生成的代理对象)。此时 的状态是"已实例化但未初始化",像一座刚搭好钢筋骨架的大楼。 第二步:填充 的属性时触发 的创建。 Spring开始为 注入属性,发现它依赖 。于是容器转向创建 ,同样先调用其构造函数实例化,并将 对应的 工厂存入三级缓存。至此,三级缓存中同时存在 和 的工厂,它们都代表未完成初始化的半成品。 第三步: 属性注入时发现循环依赖。 当Spring试图填充 的属性时,检测到它需要注入 。此时容器启动依赖查找: 1. 在一级缓存(存放完整Bean)中未找到 ; 2. 在二级缓存(存放已暴露的早期引用)中同样未命中; 3. 最终在三级缓存中定位到 的工厂。 Spring立即调用该工厂的 方法。这个方法会执行关键决策:若 需要AOP代理,则动态生成代理对象(即使 还未初始化);若无需代理,则直接返回原始对象。得到的这个早期引用(可能是代理)被放入二级缓存( ),同时从三级缓存清理工厂条目。最后,Spring将这个早期引用注入到 的属性中。至此, 成功持有 的引用——尽管 此时仍是个半成品。 第四步:完成 的生命周期。 获得所有依赖后,Spring执行其初始化方法(如 ),将其转化为完整可用的Bean。随后, 被提升至一级缓存( ),二级和三级缓存中关于 的临时条目均被清除。此时 已准备就绪,可被其他对象使用。 第五步:回溯完成 的构建。 随着 创建完毕,流程回溯到最初中断的 属性注入环节。Spring将已完备的 实例注入 ,接着执行 的初始化方法。这里有个精妙细节:若之前为 生成过早期代理,Spring会直接复用二级缓存中的代理对象作为最终Bean,而非重复创建。最终,完全初始化的 (可能是原始对象或代理)入驻一级缓存,其早期引用从二级缓存移除。至此循环闭环完成,两个Bean皆可用。 三级缓存的设计的精髓: 三级缓存工厂 ( )负责在实例化后立刻暴露对象生成能力,兼顾AOP代理的提前生成; 二级缓存 ( )临时存储已确定的早期引用,避免重复生成代理; 一级缓存 ( )最终交付完整Bean。 整个机制通过 中断初始化流程、逆向注入半成品、延迟代理生成 三大策略,将循环依赖的死结转化为有序的接力协作。 值得注意的是,此方案仅适用于Setter/Field注入的 单例Bean ;构造器注入因必须在实例化前获得依赖,仍会导致无解的死锁。 Spring为什么用3级缓存解决循环依赖问题?用2级缓存不行吗? Spring 必须用三级缓存解决循环依赖,核心是为了 正确处理需要 AOP 代理的 Bean 。如果只用二级缓存,会导致注入的对象形态错误,甚至破坏单例原则。 举个例子:假设 Bean A 依赖 B,B 又依赖 A,且 A 需要被动态代理(比如加了 )。如果只有二级缓存,当 B 创建时去注入 A,拿到的是 A 的原始对象。但 A 在后续初始化完成后才会生成代理对象,结果就是:B 拿着原始对象 A,而 Spring 容器里存的是代理对象 A —— 同一个 Bean 出现了两个不同实例,这直接违反了单例的核心约束。 三级缓存中的 就是解决这个问题的关键。它不是直接缓存对象,而是存了一个能生产对象的工厂。当发生循环依赖时,调用这个工厂的 方法,这时 Spring 会智能判断:如果这个 Bean 最终需要代理,就提前生成代理对象并放入二级缓存;如果不需要代理,就返回原始对象。这样一来,B 注入的 A 就是最终形态(可能是代理对象),后续 A 初始化完成后也不会再创建新代理,保证了对象全局唯一。 简单说,三级缓存的本质是 “按需延迟生成正确引用” 。它既维持了 Bean 生命周期的完整性(正常流程在初始化后生成代理),又在循环依赖时特殊处理,避免逻辑矛盾。而二级缓存缺乏这种动态决策能力,因此无法替代三级缓存。 spring三级缓存的数据结构是什么? 三者都是 Map 类型的缓存,但 value 的类型不同: 1. 一级缓存(Singleton Objects) : ,存储的是已经完全初始化好的 bean,即完全准备好可以使用的 bean 实例。键是 bean 的名称,值是 bean 的实例。对应 类中的 属性。 2. 二级缓存(Early Singleton Objects) : ,存储的是早期的 bean 引用,即已经实例化但还未完全初始化的 bean(可能是原始对象,也可能是为解决 AOP 循环依赖而提前生成的代理对象)。这些 bean 已经被实例化,但可能还没有完成属性注入等操作。对应 类中的 属性。 3. 三级缓存(Singleton Factories) : ,注意 value 不是 bean 实例本身,而是一个 工厂函数。当一个 bean 正在创建过程中被其他 bean 依赖时,就会调用这个工厂的 方法生成早期引用(必要时会提前生成代理),从而解决循环依赖与 AOP 协同的问题。对应 类中的 属性。 spring框架中都用到了哪些设计模式 工厂设计模式 : Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。 代理设计模式 : Spring AOP 功能的实现。 单例设计模式 : Spring 中的 Bean 默认都是单例的。 模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。 适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。 spring 常用注解有什么? @Autowired 注解 @Autowired:主要用于自动装配bean。当Spring容器中存在与要注入的属性类型匹配的bean时,它会自动将bean注入到属性中。就跟我们new 对象一样。 用法很简单,如下示例代码: 在上面的示例代码中,MyController类中的myService属性被@Autowired注解标记,Spring会自动将MyService类型的bean注入到myService属性中。 @Component 这个注解用于标记一个类作为Spring的bean。当一个类被@Component注解标记时,Spring会将其实例化为一个bean,并将其添加到Spring容器中。在上面讲解@Autowired的时候也看到了,示例代码: 在上面的示例代码中,MyComponent类被@Component注解标记,Spring会将其实例化为一个bean,并将其添加到Spring容器中。 @Configuration @Configuration,注解用于标记一个类作为Spring的配置类。配置类可以包含@Bean注解的方法,用于定义和配置bean,作为全局配置。示例代码: @Bean @Bean注解用于标记一个方法作为Spring的bean工厂方法。当一个方法被@Bean注解标记时,Spring会将该方法的返回值作为一个bean,并将其添加到Spring容器中,如果自定义配置,经常用到这个注解。 @Service @Service,这个注解用于标记一个类作为服务层的组件。它是@Component注解的特例,用于标记服务层的bean,一般标记在业务service的实现类。 @Repository @Repository注解用于标记一个类作为数据访问层的组件。它也是@Component注解的特例,用于标记数据访问层的bean。加上这个注解后,Spring 会通过 对持久层抛出的异常进行统一翻译(转换为 Spring 的 体系),忽略它会丢失这层异常转换能力。 在上面的示例代码中,MyRepository类被@Repository注解标记,Spring会将其实例化为一个bean,并将其添加到Spring容器中。 @Controller @Controller注解用于标记一个类作为控制层的组件。它也是@Component注解的特例,用于标记控制