Skip to main content

技术面试问答集

本文档以面试官与面试者对话的形式,整理了常见的技术面试问题及简洁答案。

使用说明
  • 答案简洁明了,突出核心要点
  • 采用对话形式,贴近真实面试场景
  • 提供详细文档链接,便于深入学习
  • 涵盖 Java 基础、框架、中间件、分布式等核心技术栈

目录导航​

一、Java 基础​

  1. Java 的三大特性
  2. 重载和重写的区别
  3. 基本数据类型
  4. 自动装箱和拆箱

二、Java 集合框架​

  1. 集合框架结构
  2. ArrayList 和 LinkedList 的区别
  3. HashMap 的底层原理
  4. ConcurrentHashMap 的实现原理

三、Java 并发编程​

  1. 创建线程的方式
  2. 线程的生命周期
  3. synchronized 和 ReentrantLock 的区别
  4. volatile 关键字的作用
  5. 线程池的核心参数
  6. 线程池的执行流程

四、Spring 框架​

  1. Spring 的核心特性
  2. Spring Bean 的生命周期
  3. Spring AOP 的实现原理
  4. Spring 事务的传播行为
  5. @Transactional 失效的场景

五、Spring Boot​

  1. Spring Boot 的核心特性
  2. Spring Boot 自动配置原理
  3. Spring Boot 配置文件的加载顺序

六、MyBatis​

  1. MyBatis 的核心组件
  2. MyBatis 的一级缓存和二级缓存

七、Redis​

  1. Redis 支持的数据类型
  2. Redis 为什么这么快
  3. Redis 的持久化方式
  4. Redis 的过期策略
  5. 缓存穿透、击穿、雪崩的解决方案
  6. Redis 分布式锁的实现

八、Kafka​

  1. Kafka 的核心概念
  2. Kafka 如何保证消息不丢失
  3. Kafka 为什么吞吐量这么高

九、MySQL​

  1. MySQL 的索引类型
  2. 什么情况下索引会失效
  3. MySQL 事务的隔离级别
  4. MVCC 的实现原理
  5. MySQL 的锁类型

十、分布式系统​

  1. CAP 理论
  2. 分布式事务的解决方案
  3. 分布式锁的实现方式
  4. 分布式 ID 生成方案
  5. 如何设计秒杀系统

十一、JVM 虚拟机​

  1. JVM 内存结构
  2. 垃圾回收算法
  3. 常见的垃圾回收器
  4. 如何排查 OOM 问题

十二、设计模式​

  1. 单例模式的实现方式
  2. 工厂模式和抽象工厂模式
  3. 代理模式的应用

十三、微服务架构​

  1. 微服务的优缺点
  2. 服务注册与发现
  3. 服务熔断和降级
  4. API 网关的作用

十四、网络与安全​

  1. HTTP 和 HTTPS 的区别
  2. TCP 三次握手和四次挥手
  3. 如何防止 SQL 注入
  4. XSS 和 CSRF 攻击

十五、认证与授权​

  1. JWT 的工作原理
  2. JWT 的优缺点
  3. OAuth2 的授权模式
  4. OAuth2 和 JWT 的区别

十六、工作流引擎​

  1. Flowable 的核心概念
  2. Flowable 和 Activiti 的区别
  3. BPMN 2.0 的常用节点
  4. Flowable 的流程部署和启动

一、Java 基础​

1.1 Java 的三大特性​

面试官: 请简单说说 Java 的三大特性?

面试者: Java 的三大特性是封装、继承和多态:

  • 封装:将数据和操作数据的方法封装在类中,通过访问修饰符控制访问权限
  • 继承:子类继承父类的属性和方法,实现代码复用
  • 多态:同一个方法调用可以有不同的执行结果,分为编译时多态(重载)和运行时多态(重写)

📖 详细内容:Java 基础 - 面向对象


1.2 重载和重写的区别​

面试官: 重载和重写有什么区别?

面试者:

特性重载(Overload)重写(Override)
发生位置同一个类子类
参数必须不同必须相同
返回类型可以不同必须相同或子类型
访问修饰符可以不同不能更严格
多态类型编译时多态运行时多态

📖 详细内容:Java 基础 - 方法重载和重写


1.3 基本数据类型​

面试官: Java 有哪些基本数据类型?

面试者: Java 有 8 种基本数据类型:

类型字节数范围包装类
byte1-128 ~ 127Byte
short2-32768 ~ 32767Short
int4-2³¹ ~ 2³¹-1Integer
long8-2⁶³ ~ 2⁶³-1Long
float4单精度浮点数Float
double8双精度浮点数Double
char20 ~ 65535Character
boolean1true/falseBoolean

📖 详细内容:Java 基础 - 包装类型和基本类型


1.4 自动装箱和拆箱​

面试官: 自动装箱和拆箱是什么?

面试者:

  • 自动装箱:基本类型自动转换为包装类,如 Integer i = 10
  • 自动拆箱:包装类自动转换为基本类型,如 int n = i
  • 底层通过 valueOf() 和 xxxValue() 方法实现
  • 注意:Integer 缓存池范围是 -128 到 127

二、Java 集合框架​

2.1 集合框架结构​

面试官: 说说 Java 集合框架的结构?

面试者: Java 集合框架主要分为两大体系:

  • Collection 接口:
    • List:有序可重复(ArrayList、LinkedList、Vector)
    • Set:无序不重复(HashSet、TreeSet、LinkedHashSet)
    • Queue:队列(PriorityQueue、ArrayDeque)
  • Map 接口:键值对存储(HashMap、TreeMap、LinkedHashMap、Hashtable)

📖 详细内容:Java 集合框架


2.2 ArrayList 和 LinkedList 的区别​

面试官: ArrayList 和 LinkedList 的区别?

面试者:

特性ArrayListLinkedList
底层结构动态数组双向链表
随机访问O(1) 快O(n) 慢
插入删除O(n) 需移动元素O(1) 只改指针
内存占用连续内存额外指针开销
适用场景查询多增删多

2.3 HashMap 的底层原理​

面试官: HashMap 的底层原理?

面试者:

  • JDK 1.7:数组 + 链表
  • JDK 1.8+:数组 + 链表 + 红黑树
  • 当链表长度超过 8 且数组长度 ≥ 64 时,链表转红黑树
  • 默认初始容量 16,负载因子 0.75
  • 扩容时容量翻倍,重新计算元素位置

📖 详细内容:Java 集合 - HashMap


2.4 ConcurrentHashMap 的实现原理​

面试官: ConcurrentHashMap 的实现原理?

面试者:

  • JDK 1.7:分段锁(Segment),默认 16 个段,降低锁粒度
  • JDK 1.8:CAS + synchronized,锁粒度更细(锁单个节点)
  • 使用 volatile 保证可见性
  • size() 方法通过累加各节点的 count 实现

📖 详细内容:Java 并发 - ConcurrentHashMap


三、Java 并发编程​

3.1 创建线程的方式​

面试官: 创建线程有哪几种方式?

面试者: 主要有 4 种方式:

  1. 继承 Thread 类:重写 run() 方法
  2. 实现 Runnable 接口:实现 run() 方法,推荐方式
  3. 实现 Callable 接口:可以有返回值,配合 FutureTask 使用
  4. 线程池:通过 Executor 框架创建,生产环境推荐

📖 详细内容:Java 并发 - 创建线程


3.2 线程的生命周期​

面试官: 线程的生命周期有哪些状态?

面试者: 线程有 6 种状态:

  1. NEW:新建,尚未启动
  2. RUNNABLE:可运行,包括就绪和运行中
  3. BLOCKED:阻塞,等待获取锁
  4. WAITING:等待,调用 wait()、join() 等方法
  5. TIMED_WAITING:超时等待,调用 sleep()、wait(timeout) 等
  6. TERMINATED:终止,线程执行完毕

📖 详细内容:Java 并发 - 线程生命周期


3.3 synchronized 和 ReentrantLock 的区别​

面试官: synchronized 和 ReentrantLock 的区别?

面试者:

特性synchronizedReentrantLock
类型关键字,JVM 层面类,API 层面
锁释放自动释放手动释放(finally)
可中断不可中断可中断
公平锁非公平可选公平/非公平
条件变量单个多个(Condition)

📖 详细内容:Java 并发 - 锁机制


3.4 volatile 关键字的作用​

面试官: volatile 关键字的作用?

面试者:

  • 保证可见性:一个线程修改后,其他线程立即可见
  • 禁止指令重排序:通过内存屏障实现
  • 不保证原子性:i++ 操作不是原子的
  • 适用场景:状态标志、双重检查锁定(DCL)

3.5 线程池的核心参数​

面试官: 线程池的核心参数有哪些?

面试者: ThreadPoolExecutor 有 7 个核心参数:

  1. corePoolSize:核心线程数
  2. maximumPoolSize:最大线程数
  3. keepAliveTime:非核心线程空闲存活时间
  4. unit:时间单位
  5. workQueue:任务队列
  6. threadFactory:线程工厂
  7. handler:拒绝策略

📖 详细内容:Java 并发 - 线程池


3.6 线程池的执行流程​

面试官: 线程池的执行流程?

面试者:

  1. 提交任务时,如果线程数 < corePoolSize,创建核心线程执行
  2. 如果线程数 ≥ corePoolSize,任务放入队列
  3. 如果队列满了且线程数 < maximumPoolSize,创建非核心线程
  4. 如果线程数 ≥ maximumPoolSize 且队列满,执行拒绝策略

四、Spring 框架​

4.1 Spring 的核心特性​

面试官: Spring 的核心特性是什么?

面试者: Spring 的两大核心特性:

  • IoC(控制反转):将对象的创建和依赖关系交给 Spring 容器管理
  • AOP(面向切面编程):将横切关注点(日志、事务等)从业务逻辑中分离
  • 其他特性:声明式事务、MVC 框架、数据访问抽象等

📖 详细内容:Java Spring


4.2 Spring Bean 的生命周期​

面试官: Spring Bean 的生命周期?

面试者:

  1. 实例化:通过反射创建 Bean 实例
  2. 属性赋值:依赖注入,填充属性
  3. 初始化前:调用 BeanPostProcessor 的 postProcessBeforeInitialization
  4. 初始化:调用 InitializingBean 的 afterPropertiesSet 或自定义 init-method
  5. 初始化后:调用 BeanPostProcessor 的 postProcessAfterInitialization
  6. 使用:Bean 可以被使用
  7. 销毁:调用 DisposableBean 的 destroy 或自定义 destroy-method

📖 详细内容:Java Spring - Bean 生命周期


4.3 Spring AOP 的实现原理​

面试官: Spring AOP 的实现原理?

面试者:

  • JDK 动态代理:基于接口,通过反射实现
  • CGLIB 代理:基于继承,通过字节码生成子类
  • Spring 默认策略:有接口用 JDK 代理,无接口用 CGLIB
  • 通知类型:前置、后置、环绕、异常、最终通知

📖 详细内容:Java Spring - AOP


4.4 Spring 事务的传播行为​

面试官: Spring 事务的传播行为有哪些?

面试者: 常用的 7 种传播行为:

传播行为说明
REQUIRED默认,有事务加入,无事务新建
SUPPORTS有事务加入,无事务非事务执行
MANDATORY必须在事务中,否则抛异常
REQUIRES_NEW总是新建事务,挂起当前事务
NOT_SUPPORTED非事务执行,挂起当前事务
NEVER非事务执行,有事务抛异常
NESTED嵌套事务,有保存点可回滚

📖 详细内容:Java Spring - 事务管理


4.5 @Transactional 失效的场景​

面试官: @Transactional 失效的场景?

面试者:

  1. 方法不是 public:Spring AOP 只能代理 public 方法
  2. 同类调用:内部方法调用不走代理
  3. 异常被捕获:事务默认只回滚 RuntimeException
  4. 数据库不支持事务:如 MyISAM 引擎
  5. 未被 Spring 管理:类没有被 @Component 等注解标注

五、Spring Boot​

5.1 Spring Boot 的核心特性​

面试官: Spring Boot 的核心特性?

面试者:

  • 自动配置:根据依赖自动配置 Spring 应用
  • 起步依赖:简化 Maven/Gradle 配置
  • 内嵌服务器:Tomcat、Jetty、Undertow
  • 生产就绪:Actuator 提供监控、健康检查等功能
  • 无代码生成:不需要 XML 配置

📖 详细内容:Java Spring Boot


5.2 Spring Boot 自动配置原理​

面试官: Spring Boot 自动配置原理?

面试者:

  1. @SpringBootApplication 包含 @EnableAutoConfiguration
  2. @EnableAutoConfiguration 导入 AutoConfigurationImportSelector
  3. 读取 META-INF/spring.factories 中的自动配置类
  4. 根据 @Conditional 条件注解判断是否生效
  5. 加载满足条件的配置类到容器

📖 详细内容:Java Spring Boot - 自动配置


5.3 Spring Boot 配置文件的加载顺序​

面试官: Spring Boot 配置文件的加载顺序?

面试者: 优先级从高到低:

  1. 命令行参数
  2. SPRING_APPLICATION_JSON 环境变量
  3. ServletConfig/ServletContext 初始化参数
  4. JNDI 属性
  5. Java 系统属性
  6. 操作系统环境变量
  7. jar 包外的 application-{profile}.properties/yml
  8. jar 包内的 application-{profile}.properties/yml
  9. jar 包外的 application.properties/yml
  10. jar 包内的 application.properties/yml

六、MyBatis​

6.1 MyBatis 的核心组件​

面试官: MyBatis 的核心组件有哪些?

面试者:

  • SqlSessionFactory:会话工厂,创建 SqlSession
  • SqlSession:执行 SQL 的会话,线程不安全
  • Mapper:映射器接口,定义 SQL 操作
  • Executor:执行器,负责 SQL 执行和缓存维护
  • StatementHandler:语句处理器,处理 JDBC Statement
  • ParameterHandler:参数处理器,设置预编译参数
  • ResultSetHandler:结果集处理器,封装返回结果

📖 详细内容:Java MyBatis


6.2 MyBatis 的一级缓存和二级缓存​

面试官: MyBatis 的一级缓存和二级缓存?

面试者:

  • 一级缓存:
    • SqlSession 级别,默认开启
    • 同一个 SqlSession 内有效
    • 执行 update/insert/delete 或手动清空时失效
  • 二级缓存:
    • Mapper 级别,需手动开启
    • 多个 SqlSession 共享
    • 需要实体类实现 Serializable
    • 事务提交后才会写入二级缓存

📖 详细内容:Java MyBatis - 缓存机制


七、Redis​

7.1 Redis 支持的数据类型​

面试官: Redis 支持哪些数据类型?

面试者: Redis 支持 5 种基本数据类型和 3 种特殊类型:

基本类型:

  • String:字符串,最大 512MB
  • Hash:哈希表,适合存储对象
  • List:列表,双向链表
  • Set:集合,无序不重复
  • ZSet:有序集合,带分数排序

特殊类型:

  • Bitmap:位图,适合签到、布隆过滤器
  • HyperLogLog:基数统计,UV 统计
  • Geo:地理位置,附近的人

📖 详细内容:Redis 面试题 - 数据类型


7.2 Redis 为什么这么快​

面试官: Redis 为什么这么快?

面试者:

  1. 基于内存操作:数据存储在内存中,避免磁盘 I/O
  2. 高效的数据结构:专为内存访问优化
  3. 单线程模型:避免线程切换和锁竞争(6.0 后 I/O 多线程)
  4. I/O 多路复用:epoll/kqueue 高效处理并发连接
  5. 代码级优化:C 语言实现,贴近底层硬件

📖 详细内容:Redis 面试题 - 性能


7.3 Redis 的持久化方式​

面试官: Redis 的持久化方式有哪些?

面试者:

方式RDBAOF
全称Redis DatabaseAppend Only File
原理快照,保存某时刻数据记录写命令
优点文件小,恢复快数据完整性好
缺点可能丢失数据文件大,恢复慢
触发save/bgsave 命令always/everysec/no

混合持久化(4.0+):RDB + AOF,兼顾性能和数据安全

📖 详细内容:Redis 面试题 - 持久化


7.4 Redis 的过期策略​

面试官: Redis 的过期策略有哪些?

面试者: Redis 使用定期删除 + 惰性删除的组合策略:

惰性删除:

  • 访问键时检查是否过期
  • 过期则删除并返回空

定期删除:

  • 每隔一段时间随机抽取一批键检查过期
  • 默认每秒执行 10 次

内存淘汰策略:

  • volatile-lru:从设置过期时间的键中,淘汰最少使用的
  • allkeys-lru:从所有键中,淘汰最少使用的
  • volatile-lfu:从设置过期时间的键中,淘汰最不经常使用的
  • allkeys-lfu:从所有键中,淘汰最不经常使用的
  • noeviction:不淘汰,写入时返回错误(默认)

📖 详细内容:Redis 面试题 - 过期策略


7.5 缓存穿透、击穿、雪崩的解决方案​

面试官: 如何解决缓存穿透、击穿、雪崩?

面试者:

缓存穿透(查询不存在的数据):

  • 布隆过滤器:拦截不存在的 key
  • 缓存空值:设置较短过期时间

缓存击穿(热点 key 过期):

  • 互斥锁:只允许一个线程查询数据库
  • 热点数据永不过期:逻辑过期,异步更新

缓存雪崩(大量 key 同时过期):

  • 过期时间加随机值:避免同时过期
  • 多级缓存:本地缓存 + Redis
  • 限流降级:保护数据库

📖 详细内容:Redis 面试题 - 缓存问题


7.6 Redis 分布式锁的实现​

面试官: Redis 分布式锁如何实现?

面试者:

基本实现:

java
1// 加锁
2String lockKey = "lock:resource";
3String requestId = UUID.randomUUID().toString();
4Boolean success = redis.set(lockKey, requestId, "NX", "PX", 30000);
5
6// 解锁(Lua 脚本保证原子性)
7String script =
8 "if redis.call('get', KEYS[1]) == ARGV[1] then " +
9 " return redis.call('del', KEYS[1]) " +
10 "else " +
11 " return 0 " +
12 "end";
13redis.eval(script, Collections.singletonList(lockKey),
14 Collections.singletonList(requestId));

关键要点:

  1. 设置过期时间:防止死锁
  2. 唯一标识:防止误删
  3. 原子操作:使用 Lua 脚本
  4. 可重入:使用 Redisson
  5. 主从切换:使用 RedLock

📖 详细内容:Redis 面试题 - 分布式锁


八、Kafka​

8.1 Kafka 的核心概念​

面试官: Kafka 的核心概念有哪些?

面试者:

  • Producer:生产者,发送消息
  • Consumer:消费者,消费消息
  • Broker:服务器节点,存储消息
  • Topic:主题,消息分类
  • Partition:分区,Topic 的物理分组
  • Offset:偏移量,消息在分区中的位置
  • Consumer Group:消费者组,实现负载均衡

📖 详细内容:Java 消息队列


8.2 Kafka 如何保证消息不丢失​

面试官: Kafka 如何保证消息不丢失?

面试者:

生产者端:

  • 设置 acks=all:所有副本确认后才返回
  • 设置 retries > 0:失败自动重试
  • 设置 max.in.flight.requests.per.connection=1:保证顺序

Broker 端:

  • 设置 replication.factor ≥ 3:多副本
  • 设置 min.insync.replicas ≥ 2:最少同步副本数

消费者端:

  • 手动提交 offset:处理完再提交
  • 关闭自动提交:enable.auto.commit=false

📖 详细内容:Java 消息队列 - Kafka


8.3 Kafka 为什么吞吐量这么高​

面试官: Kafka 为什么吞吐量这么高?

面试者:

  1. 顺序写磁盘:追加写入,性能接近内存
  2. 零拷贝:sendfile 系统调用,减少数据拷贝
  3. 批量发送:减少网络开销
  4. 消息压缩:支持 GZIP、Snappy、LZ4
  5. 分区并行:多分区并行读写

九、MySQL​

9.1 MySQL 的索引类型​

面试官: MySQL 的索引类型有哪些?

面试者:

按数据结构分:

  • B+Tree 索引:InnoDB 默认,适合范围查询
  • Hash 索引:Memory 引擎,适合等值查询
  • Full-Text 索引:全文索引,适合文本搜索

按功能分:

  • 主键索引:PRIMARY KEY,唯一且非空
  • 唯一索引:UNIQUE,值唯一但可为 NULL
  • 普通索引:INDEX,无限制
  • 组合索引:多列组合,遵循最左前缀原则

📖 详细内容:MySQL 面试题


9.2 什么情况下索引会失效​

面试官: 什么情况下索引会失效?

面试者:

  1. 使用函数或表达式:WHERE YEAR(date) = 2024
  2. 隐式类型转换:WHERE varchar_col = 123
  3. 模糊查询前缀通配符:WHERE name LIKE '%abc'
  4. OR 条件:OR 两边的列都要有索引
  5. NOT、!=、不等于:无法使用索引
  6. IS NULL、IS NOT NULL:可能失效
  7. 违反最左前缀:组合索引 (a,b,c),查询 b 或 c

9.3 MySQL 事务的隔离级别​

面试官: MySQL 事务的隔离级别有哪些?

面试者:

隔离级别脏读不可重复读幻读
READ UNCOMMITTED可能可能可能
READ COMMITTED不可能可能可能
REPEATABLE READ不可能不可能可能
SERIALIZABLE不可能不可能不可能

InnoDB 默认:REPEATABLE READ,通过 MVCC 和间隙锁解决幻读

📖 详细内容:MySQL 面试题 - 事务


9.4 MVCC 的实现原理​

面试官: MVCC 的实现原理?

面试者:

  • 隐藏字段:
    • DB_TRX_ID:事务 ID
    • DB_ROLL_PTR:回滚指针,指向 undo log
    • DB_ROW_ID:隐藏主键
  • Read View:读视图,记录活跃事务列表
  • undo log:版本链,记录历史版本
  • 可见性判断:根据 Read View 判断版本是否可见

9.5 MySQL 的锁类型​

面试官: MySQL 的锁有哪些类型?

面试者:

按粒度分:

  • 表锁:锁整张表,开销小,并发低
  • 行锁:锁单行,开销大,并发高
  • 间隙锁:锁索引记录之间的间隙,防止幻读

按模式分:

  • 共享锁(S锁):读锁,多个事务可同时持有
  • 排他锁(X锁):写锁,独占

按算法分:

  • Record Lock:记录锁,锁单条记录
  • Gap Lock:间隙锁,锁索引间隙
  • Next-Key Lock:Record Lock + Gap Lock

十、分布式系统​

10.1 CAP 理论​

面试官: CAP 理论是什么?

面试者: CAP 理论指分布式系统无法同时满足三个特性:

  • C(Consistency):一致性,所有节点数据一致
  • A(Availability):可用性,服务一直可用
  • P(Partition Tolerance):分区容错性,网络分区时系统仍能工作

权衡:

  • CP:牺牲可用性,如 ZooKeeper、HBase
  • AP:牺牲一致性,如 Cassandra、DynamoDB
  • CA:不存在,网络分区无法避免

📖 详细内容:分布式系统设计


10.2 分布式事务的解决方案​

面试官: 分布式事务有哪些解决方案?

面试者: 主要有以下几种:

1. 2PC(两阶段提交):

  • 准备阶段 + 提交阶段
  • 缺点:同步阻塞,单点故障

2. TCC(Try-Confirm-Cancel):

  • Try:预留资源
  • Confirm:确认提交
  • Cancel:取消回滚
  • 优点:性能好,业务侵入性强

3. Saga 模式:

  • 将长事务拆分为多个本地事务
  • 每个本地事务有对应的补偿事务
  • 优点:适合长流程,最终一致性

4. 本地消息表:

  • 业务操作和消息发送在同一事务
  • 定时任务扫描消息表并发送

5. MQ 事务消息:

  • 使用 RocketMQ 等支持事务消息的 MQ

📖 详细内容:Spring Cloud - 分布式事务


10.3 分布式锁的实现方式​

面试官: 分布式锁有哪些实现方式?

面试者:

1. Redis 实现:

  • SETNX + EXPIRE:设置 key 和过期时间
  • Redisson:看门狗机制,自动续期
  • RedLock:多节点加锁,提高可靠性

2. ZooKeeper 实现:

  • 创建临时顺序节点
  • 判断是否是最小节点
  • 监听前一个节点删除事件

3. 数据库实现:

  • 唯一索引 + 乐观锁
  • for update 悲观锁

📖 详细内容:Redis 分布式锁


10.4 分布式 ID 生成方案​

面试官: 分布式 ID 生成方案有哪些?

面试者:

1. UUID:

  • 优点:简单,本地生成
  • 缺点:无序,占用空间大

2. 数据库自增 ID:

  • 优点:简单,有序
  • 缺点:性能瓶颈,单点故障

3. 号段模式:

  • 批量获取 ID 段,本地分配
  • 优点:性能好,减少数据库压力

4. 雪花算法(Snowflake):

164位ID结构:
21位符号位 + 41位时间戳 + 10位机器ID + 12位序列号
  • 优点:趋势递增,高性能
  • 缺点:依赖系统时钟

5. 美团 Leaf:

  • 支持号段模式和雪花算法
  • 解决了时钟回拨问题

10.5 如何设计秒杀系统​

面试官: 如何设计一个秒杀系统?

面试者:

核心问题:

  • 高并发:瞬间大量请求
  • 超卖:库存扣减不准确
  • 恶意请求:刷单、爬虫

解决方案:

1. 前端优化:

  • 按钮置灰,防止重复提交
  • 验证码,防止机器刷单
  • 静态资源 CDN 加速

2. 后端限流:

  • 令牌桶、漏桶算法
  • Sentinel/Hystrix 限流

3. Redis 预减库存:

java
1// Redis预减库存
2Long stock = redis.decr("stock:" + productId);
3if (stock < 0) {
4 return Result.fail("库存不足");
5}

4. MQ 削峰:

  • 发送 MQ 消息异步处理
  • 数据库扣减库存

5. 防止超卖:

sql
1-- 乐观锁
2UPDATE product
3SET stock = stock - 1
4WHERE id = #{id} AND stock > 0;

📖 详细内容:后端系统设计 - 秒杀系统


十一、JVM 虚拟机​

11.1 JVM 内存结构​

面试官: JVM 内存结构有哪些部分?

面试者: JVM 内存主要分为以下几个区域:

线程共享区域:

  • 堆(Heap):存储对象实例,GC 主要区域
    • 年轻代(Young Generation):Eden + Survivor
    • 老年代(Old Generation)
  • 方法区(Method Area):存储类信息、常量、静态变量
    • JDK 8 之前:永久代(PermGen)
    • JDK 8 之后:元空间(Metaspace)

线程私有区域:

  • 程序计数器(PC Register):记录当前线程执行的字节码行号
  • 虚拟机栈(VM Stack):存储局部变量、操作数栈、方法出口等
  • 本地方法栈(Native Method Stack):为 Native 方法服务

📖 详细内容:Java 虚拟机


11.2 垃圾回收算法​

面试官: 常见的垃圾回收算法有哪些?

面试者:

1. 标记-清除(Mark-Sweep):

  • 标记所有需要回收的对象,然后清除
  • 缺点:产生内存碎片

2. 标记-复制(Mark-Copy):

  • 将内存分为两块,每次只使用一块
  • 垃圾回收时将存活对象复制到另一块
  • 优点:无碎片,缺点:浪费空间

3. 标记-整理(Mark-Compact):

  • 标记后将存活对象移动到一端
  • 优点:无碎片,缺点:移动对象开销大

4. 分代收集:

  • 年轻代:标记-复制(对象存活率低)
  • 老年代:标记-清除或标记-整理(对象存活率高)

📖 详细内容:Java 虚拟机 - GC 算法


11.3 常见的垃圾回收器​

面试官: 常见的垃圾回收器有哪些?

面试者:

回收器类型特点适用场景
Serial单线程简单高效客户端应用
ParNew多线程Serial 的多线程版本配合 CMS
Parallel Scavenge多线程关注吞吐量后台计算
CMS并发关注停顿时间互联网应用
G1分区可预测停顿大内存应用
ZGC并发超低延迟大内存低延迟

推荐组合:

  • JDK 8:ParNew + CMS
  • JDK 11+:G1(默认)
  • 大内存低延迟:ZGC

11.4 如何排查 OOM 问题​

面试官: 如何排查 OOM 问题?

面试者:

1. 分析堆转储文件:

bash
1# 生成堆转储
2jmap -dump:format=b,file=heap.hprof <pid>
3
4# 使用 MAT 或 jhat 分析
5jhat heap.hprof

2. 查看内存使用情况:

bash
1# 查看堆内存
2jmap -heap <pid>
3
4# 查看对象统计
5jmap -histo <pid> | head -20

3. 监控 GC 情况:

bash
1# 实时查看 GC
2jstat -gc <pid> 1000

4. 常见 OOM 原因:

  • 堆内存溢出:对象过多,内存不足
  • 栈溢出:递归调用过深
  • 方法区溢出:类加载过多
  • 直接内存溢出:NIO 使用过多

📖 详细内容:Java 虚拟机 - 问题排查


十二、设计模式​

12.1 单例模式的实现方式​

面试官: 单例模式有哪些实现方式?

面试者:

1. 饿汉式(线程安全):

java
1public class Singleton {
2 private static final Singleton INSTANCE = new Singleton();
3 private Singleton() {}
4 public static Singleton getInstance() {
5 return INSTANCE;
6 }
7}

2. 懒汉式(双重检查锁):

java
1public class Singleton {
2 private static volatile Singleton instance;
3 private Singleton() {}
4
5 public static Singleton getInstance() {
6 if (instance == null) {
7 synchronized (Singleton.class) {
8 if (instance == null) {
9 instance = new Singleton();
10 }
11 }
12 }
13 return instance;
14 }
15}

3. 静态内部类(推荐):

java
1public class Singleton {
2 private Singleton() {}
3
4 private static class Holder {
5 private static final Singleton INSTANCE = new Singleton();
6 }
7
8 public static Singleton getInstance() {
9 return Holder.INSTANCE;
10 }
11}

4. 枚举(最安全):

java
1public enum Singleton {
2 INSTANCE;
3
4 public void doSomething() {
5 // 业务方法
6 }
7}

📖 详细内容:Java 设计模式


12.2 工厂模式和抽象工厂模式​

面试官: 工厂模式和抽象工厂模式的区别?

面试者:

工厂模式:

  • 定义一个创建对象的接口,让子类决定实例化哪个类
  • 适用于创建单一产品

抽象工厂模式:

  • 提供一个创建一系列相关或相互依赖对象的接口
  • 适用于创建产品族

区别:

  • 工厂模式:一个工厂创建一种产品
  • 抽象工厂:一个工厂创建多种相关产品

📖 详细内容:Java 设计模式 - 工厂模式


12.3 代理模式的应用​

面试官: 代理模式在实际开发中的应用?

面试者:

应用场景:

  1. Spring AOP:方法拦截、事务管理、日志记录
  2. RPC 框架:Dubbo、Feign 远程调用
  3. 延迟加载:Hibernate 懒加载
  4. 权限控制:访问控制、安全检查
  5. 缓存代理:结果缓存

实现方式:

  • 静态代理:编译时确定代理类
  • JDK 动态代理:基于接口,运行时生成
  • CGLIB 代理:基于继承,运行时生成

📖 详细内容:Java 设计模式 - 代理模式


十三、微服务架构​

13.1 微服务的优缺点​

面试官: 微服务架构的优缺点?

面试者:

优点:

  • 独立部署:服务独立,部署灵活
  • 技术异构:不同服务可用不同技术栈
  • 故障隔离:单个服务故障不影响整体
  • 团队自治:小团队独立开发维护
  • 可扩展性:按需扩展单个服务

缺点:

  • 复杂度增加:分布式系统复杂
  • 数据一致性:分布式事务难处理
  • 运维成本:需要完善的监控和运维
  • 网络延迟:服务间调用增加延迟
  • 测试困难:集成测试复杂

📖 详细内容:Spring Cloud


13.2 服务注册与发现​

面试官: 服务注册与发现的原理?

面试者:

核心流程:

  1. 服务注册:服务启动时向注册中心注册
  2. 健康检查:定期发送心跳保持注册状态
  3. 服务发现:消费者从注册中心获取服务列表
  4. 负载均衡:根据策略选择服务实例
  5. 服务下线:服务停止时注销注册

常见注册中心:

  • Eureka:Netflix 开源,AP 模型
  • Nacos:阿里开源,支持 AP 和 CP
  • Consul:HashiCorp 开源,CP 模型
  • ZooKeeper:Apache 开源,CP 模型

📖 详细内容:Spring Cloud - 服务注册


13.3 服务熔断和降级​

面试官: 服务熔断和降级的区别?

面试者:

服务熔断:

  • 当服务调用失败率达到阈值时,自动切断服务调用
  • 类似电路保护器,防止故障扩散
  • 状态:关闭 → 打开 → 半开

服务降级:

  • 当系统压力过大时,暂时关闭非核心功能
  • 保证核心功能可用
  • 返回默认值或友好提示

区别:

  • 熔断:被动触发,保护系统
  • 降级:主动触发,保证核心

实现方案:

  • Hystrix(Netflix,已停更)
  • Sentinel(阿里,推荐)
  • Resilience4j(轻量级)

📖 详细内容:Spring Cloud - 熔断降级


13.4 API 网关的作用​

面试官: API 网关的作用是什么?

面试者:

核心功能:

  1. 路由转发:请求路由到对应的微服务
  2. 负载均衡:分发请求到多个实例
  3. 统一鉴权:集中处理认证授权
  4. 限流熔断:保护后端服务
  5. 日志监控:统一日志收集和监控
  6. 协议转换:HTTP/gRPC 等协议转换

常见网关:

  • Spring Cloud Gateway:Spring 官方,基于 WebFlux
  • Zuul:Netflix 开源,基于 Servlet
  • Kong:基于 Nginx,高性能
  • Nginx:传统反向代理

📖 详细内容:Spring Cloud - API 网关


十四、网络与安全​

14.1 HTTP 和 HTTPS 的区别​

面试官: HTTP 和 HTTPS 的区别?

面试者:

特性HTTPHTTPS
协议明文传输SSL/TLS 加密
端口80443
安全性不安全安全
证书不需要需要 CA 证书
性能快稍慢(加密开销)
SEO一般更好

HTTPS 工作流程:

  1. 客户端发起 HTTPS 请求
  2. 服务器返回证书
  3. 客户端验证证书
  4. 协商加密算法
  5. 生成会话密钥
  6. 加密通信

14.2 TCP 三次握手和四次挥手​

面试官: TCP 三次握手和四次挥手的过程?

面试者:

三次握手(建立连接):

  1. SYN:客户端发送 SYN 包,进入 SYN_SENT 状态
  2. SYN+ACK:服务器返回 SYN+ACK 包,进入 SYN_RCVD 状态
  3. ACK:客户端发送 ACK 包,双方进入 ESTABLISHED 状态

为什么三次:

  • 防止已失效的连接请求到达服务器
  • 确认双方收发能力正常

四次挥手(断开连接):

  1. FIN:客户端发送 FIN 包,进入 FIN_WAIT_1 状态
  2. ACK:服务器返回 ACK 包,进入 CLOSE_WAIT 状态
  3. FIN:服务器发送 FIN 包,进入 LAST_ACK 状态
  4. ACK:客户端发送 ACK 包,进入 TIME_WAIT 状态

为什么四次:

  • 服务器可能还有数据要发送
  • 需要等待数据发送完毕

14.3 如何防止 SQL 注入​

面试官: 如何防止 SQL 注入?

面试者:

1. 使用预编译语句:

java
1// 不安全
2String sql = "SELECT * FROM users WHERE name = '" + name + "'";
3
4// 安全
5String sql = "SELECT * FROM users WHERE name = ?";
6PreparedStatement ps = conn.prepareStatement(sql);
7ps.setString(1, name);

2. 使用 ORM 框架:

java
1// MyBatis 参数化查询
2@Select("SELECT * FROM users WHERE name = #{name}")
3User findByName(@Param("name") String name);

3. 输入验证:

  • 白名单验证
  • 长度限制
  • 类型检查

4. 最小权限原则:

  • 数据库账号只授予必要权限
  • 禁止使用 root 账号

14.4 XSS 和 CSRF 攻击​

面试官: XSS 和 CSRF 攻击如何防御?

面试者:

XSS(跨站脚本攻击):

  • 攻击方式:注入恶意脚本到网页
  • 防御措施:
    • 输入过滤和转义
    • 使用 HttpOnly Cookie
    • Content Security Policy(CSP)
    • 前端框架自动转义(React、Vue)

CSRF(跨站请求伪造):

  • 攻击方式:诱导用户在已登录状态下执行非本意操作
  • 防御措施:
    • CSRF Token 验证
    • 验证 Referer 头
    • SameSite Cookie 属性
    • 重要操作二次验证

区别:

  • XSS:注入恶意脚本
  • CSRF:伪造用户请求

十五、认证与授权​

15.1 JWT 的工作原理​

面试官: JWT 的工作原理是什么?

面试者: JWT(JSON Web Token)是一种用于身份验证的开放标准:

JWT 结构(三部分,用 . 分隔):

1Header.Payload.Signature

1. Header(头部):

json
1{
2 "alg": "HS256", // 签名算法
3 "typ": "JWT" // 令牌类型
4}

2. Payload(载荷):

json
1{
2 "sub": "1234567890", // 主题(用户ID)
3 "name": "John Doe", // 用户名
4 "iat": 1516239022, // 签发时间
5 "exp": 1516242622 // 过期时间
6}

3. Signature(签名):

1HMACSHA256(
2 base64UrlEncode(header) + "." +
3 base64UrlEncode(payload),
4 secret
5)

工作流程:

  1. 用户登录,服务器验证成功后生成 JWT
  2. 服务器将 JWT 返回给客户端
  3. 客户端存储 JWT(localStorage/Cookie)
  4. 后续请求在 Header 中携带 JWT:Authorization: Bearer <token>
  5. 服务器验证 JWT 签名和有效期

代码示例:

java
1// 生成 JWT
2String token = Jwts.builder()
3 .setSubject(userId)
4 .setIssuedAt(new Date())
5 .setExpiration(new Date(System.currentTimeMillis() + 3600000))
6 .signWith(SignatureAlgorithm.HS256, secretKey)
7 .compact();
8
9// 验证 JWT
10Claims claims = Jwts.parser()
11 .setSigningKey(secretKey)
12 .parseClaimsJws(token)
13 .getBody();

📖 详细内容:Spring Security - JWT


15.2 JWT 的优缺点​

面试官: JWT 有哪些优缺点?

面试者:

优点:

  • 无状态:服务器不需要存储 Session,易于扩展
  • 跨域支持:可以跨域使用,适合分布式系统
  • 性能好:不需要查询数据库验证
  • 移动端友好:适合 App、小程序等场景
  • 自包含:Token 包含用户信息,减少查询

缺点:

  • 无法主动失效:Token 签发后无法撤销,只能等过期
  • Token 较大:包含用户信息,比 Session ID 大
  • 安全性:需要妥善保管密钥,防止泄露
  • 续期问题:Token 过期需要重新登录或使用 Refresh Token
  • 时间敏感:依赖服务器时间,需要时间同步

解决方案:

  • Token 失效:使用 Redis 黑名单
  • Token 续期:双 Token 机制(Access Token + Refresh Token)
  • 安全性:HTTPS 传输,HttpOnly Cookie
特性JWTSession
存储位置客户端服务器
扩展性好差(需要共享)
性能好需要查询
安全性需要加密传输相对安全
失效控制困难容易

15.3 OAuth2 的授权模式​

面试官: OAuth2 有哪些授权模式?

面试者: OAuth2 定义了 4 种授权模式:

1. 授权码模式(Authorization Code):

  • 最安全,适合有后端的 Web 应用
  • 流程:
    1. 用户访问客户端,重定向到授权服务器
    2. 用户同意授权,返回授权码
    3. 客户端用授权码换取 Access Token
    4. 使用 Access Token 访问资源

2. 隐式模式(Implicit):

  • 简化版,适合纯前端应用(已不推荐)
  • 直接返回 Access Token,跳过授权码步骤
  • 安全性较低,Token 暴露在 URL 中

3. 密码模式(Password):

  • 用户直接提供用户名密码给客户端
  • 适合高度信任的应用(如官方 App)
  • 不推荐使用,违背 OAuth2 初衷

4. 客户端模式(Client Credentials):

  • 客户端以自己的名义访问资源
  • 适合服务器间通信(M2M)
  • 不涉及用户授权

推荐使用:

  • Web 应用:授权码模式 + PKCE
  • 单页应用:授权码模式 + PKCE
  • 移动应用:授权码模式 + PKCE
  • 服务间调用:客户端模式

授权码模式示例:

java
1// 1. 重定向到授权页面
2String authUrl = "https://auth.example.com/oauth/authorize?" +
3 "response_type=code&" +
4 "client_id=CLIENT_ID&" +
5 "redirect_uri=CALLBACK_URL&" +
6 "scope=read write";
7
8// 2. 用授权码换取 Token
9RestTemplate restTemplate = new RestTemplate();
10MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
11params.add("grant_type", "authorization_code");
12params.add("code", authCode);
13params.add("client_id", clientId);
14params.add("client_secret", clientSecret);
15params.add("redirect_uri", redirectUri);
16
17TokenResponse response = restTemplate.postForObject(
18 "https://auth.example.com/oauth/token",
19 params,
20 TokenResponse.class
21);

📖 详细内容:Spring Security - OAuth2


15.4 OAuth2 和 JWT 的区别​

面试官: OAuth2 和 JWT 有什么区别?

面试者: OAuth2 和 JWT 是两个不同层面的概念:

本质区别:

  • OAuth2:授权框架,解决"如何授权"的问题
  • JWT:Token 格式,解决"如何表示 Token"的问题

关系:

  • OAuth2 可以使用 JWT 作为 Token 格式
  • JWT 也可以独立使用,不依赖 OAuth2
对比项OAuth2JWT
类型授权协议Token 格式
目的第三方授权身份认证
场景微信登录、GitHub 登录用户登录、API 认证
Token可以是任意格式固定的 JSON 格式
验证需要调用授权服务器本地验证签名

实际应用:

java
1// OAuth2 + JWT 组合使用
2@Configuration
3@EnableAuthorizationServer
4public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
5
6 @Override
7 public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
8 endpoints
9 .tokenStore(tokenStore())
10 .accessTokenConverter(jwtAccessTokenConverter()); // 使用 JWT
11 }
12
13 @Bean
14 public JwtAccessTokenConverter jwtAccessTokenConverter() {
15 JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
16 converter.setSigningKey("secret-key");
17 return converter;
18 }
19}

使用场景:

  • 单独使用 JWT:自己的系统内部认证
  • 单独使用 OAuth2:第三方登录(微信、QQ)
  • 组合使用:微服务架构,OAuth2 授权 + JWT Token

十六、工作流引擎​

16.1 Flowable 的核心概念​

面试官: Flowable 的核心概念有哪些?

面试者: Flowable 是一个轻量级的工作流和业务流程管理(BPM)平台,核心概念包括:

核心组件:

1. ProcessEngine(流程引擎):

  • Flowable 的核心,管理所有服务
  • 通过配置文件创建,线程安全

2. 七大 Service:

Service作用
RepositoryService流程定义和部署管理
RuntimeService流程实例管理(启动、查询、删除)
TaskService任务管理(查询、完成、委派)
HistoryService历史数据查询
ManagementService引擎管理和维护
IdentityService用户和组管理
FormService表单管理

核心概念:

流程定义(Process Definition):

  • BPMN 2.0 XML 文件
  • 描述业务流程的静态结构
  • 可以多次部署,每次生成新版本

流程实例(Process Instance):

  • 流程定义的运行实例
  • 一个流程定义可以启动多个实例
  • 包含流程变量(Process Variables)

任务(Task):

  • 用户任务(User Task):需要人工处理
  • 服务任务(Service Task):自动执行
  • 脚本任务(Script Task):执行脚本

执行(Execution):

  • 流程实例的执行路径
  • 并行网关会产生多个执行

代码示例:

java
1// 1. 创建流程引擎
2ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
3
4// 2. 获取服务
5RepositoryService repositoryService = processEngine.getRepositoryService();
6RuntimeService runtimeService = processEngine.getRuntimeService();
7TaskService taskService = processEngine.getTaskService();
8
9// 3. 部署流程
10Deployment deployment = repositoryService.createDeployment()
11 .addClasspathResource("processes/leave.bpmn20.xml")
12 .name("请假流程")
13 .deploy();
14
15// 4. 启动流程实例
16Map<String, Object> variables = new HashMap<>();
17variables.put("days", 3);
18variables.put("reason", "旅游");
19ProcessInstance processInstance = runtimeService
20 .startProcessInstanceByKey("leaveProcess", variables);
21
22// 5. 查询任务
23List<Task> tasks = taskService.createTaskQuery()
24 .processInstanceId(processInstance.getId())
25 .list();
26
27// 6. 完成任务
28taskService.complete(tasks.get(0).getId());

16.2 Flowable 和 Activiti 的区别​

面试官: Flowable 和 Activiti 有什么区别?

面试者: Flowable 是从 Activiti 6 分支出来的,两者有以下区别:

历史关系:

  • Activiti 由 jBPM 创始人开发
  • 2016 年核心团队离开,创建 Flowable
  • Flowable 基于 Activiti 6,持续演进

主要区别:

对比项FlowableActiviti
开发团队原 Activiti 核心团队新团队
更新频率活跃,频繁更新相对缓慢
Spring Boot完美支持支持一般
异步执行器改进的异步执行器传统异步执行器
DMN 支持原生支持决策引擎支持较弱
CMMN 支持支持案例管理不支持
事务管理更好的事务处理一般
性能更优一般
社区活跃度高中等

Flowable 的优势:

1. 更好的 Spring Boot 集成:

java
1@SpringBootApplication
2public class FlowableApplication {
3 public static void main(String[] args) {
4 SpringApplication.run(FlowableApplication.class, args);
5 }
6}
7
8// 自动注入服务
9@Service
10public class LeaveService {
11 @Autowired
12 private RuntimeService runtimeService;
13
14 @Autowired
15 private TaskService taskService;
16}

2. 多引擎支持:

  • BPMN 引擎(流程)
  • DMN 引擎(决策)
  • CMMN 引擎(案例)
  • Form 引擎(表单)

3. 异步执行器改进:

  • 更高效的异步任务处理
  • 更好的线程池管理
  • 支持消息队列集成

选择建议:

  • 新项目:推荐 Flowable(更活跃,功能更强)
  • 老项目:Activiti 迁移到 Flowable 成本较低
  • 简单场景:两者都可以
  • 复杂场景:Flowable 更合适(DMN、CMMN)

16.3 BPMN 2.0 的常用节点​

面试官: BPMN 2.0 有哪些常用节点?

面试者: BPMN 2.0(Business Process Model and Notation)定义了丰富的流程元素:

事件(Events):

事件类型说明示例
开始事件流程的起点启动请假流程
结束事件流程的终点请假流程结束
定时事件定时触发每天凌晨执行
消息事件接收/发送消息收到审批通知
错误事件捕获错误处理异常情况

任务(Tasks):

1. 用户任务(User Task):

xml
1<userTask id="approveTask" name="经理审批"
2 flowable:assignee="${manager}">
3 <extensionElements>
4 <flowable:formProperty id="approved" name="是否同意"
5 type="boolean" required="true"/>
6 <flowable:formProperty id="comment" name="审批意见"
7 type="string"/>
8 </extensionElements>
9</userTask>

2. 服务任务(Service Task):

xml
1<!-- Java 类实现 -->
2<serviceTask id="sendEmail" name="发送邮件"
3 flowable:class="com.example.SendEmailTask"/>
4
5<!-- 表达式 -->
6<serviceTask id="calculateTask" name="计算"
7 flowable:expression="${calculationService.calculate(days)}"/>
8
9<!-- 委托表达式 -->
10<serviceTask id="notifyTask" name="通知"
11 flowable:delegateExpression="${notificationDelegate}"/>

3. 脚本任务(Script Task):

xml
1<scriptTask id="scriptTask" name="计算总价"
2 scriptFormat="groovy">
3 <script>
4 def total = price * quantity
5 execution.setVariable("total", total)
6 </script>
7</scriptTask>

网关(Gateways):

1. 排他网关(Exclusive Gateway):

xml
1<exclusiveGateway id="decision" name="判断天数"/>
2
3<sequenceFlow sourceRef="decision" targetRef="managerApprove">
4 <conditionExpression xsi:type="tFormalExpression">
5 ${days <= 3}
6 </conditionExpression>
7</sequenceFlow>
8
9<sequenceFlow sourceRef="decision" targetRef="bossApprove">
10 <conditionExpression xsi:type="tFormalExpression">
11 ${days > 3}
12 </conditionExpression>
13</sequenceFlow>

2. 并行网关(Parallel Gateway):

xml
1<!-- 并行执行多个任务 -->
2<parallelGateway id="fork" name="并行开始"/>
3<parallelGateway id="join" name="并行结束"/>
4
5<sequenceFlow sourceRef="fork" targetRef="task1"/>
6<sequenceFlow sourceRef="fork" targetRef="task2"/>
7<sequenceFlow sourceRef="task1" targetRef="join"/>
8<sequenceFlow sourceRef="task2" targetRef="join"/>

3. 包容网关(Inclusive Gateway):

  • 可以选择一个或多个分支
  • 类似排他网关 + 并行网关

子流程(Sub-Process):

xml
1<subProcess id="subProcess" name="子流程">
2 <startEvent id="subStart"/>
3 <userTask id="subTask" name="子任务"/>
4 <endEvent id="subEnd"/>
5 <sequenceFlow sourceRef="subStart" targetRef="subTask"/>
6 <sequenceFlow sourceRef="subTask" targetRef="subEnd"/>
7</subProcess>

完整示例(请假流程):

xml
1<?xml version="1.0" encoding="UTF-8"?>
2<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
3 targetNamespace="http://flowable.org/test">
4
5 <process id="leaveProcess" name="请假流程" isExecutable="true">
6
7 <!-- 开始事件 -->
8 <startEvent id="start" name="开始"/>
9
10 <!-- 用户任务:填写请假单 -->
11 <userTask id="fillForm" name="填写请假单"
12 flowable:assignee="${applicant}"/>
13
14 <!-- 排他网关:判断天数 -->
15 <exclusiveGateway id="daysDecision" name="判断天数"/>
16
17 <!-- 用户任务:经理审批 -->
18 <userTask id="managerApprove" name="经理审批"
19 flowable:candidateGroups="managers"/>
20
21 <!-- 用户任务:总监审批 -->
22 <userTask id="bossApprove" name="总监审批"
23 flowable:candidateGroups="directors"/>
24
25 <!-- 服务任务:发送通知 -->
26 <serviceTask id="sendNotification" name="发送通知"
27 flowable:class="com.example.SendNotificationTask"/>
28
29 <!-- 结束事件 -->
30 <endEvent id="end" name="结束"/>
31
32 <!-- 连线 -->
33 <sequenceFlow sourceRef="start" targetRef="fillForm"/>
34 <sequenceFlow sourceRef="fillForm" targetRef="daysDecision"/>
35
36 <sequenceFlow sourceRef="daysDecision" targetRef="managerApprove">
37 <conditionExpression>${days <= 3}</conditionExpression>
38 </sequenceFlow>
39
40 <sequenceFlow sourceRef="daysDecision" targetRef="bossApprove">
41 <conditionExpression>${days > 3}</conditionExpression>
42 </sequenceFlow>
43
44 <sequenceFlow sourceRef="managerApprove" targetRef="sendNotification"/>
45 <sequenceFlow sourceRef="bossApprove" targetRef="sendNotification"/>
46 <sequenceFlow sourceRef="sendNotification" targetRef="end"/>
47
48 </process>
49</definitions>

16.4 Flowable 的流程部署和启动​

面试官: Flowable 如何部署和启动流程?

面试者: Flowable 的流程部署和启动主要通过 API 完成:

1. 流程部署:

方式一:通过文件部署:

java
1@Service
2public class ProcessDeploymentService {
3
4 @Autowired
5 private RepositoryService repositoryService;
6
7 // 部署单个文件
8 public String deploySingleFile() {
9 Deployment deployment = repositoryService.createDeployment()
10 .addClasspathResource("processes/leave.bpmn20.xml")
11 .name("请假流程")
12 .category("HR")
13 .deploy();
14
15 return deployment.getId();
16 }
17
18 // 部署多个文件
19 public String deployMultipleFiles() {
20 Deployment deployment = repositoryService.createDeployment()
21 .addClasspathResource("processes/leave.bpmn20.xml")
22 .addClasspathResource("processes/leave.png")
23 .addClasspathResource("forms/leave-form.form")
24 .name("请假流程包")
25 .deploy();
26
27 return deployment.getId();
28 }
29
30 // 部署 ZIP 文件
31 public String deployZip() {
32 InputStream inputStream = this.getClass()
33 .getClassLoader()
34 .getResourceAsStream("processes/leave.zip");
35
36 Deployment deployment = repositoryService.createDeployment()
37 .addZipInputStream(new ZipInputStream(inputStream))
38 .name("请假流程ZIP")
39 .deploy();
40
41 return deployment.getId();
42 }
43}

方式二:Spring Boot 自动部署:

yaml
1# application.yml
2flowable:
3 # 自动部署
4 check-process-definitions: true
5 # 流程定义位置
6 process-definition-location-prefix: classpath*:/processes/
7 process-definition-location-suffixes:
8 - **.bpmn20.xml
9 - **.bpmn

2. 流程启动:

基本启动:

java
1@Service
2public class ProcessStartService {
3
4 @Autowired
5 private RuntimeService runtimeService;
6
7 // 通过流程定义 Key 启动
8 public String startByKey() {
9 ProcessInstance processInstance = runtimeService
10 .startProcessInstanceByKey("leaveProcess");
11
12 return processInstance.getId();
13 }
14
15 // 带流程变量启动
16 public String startWithVariables() {
17 Map<String, Object> variables = new HashMap<>();
18 variables.put("applicant", "zhangsan");
19 variables.put("days", 3);
20 variables.put("reason", "旅游");
21 variables.put("startDate", new Date());
22
23 ProcessInstance processInstance = runtimeService
24 .startProcessInstanceByKey("leaveProcess", variables);
25
26 return processInstance.getId();
27 }
28
29 // 带业务 Key 启动
30 public String startWithBusinessKey() {
31 String businessKey = "LEAVE-2024-001";
32
33 ProcessInstance processInstance = runtimeService
34 .startProcessInstanceByKey(
35 "leaveProcess",
36 businessKey,
37 variables
38 );
39
40 return processInstance.getId();
41 }
42}

3. 任务处理:

java
1@Service
2public class TaskHandleService {
3
4 @Autowired
5 private TaskService taskService;
6
7 // 查询待办任务
8 public List<Task> queryTasks(String assignee) {
9 return taskService.createTaskQuery()
10 .taskAssignee(assignee)
11 .orderByTaskCreateTime()
12 .desc()
13 .list();
14 }
15
16 // 查询候选任务
17 public List<Task> queryCandidateTasks(String userId) {
18 return taskService.createTaskQuery()
19 .taskCandidateUser(userId)
20 .list();
21 }
22
23 // 认领任务
24 public void claimTask(String taskId, String userId) {
25 taskService.claim(taskId, userId);
26 }
27
28 // 完成任务
29 public void completeTask(String taskId) {
30 taskService.complete(taskId);
31 }
32
33 // 完成任务并传递变量
34 public void completeTaskWithVariables(String taskId) {
35 Map<String, Object> variables = new HashMap<>();
36 variables.put("approved", true);
37 variables.put("comment", "同意");
38
39 taskService.complete(taskId, variables);
40 }
41
42 // 委派任务
43 public void delegateTask(String taskId, String userId) {
44 taskService.delegateTask(taskId, userId);
45 }
46
47 // 转办任务
48 public void transferTask(String taskId, String userId) {
49 taskService.setAssignee(taskId, userId);
50 }
51}

4. 流程查询:

java
1@Service
2public class ProcessQueryService {
3
4 @Autowired
5 private RuntimeService runtimeService;
6
7 @Autowired
8 private HistoryService historyService;
9
10 // 查询运行中的流程实例
11 public List<ProcessInstance> queryRunningProcesses() {
12 return runtimeService.createProcessInstanceQuery()
13 .active()
14 .orderByProcessInstanceStartTime()
15 .desc()
16 .list();
17 }
18
19 // 查询流程变量
20 public Map<String, Object> getVariables(String processInstanceId) {
21 return runtimeService.getVariables(processInstanceId);
22 }
23
24 // 查询历史流程实例
25 public List<HistoricProcessInstance> queryHistoricProcesses() {
26 return historyService.createHistoricProcessInstanceQuery()
27 .finished()
28 .orderByProcessInstanceEndTime()
29 .desc()
30 .list();
31 }
32
33 // 查询历史任务
34 public List<HistoricTaskInstance> queryHistoricTasks(String processInstanceId) {
35 return historyService.createHistoricTaskInstanceQuery()
36 .processInstanceId(processInstanceId)
37 .orderByHistoricTaskInstanceEndTime()
38 .desc()
39 .list();
40 }
41}

5. 流程控制:

java
1@Service
2public class ProcessControlService {
3
4 @Autowired
5 private RuntimeService runtimeService;
6
7 // 挂起流程实例
8 public void suspendProcessInstance(String processInstanceId) {
9 runtimeService.suspendProcessInstanceById(processInstanceId);
10 }
11
12 // 激活流程实例
13 public void activateProcessInstance(String processInstanceId) {
14 runtimeService.activateProcessInstanceById(processInstanceId);
15 }
16
17 // 删除流程实例
18 public void deleteProcessInstance(String processInstanceId, String reason) {
19 runtimeService.deleteProcessInstance(processInstanceId, reason);
20 }
21
22 // 设置流程变量
23 public void setVariable(String processInstanceId, String key, Object value) {
24 runtimeService.setVariable(processInstanceId, key, value);
25 }
26}

完整示例(请假流程):

java
1@RestController
2@RequestMapping("/leave")
3public class LeaveController {
4
5 @Autowired
6 private RuntimeService runtimeService;
7
8 @Autowired
9 private TaskService taskService;
10
11 // 提交请假申请
12 @PostMapping("/apply")
13 public Result apply(@RequestBody LeaveRequest request) {
14 Map<String, Object> variables = new HashMap<>();
15 variables.put("applicant", request.getApplicant());
16 variables.put("days", request.getDays());
17 variables.put("reason", request.getReason());
18
19 ProcessInstance processInstance = runtimeService
20 .startProcessInstanceByKey("leaveProcess", variables);
21
22 return Result.success(processInstance.getId());
23 }
24
25 // 查询待办任务
26 @GetMapping("/tasks")
27 public Result queryTasks(@RequestParam String userId) {
28 List<Task> tasks = taskService.createTaskQuery()
29 .taskAssignee(userId)
30 .list();
31
32 return Result.success(tasks);
33 }
34
35 // 审批任务
36 @PostMapping("/approve")
37 public Result approve(@RequestBody ApproveRequest request) {
38 Map<String, Object> variables = new HashMap<>();
39 variables.put("approved", request.isApproved());
40 variables.put("comment", request.getComment());
41
42 taskService.complete(request.getTaskId(), variables);
43
44 return Result.success();
45 }
46}

关键点:

  • 流程定义是静态的,可以多次部署
  • 流程实例是动态的,每次启动创建新实例
  • 流程变量在整个流程中共享
  • 任务分配可以使用 assignee(指定人)或 candidateUsers/candidateGroups(候选人/组)

总结​

面试建议
  • 基础扎实:Java 基础、集合、并发是重点
  • 框架原理:不仅会用,还要懂原理
  • 中间件:Redis、Kafka、MySQL 是高频考点
  • 系统设计:分布式、高并发场景设计能力
  • 认证授权:JWT、OAuth2 是现代应用必备
  • 工作流引擎:Flowable/Activiti 在企业应用中很常见
  • 项目经验:结合实际项目,突出亮点
  • 持续学习:技术更新快,保持学习热情

最后更新时间: 2026-05-04

相关文档:

forum

评论区 / Comments