【问题标题】:ThreadLocal in DispatcherServletDispatcherServlet 中的 ThreadLocal
【发布时间】:2016-05-12 14:37:09
【问题描述】:

我有一个带有 javascript UI 的 Spring MVC (v4.1.3) Web 应用程序。我已经实现了一个自定义 DispatcherServlet 并在 web.xml 中进行了相同的配置

UI 向服务器发出的每个请求的 HTTP 标头中都会发送一个唯一的屏幕代码。

在我的自定义调度程序 servlet 的 doService 方法中,我捕获 HTTP 标头并将值放入 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 变量中存在同名的空 DTO - 因此,有时当我访问服务层中的 ThreadLocal 时,我得到一个空值

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

提前致谢。

【问题讨论】:

  • 因为容器使用线程池来服务请求,因为你只是设置东西而不是清除值。旁边检索值的方式是非常可怕的恕我直言。我也不明白为什么你需要一个自定义的DispatcherServlet。使用Filter,它使用UiCodeHolder(很像LocaleResolver 和朋友的工作)来设置、检索和清除ThreadLocal
  • 添加@M.Deinum 所说的内容。只要有线程池被使用,就不应该使用 ThreadLocal。这会导致内存泄漏。
  • 为什么在doservice中使用threadlocal?

标签: java spring spring-mvc thread-local


【解决方案1】:

更多的 Spring 方法是使用 request-scoped bean 来提取并保存标题:

@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());
    }
}

【讨论】:

    【解决方案2】:

    您的代码容易出错并且难以理解为什么您需要自定义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 有一个 static ThreadLocal 来保留值。

    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 和框架等使用。

    【讨论】:

    • 即使Holder 也有点多余,因为ThreadLocal 可以直接作为静态字段放置在过滤器中。
    • 你可以,但是从 servlet 层引用 Web 相关的东西在我的书中是不行的。然后,您的服务层对 Web 有(直接)依赖关系,这在正确分离的应用程序中不是您想要的。
    猜你喜欢
    • 1970-01-01
    • 2014-09-22
    • 2017-12-05
    • 2021-09-27
    • 1970-01-01
    • 2016-09-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多