You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 03:39:04