陌生Java项目历险记 3 - 断点回溯法

近期接手了一个陌生的 Java 项目,在阅读源码的过程中,一路踩坑,一路总结。本文是这一系列的第三篇。在这篇文章里,进一步确认了"断点回溯法"的便捷之处,并尝试提出源码阅读的一般回溯流程,最后针对一些特定场景,指出了该方法的不足之处。

在接手一个项目的过程中,第一件事情,一般是 Run 起来,并尝试调用 API 输入数据,观察输出数据,然后通过阅读代码,了解从输入到输出的整个处理流程。

一个直觉的代码阅读方法,是从输入开始,一步步跟踪代码执行过程,直至获得输出为止。当我们在学校期间,为了知晓一个算法的执行过程,可能采用的都是这种办法。

但是,工作之后,面对真实的项目,其代码量是庞大的,其组件是复杂的,从入口开始一步步跟着看,是很容易迷路的,看着看着大概率就晕掉了。

为了应对这个问题,在之前的一篇文章中,我提出了一个阅读源码的方法,称之为"断点回溯法"。这个方法的最大特点,不是从入口开始看,而是直接跳到一个关键节点,通过这个关键节点往回溯,一次性了解整个关键链路的执行过程。

而关键节点,一般都是从日志中来找的。大部分的项目,都会在关键节点处打日志。如果没看到,考虑把日志级别调成DEBUG甚至TRACE看看,大概率是可以看到不少日志的。

以我最近看的项目为例,在调用 "/admin/list" 接口时,看到了以下日志:

js 复制代码
2024-04-09 15:36:12.997 INFO 98838 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' 
2024-04-09 15:36:12.997 INFO 98838 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' 
2024-04-09 15:36:13.002 INFO 98838 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 5 ms 
2024-04-09 15:36:13.031 DEBUG 98838 --- [nio-8080-exec-1] c.x.t.s.c.DynamicSecurityFilter : Authorized filter invocation [GET /admin/list?pageNum=1&pageSize=5] with attributes [25:UserManager] 
2024-04-09 15:36:13.044 DEBUG 98838 --- [nio-8080-exec-1] c.x.t.s.c.DynamicSecurityFilter : Authorized public object filter invocation [GET /error?pageNum=1&pageSize=5]

很明显,DynamicSecurityFilter 是一个关键类,其打了两行相似的日志,暗示可能有两个关键的执行点。这时,我也无需去猜测其到底是干嘛的,直接代码搜索"Authorized.*with attributes" (这里有个小细节,如果全文搜索不到时,使用关键字的正则表达式),定位到代码。

然后设置断点,触发执行,回溯堆栈,一气呵成。 在回溯堆栈的过程中,我们可以看到整个堆栈的执行函数以及相关参数。通过这些函数和参数,我们可以轻易知道,整个链路是从哪里来的,其大致执行过程是咋样的。

但这里也引出了一些问题,我们可以直接看到参数的内容,却不能直接看出来参数是谁生成的。 比如上面的例子,我们看到了 AnonymousAuthenticationToken,这明显是一个身份信息。但谁生成的呢?

我们尝试回溯一下堆栈,浏览一下各个函数的源码,会发现一些线索。在这个例子中,至少有两个明显的线索。 第一个是 AnonymousAuthenticationFilter,其有可能会设置 Authentication。

第二个是 JwtAuthenticationTokenFilter,其也有可能会设置 Authentication

到这里,就很简单了,在这两个地方设置断点,再触发一次,就可以知晓结果了。

当然,我们如果知晓 Spring Security 的原理,知晓 Authentication 是通过下面这个方法设置的:

则把这个地方当作"关键节点",运用一次"断点回溯法",就立即知晓谁生成的这个 Authentication了。

通过这个回溯可以发现,Spring 内部集成了一个 AnonymousAuthenticationFilter,这个Filter在当前上下文没发现有效的Authentication时,会生成一个匿名的Authentication。

很显然,"断点回溯法"是非常香的,它极大地降低了认知负担,只需要知晓少量"关键节点"这样的信息,通过回溯,就可以快速了解代码的运行过程。

在大部分情况下,找寻这样的"关键节点",并不困难。程序自身日志,架构设计文档,网上源码解读文章,都包含了大量的这样的关键信息。

然而,在实践中,"断点回溯法"也不是完美的,至少存在以下问题:

  • 到达"关键节点"的路径不止一条。这就意味着,在这个过程中,需要去比较不同的链路。而比较,就意味着大脑需要记住之前的链路,而"断点回溯法"一次只能展示一条链路,在这种情况下,会对大脑提出较高的认知负担。
  • 多线程和异步。遇到这种情况,回溯一次是不够的。往往需要把多条链路拼接起来,才能完整理解整个过程。同上,也会对大脑提出较高的认知负担。
  • 到了代码的深入处,我们可能无法事先知道"关键节点"。这个时候,无法运用"断点回溯点",只能退回"正向搜索法",一行一行挨个找。

理解一个真实的代码项目,总体来说是一个比较复杂的过程。运用适当的方法,可以降低我们的认知负担。只是当前阶段,还没有一个非常完美的方法。我会持续探索和实践,不断优化,敬请期待下篇分享。

相关推荐
间彧1 小时前
Kubernetes的Pod与Docker Compose中的服务在概念上有何异同?
后端
间彧1 小时前
从开发到生产,如何将Docker Compose项目平滑迁移到Kubernetes?
后端
间彧1 小时前
如何结合CI/CD流水线自动选择正确的Docker Compose配置?
后端
间彧1 小时前
在多环境(开发、测试、生产)下,如何管理不同的Docker Compose配置?
后端
间彧1 小时前
如何为Docker Compose中的服务配置健康检查,确保服务真正可用?
后端
间彧1 小时前
Docker Compose和Kubernetes在编排服务时有哪些核心区别?
后端
间彧2 小时前
如何在实际项目中集成Arthas Tunnel Server实现Kubernetes集群的远程诊断?
后端
brzhang2 小时前
读懂 MiniMax Agent 的设计逻辑,然后我复刻了一个MiniMax Agent
前端·后端·架构
草明3 小时前
Go 的 IO 多路复用
开发语言·后端·golang
蓝-萧3 小时前
Plugin ‘mysql_native_password‘ is not loaded`
java·后端