一 簡介
如題所示,有時候我們需要在一個請求到達Controller之前能夠截獲其請求,並且根據其具體情況對 HttpServletRequest 中的參數進行過濾或者修改。這時,有的同學可能會想:我們是否可以在一個Filter中將 HttpServletRequest 裏的所有參數都取出來分別進行過濾然後再放回到該HttpServletRequest 中呢?
很顯然,在 HttpServletRequest 貌似只有 setAttribute(String name, Object o) 這個方法可以設置參數,但是我們經過嘗試之後可以發現:使用 setAttribute(String name, Object o) 方法來重新設置參數顯然是不行的,因爲在Controller中獲取參數本質上還是調用的ServletRequest的public String getParameter(String name) 或者 public String[] getParameterValues(String name) 方法,因此想要達到“在Filter中修改HttpServletRequest的參數”的目的,顯然是需要使用裝飾模式來複寫這些方法才行的
在正式代碼之前,我還是先簡單介紹下ServletRequest、HttpServletRequest、ServletRequestWrapper以及HttpServletRequestWrapper這幾個接口或者類之間的層次關係,並說明“繼承HttpServletRequestWrapper類以實現在Filter中修改HttpServletRequest的參數”這種方式的原理是什麼
如果我們從網上下載tomcat的源代碼並查看的話,就可以很清楚地看到這幾個類之間的層次關係了,在eclipse中看,它們之間的層次關係是這樣的:
如果這個圖表還不夠清楚地話,我還畫了一個簡單的UML結構圖:
注:因爲我現在沒有下載專門的UML建模工具,因此就使用“畫圖”工具簡單地畫了一下類圖,同時這裏的ModifyParametersWrapper 是我後面舉例用到的的一個自定義的類
如果學過“裝飾模式”的童鞋可能已經發現了,上面這個關係毫無疑問是一個很標準的裝飾模式:
- ServletRequest 抽象組件
- HttpServletRequest 抽象組件的一個子類,它的實例被稱作“被裝飾者”
- ServletRequestWrapper 一個基本的裝飾類,這裏是非抽象的
- HttpServletRequestWrapper 一個具體的裝飾者,當然這裏也繼承了HttpServletRequest這個接口,是爲了獲取一些在ServletRequest中沒有的方法
- ModifyParametersWrapper 同樣是 一個具體的裝飾者(PS:我自定義的一個類)
注:一個標準的裝飾模式的UML類圖是這樣的:
那麼問題來了,如何在Filter中修改後臺Controller中獲取到的HttpServletRequest中的參數?
答:很簡單,只需要在Filter中自定義一個類繼承於HttpServletRequestWrapper,並複寫getParameterNames、getParameter、getParameterValues等方法即可
二 代碼實現
(1)自定義的過濾器ModifyParametersFilter.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | package cn.zifangsky.filter; import java.io.IOException; import java.util.Enumeration; import java.util.Map; import java.util.Vector; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; import org.springframework.web.filter.OncePerRequestFilter; public class ModifyParametersFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { ModifyParametersWrapper mParametersWrapper = new ModifyParametersWrapper(request); filterChain.doFilter(mParametersWrapper, response); } /** * 繼承HttpServletRequestWrapper,創建裝飾類,以達到修改HttpServletRequest參數的目的 */ private class ModifyParametersWrapper extends HttpServletRequestWrapper { private Map<String, String[]> parameterMap; // 所有參數的Map集合 public ModifyParametersWrapper(HttpServletRequest request) { super(request); parameterMap = request.getParameterMap(); } // 重寫幾個HttpServletRequestWrapper中的方法 /** * 獲取所有參數名 * * @return 返回所有參數名 */ @Override public Enumeration<String> getParameterNames() { Vector<String> vector = new Vector<String>(parameterMap.keySet()); return vector.elements(); } /** * 獲取指定參數名的值,如果有重複的參數名,則返回第一個的值 接收一般變量 ,如text類型 * * @param name * 指定參數名 * @return 指定參數名的值 */ @Override public String getParameter(String name) { String[] results = parameterMap.get(name); if (results == null || results.length <= 0) return null; else { System.out.println("修改之前: " + results[0]); return modify(results[0]); } } /** * 獲取指定參數名的所有值的數組,如:checkbox的所有數據 * 接收數組變量 ,如checkobx類型 */ @Override public String[] getParameterValues(String name) { String[] results = parameterMap.get(name); if (results == null || results.length <= 0) return null; else { int length = results.length; for (int i = 0; i < length; i++) { System.out.println("修改之前2: " + results[i]); results[i] = modify(results[i]); } return results; } } /** * 自定義的一個簡單修改原參數的方法,即:給原來的參數值前面添加了一個修改標誌的字符串 * * @param string * 原參數值 * @return 修改之後的值 */ private String modify(String string) { return "Modified: " + string; } } } |
上面的代碼很簡單,就是添加了一個內部類:ModifyParametersWrapper,然後複寫了ServletRequest中的幾個方法,具體來說就是將原來的每個參數的值的前面加上了“Modified: ”這個字符串
(2)在web.xml中註冊該過濾器:
1 2 3 4 5 6 7 8 9 10 11 | <filter> <filter-name>ModifyParametersFilter</filter-name> <filter-class>cn.zifangsky.filter.ModifyParametersFilter</filter-class> </filter> <filter-mapping> <filter-name>ModifyParametersFilter</filter-name> <url-pattern>/param/*</url-pattern> <!-- 直接從客戶端過來的請求以及通過forward過來的請求都要經過該過濾器 --> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> </filter-mapping> |
(3)添加一個測試使用的Controller,即:TestModifyController.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | package cn.zifangsky.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @Controller public class TestModifyController { @RequestMapping("/param/modify.html") public void modify(@RequestParam("name") String name){ System.out.println("修改之後: " + name); } } |
這裏沒有處理複雜的邏輯,僅僅只是簡單地輸出
(4)測試:
啓動項目之後訪問:http://localhost:9180/FilterDemo/param/modify.html?name=abc
可以發現,在控制檯中輸出如下:
1 2 | 修改之前2: abc 修改之後: Modified: abc |
這就表明了我們前面自定義的過濾器已經將HttpServletRequest中原來的參數成功修改了。同時,還說明SpringMVC的@RequestParam註解本質上調用的是ServletRequest中的 getParameterValues(String name) 方法而不是 getParameter(String name) 方法
注:tomcat-8.5.5源碼下載地址:http://pan.baidu.com/s/1skWOso9
參考文章: