Spring Boot 2.2.6.RELEASE Rest Controller 请求参数混杂问题排查求助
这种参数串扰的问题我之前也碰到过,几乎可以100%确定是控制器类的线程安全漏洞导致的——你的RefDataServerController作为Spring默认的单例Bean,所有请求都会复用同一个实例,如果类里定义了成员变量来存储请求参数或临时业务数据,多个并发请求(线程)就会互相覆盖这些变量的值,最终出现你看到的“两个请求的参数混到同一次调用里”的诡异现象。
为什么会发生这种情况?
Spring Boot的Rest Controller默认是单例模式,也就是说整个应用生命周期里只有这一个控制器实例在处理所有请求。当多个应用同时发请求时,每个请求对应一个线程,这些线程会同时操作控制器实例的成员变量,线程切换时就会出现数据覆盖、脏读的问题,比如第二个请求的cert值覆盖了第一个请求的,日志里就会出现两个cert参数的奇怪URL(其实是日志记录时,成员变量已经被另一个线程修改了)。
具体解决方案
1. 优先使用方法局部变量(最推荐)
把所有和当前请求相关的参数、临时数据都放在控制器方法的内部,不要放在类的成员变量里。局部变量是线程安全的,每个请求线程都会拥有自己的变量副本,不会互相干扰。
错误写法(线程不安全):
@RestController public class RefDataServerController { // 危险:成员变量,会被多个线程共享 private String cert; private String instance; @GetMapping("/refdata/exec/text/getParticipantBitCross") public String getParticipantBitCross(@RequestParam String cert, @RequestParam String instance) { this.cert = cert; this.instance = instance; // 业务逻辑中使用this.cert和this.instance return processData(); } }
正确写法(线程安全):
@RestController public class RefDataServerController { @GetMapping("/refdata/exec/text/getParticipantBitCross") public String getParticipantBitCross(@RequestParam String cert, @RequestParam String instance) { // 局部变量,每个请求线程独立拥有 String currentCert = cert; String currentInstance = instance; // 业务逻辑直接使用局部变量 return processData(currentCert, currentInstance); } }
2. 若必须用成员变量,使用ThreadLocal隔离线程数据
如果因为某些业务需求必须用成员变量,一定要用ThreadLocal来存储每个线程独有的数据,它会为每个线程创建独立的变量副本,避免线程间的数据干扰。注意使用后必须在finally块中移除数据,防止线程池复用导致的内存泄漏。
@RestController public class RefDataServerController { // ThreadLocal保证每个线程有独立的数据副本 private final ThreadLocal<String> certThreadLocal = new ThreadLocal<>(); private final ThreadLocal<String> instanceThreadLocal = new ThreadLocal<>(); @GetMapping("/refdata/exec/text/getParticipantBitCross") public String getParticipantBitCross(@RequestParam String cert, @RequestParam String instance) { try { certThreadLocal.set(cert); instanceThreadLocal.set(instance); // 业务逻辑中获取当前线程的变量 return processData(certThreadLocal.get(), instanceThreadLocal.get()); } finally { // 必须移除,防止内存泄漏 certThreadLocal.remove(); instanceThreadLocal.remove(); } } }
3. 检查第三方依赖的线程安全性
如果业务逻辑中用到了非线程安全的工具类(比如ArrayList、HashMap)作为控制器的成员变量,也要改为局部变量,或者替换成线程安全的实现(比如ConcurrentHashMap、CopyOnWriteArrayList)。
验证方法
修改代码后,可以用JMeter等压测工具模拟多个并发请求,同时在控制器方法开头打印当前线程ID和请求参数,确认每个线程的参数都是独立的,不会再出现参数混杂的情况。
内容的提问来源于stack exchange,提问作者Michael Starkie

