ThreadLocale值在Servlet过滤器中混淆

时间:2017-03-20 15:16:22

标签: java session thread-safety struts-1

我正在研究一个凌乱的Struts 1应用程序,该应用程序利用自定义上下文类在整个应用程序中存储值。基本上它只用于存储会话范围变量。我想使用这个自定义类的原因是,其他无法访问http会话的类仍然可以获取和设置会话变量。

无论如何,大多数情况下这都很好。在整个Actions和服务类中使用自定义上下文来共享变量没有问题。但是,我刚刚发现在Http Filter中使用这个自定义上下文的事情并没有那么好用!它似乎随机地从不同的会话中提取值。通过会话,我实际上是指线程,因为这个自定义上下文使用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));
    }
}

同样,除了过滤器之外,这似乎在其他所有类中都很好用。让我告诉你它混乱的过滤器。

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方法中抓取用户时,它将从另一个登录用户中随机获取用户对象。显然不是一个好的情况!

这种情况发生的唯一时间是来自其他登录用户的某些活动。我可以整天坐在那里继续刷新用户A的会话,这不会是一个问题。但是,在以用户B执行某些操作然后再次刷新用户A的会话之后,通常会交换它。但是如果我再次刷新用户A的会话,事情就会恢复正常。

我注意到,当应用程序实际部署到远程开发tomcat服务器时,这种情况发生得更频繁。它仍然在本地运行时发生,但不像远程部署时那样频繁。它几乎100%的时间都是远程发生的。

我已经检查了过滤器内部的会话变量,并且似乎不存在会话本身的问题。我已经确认,即使从ThreadLocale变量中提取了错误的用户,会话ID仍然是正确的。

任何人都可以帮助我吗?感谢。

1 个答案:

答案 0 :(得分:2)

您的策略无可挽回地存在缺陷。除非您的servlet引擎是单线程的,否则您不能依赖相同的线程来处理给定会话中的每个请求。而且,与所述问题更相关,即使在给定会话中处理一个请求的同一线程也用于处理该会话中的下一个请求时,假设它不处理属于该请求的请求是不安全的。两者之间的不同会话。无法保证线程/会话绑定。

无论如何,你似乎错过了显而易见的事实。如果要存储会话范围变量,则将它们存储在会话中。这就是会话属性的用途。请参阅HttpSession.setAttribute()HttpSession.getAttribute() - 您可以使用它们来设置和检索您的上下文对象。