Java REST服务请求级单例静态变量实现方案问询
解决请求级“静态”计数的两种靠谱方案
你这个需求太典型了——既要Controller保持单例,又要每个请求有自己独立的计数,还不想在方法间传参数对吧?我之前做接口的时候也碰到过类似场景,给你两个实用的解决思路:
方案一:Spring原生的请求作用域Bean(推荐)
Spring本身就支持**请求作用域(Request Scope)**的Bean,每个HTTP请求都会生成一个全新的Bean实例,请求结束后自动销毁,完美契合你的需求。
步骤1:创建请求作用域的计数类
先写一个专门存计数的类,用@Scope标注为请求作用域:
import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import org.springframework.web.context.WebApplicationContext; @Component @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS) public class RequestCounter { private int count = 0; // 每次调用自增并返回当前值 public int incrementAndGet() { return ++count; } }
这里的proxyMode是为了让单例的Controller能正确注入请求作用域的Bean,因为默认情况下Spring不会给单例Bean注入非单例的Bean,代理模式可以解决这个问题。
步骤2:在Controller中注入使用
把这个计数Bean注入到你的APIController里,因为是请求作用域,每个请求进来都会拿到属于自己的RequestCounter实例:
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class APIController { private final RequestCounter requestCounter; // 构造注入(推荐的Spring注入方式) public APIController(RequestCounter requestCounter) { this.requestCounter = requestCounter; } @GetMapping("/getData") public String getData() { // 两次调用同一个请求内的计数 int first = requestCounter.incrementAndGet(); int second = requestCounter.incrementAndGet(); return String.format("首次调用返回%d,第二次返回%d", first, second); } // 其他需要用到计数的方法也可以直接调用requestCounter private int anotherMethod() { return requestCounter.incrementAndGet(); } }
这样不管你在Controller的哪个方法里调用incrementAndGet,都是当前请求的计数,不同请求之间完全隔离,Controller也保持单例,完美满足你的要求!
方案二:ThreadLocal(备选方案)
如果你不想单独创建一个Bean,也可以用ThreadLocal来实现——因为Spring默认是每个请求用一个线程处理,ThreadLocal会给每个线程存一个独立的变量副本。
代码实现
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; @RestController public class APIController { // 定义ThreadLocal存计数 private static final ThreadLocal<Integer> requestCounter = new ThreadLocal<>(); @GetMapping("/getData") public String getData() { // 初始化当前请求的计数为0 requestCounter.set(0); int first = getSingletonData(); int second = getSingletonData(); // 重要:请求结束后清理ThreadLocal,防止内存泄漏 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attributes != null) { attributes.getRequest().setAttribute("cleanup", () -> requestCounter.remove()); } return String.format("首次调用返回%d,第二次返回%d", first, second); } private int getSingletonData() { int current = requestCounter.get(); current++; requestCounter.set(current); return current; } }
注意事项
- 一定要记得在请求结束后调用
requestCounter.remove(),不然如果用了线程池(Tomcat默认是线程池),线程被复用的时候会带上之前请求的计数,而且可能导致内存泄漏。 - 这种方式比请求作用域Bean更“轻量”,但需要自己管理变量的生命周期,不如Spring原生的方案省心。
为什么不能用普通静态变量/局部变量?
- 普通静态变量:是应用级别的全局变量,所有请求共享,计数会一直累加,完全不符合“请求级隔离”的需求。
- 局部变量:每个方法调用都会重新初始化,没法在多个方法之间共享同一个计数,你就得在方法间传参数,不符合你的要求。
内容的提问来源于stack exchange,提问作者Tom Shir
相关产品推荐
相关产品推荐

