首页 / 资讯中心 / 文章详情

Spring面试高频考点:IoC容器、AOP代理、事务失效与自动配置

Spring面试高频考点:IoC容器、AOP代理、事务失效与自动配置 ★ FEATURED ARTICLE
简介一套经典的Spring面试题及答案面向Java开发者和准备Spring相关面试的求职者。内容以问答形式集中梳理了25个高频考点涵盖Spring框架模块划分、控制反转IOC与依赖注入原理、BeanFactory与ApplicationContext区别、Bean生命周期与作用域、基于注解的自动装配、构造器注入与setter注入对比、Spring事件机制及框架中设计模式等核心主题并配有详细解析便于系统复习和重点突破。资源压缩包仅包含1个docx文档大小88KB内容精炼方便离线阅读和快速查阅。目前已有403人学习适合正在备战Spring面试或需要巩固框架基础知识的开发者收藏使用。1. 经典的Spring面试题答案背得再熟不如先看面试官想问什么做过几年Java面试的人都有个直觉Spring面试题早就不是「背出Bean的生命周期有几步」那种八股了。现在一线团队问的Spring面试题表面上是考框架用法实际上是在考候选人对「容器、代理、配置、事务」这四个抽象有没有真正想明白。同一个问题背答案的人只能说出DefineBean、InitBean这些名词而真正用过Spring的人能顺着「Bean是何时被创建的、注入发生在哪一步、代理在哪一层生效」把整条链路讲下来这就是差距。这篇文章不打算给你一份贴在墙上的题库。我按面试里最常被追问的六个方向把经典Spring面试题拆成「题目本身、怎么回答、追问点、踩坑记录」四段来写。新手能照着把每个demo跑通熟手可以直接拿去当自查清单。全文用的是Spring Framework 5.x/6.x的通用行为不绑定某个具体小版本因为面试官真正在意的是你的推理路径不是版本号。2. IoC与Bean生命周期两道最容易被追问到源码的Spring面试题2.1 Bean的完整生命周期从BeanFactoryPostProcessor到PostConstruct的执行顺序面试官问「Bean的生命周期」时想听的绝对不是「实例化、属性填充、初始化、销毁」这四步。真正的考点在两条线上工厂后置处理器的时机以及初始化回调的完整顺序。我在模拟项目X里排查过一个问题某个全局配置在Bean初始化阶段读不到值最后定位到是BeanFactoryPostProcessor里的修改被PostConstruct抢先消费了。如果你要在本地复现生命周期顺序最快的方式是写一个Bean并注册所有回调接口打印执行日志Component public class LifecycleDemo implements BeanNameAware, InitializingBean, DisposableBean { private String name; // BeanNameAware 回调在属性填充之后、初始化之前触发 Override public void setBeanName(String name) { System.out.println(1-Aware: setBeanName - name); this.name name; } // InitializingBean 回调在PostConstruct之后触发 Override public void afterPropertiesSet() throws Exception { System.out.println(3-InitializingBean: afterPropertiesSet); } // PostConstruct在Aware回调与InitializingBean之间触发 PostConstruct public void postConstruct() { System.out.println(2-PostConstruct: init for name); } // init-method 指定的方法最后执行 Bean(initMethod customInit, destroyMethod customDestroy) public LifecycleDemo lifecycleDemo() { return new LifecycleDemo(); } public void customInit() { System.out.println(4-init-method: customInit); } public void customDestroy() { System.out.println(5-destroy: customDestroy); } PreDestroy public void preDestroy() { System.out.println(pre-destroy: PreDestroy); } }运行后会看到稳定顺序setBeanName - PostConstruct - afterPropertiesSet - customInit销毁时PreDestroy先于customDestroy。这个顺序不是玄学它对应Spring源码里AbstractAutowireCapableBeanFactory.initializeBean方法的调用栈先Aware回调族再applyBeanPostProcessorsBeforeInitialization这里触发PostConstruct然后invokeInitMethods先InitializingBean后自定义init-method。面试追问通常是两个方向。第一个是「Aware回调有多少种」你至少要能说出BeanNameAware、BeanFactoryAware、ApplicationContextAware并说明后两者拿到的对象有什么区别。第二个是「如果BeanPostProcessor里有逻辑抛异常发生在哪一步」答案是初始化前后都有机会因为BeanPostProcessor的存在就是为了在postProcessBeforeInitialization和postProcessAfterInitialization两个时间点插入你的逻辑异常发生在哪个阶段取决于你写在哪。我一般建议候选人画一条时间轴把「实例化 - 属性填充 - Aware - PostConstruct - InitializingBean - init-method - BeanPostProcessor后置处理」串起来面试官看到这条轴基本就会往下一个深度话题走。2.2 循环依赖三级缓存的真相与singleton池的兜底循环依赖是Spring面试题里最经典的「背了答案也翻车」问题。标准答案是「三级缓存」但面试官追问「三级缓存为什么是三级而不是两级」时大多数候选人就卡住了。我踩过的坑是只记住了singletonObjects、earlySingletonObjects、singletonFactories三个Map的名字却没有解释每一级存在的理由。先明确结论三级缓存解决的只是「singleton 非构造器注入」的循环依赖prototype作用域和构造器注入的循环依赖Spring不处理、也处理不了。这三级的职责分别是一级缓存存的是完整Bean初始化完成二级缓存存的是早期曝光Bean已实例化但未完成属性填充与初始化三级缓存存的是ObjectFactory只有当发生循环引用时才被调用生成代理对象并提前曝光。// 这是Spring内部DefaultSingletonBeanRegistry的核心结构面试时按这个讲 private final MapString, Object singletonObjects new ConcurrentHashMap(256); private final MapString, Object earlySingletonObjects new ConcurrentHashMap(16); private final MapString, ObjectFactory? singletonFactories new HashMap(16); // 获取单例Bean的核心逻辑简化版 protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject this.singletonObjects.get(beanName); if (singletonObject null isSingletonCurrentlyInCreation(beanName)) { singletonObject this.earlySingletonObjects.get(beanName); if (singletonObject null allowEarlyReference) { // 三级缓存拿到工厂执行getObject()生成早期Bean ObjectFactory? singletonFactory this.singletonFactories.get(beanName); if (singletonFactory ! null) { singletonObject singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } return singletonObject; }关键点在这里为什么不直接把早期Bean放进二级缓存因为Spring允许在早期暴露阶段就生成AOP代理。如果A类被事务或切面代理那么它依赖B类时暴露出去的必须是代理对象否则后续初始化完的A和之前注入到B里的A不是同一个引用。三级缓存放的ObjectFactory就是用来延迟决定「返回原始Bean还是返回代理Bean」的。只有真正发生循环依赖时这个工厂才会被触发这也意味着不需要为每次创建都提前生成代理。回答这个问题时我的建议是主动讲透「为什么二级缓存不够」如果只有二级缓存Spring必须在实例化后立刻决定是否代理但此时AOP的通知器可能还没完全准备好代理决策时机不对。所以三级缓存把「创建完整Bean」和「暴露早期引用」解耦。面试官如果接着问「循环依赖能不能彻底去掉」你也不用慌直接说Spring官方在新版本中已经不建议依赖这个机制理想的方案是重构出无环依赖或者用Lazy在注入点做延迟代理。3. AOP与事务Spring面试题里翻车率最高的两个考点3.1 代理模式选择JDK动态代理与CGLIB的边界AOP的第一道经典题就是「Spring默认用什么代理怎么切换」。很多候选人能说出「接口用JDK代理类用CGLIB」但再追问「为什么Spring Boot 2.x之后默认用CGLIB」就答不上来了。这个决策的背景是早期Spring AOP默认用JDK动态代理因为它是JDK内置、无需额外依赖、只对接口生效但大量项目里的Service没有提取接口被迫加配置切到CGLIB。Spring Boot团队后来干脆把默认改成CGLIB让开发者不用为了AOP去强行抽象接口。JDK动态代理的核心是InvocationHandler配合Proxy.newProxyInstance它生成的代理类和目标类实现同一个接口。CGLIB则通过生成目标类的子类来覆写方法Configuration类被代理靠的也是CGLIB这样才能保证Bean方法返回单例。这里有个必须知道的边界CGLIB不能代理final类不能覆写final方法JDK代理则没有这个限制但要求目标必须有接口。还有一个隐藏坑——JDK代理的调用是走InvocationHandler.invoke如果目标方法内部调用另一个方法这个内部调用不会经过代理只有外部进来的第一次调用才被拦截。// 手动验证两种代理方式的行为差异 public class ProxyDemo { // 接口 实现类 interface GreetingService { void sayHello(); } static class GreetingServiceImpl implements GreetingService { Override public void sayHello() { System.out.println(hello from impl); } } public static void main(String[] args) { GreetingService target new GreetingServiceImpl(); // JDK动态代理只能针对接口创建代理 GreetingService jdkProxy (GreetingService) Proxy.newProxyInstance( GreetingService.class.getClassLoader(), new Class[]{GreetingService.class}, (proxy, method, methodArgs) - { System.out.println(before jdk proxy); Object result method.invoke(target, methodArgs); System.out.println(after jdk proxy); return result; }); jdkProxy.sayHello(); // 验证代理类型 System.out.println(JDK proxy class: jdkProxy.getClass()); } }运行输出里能看到代理类名是com.sun.proxy.$Proxy0不是我们的实现类。面试时我建议顺手提一句「判断当前Bean用的是哪种代理可以在启动日志里找Agent字样或者直接bean.getClass()看类名」。很多线上事故的根因是某个类没有接口但项目里全局配置了proxyTargetClassfalse结果AOP悄悄失效事务也没生效。排查方式是看启动日志里有没有「All Spring beans are not proxied」之类的警告以及检查类是否被final修饰。3.2 事务失效场景同类调用、private方法、异常被吞的排查路径事务失效是Spring面试题的「血泪经验」聚集地几乎每个候选人都能说出两三个场景但能系统分类的人很少。我习惯把失效场景分成四大类访问权限问题、方法修饰符问题、内部调用问题、异常处理问题。其中最坑的是「同类内部调用」因为代码看起来完全正常但事务就是不回滚。内部调用失效的原理要推到代理机制Transactional的生效依赖Spring创建的代理对象外部调用走代理但同类方法里this.doSomething()是直接调用目标对象的方法绕过了代理。所以Transactional方法被同类方法调用时事务注解没有任何效果。解决方式有三种注入自身代理、拆到另一个Bean、用AopContext.currentProxy()前提是开启exposeProxy。Service public class OrderService { // 错误的示范同类内部直接调用事务不生效 public void createOrderWithLog(Order order) { // 这里调用的是this.insertOrder绕过代理Transactional失效 this.insertOrder(order); } Transactional public void insertOrder(Order order) { // 数据库写入 } // 解决方案二注入自身的代理对象通过代理调用 Autowired private OrderService self; public void createOrderCorrect(Order order) { self.insertOrder(order); // 走代理事务生效 } }异常被吞的场景同样高频Transactional默认只对RuntimeException回滚如果你在方法里捕获了异常并打了日志而没有抛出Spring感知不到失败事务照常提交数据就错了。排查路径是先确认注解有没有配rollbackFor再看方法是不是public再确认是不是同类调用最后检查异常是不是被try-catch吞掉了。面试时把这条排查顺序讲出来比只背列表更能证明你真的在线上排过故障。关于事务传播属性我建议重点准备REQUIRED与REQUIRES_NEW的对比。经典题是「在同一个事务方法里调用另一个REQUIRES_NEW方法内层事务回滚会不会把外层一起带回去」答案是取决于外层有没有捕获内层异常。如果内层抛了异常而外层没捕获外层事务会因异常标记回滚如果外层捕获了异常内层事务回滚不影响外层继续提交。这就是事务边界的计算题面试官很喜欢拿它来考察你对嵌套事务的理解。4. Spring Boot自动配置一道题看出候选人有没有读过源码4.1 从SpringBootApplication到AutoConfiguration.imports自动配置的加载链路Spring Boot的自动配置是面试题里的「黑匣子」天天在用但绝大多数候选人说不清加载链路。第一道经典题是「SpringBootApplication到底做了什么」答案拆开是三合一SpringBootConfiguration标记配置类、EnableAutoConfiguration开启自动配置、ComponentScan扫包。而EnableAutoConfiguration的核心是Import(AutoConfigurationImportSelector.class)这个Selector会去读classpath下的自动配置类列表。自动配置的候选清单在不同版本位置不同2.7之前是spring.factories里的EnableAutoConfiguration键2.7之后换成了META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports每行一个配置类全限定名。面试讲到这个版本差异是很加分的因为说明你真的更新过知识体系。加载后的关键动作是条件装配那些ConditionalOnClass、ConditionalOnMissingBean、ConditionalOnProperty注解决定了一个自动配置类里的Bean是否真的被注册。# 用来观察自动配置生效范围的配置项适合面试前自测 # 启动时打印所有自动配置类的匹配报告positive表示生效negative表示未生效 debug: true启动后控制台会输出CONDITIONS EVALUATION REPORT这是排查自动配置不生效的第一工具。比如你发现某个DataSource配置没生效去看report里对应配置类的negative匹配条件就能看到是ConditionalOnClass没找到类还是ConditionalOnMissingBean被自己定义的Bean挡住了。面试时给面试官讲这个report怎么读比空背流程要立体得多。还有一个必问的题「如何覆盖自动配置」。常见做法有三种定义自己的Bean因为ConditionalOnMissingBean会让自动配置让位排除特定自动配置类exclude {DataSourceAutoConfiguration.class}通过属性开关关闭比如spring.autoconfigure.exclude。我一般建议优先用自己的Bean覆盖而不是直接排除配置类因为排除是全局行为容易误伤其他依赖。4.2 自定义starter把自动配置题变成加分项能把自动配置讲清楚还不够面试官通常会在同一个话题里追问「如果让你给自家中间件写一个starter目录结构和核心类怎么设计」。这题答好了直接变成加分项因为它综合考察了配置装载、条件判断、属性绑定三项能力。自定义starter的标准骨架是自动配置类用AutoConfiguration标记用EnableConfigurationProperties绑定配置属性类用ConditionalOnXxx控制Bean装配时机。如果你想让starter内嵌一个拦截器或过滤器还需要借助WebMvcConfigurer或FilterRegistrationBean来注册。我给某公司写过一个内部starter目录结构就是按这个套路// 自定义starter的自动配置类 AutoConfiguration // 绑定配置文件里 swagger.custom.enabled 等前缀为 swagger.custom 的属性 EnableConfigurationProperties(CustomProperties.class) // 仅当类路径存在目标类时才加载 ConditionalOnClass(HandlerInterceptor.class) public class CustomAutoConfiguration { Bean ConditionalOnMissingBean ConditionalOnProperty(prefix custom.interceptor, name enabled, havingValue true, matchIfMissing true) public HandlerInterceptor customInterceptor(CustomProperties properties) { return new CustomInterceptor(properties); } Bean public WebMvcConfigurer customWebMvcConfigurer(HandlerInterceptor customInterceptor) { return new WebMvcConfigurer() { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(customInterceptor) .addPathPatterns(/api/**) .excludePathPatterns(/api/public/**); } }; } }配置属性类CustomProperties用ConfigurationProperties(prefix custom)标注字段名对应配置文件里的kebab-case键。这里有个容易翻车的地方属性字段命名用驼峰没问题但如果你的配置里有custom.max-size这类带连字符的键一定要在字段上加Value(${custom.max-size})或确保Spring Boot能正确做宽松绑定。另外ConditionalOnMissingBean放在方法上有个陷阱——它判断的是当前配置类中是否已经手动声明过同类型Bean如果另一个配置类里也定义了同类型可能两个都会注册要看AutoConfiguration类的排序。面试官如果继续追问「starter里的自动配置类怎么被Spring找到」回答是META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件里列出自动配置类全限定名。注意2.7版本之前的项目用的是spring.factories如果你在面试现场能把这个迁移路径说清楚就能和那些只背了SpringBootApplication的候选人明显区分开。5. Spring高频题避坑5个答错就翻车的细节5.1 把BeanFactory和FactoryBean混为一谈现象候选人被问「BeanFactory和FactoryBean的区别」时直接说「一个是容器一个是Bean」。原因两个名字太像而且平时都用不上FactoryBean。解决BeanFactory是IoC容器的最顶层接口管理所有Bean的生命周期FactoryBean是一个特殊的Bean它本身也是一个Bean但当容器获取这个Bean时返回的是getObject()方法生成的对象而不是FactoryBean本身。经典实现是MyBatis的SqlSessionFactoryBean面试时提这个例子比背定义好使。5.2 断言「Configuration是CGLIB代理」却解释不清为什么现象问「Configuration和Component的区别」答「前者会被CGLIB增强」但追问增强的目的时卡壳。原因只知道结论不知道动机。解决Configuration的CGLIB代理是为了保证Bean方法返回单例——当A类Bean方法里调用了B类的Bean方法代理会拦截这个调用并检查容器里是否已有该Bean有就直接返回已创建实例避免重复实例化。如果换成Component内部Bean方法调用不会走代理每次调用都new一个对象。演示方法是在启动类里输出两个Bean方法的HashCode对比。5.3 事务传播背了定义却算不对嵌套场景的提交结果现象能背出REQUIRED、REQUIRES_NEW、NESTED的定义但给一个两段事务嵌套的代码判断不出最终是否回滚。原因死记硬背没有建立「事务边界 异常传播」的心智模型。解决动手画调用栈。REQUIRED是外层的延续加入当前事务REQUIRES_NEW是挂起外层、新开一个独立事务内层回滚不影响外层外层回滚也管不到内层已提交的结果NESTED是JDBC保存点外层回滚会带内层一起回滚但内层失败不影响外层。遇到场景题先把事务边界画出来再判断异常在哪一层被捕获答案就出来了。5.4 答错「singleton Bean线程安全吗」现象候选人斩钉截铁说「单例Bean是线程安全的」或者相反说「不安全」。原因把「单例」和「线程安全」两个概念绑定。解决单例Bean只是容器中只有一个实例线程安全性取决于Bean本身有没有可变状态。如果Bean里全是无状态方法或不可变字段它就是线程安全的如果有一个private int count且被多个线程并发修改就不安全。Spring容器不承担同步责任。面试官问这个题真正想听的是「Spring为什么不把Bean设计成线程安全的」回答是状态管理属于业务代码的职责容器强制加锁会让无状态Bean白白损失性能。5.5 不知道Bean方法的调用时机与配置属性绑定链现象被问「Bean方法里的参数Spring是怎么注入的」只会回答「自动注入」追问「注入的优先级顺序」就哑火。原因忽略了Bean方法参数解析与Autowired解析走的是同一套AutowireCapableBeanFactory逻辑。解决Bean方法参数支持Qualifier指定名称、Value注入配置文件值、ObjectProvider延迟注入还支持直接把ApplicationContext、Environment等基础设施作为参数。优先级上先看有没有显式注解再看类型匹配最后看是否唯一候选。这个细节在自研框架封装时特别容易踩坑建议直接打断点看AutowiredAnnotationBeanPostProcessor的解析路径。6. 用「一句式」把Spring面试题答出层次感WHAT-WHY-HOW框架很多候选人知识是满的但表达像流水账。我观察过一个现象同样知道三级缓存的原理有人能讲三分钟让面试官频频点头有人讲三十秒就暴露理解漏洞。区别不在于记了多少细节而在于有没有一个稳定的回答结构。我用的框架是「WHAT-WHY-HOW」不管哪一类Spring面试题第一句先说它是什么第二句说为什么需要它第三句说怎么用以及代价是什么。拿「Spring AOP」举例第一句WHAT——AOP是通过动态代理在方法执行前后插入横切逻辑的机制第二句WHY——因为事务、日志、权限这些逻辑不适合写在业务代码里需要一种非侵入的织入方式第三句HOW——通过Aspect定义切面Before/After/Around指点切逻辑运行时由ProxyFactory决定用JDK代理还是CGLIB并且要主动说明「默认CGLIB、接口代理的代价是无法拦截内部调用」。这三句话说完面试官就有了继续追问的坐标系你后面补充代理机制差异、事务传播边界都是在这个坐标系上加细节。这个框架的第二个价值是逼你说出「代价」。面试官最反感只讲好处不讲限制的候选人因为任何技术选型都意味着取舍。回答循环依赖时说「三级缓存解决singleton的循环依赖但构造器注入和prototype作用域解决不了」回答自动配置时说「方便的同时带来隐式装配排查成本变高所以需要debug: true去看装配报告」这些都是在一句话里带出代价。我自己做技术面试时最看重的就是候选人能不能在讲方案时主动说「这里有个坑」。最后一个实用建议把经典的Spring面试题做成自己的关键词卡片每张卡片只写「问题、一句式答案、三个追问点」。背是没有用的你要做的是对着空白卡片自己讲一遍卡住的地方就是知识缺口。这个习惯我从带某位实习生时就开始用他靠这份卡片在两个星期内把Spring面试题的通过率从勉强及格练到稳定输出后来他自己去做面试官也沿用这套方法。希望今天的这篇梳理能帮你在下一次面试里把Spring讲得比书上的答案多一层「为什么」祝顺利。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站