DispatcherServlet 中的 ThreadLocal

ThreadLocal in DispatcherServlet

我有一个 Spring MVC (v4.1.3) Web 应用程序 javascript UI。我已经实现了自定义 DispatcherServlet 并在 web.xml

中进行了相同的配置

在 UI 向服务器发出的每个请求的 HTTP Header 中发送了一个唯一的屏幕代码。

在我的自定义调度程序 servlet 的 doService 方法中,我捕获了 HTTP Header 并将该值放入 ThreadLocal dto 变量中。我在服务层访问这个 ThreadLocal 变量以执行一些对所有请求通用的审计逻辑。

来自 CustomDispatcherServlet 的代码:

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    String uiCode = request.getHeader("uiCode");

    if ((uiCode != null && !uiCode.trim().isEmpty())) {
        UiCodeDto uiCodeDto = new UiCodeDto(uiCode);
        final ThreadLocal<UiCodeDto> threadLocalUser = new ThreadLocal<UiCodeDto>();
        threadLocalUser.set(uiCodeDto);
    }

    ...
    super.doService(request, response);
}

来自服务层的代码:

UiCodeDto temp = ThreadLocalUtil.getUiCodeDto(Thread.currentThread());

从 ThreadLocal 检索值的 ThreadLocalUtil 代码:

public final class ThreadLocalUtil {
    public static UiCodeDto getUiCodeDto(Thread currThread) {
        UiCodeDto UiCodeDto = null;

        try {
            Field threadLocals = Thread.class.getDeclaredField("threadLocals");
            threadLocals.setAccessible(true);
            Object currentThread = threadLocals.get(currThread);
            Field threadLocalsMap = currentThread.getClass().getDeclaredField("table");
            threadLocalsMap.setAccessible(true);
            threadLocalsMap.setAccessible(true);
            Object[] objectKeys = (Object[]) threadLocalsMap.get(currentThread);
            for (Object objectKey : objectKeys) {
                if (objectKey != null) {
                    Field objectMap = objectKey.getClass().getDeclaredField("value");
                    objectMap.setAccessible(true);
                    Object object = objectMap.get(objectKey);

                    if (object instanceof UiCodeDto) {
                        UiCodeDto = (UiCodeDto) object;
                        break;
                    }
                }
            }
        } catch (Exception e) {
            ...
        }

        return UiCodeDto;
    }
}

问题如下—— 1. 我得到屏幕代码的随机值——这意味着一些 http 请求 N 的值来自 http 请求 N+1。 2. ThreadLocal 变量中有同名的 null DTO - 因此,有时当我访问服务层中的 ThreadLocal 时,我得到一个 null

我需要帮助来理解 DispatcherServlet 中 ThreadLocal 的行为 - 为什么它会在 doService 方法中获取另一个请求的值?

提前致谢。

您的代码容易出错且难以理解,为什么您需要自定义 DispatcherServlet。过滤器似乎更适合这项任务。

public class UiCodeFilter extends OncePerRequestFilter {

    protected void doFilterInternally(HttpServletRequest req, HttpServletResponse res, FilterChain chain) {
        try {
            String uiCode = req.getHeader("uiCode");

            if ((uiCode != null && !uiCode.trim().isEmpty())) {
                UiCodeDto uiCodeDto = new UiCodeDto(uiCode);
                UiCodeHolder.set(uiCodeDta);
            }
            chain.doFilter(req, res);
        } finally {
            UiCodeHolder.clear(); // Always clear!
        }

    }
}

UiCodeHolder有个staticThreadLocal保持值

public abstract class UiCodeHolder {
    static ThreadLocal<UiCodeDto> current = new ThreadLocal<>()

    public void set(UiCodeDto uiCode) {
        current.set(uiCode);
    }

    public UiCodeDta get() {
        return current.get();
    }

    public void clear() {
        current.remove(); // for older versions use current.set(null);
    }
}

在您的服务中,您现在只需执行 UiContextHolder.get() 即可获得正确的值。 UiCodeFilter 负责设置值,并在请求结束时再次清除该值以防止泄漏。

这种方法不需要丑陋的反射挂钩,很容易理解,被 Spring、Hibernate 和类似的框架使用。

更 Spring 的方法是使用 request-scoped bean 提取并保存 header:

@Component
@Scope(scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UiCodeDto {
    private String uiCode;

    @Inject
    public void setCode(HttpServletRequest req) {
        uiCode = req.getHeader("uiCode");
    }

    public String getUiCode() {
        return uiCode;
    }
}

您可以像普通 bean 一样使用它:

@Service
public class RandomService {

    @Inject
    UiCodeDto uiCodeDto;

    public void handle() {
        System.out.println(uiCodeDto.getUiCode());
    }
}