Servlet規範中的Filter引入了一個功能強大的攔截模式。Filter能在request到達servlet的服務方法以前攔截request對象,而在服務方法轉移控制後又能攔截response對象。html
Tomcat 爲了屏蔽內部的catalina容器的相關方法,使用戶免受非sevlet標準方法的干擾。使用了兩個包裝類(RequestFacade 和 ResponseFacade)的實例傳遞給Servlet使用。這兩個包裝類分別實現了Servlet標準的(HttpServletRequest 和 HttpServletResponse)接口。關於其關係另起一文介紹:關於Tomcat中封裝請求-響應的結構的分析java
客戶端發來的請求request對象中的參數是沒法改變的,HttpServletRequest沒有定義setter方法,沒法進行修改操做。使用response輸出的數據會寫入到默認的輸出端,因此沒法獲取到數據。app
HttpServletRequestWrapper 和 HttpServletResponseWrapperide
然而Servlet標準中的兩個包裝類(HttpServletRequestWrapper 和 HttpServletResponseWrapper)是(HttpServletRequest 和 HttpServletResponse)接口的實現類,它們的實例經過惟一的公開構造方法持有一個(HttpServletRequest 和 HttpServletResponse)的對象,並經過實現的(HttpServletRequest 和 HttpServletResponse)接口方法直接調用內部對象的對應方法,即該類包裝了對請求和響應的全部接口操做。函數
經過繼承這兩個類,重寫咱們須要改變的代理方法獲得咱們須要的包裝類,再將request和response對象包裝到其實例中,將包裝後的對象經過調用鏈傳遞下去便可實現某些特殊操做。例如:url
繼承HttpServletRequestWrapper類並重寫部分getter方法,達到「修改」請求參數的需求(代理對象中持有的原對象並未改變,只是經過代理對象中重寫的getter方法返回設置的值)。spa
繼承HttpServletResponseWrapper類並重寫getWriter()方法,攔截後續響應數據,使之不經過原對象直接輸出到客戶端,便於處理數據。代理
實例excel
一個攔截response響應數據的例子:code
首先編寫了一個過濾器過濾指定url-pattern上的Servlet:
Filter.java的doFilter方法:
/** * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain) */ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // TODO Auto-generated method stub request.setCharacterEncoding("utf-8"); response.setCharacterEncoding("utf-8"); response.setContentType("text/html;charset=utf-8"); // place your code here SimpleDateFormat sdf = new SimpleDateFormat("yy-MM-dd HH:mm:ss.SSS"); response.getWriter().append(sdf.format(new Date()) + "過濾器LogFilter前半段被執行<br />"); // pass the request along the filter chain chain.doFilter(request, response); response.getWriter().append("<br />" + sdf.format(new Date()) + "過濾器LogFilter後半段被執行"); }
而後新建一個包裝類繼承於HttpServletResponseWrapper,編寫構造函數傳入HttpServletResponse參數並直接傳給父類構造方法,設置一個用來存放攔截到的響應數據的CharArrayWriter對象,重寫getWriter()方法將CharArrayWriter對象返回,從而攔截調用getWriter()方法輸出的響應數據到CharArrayWriter對象中,再設置一個獲取CharArrayWriter對象的方法用於讀取攔截到的響應數據:
<代理類>.java
public class CheckResponseData extends HttpServletResponseWrapper { private CharArrayWriter charArrayWriter = new CharArrayWriter(); public CheckResponseData(HttpServletResponse response) { super(response); // TODO 自動生成的構造函數存根 } @Override public PrintWriter getWriter() throws IOException { return new PrintWriter(charArrayWriter); } public CharArrayWriter getCharWriter() { return charArrayWriter; } }
修改以前的過濾器的doFilter方法爲:
/** * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain) */ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // TODO Auto-generated method stub request.setCharacterEncoding("utf-8"); response.setCharacterEncoding("utf-8"); response.setContentType("text/html;charset=utf-8"); // place your code here SimpleDateFormat sdf = new SimpleDateFormat("yy-MM-dd HH:mm:ss.SSS"); response.getWriter().append(sdf.format(new Date()) + "過濾器LogFilter前半段被執行<br />"); // pass the request along the filter chain System.out.println("構造響應數據攔截對象"); CheckResponseData checkResponseData = new CheckResponseData((HttpServletResponse) response); checkResponseData.setCharacterEncoding("utf-8"); System.out.println("開始調用鏈"); chain.doFilter(request, checkResponseData); System.out.println("調用鏈返回"); response.getWriter().append("<br />" + sdf.format(new Date()) + "過濾器LogFilter後半段被執行"); String realRes = checkResponseData.getCharWriter().toString(); System.out.println(realRes); response.getWriter().append("<br />Filter經過代理對象截獲了自調用鏈開始到返回時的響應數據:<br />"+realRes+"<br />#到此結束#<br />"); /* chain.doFilter(request, response); response.getWriter().append("<br />" + sdf.format(new Date()) + "過濾器LogFilter後半段被執行"); */ }
這樣在過濾器被執行時會向後續調用鏈傳遞一個包裝了原響應對象的代理對象,經過重寫的getWriter()代理方法攔截了後續響應數據到代理對象中的CharArrayWriter對象裏,在調用鏈返回該過濾器時再將攔截到的數據放在兩行標識之間輸出,效果以下:
18-01-18 02:51:50.963過濾器LogFilter前半段被執行 18-01-18 02:51:50.976過濾器LogFilter後半段被執行 Filter經過代理對象截獲了自調用鏈開始到返回時的響應數據: Servlet被執行 #即將使用include轉發 HTTP請求實例 accept:application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg, application/x-ms-xbap, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */* accept-language:zh-CN cache-control:no-cache ua-cpu:*********** accept-encoding:gzip, deflate user-agent:************************** host:localhost:8080 connection:Keep-Alive Servlet繼續執行 #到此結束#
成功攔截到了後續響應數據。
更多用法參照上例,基本思路就是經過代理對象中重寫的代理方法改變原請求-響應流程。