第七节 面试问题-中级版

亮子 2025-09-06 11:32:45 54 0 0 0

1、你都用过哪些设计模式或者了解哪些设计模式?

  • 创建型:单例模式(全局唯一实例)、工厂模式(封装对象创建)、建造者模式(复杂对象分步构建)
  • 结构型:适配器模式(兼容不同接口)、装饰器模式(动态扩展功能)、代理模式(控制访问)
  • 行为型:观察者模式(发布-订阅机制)、策略模式(算法动态切换)、模板方法(固定流程+可变步骤)

记忆方法:
“创建单工建,结构适装代,行为观策模”
(每类取3个常用模式首字,结合场景功能:创建型管对象生成,结构型管类/对象组合,行为型管交互)

2、重写和重载有什么区别?

  • 重写(Override):子类重写父类方法,方法名、参数列表、返回值完全相同,访问权限不能更严格,用于多态。
  • 重载(Overload):同一类中方法名相同,参数列表不同(个数/类型/顺序),返回值可不同,用于增强方法灵活性。

记忆方法:
“重写看父子,签名(方法名+参数)必须同;重载看同类,签名必须异”
(核心区别:重写是父子类关系+签名相同,重载是同类关系+签名不同)

3、什么是深克隆和浅克隆?有什么区别?

  • 浅克隆:只复制对象本身及基本类型字段,引用类型字段仍指向原对象(共享引用)。
  • 深克隆:不仅复制对象本身,还递归复制所有引用类型字段指向的对象(完全独立)。
  • 区别:是否递归复制引用类型的内部对象,深克隆可避免原对象与克隆对象相互影响。

记忆方法:
“浅克隆,抄表面;深克隆,抄到底”
(浅克隆仅复制表层结构,深克隆递归复制所有层级,强调是否复制引用对象的内部数据)

4、Java 的引用类型有哪些?

  • 强引用:默认引用类型,只要存在就不会被GC回收(如Object o = new Object())。
  • 软引用(SoftReference):内存不足时才会被回收,适合缓存。
  • 弱引用(WeakReference):GC时无论内存是否充足都会被回收,适合临时关联。
  • 虚引用(PhantomReference):不影响对象生命周期,仅用于跟踪GC,必须配合引用队列使用。

记忆方法:
“强永不收,软缺才收,弱遇GC收,虚仅跟踪”
(按GC回收的严格程度排序,突出每种引用被回收的条件)

5、说一下 JVM 中的垃圾收集器有哪些?

  • SerialGC:**单线程垃圾收集器**,暂停时间长,适用于简单应用。
  • ParallelGC:**多线程垃圾收集器**,注重吞吐量(JDK8默认)。
  • CMS(Concurrent Mark Sweep):**并发垃圾收集器**,低延迟,需更多内存。
  • G1(Garbage-First):**G1垃圾收集器**区域化分代式,兼顾吞吐量与延迟(JDK9+默认)。
  • ZGC/Shenandoah:超低延迟,适用于大堆场景(JDK11+引入)。

记忆方法:
“Serial单线程,Parallel重吞吐,CMS求低延,G1两边顾,ZGC/Shenandoah超快速”
(按核心特点和适用场景区分,从简单到复杂排序)

6、如何破坏双亲委派机制?

  • 重写ClassLoader的loadClass()方法,打破“先委托父加载器”的逻辑。
  • 示例:自定义类加载器时,直接加载指定类而不先委托父加载器。
  • 典型例:Tomcat的WebAppClassLoader,为隔离隔不同应用的类而破坏委派。

记忆方法:
“改写loadClass,跳过父直接加”
(核心是通过重写加载方法,绕过双亲委派的委托流程)

7、如何判断对象是否可以被回收?

  • 引用计数法:对象被引用时计数+1,引用失效时-1,计数为0则可回收(难以解决循环引用问题)。
  • 可达性分析:以GC Roots为起点,遍历对象引用链,不可达的对象可回收(JVM实际采用的方法)。
  • GC Roots包括:虚拟机栈中引用的对象、方法区中类静态属性/常量引用的对象、本地方法栈中引用的对象等。

记忆方法:
“计数看数字,为0就回收;可达看链条,断了就能收”
(前者记数字变化,后者记引用链是否可达,突出两种方法的核心判断逻辑)

8、synchronized锁的升级机制是什么?

  • 无锁:初始状态,无竞争时的状态。
  • 偏向锁:同一线程多次获取同一锁时,仅记录线程ID,减少开销。
  • 轻量级锁:多线程交替执行时,通过CAS竞争锁,避免重量级锁的开销。
  • 重量级锁:多线程同时竞争时,依赖操作系统互斥量,会导致线程阻塞。
  • 升级路径:无锁 → 偏向锁 → 轻量级锁 → 重量级锁(不可逆)。

记忆方法:
“无锁起步,偏向单线程,轻量交替争,重量多线程”
(按竞争程度从小到大,对应锁的升级阶段,突出各阶段适用场景)

9、数据库查询语句中的where和having有啥区别?

  • where:用于过滤行,在分组(group by)前执行,不能使用聚合函数(如sum、count)。
  • having:用于过滤分组组,在分组后执行,可使用聚合函数,通常与group by配合使用。

记忆方法:
“where在前筛行,having在后筛组;where不用聚合,having可带聚合”
(按执行时机和是否支持聚合函数区分,突出“行”与“组”的过滤对象差异)

10、左连接、右连接、内连接、全连接有什么区别?

  • 内连接(INNER JOIN):只保留两表中匹配的行,不匹配的不显示。
  • 左连接(LEFT JOIN):保留左表所有行,右表匹配的行显示,不匹配的用NULL填充。
  • 右连接(RIGHT JOIN):保留右表所有行,左表匹配的行显示,不匹配的用NULL填充。
  • 全连接(FULL JOIN):保留两表所有行,不匹配的部分用NULL填充(MySQL不直接支持,需用UNION模拟)。

记忆方法:
“内连取交集,左连保左全,右连保右全,全连取并集”
(用“交集”“保左/右全”“并集”形象化范围,结合左右表的保留规则记忆)

11、请说说事务中的MVCC?

  • MVCC(多版本并发控制):通过为每行数据创建多个版本,实现读写不阻塞、读不加锁,提升并发性能。
  • 核心机制:每行数据包含隐藏列(创建版本号、删除版本号),事务操作时生成新版本,通过版本号判断数据可见性。
  • 适用场景:InnoDB引擎的读已提交(RC)和可重复读(RR)隔离级别。

记忆方法:
“多版本,读写欢;版本号,判可见;不阻塞,并发赞”
(突出多版本特性、版本号作用及无锁并发的优势)

12、如果Redis服务崩溃了怎么办?

**回答: **
1. 若启用RDB/AOF持久化,重启后可通过备份文件恢复数据;
2. 若主从架构,从库升级为主库继续提供服务,修复主库后再同步;
3. 集群模式下,其他节点会自动接管,保证可用性(哨兵模式集群)。

**记忆法: **
“持久化救数据,主从/集群保服务”——崩溃后先靠持久化恢复数据,再通过主从或集群架构保障服务不中断。

13、请说说redis的哨兵模式的机制?

**回答: **
哨兵模式是Redis主从架构的高可用方案,机制为:
1. 监控:哨兵进程持续检查主从节点健康状态;
2. 自动故障转移:主库故障时,哨兵选举新主库(从库晋升),并让其他从库同步新主库;
3. 通知:将故障转移结果通知客户端。

理解:
- 哨兵是一个独立的进程
- 每个哨兵监控一个服务器
- 哨兵实现消息通知和执行选举

**记忆法: **
“监(监控)选(选新主)同(同步)告(通知)”——哨兵核心动作就是这四步,按顺序记住即可。

14、说说Redis分布式集群是如何存储数据的吗?

**回答: **
Redis分布式集群通过哈希槽(共16384个)存储数据:
1. 每个键经哈希计算映射到一个槽;
2. 集群节点分管不同槽(可配置分配);
3. 客户端请求时,节点会根据槽归属,转发请求到对应节点。

**记忆法: **
“键哈希找槽,槽归节点管,请求按槽转”——核心是哈希槽作为数据与节点的中间映射桥梁。


15、Mybatis的一级缓存和二级缓存有什么区别,怎么开启?

**区别: **
1. 一级缓存:默认开启,作用于SqlSession级别,同一会话内重复查询同SQL会命中缓存;
2. 二级缓存:需手动开启,作用于Mapper namespace级别,跨SqlSession共享,可自定义存储介质。

**开启方式: **
- 一级缓存:无需额外配置,默认生效;
- 二级缓存:在Mapper.xml中加<cache/>标签,或在接口类/方法加@CacheNamespace注解。

**记忆法: **
“一级会话内,二级跨会话;一级默认开,二级XML/注解来”——按作用范围和开启方式区分,口诀串联核心特点。

16、Mybatis的xml文件中,#{} 和 ${} 的区别是什么?

**回答: **
- #{}:会预编译SQL,将参数作为占位符处理,自动添加引号,能防止SQL注入,比较安全;
- ${}:直接拼接参数到SQL中,不预编译,有SQL的注入风险,不安全,但可用于动态表名、排序字段等场景。

**记忆法: **
“#安全带引号,$直接拼有风险”——#{}更安全带参数处理,${}直接拼接需谨慎。

17、MyBatis的动态标签有哪些?

**回答: **
常用动态标签:
1. <if>:条件判断,满足条件才拼接 SQL;
2. <where>:自动处理AND/OR前缀,配合<if>使用;
3. <set>:用于UPDATE,自动处理逗号,适配字段动态更新;
4. <foreach>:遍历集合,生成IN、批量插入等语句;
5. <choose><when><otherwise>:多条件分支,类似if-else if-else。

**记忆法: **
“if判条件,where管拼接,set改字段,foreach遍历忙,choose分支强”——按功能场景串联记,关联使用场景更易记。


18、说说类的反射机制?

回答:
反射是指程序在运行时可获取类的信息(属性、方法、构造器等),并动态操作类或对象的机制。核心是通过Class对象访问类元数据,实现动态创建实例、调用方法、修改属性等。

记忆法:
“运行时获类信息,动态操作靠反射;Class对象是核心,元数据访问全靠它”——抓住“运行时”“动态操作”和核心载体Class对象这两个关键点。

19、 获取Class对象的三种方式?

**回答: **
1. 类名.class:通过类的静态属性获取,如 User.class
2. 对象.getClass():通过实例对象调用方法获取,如 new User().getClass()
3. Class.forName(“全类名”):通过全类名字符串加载类获取,如 Class.forName("com.example.User")

**记忆法: **
“类名点class,对象调getClass,字符串用forName”——按获取方式的主体(类、对象、字符串)对应记忆,简洁好记。

20、Java创建对象的方式有哪些?

**回答: **
1. new关键字:最常用,如new User()
2. 反射:通过Class的newInstance()或Constructor的newInstance()
3. 克隆:实现Cloneable接口,调用clone()方法;
4. 反序列化:从IO流中恢复对象,如ObjectInputStream的readObject()

**记忆法: **
“new最直接,反射动态建,克隆复一份,反序列化读”——按创建逻辑(直接创建、动态创建、复制、读取)对应记忆。

21、对于IOC你是怎么理解的?

**回答: **
IOC(控制反转)是Spring核心思想,指将对象创建、依赖管理的控制权从代码转移到容器(Spring容器),由容器统一管理对象生命周期和依赖注入。
核心是“反转”:传统是代码主动new对象,IOC是容器被动注入,降低耦合。

**记忆法: **
“对象不由自己new,交给容器来伺候;依赖注入解耦合,控制反转是核心”——抓住“容器管理”“被动注入”“解耦”三个关键点。

22、对于AOP你是怎么理解的?

回答:
AOP(面向切面编程)**是一种编程思想**,通过分离通用功能(如日志、事务、权限)与业务逻辑,实现代码复用和解耦。
核心是将分散在各处的横切逻辑(切面)抽离,在不修改原有代码的情况下,通过动态代理等技术在指定切点(如方法执行前/后)织入增强逻辑。

**记忆法: **
“横切逻辑抽成面,不改代码织进去;代理帮忙做增强,解耦复用效率提”——抓住“抽离切面”“动态织入”“解耦复用”三个核心点。

23、AOP的底层是如何实现的?

**回答: **
AOP底层通过动态代理实现,Spring会根据目标类是否实现接口选择不同方式:
1. 若实现接口:使用 /JDK动态代理/ ,生成接口的代理类,通过反射调用目标方法并织入增强逻辑;
2. 若未实现接口:使用 /CGLIB动态代理/ ,通过继承目标类生成子类代理,重写方法实现增强。

**记忆法: **
“有接口用JDK,无接口靠CGLIB;代理类来做增强,反射/继承是根基”——按是否有接口区分实现方式,核心是动态生成代理类并织入逻辑。

24、使用AOP技术,需要用到哪些注解?

**回答: **
常用AOP注解(基于Spring):
1. @Aspect:标识类为切面类;
2. @Pointcut:定义切点(指定增强位置,如execution(* com..*Service.*(..)));
3. @Before:前置增强(目标方法执行前);
4. @After:后置增强(目标方法执行后,无论是否异常);
5. @AfterReturning:返回后增强(目标方法正常返回后);
6. @AfterThrowing:异常增强(目标方法抛出异常后);
7. @Around:环绕增强(包裹目标方法,可控制执行时机)。

**记忆法: **
“@Aspect标切面,@Pointcut定切点;前Before后After,返回Returning异常Throwing,Around环绕全包含”——按切面定义、切点声明、增强类型分类记忆,关联执行时机更易记。

25、AOP都有哪些通知方式?

**回答: **
AOP有5种通知方式(增强类型):
1. 前置通知(Before):目标方法执行前执行;
2. 后置通知(After):目标方法执行后执行,无论是否异常;
3. 返回后通知(AfterReturning):目标方法正常返回后执行;
4. 异常通知(AfterThrowing):目标方法抛出异常后执行;
5. 环绕通知(Around):包裹目标方法,可在执行前后自定义逻辑,甚至控制是否执行目标方法。

**记忆法: **
“前Before、后After,正常返回Returning,抛异常Throwing,环绕Around全包揽”——按执行时机和范围区分,环绕通知覆盖最全面。

26、AOP通知的先后顺序是什么?

**回答: **
同一切面内通知执行顺序:
1. 环绕通知(Around)的前半部分 →
2. 前置通知(Before) →
3. 目标方法执行 →
4. 环绕通知(Around)的后半部分 →
5. 返回后通知(AfterReturning)/异常通知(AfterThrowing) →
6. 后置通知(After)。

不同切面间:默认按类名排序(字母序),可通过@Order指定优先级(值越小越先执行)。

**记忆法: **
“环绕前→前置→目标→环绕后→返回/异常→后置”,联想“环绕包裹目标,前后置和返回/异常按执行阶段递进,后置收尾”。

27、Spring对象常用的注入方式(DI)有哪些?

**回答: **
Spring常用的依赖注入(DI)方式有3种:
1. 构造器注入:通过构造方法参数注入依赖,如@Autowired标注构造器;
2. Setter方法注入:通过Setter方法注入,如@Autowired标注setter方法;
3. 字段注入:直接在字段上标注@Autowired,无需setter或构造器。

**记忆法: **
“构造器传参,setter方法设,字段直接标”——按注入位置(构造器、setter方法、字段)对应记忆,关联各自的实现方式。

面试回答建议
1. 首先明确指出:Spring 常用的注入方式主要有三种:**构造器注入**、**Setter 注入**和**字段注入**。
2. 重点强调:**构造器注入是官方推荐的方式**,并解释其优点(不可变、完全初始化、易于测试、避免循环依赖)。
3. 提到其他方式:也可以提一下 @Resource 和基于 Java/XM L配置的方式,展示你的知识广度。
4. 表达个人见解:可以说在现代 Spring Boot 开发中,你通常会使用构造器注入来编写更健壮、更易于测试的代码。

28、@Autowired 与@Resource?

**回答: **
- 来源:@Autowired 是Spring注解;@Resource 是JDK自带注解(javax.annotation包)。
- 匹配方式:@Autowired 默认按类型(byType)匹配,需配合@Qualifier指定名称;@Resource 默认按名称(byName)匹配,名称不符再按类型。
- 适用场景:@Autowired 可用于构造器、方法、字段;@Resource 不支持构造器注入。

**记忆法: **
“Autowired是Spring的,按类型配;Resource是JDK的,先名后类型”——从来源和匹配逻辑区分,核心差异在匹配方式和所属框架。

29、Spring Bean的作用域有哪些?

**回答: **
Spring Bean的常用作用域有5种:
1. singleton(单例):默认,容器中只有一个实例,全局共享;
2. prototype(原型):每次请求创建新实例,每次获取都是新对象;
3. request:web环境中,每个HTTP请求对应一个实例;
4. session:web环境中,每个会话对应一个实例;
5. application:web环境中,整个应用生命周期一个实例。

**记忆法: **
“单例(singleton)默认全局用,原型(prototype)每次新;request随请求,session随会话,application全应用”——按适用范围从小到大(实例→请求→会话→应用)记忆,区分单例与原型的核心差异。

30、bean生命周期有哪些?

**回答: **
Spring Bean生命周期可概括为8步:
1. 实例化(通过构造器创建对象);
2. 属性注入(依赖注入,如@Autowired赋值);
3. 调用BeanNameAware的setBeanName()(获取Bean名称);
4. 调用BeanFactoryAware的setBeanFactory()(获取BeanFactory);
5. 调用ApplicationContextAware的setApplicationContext()(获取应用上下文,若实现);
6. 调用BeanPostProcessor的postProcessBeforeInitialization()(初始化前增强);
7. 调用初始化方法(如@PostConstruct、afterPropertiesSet());
8. 调用BeanPostProcessor的postProcessAfterInitialization()(初始化后增强);
(容器关闭时)
9. 调用销毁方法(如@PreDestroy、destroy())。

**记忆法: **
“实例化→注值→Aware回调→前后处理→初始化→后处理→销毁”,按对象创建到销毁的时间线串联记,关键节点是注入、回调、初始化、销毁。

31、Spring Bean生命周期有哪些?

Spring Bean生命周期主要包括:
1. 实例化(Instantiation)
2. 属性赋值(Populate)
3. 初始化(Initialization):包括各种Aware接口回调、BeanPostProcessor前置处理、自定义初始化方法、BeanPostProcessor后置处理
4. 使用(In Use)
5. 销毁(Destruction):包括自定义销毁方法、各种销毁回调

记忆方法:可记为“实赋初用毁”(实例化、赋值、初始化、使用、销毁),再细化初始化阶段的关键步骤,结合“先Aware后处理器,自定义方法中间插”辅助记忆。

32、Spring如何解决Bean循环依赖问题?

Spring通过三级缓存解决Bean循环依赖:
1. 一级缓存(singletonObjects):存储完全初始化的Bean
2. 二级缓存(earlySingletonObjects):存储提前暴露的原始Bean(未完成属性注入)
3. 三级缓存(singletonFactories):存储Bean工厂,用于生成原始Bean的代理对象

流程:当A依赖B、B依赖A时,A实例化后提前暴露到三级缓存,B实例化时从三级缓存获取A的原始对象并完成注入,B初始化后存入一级缓存,A再从一级缓存获取B完成注入。

记忆方法:“三缓存,先暴露,原始对象解循环”,三级缓存依次为“成品、半成品、生产厂”。

33、SpringMVC常用的注解有哪些?

SpringMVC常用注解:
1. @Controller:标识控制器类
2. @RequestMapping:映射请求路径和方法
3. @GetMapping/@PostMapping等:简化特定HTTP方法的@RequestMapping
4. @RequestParam:绑定请求参数
5. @PathVariable:绑定URL路径变量
6. @RequestBody:接收请求体数据
7. @ResponseBody:将返回值直接作为响应体
8. @ModelAttribute:绑定请求参数到模型
9. @SessionAttributes:将模型数据存入会话

记忆方法:“控(Controller)制请求(RequestMapping)方式(Get/Post),参数(RequestParam)路径(PathVariable)体(RequestBody),响应(ResponseBody)模型(ModelAttribute)会话(SessionAttributes)存”。按“控制器标识-请求处理-参数绑定-响应处理-数据存储”逻辑串联。

34、SpringMVC的主要组件有哪些?

SpringMVC主要组件:
1. DispatcherServlet:前端控制器,统一处理请求分发
2. HandlerMapping:映射请求到对应的处理器
3. HandlerAdapter:适配处理器执行具体方法
4. Handler:处理器(Controller),业务逻辑处理
5. ViewResolver:解析视图名到具体视图
6. View:渲染数据并返回响应
7. Interceptor:拦截器,处理请求前后逻辑

记忆方法:“前控(DispatcherServlet)找映射(HandlerMapping),适配(HandlerAdapter)处理器(Handler),视图解析(ViewResolver)加渲染(View),拦截(Interceptor)前后做处理”。按请求流转顺序串联:请求进入→查找映射→执行处理→视图渲染,拦截器贯穿全程。

35、SpringMVC的运行流程是什么?

SpringMVC运行流程:
1. 客户端发送请求至DispatcherServlet
2. DispatcherServlet调用HandlerMapping获取Handler
3. 再调用HandlerAdapter执行Handler(Controller)
4. Handler返回ModelAndView给DispatcherServlet
5. DispatcherServlet请求ViewResolver解析视图
6. ViewResolver返回具体View
7. DispatcherServlet渲染View并响应客户端

记忆方法:“请求到 /前端控制器/ (DispatcherServlet),映射(HandlerMapping)找处理器, /适配器/ (HandlerAdapter)来执行,返回 /数据视图/ (ModelAndView), /视图解析器/ (ViewResolver)渲染(View)后响应”。按“请求进入→处理匹配→执行逻辑→视图处理→响应返回”的顺序记忆。

36、Spring Bean自动装配有哪些方式?

Spring Bean自动装配方式:
1. byName:按属性名匹配Bean(属性名与Bean的id一致)
2. byType:按属性类型匹配Bean(需保证同类型Bean唯一)
3. constructor:构造器注入,按构造器参数类型匹配
4. autodetect:先尝试constructor,再尝试byType(已废弃)

记忆方法:“名(byName)型(byType)构造(constructor)”,按“名称匹配-类型匹配-构造器注入”逻辑记忆,重点区分byName(依赖id)和byType(依赖类型)的核心差异。

37、Spring 框架中都用到了哪些设计模式?

Spring框架中常用的设计模式:
1. 工厂模式:BeanFactory、ApplicationContext(创建Bean实例)
2. 单例模式:默认的Bean实例(scope=“singleton”)
3. 代理模式:AOP中的JDK动态代理和CGLIB代理
4. 模板方法模式:JdbcTemplate、RestTemplate等(固定流程+钩子方法)
5. 观察者模式:事件监听机制(ApplicationEvent、ApplicationListener)
6. 适配器模式:HandlerAdapter(适配不同处理器)
7. 装饰器模式:BeanWrapper(包装Bean增强功能)

记忆方法:“工(工厂)单(单例)代(代理)模(模板),观(观察者)适(适配器)装(装饰器)”,结合各模式在Spring中的典型应用场景辅助记忆。

38、Spring框架中的单例bean是线程安全的吗?

不是。

单例bean在Spring中是全局共享的,但线程安全与否取决于bean自身实现:若bean是无状态的(无成员变量或成员变量不可变),则线程安全;若有可修改的成员变量,则线程不安全。

记忆方法:“单例共享,安全看状态”——单例是共享实例,线程安全与否由bean是否有可变状态决定。

39、Spring 事务实现方式有哪些?

Spring 事务实现方式主要有两种:

  1. 编程式事务:通过代码手动控制事务(如使用 TransactionTemplatePlatformTransactionManager
  2. 声明式事务:基于 AOP 实现,无需手动编码,通过 @Transactional 注解或 XML 配置声明事务

记忆方法:“编程手动控,声明注解通”——编程式需手动控制事务,声明式通过注解等方式更便捷通用。

40、Spring的事务传播行为有哪些?

Spring定义了7种事务传播行为:

  1. REQUIRED(默认):如果当前有事务,加入;无则新建
  2. SUPPORTS:有事务则加入,无则以非事务执行
  3. MANDATORY:必须在事务中执行,否则抛异常
  4. REQUIRES_NEW:无论当前是否有事务,都新建事务
  5. NOT_SUPPORTED:以非事务执行,若当前有事务则挂起
  6. NEVER:必须在非事务中执行,有事务则抛异常
  7. NESTED:若当前有事务,则嵌套在其内部执行(保存点机制)

记忆方法:“需(REQUIRED)支(SUPPORTS)强(MANDATORY),新(REQUIRES_NEW)不(NOT_SUPPORTED)绝(NEVER)套(NESTED)”——取每种行为首字/核心含义串联,辅助记忆7种类型。

41、哪些种情况会导致Spring事务失效?

Spring事务失效的常见情况:

  1. 非public方法:@Transactional 仅对public方法生效
  2. 自调用问题:同一类内方法互相调,事务注解可能失效
  3. 异常被捕获:异常被try-catch捕获未抛出,事务不回滚
  4. 错误的异常类型:默认默认只回滚RuntimeExceptionError
  5. 未配置事务管理器:缺少事务管理器配置
  6. 传播行为设置不当:如NOT_SUPPORTED等会使事务失效

记忆方法:“非公自捕错类型,未配传播有问题”——浓缩关键失效场景,便于记忆。

42、Spring拦截器和过滤器的区别?

Spring拦截器(Interceptor)和过滤器(Filter)的区别主要有:

  1. 所属层面:过滤器属于Servlet规范,拦截器是Spring框架特有
  2. 执行时机:过滤器在请求进入容器后、Servlet之前执行;拦截器在DispatcherServlet之后、控制器方法前后执行
  3. 功能范围:过滤器可处理所有请求(包括静态资源),拦截器只处理Spring管理的请求
  4. 依赖环境:过滤器依赖Servlet容器,拦截器不依赖
  5. 调用方式:过滤器基于函数回调,拦截器基于反射(AOP思想)

记忆方法:“过Servlet拦Spring,时机前后范围清”——过滤器属于Servlet,拦截器属于Spring;执行时机和处理范围有明确区分。


43、说说你对CAP理论的理解?

CAP理论是分布式系统设计中的基础理论,它指出分布式系统无法同时满足以下三个特性:
- 一致性(Consistency):所有节点数据实时保持一致
- 可用性(Availability):任何请求都能收到非错误响应
- 分区容错性(Partition tolerance):网络分区时系统仍能运行

实际设计中需根据场景三选二:
- 记忆法:“一分区,两选一”。只要存在网络分区(P必选),就只能在一致性和可用性之间做选择。比如银行系统优先保证一致性,社交软件优先保证可用性。

44、SpringCloud有哪些常用组件?

SpringCloud常用组件:
- Eureka/Consul/Nacos:服务注册与发现
- Ribbon~~/LoadBalance~~:负载均衡
- Feign/OpenFeign:服务调用
- Hystrix~~/Resilience4j~~:熔断降级
- Gateway~~/Zuul~~:API网关
- Config/Nacos:配置中心
- Bus:消息总线

记忆法:“注调均断,网关配总”
注(注册发现)、调(服务调用)、均(负载均衡)、断(熔断降级)、网关(API网关)、配(配置中心)、总(消息总线)。

45、SpringBoot的常用注解有哪些?

SpringBoot常用注解:
- @SpringBootApplication:主启动类注解(组合了@Configuration、@EnableAutoConfiguration、@ComponentScan)
- @RestController:标识REST接口控制器(组合了@Controller和@ResponseBody)
- @RequestMapping/@GetMapping/@PostMapping等:映射请求路径和方法
- @Autowired:自动注入依赖
- @Service/@Repository/@Component:标识服务层、数据访问层、通用组件
- @Value:注入配置属性
- @Configuration:标识配置类
- @Bean:定义Bean对象

记忆法:“主启控,请求注,分层配”
主启(@SpringBootApplication)、控(@RestController等控制器注解)、请求(@RequestMapping等)、注(@Autowired依赖注入)、分层(@Service等分层注解)、配(@Configuration等配置相关)。

46、SpringBoot的自动装配原理是什么?

SpringBoot自动装配原理核心是:通过注解(注解)+ SPI(服务发现机制)实现自动配置。

过程:
1. @SpringBootApplication包含@EnableAutoConfiguration,触发自动配置
2. 扫描classpath下META-INF/spring.factories文件,加载自动配置类(XXXAutoConfiguration)
3. 自动配置类通过@Conditional条件注解(如@ConditionalOnClass)判断是否生效
4. 生效的配置类通过@Bean向容器注入组件

记忆法:“注解启,文件找,条件判,Bean注入”
(注解启动 -> 扫描配置文件 -> 条件判断生效 -> 注入Bean)

47、Nacos 如何同时支持 AP 和 CP 模式?如何切换?

考察点: 对 Nacos 底层一致性协议的深入理解。
回答要点
* 支持原理
* 默认模式(AP): 用于**服务发现**场景,采用自研的 Distro 协议,保证高可用和分区容错性,性能更高。
* CP 模式: 用于**配置管理**或需要强一致性的场景(如领导者选举),采用 Raft 协议,保证数据一致性。
* 如何切换: 可以通过 Nacos 的 API 或控制台,修改特定服务的**元数据**:
curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP'


48、RabbitMQ中,Topic模式时,Routingkey中的#号和*号有什么区别?

RabbitMQ的Topic模式中,#和*都是用于匹配RoutingKey的通配符:
- #:匹配0个或多个单词(单词间用.分隔),例如“a.#”可匹配“a.b”、“a.b.c”、“a”
- *:仅匹配1个单词,例如“a.*”只能匹配“a.b”,不能匹配“a”或“a.b.c”

记忆法:“#多*单”
(#能匹配多个单词,*只能匹配单个单词)

49、RabbitMQ中如何保障消息不丢失?

RabbitMQ保障消息不丢失需从**生产者、 broker(服务器)、消费者**三个环节入手,核心是通过持久化和确认机制避免各环节丢失:

  1. 生产者环节:确保消息成功发送到Broker
  • 开启消息确认机制(publisher confirm)
    生产者通过channel.confirmSelect()开启确认模式,Broker收到消息后会返回确认(ack)或失败(nack)通知,生产者根据结果重试未成功发送的消息。
  • 避免消息在内存中未发送
    确保生产者发送逻辑无异常,消息未因网络问题或客户端崩溃丢失在发送途中。
  1. Broker环节:确保消息在服务器不丢失
  • 队列持久化
    声明队列时指定durable=true,确保队列元数据(名称、属性)在Broker重启后不丢失。
  • 消息持久化
    发送消息时设置deliveryMode=2(持久化标志),确保消息内容被写入磁盘(而非仅存内存),Broker重启后可恢复。
  • 避免Broker单点故障
    开启集群模式,通过镜像队列(Mirror Queue)将消息同步到多个节点,防止单节点宕机丢失数据。
  1. 消费者环节:确保消息被正确处理
  • 关闭自动确认,开启手动确认(ack)
    消费者处理完消息后,手动发送确认信号(channel.basicAck()),Broker收到后才删除消息。若消费者崩溃未确认,Broker会重新投递消息。
  • 处理逻辑幂等性
    即使消息重复投递(如网络延迟导致的重试),也能保证业务结果正确,避免重复处理的副作用。

记忆法:生确存,服久群,消手幂
(生产者确认发送,Broker持久化+集群;消费者手动确认+幂等处理)

50、RabbitMQ/Kafka如何确保消息不会被重复消费?

RabbitMQ确保消息不被重复消费的核心是实现**消费幂等性**,即同一条消息多次消费的结果与一次消费一致。常用方案:

  1. 唯一标识+业务校验
  • 为消息生成全局唯一ID(如UUID),消费时先检查该ID是否已处理(可存在Redis/数据库)。
  • 已处理则直接返回,未处理则执行业务并标记为已处理。
  1. 数据库唯一约束
  • 利用数据库唯一索引或主键,重复插入时会触发约束报错,自然过滤重复消息。
  1. 状态机控制
  • 业务数据设计状态流转(如待处理→处理中→已完成),重复消息到达时,通过状态判断跳过处理。

记忆法:“唯一标,库约束,状态控”
(唯一标识校验、数据库约束、状态机控制)

51、RabbitMQ如何确保消息顺序消费?

考察点: 对顺序性这一难题的解决方案。
回答要点
* 问题根源: 多个消费者并发消费一个队列,自然会导致顺序错乱。
* 解决方案
1. 单一队列,单一消费者: 保证绝对顺序,但严重牺牲性能,不实用。
2. 分组: 将需要保证顺序的消息**通过路由键确保它们始终被发送到同一个队列**。例如,将同一订单 ID 的所有消息路由到同一个队列,然后为这个队列分配**唯一的一个消费者**来处理(可以在消费者内部用内存队列做二次排序)。这是最常用的方案。

52、Kafka中如何保障消息不丢失?

Kafka保障消息不丢失需从**生产者、Broker、消费者**三个环节防控:

  1. 生产者
  • 配置acks=all:确保消息被所有ISR副本确认后才返回成功
  • 开启重试(retries>0):失败时自动重试发送
  • 使用同步发送或回调确认:确保发送结果可感知
  1. Broker
  • 主题分区副本数replication.factor≥3:避免单点故障
  • 最小同步副本数min.insync.replicas≥2:确保消息写入足够副本
  • 关闭自动删除(合理设置retention.ms):防止消息提前过期
  1. 消费者
  • 关闭自动提交(enable.auto.commit=false):处理完成后手动提交offset
  • 确保消费逻辑完成后再提交:避免未处理完就记录偏移量

记忆法:“生确重,服多副,消手提”
(生产者确认+重试,服务器多副本,消费者手动提交offset)

53、Kafka如何确保消息顺序消费?

考察点: 对顺序性这一常见需求的解决方案。
回答要点
* 全局顺序: 性能代价极大,**几乎不用**。只能通过 1个Topic + 1个Partition + 1个Consumer 来实现。
* 分区顺序(**常用方案**): Kafka 只能保证在单个分区内消息是有序的。因此,要实现业务层面的顺序性,必须确保需要保序的一类消息(例如同一个订单ID的所有操作)都被发送到**同一个分区**。方法是在发送消息时**指定同一个 Key**。

54、RabbitMQ消息积压怎么办?

考察点: 线上问题处理能力。
回答要点
1. 临时紧急扩容
* 扩容消费者: 增加 Consumer 实例数量,提升消费能力。
* 扩容队列: 如果队列容量已满,可以临时增加队列的 max-length(如果业务可接受丢弃头部消息)或扩容集群。
2. 排查原因
* 检查消费者: 是否是消费者出了 bug 导致消费过慢或卡死?查看日志和监控。
* 检查消息: 是否是消息量突然激增(如促销活动)?是否是生产者出了问题在疯狂发消息?
3. 后续优化
* 修复消费者 Bug。
* 优化消费逻辑,提升单条消息的处理速度(如批量处理)。
* 做好监控和预警,在积压初期就发现问题。

55、Producer 的发送确认机制(acks)有哪几种?

考察点: 对消息可靠性级别的控制。
回答要点
* acks=0: 生产者发送后**不等任何确认**就认为成功。**吞吐最高,但可能丢失消息**。
* acks=1(默认): 生产者等待 Leader 副本成功写入本地日志就认为成功。**折中方案**。如果 Leader 刚写入就宕机且数据未同步到 Follower,则可能丢失消息。
* acks=all(或 acks=-1): 生产者等待 Leader 和所有 ISR(In-Sync Replicas) 副本都成功写入才认为成功。**最可靠,但延迟最高,吞吐最低**。

56、消息如何被路由到指定的 Partition?

考察点: 对生产者分区策略的了解。
回答要点
1. 指定 Partition: 直接发送到指定分区。
2. 指定 Key(**最常用**): 提供 key,对 key 进行哈希(默认),根据哈希值决定分配到哪个分区。**保证同一个 Key 的消息总是进入同一个分区,从而实现顺序性**。
3. 轮询(Round-Robin): 如果未提供 key,则默认以轮询方式均匀分布到所有分区。

57、说说ElasticSearch的倒排索引(为什么 ES 查询快)?

ElasticSearch的倒排索引是其快速检索的核心机制,本质是“从词到文档的映射”:

  1. 原理:
  • 对文档内容分词,得到词条(Term)
  • 建立词条与包含该词条的文档列表的映射关系(记录文档ID、词频等信息)
  • 查询时先匹配词条,再通过映射快速定位相关文档
  1. 举例:
    文档1:“Java编程” → 分词为[“Java”, “编程”]
    文档2:“Python编程” → 分词为[“Python”, “编程”]
    倒排索引中“编程”映射到[文档1, 文档2],“Java”映射到[文档1]

记忆法:“分词建映射,查词找文档”
(先分词建立词条到文档的映射,查询时通过词条快速找到对应文档)

image.png

58、textkeyword 数据类型的区别是什么?

考察点: 对 ES 字符串处理方式的理解。这是**必问**题。
回答要点
* keyword 类型
* 不分词: 将整个字符串作为一个完整的 term 存入倒排索引。
* 用于精确匹配term 查询)、**排序**(sorting)、**聚合**(aggregations)、**过滤**(filtering)。
* 例如: 邮箱地址、用户ID、标签、状态码。
* text 类型
* 分词: 字符串会被**分析器(Analyzer)** 拆分成一个个词条(term)后再存入倒排索引。
* 用于全文搜索match 查询),支持模糊查询、相关性评分。
* 通常包含多值字段: 一个 text 字段通常会有一个内置的 keyword 子字段(fieldname.keyword),用于上述精确匹配等操作。
* 例如: 邮件正文、商品描述、日志内容。

59、分片(Shard)和副本(Replica)的作用是什么?如何设置分片数?

考察点: 对数据分布、高可用和性能之间权衡的理解。
回答要点
* 分片的作用
1. 水平拆分数据: 允许索引的大小超过单台机器的硬件限制。
2. 分布式操作: 允许将操作(读写、搜索)分发到所有分片上,并行执行,提升吞吐和性能。
* 副本的作用
1. 高可用: 在主分片宕机时,其副本可以提升为主分片,防止数据丢失和服务中断。
2. 提升读取吞吐量: 搜索请求可以在所有副本之间进行负载均衡。
* 分片数设置
* 这是一个**预先规划**的重要决策,因为**主分片数在索引创建后无法修改**(除非重建索引 Reindex)。
* 原则
1. 考虑数据总量: 每个分片大小建议在 20GB - 40GB 之间,最大不建议超过 50GB。
2. 考虑集群节点数: 确保分片数能被节点数整除,以便分片在节点间均匀分布。
3. 避免过多分片: 每个分片都有开销(内存、CPU、文件句柄)。过多的分片会导致集群性能下降,恢复变慢。

60、ElasticSearch的深度分页该怎么做?

ElasticSearch深度分页(如查询第10000页以后的数据)因默认from+size方式存在性能问题(需加载前N条数据排序),推荐方案:

  1. scroll滚动查询
    初次查询生成scroll_id,后续通过scroll_id分批获取数据(类似游标),适合批量导出场景。
    缺点:非实时,占用服务器资源较久。

  2. search_after
    以上一页最后一条数据的排序字段值作为条件,查询下一页,支持实时数据。
    需指定唯一排序字段(如_id),适合实时分页场景。

  3. 避免过深分页
    业务上限制最大分页深度(如最多查100页),或用游标、滚动替代传统分页。

记忆法:“滚批导,后实时,限深度”
(scroll适合批量导出,search_after适合实时场景,业务限制深度)