watch 命令默认在方法结束(-f)时触发,params 为出参快照;要看原始入参必须加 -b;生产环境需用 -n 限频、精确类名、条件表达式过滤,并注意 spring 代理和 -b 下 #cost 不可用。

watch 命令能看哪些入参,取决于你加了什么事件点
默认 watch 只在方法结束(-f)时触发,此时 params 是“出参快照”——如果方法体内修改了入参对象的字段,看到的就不是原始入参。真要看**刚进方法时的原始入参**,必须显式加 -b(before)事件点。
常见错误是直接跑:watch com.example.service.UserService login "{params}" -x 2,结果发现 params[0] 里某个字段值和日志对不上——大概率是方法里改了它。这时候要改成:watch -b com.example.service.UserService login "{params}" -x 2。
-
-b:只在方法入口执行一次,params是原始引用,未被方法体污染 -
-s或-f:方法返回后才执行,params仍是同一引用,但内容可能已变 -
-e:仅当抛异常时触发,适合查异常前的入参状态
生产环境慎用:避免刷屏、匹配爆炸和类加载干扰
线上服务调用量大,watch 默认不限次数、不加条件,极易打满终端或拖慢 JVM。必须主动约束:
- 加
-n 5限制最多输出 5 次,防止刷屏;想持续观察就用-n 100后手动Ctrl+C中断 - 类匹配太宽(如
com.example.*)可能命中几十个类,触发maxMatch限制(默认 50),命令直接失败。用sc *UserService*先确认全限定名,再写精确类名 - Spring AOP 代理类会触发两次(原生类 +
$EnhancerBySpringCGLIB),加--exclude-class-pattern *EnhancerBySpringCGLIB*过滤掉 - 避免用
-x 4深度展开,尤其当入参是复杂 DTO 时,序列化开销大、输出冗长,-x 2通常够用
快速定位某次请求的入参:靠 condition-express 过滤
生产环境多人共用,直接 watch 会混入无关请求。用 OGNL 条件表达式精准捕获目标调用:
- 按参数值过滤:
watch -b com.example.controller.OrderController createOrder "{params}" 'params[0].getUserId() == 12345' -n 1 - 按线程名过滤(比如网关透传 traceId):
'#thread.name.contains("http-nio-8080-exec-") && #thread.name.contains("trace-abc123")' - 耗时超阈值才触发:
'#cost > 500'(注意:这个只能用在-s或-f,-b时#cost还没开始计时) - 空参保护:
'params.length > 0 && params[0] != null',避免 NPE 导致 watch 自动退出
条件表达式里所有变量都需用 # 前缀(如 #cost、#thread),params、returnObj 等不用。
看不到输出?先检查 -v 和匹配结果
执行 watch 后无任何输出,不是命令错了,而是根本没触发。加 -v 参数看底层行为:
watch -v -b com.example.service.UserService findById "{params}" 'params[0] > 0' -n 1
输出里会明确告诉你:Affect(class count: 1, method count: 1) 表示成功绑定;接着每行 Condition express: ..., result: false 就说明条件不满足,函数虽被调用但被过滤掉了。这时候要回头检查:参数索引对不对(params[0] 还是 params[1])、类型是否匹配(Long vs Integer)、条件逻辑是否反了。
真正容易被忽略的是:Spring 代理、泛型擦除导致的参数类型判断失效,以及 -b 下 #cost 不可用——这些细节不验证,光看文档容易卡住半天。










