2017-03-20 27 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)。例如,這可能是一個方便的地方,用於初始創建您的上下文對象並將其綁定到新會話,以便您的過濾器不需要關心他們是否需要處理該對象。 –