標籤:blog http io 使用 java ar 檔案 資料 2014
WEB案例
目前有一個2005年開始,基於Struts1的Web項目A,其驗證部分依賴於主站的SSO(單點登入)。在請求網站A的時候,使用者會被強制帶去做SSO驗證,通過身分識別驗證後後,主站會自動地把請求轉寄至A網站,並在request header中添加了用於儲存登入使用者ID的新屬性SM_USER,然後A網站根據使用者ID提供相應的服務。由於該項目是一個既存項目,所以其中殘餘大量像下面一樣的測試代碼。
String user_id = request.getHeader("XX_USER");if (user_id == null) { user_id = "my_hard_coded_user_id";}UserProfile userProfile = new BizDao().getUserProfile(user_id);
其根本原因是在本地測試的時候無法於生產環境的SSO對接,只有把代碼提交至公用的DEV伺服器、UAT伺服器或PROD伺服器後,才能享受SSO為A站所提供的XX_USER資料,所以程式員在無法取得header資料的情況下,直接簡單粗暴地對本地環境進行了寫入程式碼處理。在只有幾個人的小團隊,這樣的處理可能看起來也無所謂,但長年下來經手的人數也相當可觀了,很多人都習慣使用自己的ID做測試,所以在SVN的曆史版本中,硬式編碼ID從A改到B、改到C、改到D……
問題分析
每個人都根據自己的喜好選擇了使用自己的ID或是他人的ID,那麼是否有一種辦法可以統一介面一勞永逸呢?可能最容易想到的就是HttpServletRequest.setHeader,可惜HttpServletRequest並沒有這樣的API,為啥?個人猜測可能是因為request源於用戶端,伺服器端應該保持請求的原始性、純淨性和無毒性吧,而HttpServletResponse(http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletResponse.html)則是在應用程式的控制之下,所以程式員可以對其任意踐踏,setHeader、addHeader以及getHeader、getHeaders、getHeaderNames。既然沒有API可用,直接進行封裝代理吧,在Google的協助下我們可以找到現成的方案來對request中的header進行重新定製,重點是HttpServletRequestWrapper類的實現。
http://vangjee.wordpress.com/2009/02/25/how-to-modify-request-headers-in-a-j2ee-web-application/
其實這個方案是在我實現動態代理方案之後找到的,兩者思路幾乎是一樣的,都是對原始request進行封裝代理,重新實現getHeader方法。
解決方案
不廢話,直接上代碼!
private static class RequestInvocationHandler implements InvocationHandler { private HttpServletRequest wrappedRequest; public RequestInvocationHandler(HttpServletRequest r) { wrappedRequest = r; } public static String dummyData = "my_hard_coded_id"; public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("getHeader".equals(method.getName()) && args.length == 1 && "SM_USER".equals(args[0])) { return dummyData; } return method.invoke(wrappedRequest, args); } public static HttpServletRequest createRequestWapper(HttpServletRequest r) { if (null != r.getParameter("u")) { dummyData = r.getParameter("u"); } return (HttpServletRequest)(Proxy.newProxyInstance(HttpServletRequest.class.getClassLoader(), new Class[] {HttpServletRequest.class}, new RequestInvocationHandler(r))); }}
上面代碼實現了對Request對象的代理,所有針對Request對象的調用都需經過invoke方法。在invoke方法中,我們可以針對不同的方法簽名進行更為細粒度的控制。比如在WEB案例中提到的問題,可以專門針對getHeader方法進行重新定製,本來Request Header中並無該資料,但我們可以硬生生地“造”出資料。另外除了可以“造”出測試資料外,還可以通過請求中攜帶的參數“u”動態地進行資料修改,這樣就實現了使用者切換的功能。
既然代理類的問題解決了,下面該談談應在何時何地植入代理對象了。何時何地可以理解為切入時機,其實最初的是在org.apache.struts.action.RequestProcessor中植入代理對象的,但是在使用過程中發現該實現有一個弊端,那就是在非Struts請求時無法使用代理對象,比如直接存取JSP檔案或是其它Servlet所提供的服務路徑。這時自然而然地想到Filter,而使用Filter也是一痛,加入一個全新的Filter吧,有點破壞的整體設計的感覺,而放入其它Filter之中的話,看起來又不倫不類。但好歹是測試用,這兩個方案可以折衷選擇其一,具體實現如下。
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletExceptionrequest = RequestInvocationHandler.createRequestWapper((HttpServletRequest)request);chain.doFilter(request, response);}
整體工作流程如下,使用者首先發出請求,然後request對象在filter中被替換成代理對象,替換後的request被傳入doFilter方法,之後不論是Servlet、Struts還是JSP,它們用到的都是我們定製的Request對象。
+---------+ +---------+ | | | | Request | | +-----> | Servlet |+--------> | Filter | | | | +----+ | | Struts | | Request | | | Response | is | | |<--------+ | wrapped | | | | here | <-----+ | | | | | | +---------+ +---------+
後記
當然,如果想要修改request對象不止這一種方法,比如正統的方式是定義一個HttpServletRequestWrapper來重新定義request,具體案例請參考http://vangjee.wordpress.com/2009/02/25/how-to-modify-request-headers-in-a-j2ee-web-application/
動態代理在WEB與JDBC開發中的應用(WEB篇)