▲ 返回顶部 | 客服邮箱 | 官方微博 | RSS订阅
Java框架常见面试题
www.kaoacca.cn|程序员技术进阶指南

Java框架常见面试题详解|Spring/Spring Boot/MyBatis/微服务高频考点全覆盖

一、为什么Java框架面试题是技术面试的“必考项”?

在Java后端开发岗位招聘中,框架知识占比高达60%以上。企业不仅考察候选人对API的调用熟练度,更关注其对框架设计思想、底层原理、组件协同机制的深度理解。例如:

“能说出@Autowired的默认注入方式是byType,不如能讲清Spring容器启动时BeanDefinition的注册与合并流程”——某大厂技术总监

本页面系统梳理Java框架常见面试题,覆盖:

  • Spring家族:Spring Core、AOP、事务、MVC、Boot、Cloud
  • 持久层:MyBatis核心机制、动态SQL、插件体系
  • 中间件与微服务:Dubbo、Nacos、Sentinel、Seata
  • 工程实践:性能调优、故障排查、高并发设计

所有内容均基于2024年最新技术生态整理,含真实面试案例、代码片段、时序图说明,助您构建完整知识体系。

Spring核心框架高频面试题

【Spring容器启动流程】请简述Spring容器初始化的完整过程

Spring容器启动本质是ApplicationContext的创建与刷新过程,核心步骤如下:

1. 创建BeanFactory
阶段一:准备阶段
通过XmlBeanDefinitionReader加载XML或AnnotatedBeanDefinitionReader扫描注解,将配置元数据解析为BeanDefinition,注册到DefaultListableBeanFactory。
2. BeanFactory后置处理
阶段二:后置增强
调用BeanFactoryPostProcessor(如PropertySourcesPlaceholderConfigurer),允许修改BeanDefinition属性(如占位符替换)。
3. BeanPostProcessor注册
阶段三:拦截器注册
将BeanPostProcessor实例按优先级注册到容器(注意:此时不执行,仅注册)。
4. 单例Bean预实例化
阶段四:实例化
调用finishBeanFactoryInitialization(),对非懒加载单例Bean执行:
① 实例化(调用构造器)→ ② 属性填充(populate)→ ③ 初始化(aware回调、init-method)→ ④ 放入单例池

高频追问:
1. @Lazy注解对单例Bean的作用?→ 推迟初始化,首次getBean时创建
2. @DependsOn如何影响初始化顺序?→ 显式指定依赖关系,确保被依赖Bean先初始化

示例:手动触发懒加载Bean

public class LazyBeanExample { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-lazy.xml"); // 容器启动时,lazyBean尚未创建 LazyBean bean1 = (LazyBean) ctx.getBean("lazyBean"); // 此时才初始化 } }

【@Autowired vs @Resource】两者的注入机制与差异

特性 @Autowired(Spring) @Resource(JSR-250)
来源 Spring框架 Java标准规范(JSR-250)
默认注入方式 byType(类型匹配) byName(字段名/属性名)
按名称注入 需配合@Qualifier或字段名 直接通过name属性指定
required属性 默认true,未匹配则抛异常 默认true,未匹配则为null
支持位置 构造器、字段、setter方法 字段、setter方法

典型问题:当存在多个同类型Bean时如何处理?

  • 方案1:使用@Qualifier("beanName")限定
  • 方案2:在字段名与Bean名一致时,@Autowired自动按名称匹配
  • 方案3:使用@Primary标注首选Bean

示例:按名称注入的@Resource

public class UserService { @Resource(name = "userDaoImpl2") // 指定bean名称 private UserDao userDao; }

【AOP实现原理】JDK动态代理 vs CGLIB代理

Spring AOP底层通过代理模式实现,根据目标对象是否实现接口选择代理方式:

JDK动态代理
适用条件:目标类实现接口
① 创建Proxy实例
② 实现InvocationHandler接口
③ 通过反射调用目标方法
④ 代理对象与目标对象实现相同接口
CGLIB代理
适用条件:目标类无接口或强制使用
① 生成目标类子类(继承)
② 重写非final方法
③ 在方法前后织入增强逻辑
④ 注意:无法代理final类/方法

性能对比:

  • 创建阶段:CGLIB > JDK(CGLIB需生成字节码)
  • 运行阶段:JDK ≈ CGLIB(现代JDK已优化)
  • 推荐策略:接口优先→JDK;无接口→CGLIB

源码线索:

// AbstractAutowireCapableBeanFactory#initializeBean protected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) { // ... bean = applyBeanPostProcessorsAfterInitialization(bean, beanName); // ... } // AnnotationAwareAspectJAutoProxyCreator // 实现BeanPostProcessor,在Bean初始化后创建代理

Spring Boot高频面试题

【@SpringBootApplication】自动配置的底层实现

@SpringBootApplication是一个组合注解,核心包含:

注解 作用
@SpringBootConfiguration 等价于@Configuration,标识配置类
@EnableAutoConfiguration 启用自动配置,核心是@Import(AutoConfigurationImportSelector.class)
@ComponentScan 扫描当前包及子包的组件

自动配置流程:

  1. 启动时扫描META-INF/spring/org.springframework.boot.autoconfigure.AutoConfigurationImportFilter
  2. 读取META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件
  3. 加载配置类,按@Conditional条件匹配(如@ConditionalOnClass)
  4. 将符合条件的配置类注册为BeanDefinition

示例:spring.factories文件内容

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration

【Starter】自定义Starter的实现步骤

创建自定义Starter需满足:自动配置 + 条件判断 + 外部化配置

步骤1:创建配置类
核心注解
@Configuration + @ConditionalOnClass + @EnableConfigurationProperties
步骤2:定义Properties类
配置绑定
@ConfigurationProperties(prefix = "my.custom")
步骤3:创建spring.factories
注册自动配置
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

典型问题:如何避免Starter被重复加载?

  • 使用@ConditionalOnMissingBean确保仅当容器中无该Bean时才创建
  • 通过@AutoConfigureBefore/After控制加载顺序

【配置优先级】application.yml vs application.properties vs 命令行参数

配置源 优先级(高→低) 示例
命令行参数 1 --server.port=8081
JNDI属性 2 java:comp/env/spring.application.name
JVM系统属性 3 -Dserver.port=8081
操作系统环境变量 4 export SERVER_PORT=8081
jar包外部的application.yml 5 config/application.yml
jar包内部的application.yml 6 BOOT-INF/classes/application.yml

实践建议:

  • 开发环境:使用application-dev.yml + 命令行参数覆盖
  • 生产环境:通过环境变量注入敏感信息(如密码)

MyBatis高频面试题

【<choose> vs <if>】动态SQL的执行逻辑

MyBatis通过SqlSource解析动态标签,生成BoundSql对象:

执行过程:

  1. XMLScriptBuilder解析XML,识别动态标签
  2. 生成DynamicSqlSource,内部持有SqlNode列表
  3. 运行时调用getBoundSql,遍历SqlNode拼接SQL
  4. if:条件满足则保留节点内容
  5. choose:仅执行第一个满足条件的when,否则执行otherwise

常见陷阱:

  • test中使用OGNL表达式,注意空值判断(如name != null and name != '')
  • 避免where 1=1硬编码,改用<where>标签自动处理AND/OR前缀

【插件Interceptor】如何实现分页插件?

MyBatis插件基于Interceptor接口,通过动态代理拦截四大对象:

拦截对象 方法 典型用途
Executor query(), update() SQL执行拦截、性能统计
StatementHandler prepare(), parameterize() SQL改写、分页重写
ParameterHandler setParameter() 参数处理增强
ResultSetHandler handleResultSets() 结果集转换

分页插件实现逻辑:

  1. 拦截StatementHandler.prepare()方法
  2. 从BoundSql获取原始SQL
  3. 根据数据库类型生成COUNT查询和分页SQL
  4. 替换BoundSql并继续执行

示例:分页插件核心代码

@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}) }) public class PageInterceptor implements Interceptor { public Object intercept(Invocation invocation) throws Throwable { StatementHandler handler = (StatementHandler) invocation.getTarget(); BoundSql boundSql = handler.getBoundSql(); String sql = boundSql.getSql(); // 1. 解析原始SQL // 2. 生成COUNT SQL // 3. 生成分页SQL(如MySQL: SELECT * FROM user LIMIT ?,?) // 4. 替换BoundSql MetaObject metaObject = SystemMetaObject.forObject(handler); metaObject.setValue("boundSql.sql", newPageSql); return invocation.proceed(); } }

【一级/二级缓存】缓存失效场景与配置

一级缓存(SqlSession级别):

  • 默认开启,基于PerpetualCache实现
  • 同一SqlSession中,相同参数的查询仅执行一次SQL
  • 失效条件:clearCache()、增删改操作、手动关闭

二级缓存(Mapper级别):

  • 需在Mapper XML中配置<cache>标签
  • 实体类实现Serializable接口
  • 仅对select语句生效,flushCache="true"可手动清空
缓存类型 存储介质 适用场景
L1缓存 内存(HashMap) 单次会话内查询优化
L2缓存 内存/Redis(需集成) 跨会话共享数据
第三方缓存 Redis/Ehcache 分布式集群环境

实践建议:

  • 读多写少的字典表适合开启L2缓存
  • 高并发场景建议使用Redis缓存替代MyBatis原生L2

微服务高频面试题

【Eureka vs Nacos】服务注册中心对比

特性 Eureka(Netflix) Nacos(阿里)
协议 HTTP/DISCOVERY HTTP/DNS
一致性 AP(高可用) 支持CP/AP切换
健康检查 心跳(RENEW) 主动探测+心跳
配置管理 需结合Spring Cloud Config 原生支持配置中心
多集群支持 Zone/Region Namespace/Group

服务发现原理:

  1. 服务启动时,向注册中心发送注册请求(POST /nacos/v1/ns/instance)
  2. 注册中心保存实例元数据(IP、端口、健康状态)
  3. 客户端定期拉取服务列表(默认每30秒)
  4. 客户端本地缓存服务列表,实现负载均衡

典型问题:如何解决服务实例状态不一致?

  • Eureka:自我保护模式(网络分区时保留所有实例)
  • Nacos:支持临时/持久实例区分(临时实例依赖心跳)

【配置动态刷新】Spring Cloud Config vs Nacos

两种实现方式:

方式 原理 优缺点
客户端轮询 @RefreshScope + /actuator/refresh 实时性差,需手动触发
事件驱动 Nacos监听配置变更 → 发布EnvironmentChangeEvent 实时性高,推荐生产使用

配置刷新流程:

1. 配置变更
Nacos控制台修改配置
触发监听器通知客户端
2. 客户端接收
发布ConfigReceivedEvent
更新本地Environment
3. 作用域生效
刷新带@RefreshScope的Bean
销毁旧Bean,下次获取时重建

【Sentinel熔断规则】热点参数限流 vs 系统自适应

核心规则类型:

规则 适用场景 参数说明
流控规则 接口QPS控制 count: 限流阈值;grade: QPS/线程数
降级规则 慢调用/异常比例 slowRatioThreshold: 慢调用比例阈值
热点参数限流 热点数据保护 paramIdx: 热点参数索引;burstCount: 突发流量
系统保护 全局负载均衡 loadThreshold: 系统负载阈值

热点参数限流示例:

// 按用户ID限流(paramIdx=0) Rule rule = new ParamFlowRule("/getUser") .setCount(10) // 每秒10次 .setParamIdx(0) // 参数索引 .setDurationInSec(1); ParamFlowRuleManager.loadRules(Collections.singletonList(rule));

实战建议:

  • 核心接口:设置热点参数限流(防刷单)
  • 低优先级服务:开启系统自适应保护

性能调优与故障排查

【CPU 100%排查】Java服务高CPU占用的定位步骤

1. 定位进程
Linux命令
top -c → 找到高CPU进程PID
2. 定位线程
线程级分析
top -Hp PID → 获取高CPU线程TID
3. 转换线程ID
进制转换
printf "%x\n" TID → 转为16进制(如0x1a2b)
4. 生成线程栈
JVM诊断
jstack PID | grep 0x1a2b -A 20

典型场景:

  • 死循环:线程栈持续在循环代码处
  • 频繁GC:线程栈显示GC线程活跃
  • 锁竞争:线程状态为BLOCKED

【内存泄漏定位】如何判断是否存在内存泄漏?

诊断步骤:

  1. 监控指标:通过jstat -gc PID 1000观察GC频率/时间
  2. 堆转储:使用jmap -dump:live,format=b,file=heap.hprof PID
  3. 分析工具:用Eclipse MAT或jvisualvm打开堆文件
  4. 定位泄漏点:查看Dominator Tree中占用内存最大的对象

常见泄漏场景:

场景 原因 解决方案
静态集合 静态Map持续增长 改用WeakReference或定期清理
未关闭资源 IO流/数据库连接未close 使用try-with-resources
监听器未注销 事件监听器注册后未移除 生命周期结束时注销

职业发展与技术路线

【技术成长路径】Java工程师的3年规划

第1年:基础巩固
核心能力
• 掌握Spring Boot基础开发
• 熟悉MySQL索引优化
• 了解Linux常用命令
• 能独立完成CRUD模块
第2年:深度拓展
关键突破
• 深入理解JVM原理
• 掌握Redis缓存策略
• 学习微服务架构设计
• 参与高并发项目优化
第3年:架构能力
技术引领
• 主导技术方案设计
• 建立质量保障体系
• 推动自动化运维
• 输出技术文档/培训

面试加分项:

  • 开源贡献:GitHub有高质量项目
  • 技术博客:持续输出深度文章
  • 工具开发:内部效率工具开发者

【如何准备技术面试】结构化准备方法

“面试不是背答案,而是展示解决问题的思维过程”——某互联网公司技术面试官

三步准备法:

  1. 知识梳理:用思维导图整理核心知识点(如Spring生命周期图)
  2. 场景模拟:自问自答高频问题,录音后复盘
  3. 项目包装:用STAR法则(Situation-Task-Action-Result)重构项目经历

常见错误:

  • 过度背诵答案,缺乏应变能力
  • 项目描述过于笼统(“负责后端开发”)
  • 回避技术难点(“当时没遇到问题”)

网友还关心:Java框架面试题相关问题

  • Java框架面试题中高频问到的源码题有哪些?

    Spring:BeanPostProcessor执行时机;MyBatis:Plugin代理链生成;JVM:GC日志解读。建议结合源码注释+时序图理解。

  • 如何准备微服务架构设计题?

    从“选型依据→架构图→容灾方案”三步展开。例如:选Nacos因支持CP/AP切换;熔断用Sentinel的实时监控。

  • 应届生如何回答“为什么选Java后端”?

    结合技术热情+项目经历。例如:“在XX项目中通过优化MyBatis插件提升查询性能30%,激发对框架底层的兴趣”。

  • Java框架面试题中容易被忽略的细节?

    如@Transactional的 propagation=REQUIRES_NEW 与嵌套事务的关系;@Cacheable的key生成策略。

斌知网
蜀ICP备2026017620号-5