ThreadLocale 值在 Servlet 过滤器中混淆
ThreadLocale value getting mixed up in Servlet Filter
我正在开发一个凌乱的 Struts 1 应用程序,它使用自定义上下文 class 在整个应用程序中存储值。基本上它只用于存储会话范围变量。我想使用这个自定义 class 的原因是其他无法访问 http 会话的 classes 仍然可以获取和设置会话变量。
无论如何,这在大多数情况下都可以正常工作。自定义上下文在整个操作和服务 classes 中使用,可以毫无问题地共享变量。但是,我刚刚发现在 Http 过滤器中使用这个自定义上下文并不能很好地解决问题!看起来它会随机从不同的会话中提取值。至于会话,我实际上是指线程,因为这个自定义上下文使用 ThreadLocale 来完成它的脏活。
看看
package com.zero.alpha.common;
import java.io.Serializable;
import java.util.Hashtable;
import java.util.Locale;
import java.util.Map;
public final class CustomContext implements Serializable {
private static final long serialVersionUID = 400312938676062620L;
private static ThreadLocal<CustomContext> local = new ThreadLocal() {
protected CustomContext initialValue() {
return new CustomContext("0", "0", Locale.getDefault());
}
};
private String dscId;
private String sessionId;
private Locale locale;
private Map<String, Serializable> generalArea;
public CustomContext(String dscId, String sessionId, Locale locale) {
this.dscId = dscId;
this.sessionId = sessionId;
if (locale != null) {
this.locale = locale;
} else {
this.locale = Locale.getDefault();
}
this.generalArea = new Hashtable();
}
public static CustomContext get() {
return ((CustomContext) local.get());
}
public static void set(CustomContext context) {
local.set(context);
}
public String getDscId() {
return this.dscId;
}
public String getSessionId() {
return this.sessionId;
}
public Locale getLocale() {
return this.locale;
}
public Serializable getGeneralArea(String key) {
return ((Serializable) this.generalArea.get(key));
}
public Serializable putGeneralArea(String key, Serializable value) {
return ((Serializable) this.generalArea.put(key, value));
}
public void clearGeneralArea() {
this.generalArea.clear();
}
public Serializable removeGeneralArea(String key) {
return ((Serializable) this.generalArea.remove(key));
}
}
同样,这似乎在除过滤器之外的所有其他 class 中都工作得很好而且花花公子。让我向您展示过滤器出现问题的地方。
package com.zero.alpha.myapp.common.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.zero.alpha.common.CustomContext;
import com.zero.alpha.myapp.utility.CommonConstants;
import com.zero.alpha.myapp.utility.CommonHelpers;
import com.zero.alpha.myapp.UserDomain;
public class LoginFilter implements Filter {
public LoginFilter() {
}
public void init(FilterConfig config) throws ServletException {}
public void destroy() {}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
// Don't use the login filter during a login or logout request
if (req.getServletPath().equals("/login.do")
|| req.getServletPath().equals("/login-submit.do")
|| req.getServletPath().equals("/logout.do")) {
chain.doFilter(request, response);
} else {
doFilter(req, (HttpServletResponse) response, chain);
}
}
protected void doFilter(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpSession session = request.getSession(false);
// This is the problem right here. Sometimes this will grab the value of a different user currently logged in
UserDomain user = (UserDomain) CustomContext.get()
.getGeneralArea(CommonConstants.ContextKey.USER_SESSION);
if (session == null || user == null) {
// Unauthorized
response.sendRedirect(loginPage);
} else {
// Authorized
session.setAttribute("userInfo", CommonHelpers.getUserDisplay(user));
chain.doFilter(request, response);
}
}
}
在doFilter方法中使用自定义上下文抓取用户时,会随机抓取另一个登录用户的用户对象。显然不是什么好情况!
唯一一次发生这种情况是在 activity 来自不同的登录用户之后。我可以坐在那里一整天并不断刷新用户 A 的会话,这不会有问题。但是,在以用户 B 的身份执行某些操作后,然后再次刷新用户 A 的会话后,通常会被交换。但是如果我再次刷新用户 A 的会话,一切就会恢复正常。
我注意到当应用程序实际部署到远程开发 tomcat 服务器时,这种情况发生得非常频繁。当 运行 在本地时它仍然会发生,但不像远程部署时那么频繁。它几乎 100% 的时间都是远程发生的。
我检查了过滤器内部的会话变量,会话本身似乎没有问题。我已经确认即使从 ThreadLocale 变量中提取了不正确的用户,会话 ID 仍然正确。
谁能帮帮我?谢谢
您的策略存在无法挽回的缺陷。除非您的 servlet 引擎是单线程的,否则您不能依赖同一个线程来处理给定会话中的每个请求。此外,与所述问题更相关的是,即使在给定会话中处理一个请求的同一线程也用于处理该会话中的下一个请求,假设它不处理属于两者之间的不同会话。不保证线程/会话绑定。
无论如何,您似乎错过了显而易见的事情。如果要存储会话范围变量,则 将它们存储在会话中 。这就是会话属性的用途。请参阅 HttpSession.setAttribute()
和 HttpSession.getAttribute()
——您可以使用它们来设置和检索上下文对象。
我正在开发一个凌乱的 Struts 1 应用程序,它使用自定义上下文 class 在整个应用程序中存储值。基本上它只用于存储会话范围变量。我想使用这个自定义 class 的原因是其他无法访问 http 会话的 classes 仍然可以获取和设置会话变量。
无论如何,这在大多数情况下都可以正常工作。自定义上下文在整个操作和服务 classes 中使用,可以毫无问题地共享变量。但是,我刚刚发现在 Http 过滤器中使用这个自定义上下文并不能很好地解决问题!看起来它会随机从不同的会话中提取值。至于会话,我实际上是指线程,因为这个自定义上下文使用 ThreadLocale 来完成它的脏活。
看看
package com.zero.alpha.common;
import java.io.Serializable;
import java.util.Hashtable;
import java.util.Locale;
import java.util.Map;
public final class CustomContext implements Serializable {
private static final long serialVersionUID = 400312938676062620L;
private static ThreadLocal<CustomContext> local = new ThreadLocal() {
protected CustomContext initialValue() {
return new CustomContext("0", "0", Locale.getDefault());
}
};
private String dscId;
private String sessionId;
private Locale locale;
private Map<String, Serializable> generalArea;
public CustomContext(String dscId, String sessionId, Locale locale) {
this.dscId = dscId;
this.sessionId = sessionId;
if (locale != null) {
this.locale = locale;
} else {
this.locale = Locale.getDefault();
}
this.generalArea = new Hashtable();
}
public static CustomContext get() {
return ((CustomContext) local.get());
}
public static void set(CustomContext context) {
local.set(context);
}
public String getDscId() {
return this.dscId;
}
public String getSessionId() {
return this.sessionId;
}
public Locale getLocale() {
return this.locale;
}
public Serializable getGeneralArea(String key) {
return ((Serializable) this.generalArea.get(key));
}
public Serializable putGeneralArea(String key, Serializable value) {
return ((Serializable) this.generalArea.put(key, value));
}
public void clearGeneralArea() {
this.generalArea.clear();
}
public Serializable removeGeneralArea(String key) {
return ((Serializable) this.generalArea.remove(key));
}
}
同样,这似乎在除过滤器之外的所有其他 class 中都工作得很好而且花花公子。让我向您展示过滤器出现问题的地方。
package com.zero.alpha.myapp.common.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.zero.alpha.common.CustomContext;
import com.zero.alpha.myapp.utility.CommonConstants;
import com.zero.alpha.myapp.utility.CommonHelpers;
import com.zero.alpha.myapp.UserDomain;
public class LoginFilter implements Filter {
public LoginFilter() {
}
public void init(FilterConfig config) throws ServletException {}
public void destroy() {}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
// Don't use the login filter during a login or logout request
if (req.getServletPath().equals("/login.do")
|| req.getServletPath().equals("/login-submit.do")
|| req.getServletPath().equals("/logout.do")) {
chain.doFilter(request, response);
} else {
doFilter(req, (HttpServletResponse) response, chain);
}
}
protected void doFilter(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpSession session = request.getSession(false);
// This is the problem right here. Sometimes this will grab the value of a different user currently logged in
UserDomain user = (UserDomain) CustomContext.get()
.getGeneralArea(CommonConstants.ContextKey.USER_SESSION);
if (session == null || user == null) {
// Unauthorized
response.sendRedirect(loginPage);
} else {
// Authorized
session.setAttribute("userInfo", CommonHelpers.getUserDisplay(user));
chain.doFilter(request, response);
}
}
}
在doFilter方法中使用自定义上下文抓取用户时,会随机抓取另一个登录用户的用户对象。显然不是什么好情况!
唯一一次发生这种情况是在 activity 来自不同的登录用户之后。我可以坐在那里一整天并不断刷新用户 A 的会话,这不会有问题。但是,在以用户 B 的身份执行某些操作后,然后再次刷新用户 A 的会话后,通常会被交换。但是如果我再次刷新用户 A 的会话,一切就会恢复正常。
我注意到当应用程序实际部署到远程开发 tomcat 服务器时,这种情况发生得非常频繁。当 运行 在本地时它仍然会发生,但不像远程部署时那么频繁。它几乎 100% 的时间都是远程发生的。
我检查了过滤器内部的会话变量,会话本身似乎没有问题。我已经确认即使从 ThreadLocale 变量中提取了不正确的用户,会话 ID 仍然正确。
谁能帮帮我?谢谢
您的策略存在无法挽回的缺陷。除非您的 servlet 引擎是单线程的,否则您不能依赖同一个线程来处理给定会话中的每个请求。此外,与所述问题更相关的是,即使在给定会话中处理一个请求的同一线程也用于处理该会话中的下一个请求,假设它不处理属于两者之间的不同会话。不保证线程/会话绑定。
无论如何,您似乎错过了显而易见的事情。如果要存储会话范围变量,则 将它们存储在会话中 。这就是会话属性的用途。请参阅 HttpSession.setAttribute()
和 HttpSession.getAttribute()
——您可以使用它们来设置和检索上下文对象。