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
有个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 和类似的框架使用。
更 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());
}
}
我有一个 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
有个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 和类似的框架使用。
更 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());
}
}