2017-03-20 26 views
-1

我正在研究一个凌乱的Struts 1应用程序,它利用自定义上下文类来存储整个应用程序的值。基本上它只用于存储会话范围变量。我猜想使用这个自定义类的原因是为了让其他没有访问http会话的类仍然可以获取和设置会话变量。ThreadLocale值在Servlet筛选器中混合起来

不管怎么说,大多数情况下,这工作得很好。自定义上下文在动作和服务类中用于共享变量,没有任何问题。但是,我刚发现,在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)); 
    } 
} 

同样,这似乎工作很好,很正常,不是一个过滤器等所有其他类中。让我向你展示过滤器在哪里混乱。

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

您确定用户b操作不访问自定义上下文实例吗? – efekctive

+0

@efekctive用户B的操作确实使用与用户A相同的自定义上下文。 – zero01alpha

回答

2

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

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

+0

谢谢。作为后续工作,你是否知道为什么这只发生在过滤器方法内部? – zero01alpha

+1

@ zero01alpha,我倾向于猜测你只能*在你的过滤方法中看到*。也许这是因为你的过滤器首先会在每个请求中进行破解,然后执行一些隐藏其他类的问题。尽管不存在线程/会话绑定,但在处理每个请求期间*有* request/session绑定。 –

+1

@ zero01alpha,您可能还会考虑您现在在过滤器中执行的一些工作,您可能更方便地在[SessionListener]中执行(http://docs.oracle.com/javaee/6/api/javax/servlet/改为http/HttpSessionListener.html)。例如,这可能是一个方便的地方,用于初始创建您的上下文对象并将其绑定到新会话,以便您的过滤器不需要关心他们是否需要处理该对象。 –