本文全部内容可在 IntelliJ IDEA - Debug code 中找到对应章节。除去官方文档中图片,其余截图来自 IntelliJ IDEA 2020.1.2 Ultimate Edition macOS 版。
此外,由于不同系统默认修饰键不同以及 IntelliJ IDEA 允许设置不同的快捷键方案,本文不会涉及快捷键的使用。
窗口
断点
断点的用法非常简单(例如运行到指定行后挂起)也可以引入一些复杂逻辑(如检查额外条件,输出日志等等)。一旦设置,非临时断点就保存在项目中,直到主动移除。如果一个设置了断点的文件受到了版本控制系统或者其他编辑器之类的外部修改,导致代码行号发生了变更,断点也会相应地移动。需要注意的是修改发生时 IntelliJ IDEA 必须处于运行状态,否则无法跟踪这些修改。
IntelliJ IDEA 支持如下断点类型:
- 行断点(Line breakpoints):程序运行到断点设置位置时挂起。可以在任何可执行代码上设置。
如果行内存在 lambda 表达式,也可以设置在被调用时才挂起
- 方法断点(Method breakpoints):在进入/离开特定方法时挂起。
- 属性观察点(Field watchpoints):特定属性被读写时挂起程序。例如在执行一长串代码后返回的对象属性不符合期望,可以使用这种断点观察错误可能的产生位置。
- 异常断点(Exception breakpoints):在程序抛出异常时挂起,这是一个全局设定,不需要关联到具体的代码。
在断点标记上右键,可以打开断点属性,点击 More 可以配置更多特性。
暂时停用断点
取消断点将导致所有配置信息的丢失,因此对于暂时不用的断点,可以选择禁用
也可以直接 Mute
条件断点
Condition 属性可以填写一个返回 Boolean 的表达式,表达式计算结果为 True 时触发断点。
一次性断点
Remove once hit
等到如下断点触发后启用
Disable until hitting the following breakpoint
在断点处执行逻辑
笔者有一次调试需要反复重放用户的登录请求,然而用户的每次登录都会产生不同的验证码(当然防重放用的 session token 也差不多),这就导致简单的请求重放必然会被拦截。
if (StringUtils.isBlank(code)) {
throw new RuntimeException("captcha not exist.");
}
if (StringUtils.isBlank(authUser.getCode()) ||
!authUser.getCode().equalsIgnoreCase(code)) {
throw new BadRequestException("captcha not match.");
}
// ...
这时候断点的求值功能就派上了用场,通过执行如图所示的赋值语句让 code
一定能通过校验。
如果取消了断点的 Suspend
功能,看起来就像设置了「跳过这段逻辑」一样。
单步调试和其他基础功能
从左到右功能依次为
- Show Execution Point :把光标跳转到程序当前执行的位置
- Step Over :执行到下一行
- Step Into :执行到下一步,如果是方法调用,进入方法内
- Force Step Into :默认设置下 Step Into 会跳过 java.* 等方法,可以 Force Into
- Step Out :跳出当前方法
- Drop Frame :丢弃栈顶
- Run to Cursor :执行到光标位置
- Evaluate Expression :表达式求值
- Trace Current Stream Chain :调试 Java 8 引入的 Stream 运算
检查帧
frame 对应了当前的函数调用,存储局部变量,参数和用于表达式求值的上下文。
Drop Frame
丢弃当前帧,快速回到方法调用前。一般用于快速回到错过的断点位置,看起来像是“回退执行”。注意 IO 类操作,或已修改的共享变量是无法回滚的,因为这个操作只是删除栈顶的栈帧,并不是真正的“回退”。
Throw Exception & Force Return
不管程序当前运行到哪一步,都可以通过 throw new AnyException();
强制抛出指定异常或直接返回,对于有返回值的方法,需要指定返回值。
检查变量
在 Variables 可以查看变量,与剪切板中的内容比较,也可以通过 Set Value 直接赋值。
表达式求值
有时候表达式的计算结果不会赋值给某个对象,因此不能通过 variables 窗口观察。选中这个表达式,点击“计算表达式”按钮就可以获得执行结果。
由于表达式的计算基于当前执行位置的上下文,因此也可以用来给变量重新赋值。