ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring核心面试题深挖:从IoC原理到AOP事务与自动配置

Spring核心面试题深挖:从IoC原理到AOP事务与自动配置 最近这两个月我陆续帮几位准备跳槽的朋友做了几次模拟面试发现一个很有意思的现象简历上写着“精通Spring”的候选人真聊起来能讲清楚“Autowired为什么能直接注入”“Bean是什么时候变成单例的”的人其实不多。Spring面试题几乎是Java后端面试的必考区从IoC、AOP到事务、自动配置每个点都能往深里追问。这篇文章把我这些年带团队、面候选人时反复遇到的Spring核心问题整理了一遍按“结论—原理—场景—追问”的顺序展开适合正在准备校招、跳槽或者想系统补一补框架底子的开发者直接拿来用。1. 面试官问Spring时到底在问什么1.1 背下来的答案撑不过第二轮追问先说一个真实场景。候选人对“什么是IoC”倒背如流控制反转把对象的创建和依赖交给Spring容器管理。我又追问了一句“那容器启动时一个单例Bean是立刻创建还是懒加载默认情况下Spring是怎么处理的”对方卡住了。这就是典型的“背题式准备”——记住了术语但是没有把你正在运行的Spring Boot应用和这些术语对应起来。面试官要验证的核心其实只有一件事你有没有真正理解Spring的设计方法论。IoC解决的是什么问题AOP在什么场景下非用不可事务失效你实际遇到过没有这些都不是靠背能够蒙混过关的反而在追问两三轮之后真实水平暴露得非常快。尤其是现在的面试节奏很多公司一上来就是连环追问从“Spring容器怎么启动”一路问到“三级缓存里到底存的是什么”靠背题根本撑不过去。我面过不少候选人有些基础题答得很快但一被追问细节就慌。最常见的情况是知道Transactional能开事务不知道整个调用链要走代理对象知道Spring Boot能自动配置不知道AutoConfiguration.imports文件里写了什么。这背后其实不是记忆力的问题而是没有把Spring当成一个“有设计逻辑的系统”来理解只是把它当成了一堆注解的集合。1.2 把知识体系分成四个层次来准备我把Spring相关的知识梳理成四个层次用来判断自己准备到了哪个位置会用层注解、自动注入、starter依赖能写出能跑的代码。原理层IoC容器思路、Bean生命周期、AOP代理机制、事务传播行为。源码层三级缓存、自动配置加载、MVC请求处理链路。设计层为什么Spring要这样设计以及你在项目中如何基于这些机制做扩展。大多数面试题卡在第一层和第二层之间。如果你的目标是中大厂至少要把原理层和源码层的关键节点串起来。复习优先级上我的建议是IoC与Bean生命周期、循环依赖、AOP与代理、事务这四个点先吃透再去看Spring Boot自动配置和MVC流程最后补一补Spring Security、分布式锁这类场景题。这个顺序不是随便排的——IoC是地基Bean生命周期是理解容器运作的钥匙循环依赖是考察你对容器机制理解深度的标尺AOP和事务则是最常见的生产级应用。不同职级和岗位的考察侧重也有差别。校招和初级岗更看重会用层和原理层的基础题问得直接比如“BeanFactory和ApplicationContext有什么区别”中高级岗位则喜欢拿场景题来考比如“一个事务方法里开子线程去操作数据库事务还有效吗”。你准备的时候最好能根据自己投递的岗位级别调整深度别用一套固定内容应对所有面试。2. IoC容器从“控制反转”聊到Bean完整生命周期2.1 控制反转到底“反”的是什么不用Spring的时候你创建一个UserService依赖UserMapper代码里得自己newpublic class UserService { private final UserMapper userMapper new UserMapper(); }这个依赖关系写死在类里面想换一个实现得改源码重新编译。用了Spring之后你只需要声明字段容器会在合适的时候把依赖给你Service public class UserService { private final UserMapper userMapper; public UserService(UserMapper userMapper) { this.userMapper userMapper; } }“反转”两个字说人话就是从“我主动创建对象”变成“容器把对象给我”。对象的创建、组装、销毁这些事原本由程序员管现在交出去给容器管这就是控制反转。依赖注入是实现IoC最常见的方式Spring里主要有三类注入方式构造器注入、setter注入、字段注入。面试时如果被问到推荐哪一种我一般推荐构造器注入——依赖清晰、对象创建后不可变、方便写单元测试还能顺便规避构造器注入场景下的循环依赖问题。字段注入最省事但依赖是隐式的测试的时候不好处理也不利于依赖关系审查。关于Autowired和Resource的区别这个基础题也经常出现。Autowired是Spring提供的默认按类型注入Resource是JDK提供的默认按名称注入找不到名称再按类型。如果你用Autowired注入一个接口有多个实现类Spring会先按类型找发现有多个实现之后再看字段名是否和某个Bean名字匹配还不行就会报错。这个问题的价值在于考察你有没有在项目里实际面对过多实现注入的冲突而不只是背两个注解的差异。2.2 Bean的完整生命周期“Bean生命周期”是Spring面试里的高频题也是很多候选人答得最碎的一道题。完整链条大致是这样的解析配置得到BeanDefinitionSpring知道你要创建什么类、作用域是什么、是否懒加载。实例化Bean通过构造器或者工厂方法生成原始对象。属性填充处理Autowired、Resource等注解把依赖塞进去。执行Aware接口回调比如BeanNameAware、ApplicationContextAware让Bean感知到容器环境。调用BeanPostProcessor前置处理常见实现如ApplicationContextAwareProcessor就藏在这一步。执行初始化逻辑顺序是PostConstruct、InitializingBean接口、自定义的init-method。调用BeanPostProcessor后置处理这一步非常关键AOP动态代理多数情况下在这里生成。Bean进入容器单例池等待被使用。容器关闭时执行销毁逻辑PreDestroy、DisposableBean接口、destroy-method。很多候选人能背出“实例化、属性填充、初始化、销毁”但说不清“PostConstruct和InitializingBean谁先执行”也说不清“代理对象是在哪个阶段生成的”。记住上面这个顺序尤其是第5步到第7步面试官很容易在这一段往下追问。你还可以主动补充一句BeanPostProcessor是Spring扩展机制里最重要的口子像AOP、自定义注解扫描、属性注入几乎都是靠它实现的。这句话一说面试官就知道你不是只背了生命周期而是看懂了整个容器扩展模型。2.3 Bean的作用域与懒加载陷阱Bean的常见作用域有singleton、prototype、request、session。默认是singleton容器启动时大部分单例Bean就会创建除非配置了Lazy。这里有个典型的面试追问“一个prototype Bean被注入到singleton Bean里会是什么效果”答案是如果注入的是字段prototype其实只生效一次不是每次调用都拿新的。真正想每次调用都拿到新实例要用ObjectFactory 或者Lookup这个点面试里能答出来就是加分项。追问到这里还有个变体“为什么Lazy能解决循环依赖”因为Lazy会生成一个代理对象注入到依赖方真正调用时再去找目标Bean相当于把“需要真实依赖”的时刻延后了。理解了这个机制你在项目里遇到奇怪的无依赖错误时也能更快定位原因。3. 循环依赖与三级缓存这道题能刷掉一半候选人3.1 先理清循环依赖的本质循环依赖的场景很常见A依赖BB又依赖A。Spring默认管理的单例Bean如何让两个对象互相引用答案藏在一、二、三级缓存里。源码就在DefaultSingletonBeanRegistry里// 一级缓存成品Bean private final MapString, Object singletonObjects new ConcurrentHashMap(256); // 二级缓存提前暴露的半成品Bean private final MapString, Object earlySingletonObjects new ConcurrentHashMap(16); // 三级缓存保存ObjectFactory用于生成提前引用的对象 private final MapString, ObjectFactory? singletonFactories new HashMap(16);三个缓存的名字网上有各种说法但职责要记住一级放的是创建完成的最终Bean二级放的是“被提前引用出去的早期对象”三级存的是ObjectFactory工厂它决定了要不要生成一个代理对象。网上有些文章把一级缓存叫“单例池”这个说法也可以但面试时最好先说出源码中的变量名再解释用途显得你真的读过源码。3.2 三级缓存的工作流程看一个简单例子A和B互相依赖都用setter注入。A开始创建实例化出原始A对象此时A还没有属性。Spring把A的ObjectFactory放进三级缓存这个工厂可以在需要时提前产出A。这一步叫“提前暴露”。A填充属性时发现依赖B于是去创建B。B实例化完成B填充属性时发现依赖A。此时A还没创建完但是三级缓存里已经有A的ObjectFactory。B通过这个工厂拿到A的引用同时这个引用被放入二级缓存。B继续走完属性填充和初始化流程成为成品Bean进入一级缓存。回到AB已经创建好A完成属性填充。A继续初始化成为成品进入一级缓存。看起来二级缓存好像也没那么必要为什么要三级而不是二级这是绝大多数面试官的追问点。关键原因在于Spring在生成最终Bean时可能要在后期通过BeanPostProcessor生成代理对象比如Transactional、Async。如果只有二级缓存提前暴露出去的A就是原始对象等到A真正完成创建时却发现应该暴露的是代理对象二者不一致B那边持有的引用就错了。三级缓存里的ObjectFactory可以在“B需要引用A”的那一刻决定直接返回原始A还是生成一个代理A。如果需要代理就提前把代理对象放到二级缓存如果不需要代理原始对象放二级缓存。这样保证了提前暴露给B的引用和最终对外提供的对象是一致的。换句话说三级缓存解决的是“代理对象生成时机不确定”的问题这是二级缓存做不到的。3.3 哪些循环依赖Spring救不了面试里如果说“Spring能解决循环依赖”一定要加限定条件默认只解决单例Bean、setter注入/字段注入场景下的循环依赖。以下几种情况救不了构造器注入实例化A之前就必须有B实例化B之前又必须有A没法提前暴露。prototype作用域每次都是新的三级缓存里根本不会保存工厂。某些情况下Bean本身需要强制提前代理比如Async也容易出现循环依赖异常。异步场景还牵涉到提前暴露的引用不是同一对象的问题排查起来很头疼。解决手段一般有三个改设计把循环引用拆开用Lazy延迟注入其中一个依赖或者用DependsOn强制控制初始化顺序。还有一个背景知识要了解Spring Boot 2.6开始默认禁止循环依赖启动时直接报错需要通过spring.main.allow-circular-referencestrue显式打开。所以面试里被问到“你们项目里怎么看待循环依赖”不要只说“Spring能解决循环依赖”就完事最好带一句“能避免就避免项目里出现循环依赖大概率是设计有问题”。这句话才是面试官想听的——你不仅要懂机制还要有正确的工程判断。4. AOP与动态代理别再只会说“横切关注点”4.1 AOP解决什么Spring AOP的核心价值是“横切逻辑复用”。典型场景是日志、权限、事务。比如你给每个Controller写日志如果不用AOP就是一个方法一个方法加代码侵入性强还容易漏。用了AOP定义一个切面在方法执行前后自动记录日志核心业务代码保持干净。Spring AOP的实现不是编译期改字节码而是基于动态代理在运行时生成代理对象。你声明一个接口的实现类并配置切点Spring容器最终返回给你的其实是代理对象调用方法时会先经过代理逻辑。这个“代理对象”的概念是整个AOP的基础很多人只记住了“动态代理”四个字却说不清楚容器里到底是JDK代理还是CGLIB代理以及两者分别在什么条件下生效。面试官往往就是从这里开始分层的。4.2 JDK动态代理与CGLIB怎么选这是面试中出现频率非常高的对比题建议用表格记清楚维度JDK动态代理CGLIB实现方式基于接口生成目标类的子类目标类要求必须有接口才能代理目标类不能被final修饰方法也不能是final创建与调用性能创建速度快反射调用有一定开销创建字节码耗时稍多调用性能较好无法代理的情况没有接口的类final类、final方法、static方法Spring Boot默认情况传统Spring默认优先JDKSpring Boot 2.x之后默认proxyTargetClasstrue直接用CGLIB追问环节经常出现“当目标类有接口时为什么Spring Boot还是倾向用CGLIB”因为JDK代理只能代理接口里声明的方法如果类里面新增了一个不在接口中的方法事务和日志就拦截不到而且CGLIB创建出的代理对象更贴近目标类很多框架在桥接老代码时反而更省心。我在项目里确实遇到过这类问题一个Service实现了接口但有一部分方法不在接口里用JDK代理时这些方法上的日志切面完全不生效后来改成CGLIB才算彻底解决。4.3 容易被追问的两处细节第一处是Configuration类代理。Spring的Configuration类默认被CGLIB增强目的是保证Bean方法之间的调用走代理确保单例效果。比如Configuration public class MyConfig { Bean public A a() { return new A(); } Bean public B b() { return new B(a()); } }如果没有CGLIB代理b()里的a()是直接new一个新的A单例就被破坏了。Spring通过代理拦截了这次方法调用返回容器里的同一个A。这个知识点如果能在面试里主动提出来面试官会默认你对代理的理解不是停留在使用层面。顺带还可以提一句Spring在解析Configuration时Lite Mode和Full Mode的区别重点就在于这个类有没有被CGLIB增强。第二处是通知类型的执行顺序。Around的proceed是绕行Before在所有前置逻辑里最先准备After在方法结束之后执行AfterReturning和AfterThrowing根据是否抛异常二选一。一个常见的执行顺序是Around前置逻辑 → Before → 目标方法 → AfterReturning/AfterThrowing → After → Around后置逻辑。实务中如果想统一处理异常一般会在最外层用Around包住整个链路。面试时能把这个顺序画出来基本就过关了。5. Spring事务Transactional为什么会失效5.1 事务注解的底层原理直接说结论Transactional本身不实现事务它靠的是Spring AOP。容器里你的Service实际上是一个代理对象调用方法时事务拦截器会判断方法上有没有Transactional有就开启事务、执行方法、提交或回滚。所以事务生效的前提是你的Bean被Spring容器管理而且调用链路经过了代理对象。这两个点很多候选人答得出但下面这个经典场景经常翻车。面试官最常拿来试水的追问是“那整个事务是围绕什么来实现的”你如果能说出TransactionInterceptor和TransactionManager说明你真看过源码。Spring执行事务的核心逻辑通过事务拦截器拿到事务信息包装成事务回调再委托给PlatformTransactionManager完成commit或rollback。能把这条链路讲清面试官基本不会纠结你对Spring事务是“背概念”还是“懂原理”。5.2 事务失效的经典场景清单面试官说“给我说几个Transactional失效的场景”大多数候选人能说出两三个。我把高频场景整理成清单你准备的时候按这个来自调用。类内部方法A调用方法BB上面有Transactional。因为this调用不会经过代理对象事务失效。方法非public。Transactional默认只对public方法生效背后原因是Spring事务拦截器对非public方法不会主动增强这点要注意。异常被捕获后没有重新抛出。事务拦截器看不到异常自然无法回滚。抛出的异常是CheckedException且没有配置rollbackFor。Spring的默认策略是运行时异常回滚编译异常如IOException不会触发回滚。类没有被Spring管理。没加Service、Component整个类都不是代理Bean。多线程调用。事务是绑定在线程上的你在事务方法里另起一个线程操作数据库那个线程里执行的是无事务逻辑。final方法被CGLIB代理不了。传播行为设置不对比如把方法设置成NOT_SUPPORTED。5.3 从一个具体例子讲清楚自调用问题自调用失效这个问题我建议用一个例子来回答Service public class OrderService { Transactional(rollbackFor Exception.class) public void createOrder() { saveOrder(); } Transactional(propagation Propagation.REQUIRES_NEW) public void saveOrder() { // 数据库操作 } }面试时如果只是说“两个方法在一个类里事务不生效”显得太单薄。你可以继续说解决思路是把事务方法抽到另一个Service类里注入进来调用或者通过ApplicationContext.getBean拿到代理对象再调用。其实Spring官方也是建议通过被代理的实例来调用毕竟事务是基于代理的机制。这样回答既亮了知识点又体现出你踩过坑后有实际解决方案。事务传播行为里REQUIRED、REQUIRES_NEW、NESTED这三者是面试重点。REQUIRED表示加入当前事务没有就新建这是默认值REQUIRES_NEW是挂起当前事务开一个新事务NESTED是嵌套事务内层回滚可以只回滚自己并标记回滚点外层决定整体是否提交。能区分这三者在“内层方法抛出异常外层是否需要感知”上的差异事务这块基本稳了。6. Spring Boot自动配置与Spring MVC拼图的最后两块6.1 自动配置到底做了什么我看过很多人的简历写着“熟练掌握Spring Boot自动配置原理”问他“Spring Boot是怎么知道要帮你配置DataSource的”就答不上来。自动配置的核心链路其实不复杂SpringBootApplication是一个组合注解里面包含EnableAutoConfiguration。这个注解通过导入AutoConfigurationImportSelector扫描一个固定的配置文件把所有候选自动配置类读进来。在Spring Boot 2.7之前是META-INF/spring.factories从2.7开始改为META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importsorg.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration候选配置类加载之后Spring会根据条件注解决定到底启用哪些。比如DataSourceAutoConfiguration上有ConditionalOnClass只有classpath里存在javax.sql.DataSource相关类才会处理ConditionalOnMissingBean则确保你如果自定义了DataSourceSpring就不重复创建。6.2 为什么说“自动配置”其实是“按需装配”准确的说法是Spring Boot不是把所有的配置都加载进来而是“候选列表条件判断”双重过滤。很多候选人一谈自动配置就只说“加载了配置文件”少了条件判断这一层回答深度立刻下来一截。条件注解里ConditionalOnClass、ConditionalOnMissingBean、ConditionalOnProperty是最常用的三个前两个控制类和Bean层面的装配第三个控制配置项层面的装配。能把这三个说清楚面试官就不会觉得你是在只会用Spring Boot并不会“理解”它。如果面试进一步问“怎么自己写一个starter”动手思路是这样的写一个自动配置类在里面声明需要创建的Bean用ConditionalOnClass、ConditionalOnProperty做条件控制把类路径写到AutoConfiguration.imports文件里最后用ConfigurationProperties绑定配置属性。这个流程在微服务团队里经常会用到比如自己做一套通用的Redis配置、通用的消息队列封装本质上都是干这件事。能举出这类真实场景比说一百句“自动配置很方便”都管用。6.3 Spring MVC请求处理流程Spring MVC处理一次请求核心链路可以这样拆DispatcherServlet接收请求。通过HandlerMapping找到对应的Handler以及拦截器链。通过HandlerAdapter执行Handler方法也就是真正调用Controller里的代码。Controller返回逻辑视图名或者直接返回JSON数据。Spring Boot下用RestController返回的其实是Jackson序列化之后的JSON。如果返回的是视图名ViewResolver负责找到对应的模板渲染。整个链路走完后响应返回给客户端。面试官围绕这个链路常问两个点。一是父子容器的理解Spring MVC的Web层容器会以Root容器为父容器Controller扫描配置在子容器里Service、Repository扫在父容器里子容器能看到父容器的Bean反过来不行。二是RestController和Controller的区别本质上就是Controller加ResponseBody方法返回结果直接写进响应体不再经过视图解析器。把这两点答清楚Spring MVC部分基本不会被追到死角。7. 让回答“活”起来面试官真正想听到的答法7.1 把每个题目当成一个小型讲解来准备我面试完以后经常会给候选人复盘很多答案明明是对的但听起来像在背课文。一个更好的组织方式是“结论—原理—例子”三段式。比如被问到“循环依赖”你先说结论Spring通过三级缓存解决单例Bean基于setter注入的循环依赖。然后讲原理一级放成品、二级放提前暴露的对象、三级放ObjectFactory关键在代理对象什么时候生成。最后给例子A依赖B、B依赖A把创建的几个步骤串一遍。一套下来大约两三分钟信息密度高又不是纯背诵。7.2 准备一个能把多个知识点串起来的案例个人强烈建议准备一个贯穿案例把你熟悉的框架知识点串在同一个业务场景里。比如一个用户注册接口请求进来DispatcherServlet分发UserServiceImpl被IoC托管构造器注入Mapper事务注解开启事务密码安全校验走AOP切面日志记录走统一切面。面试官问任何一环时你随时能从这条链路里抽出对应环节展开比一个个孤立的题答起来自然得多。准备的时候最好还能在这个案例里嵌一两个“坑”比如自调用导致事务失效这样被追问时你也能马上切到实战视角。7.3 面试前做两轮耐压测试准备到后面建议找一个不是Java技术栈很熟的朋友来模拟面试因为外行提问更发散反而能逼你把话说清楚。第一轮测试每个必考题的“结论—原理—例子”是否顺畅第二轮专门练追问比如把“为什么需要三级缓存”连续追问三层。我自己带人准备跳槽时最常用的套路就是让候选人把每个核心问题讲给一个完全没听过的人听讲得清楚才算真的学会。另外一个实用小技巧是把每个必考题整理成一面卡片正面写问题、背面写三个关键词临面试前快速过一遍比抱着整本八股文翻来覆去效率高得多。这个办法你面试前也可以试试效果比闷头背题好得多。
返回列表