Servlet规范中的Filter引入了一个功能强大的拦截模式。Filter能在request到达servlet的服务方法之前拦截request对象,而在服务方法转移控制后又能拦截response对象。

Tomcat 为了屏蔽内部的catalina容器的相关方法,使用户免受非sevlet标准方法的干扰。使用了两个包装类(RequestFacade 和 ResponseFacade)的实例传递给Servlet使用。这两个包装类分别实现了Servlet标准的(HttpServletRequest 和 HttpServletResponse)接口。关于其关系另起一文介绍:关于Tomcat中封装请求-响应的结构的分析

客户端发来的请求request对象中的参数是无法改变的,HttpServletRequest没有定义setter方法,无法进行修改操作。使用response输出的数据会写入到默认的输出端,所以无法获取到数据。


HttpServletRequestWrapper 和 HttpServletResponseWrapper

然而Servlet标准中的两个包装类(HttpServletRequestWrapper 和 HttpServletResponseWrapper)是(HttpServletRequest 和 HttpServletResponse)接口的实现类,它们的实例通过唯一的公开构造方法持有一个(HttpServletRequest 和 HttpServletResponse)的对象,并通过实现的(HttpServletRequest 和 HttpServletResponse)接口方法直接调用内部对象的对应方法,即该类包装了对请求和响应的所有接口操作。

通过继承这两个类,重写我们需要改变的代理方法得到我们需要的包装类,再将request和response对象包装到其实例中,将包装后的对象通过调用链传递下去即可实现某些特殊操作。例如:

继承HttpServletRequestWrapper类并重写部分getter方法,达到“修改”请求参数的需求(代理对象中持有的原对象并未改变,只是通过代理对象中重写的getter方法返回设置的值)。

继承HttpServletResponseWrapper类并重写getWriter()方法,拦截后续响应数据,使之不通过原对象直接输出到客户端,便于处理数据。


实例

一个拦截response响应数据的例子:

首先编写了一个过滤器过滤指定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继续执行
#到此结束#

成功拦截到了后续响应数据。


更多用法参照上例,基本思路就是通过代理对象中重写的代理方法改变原请求-响应流程。

最新文章

  1. Vmware虚拟机Devstack安装openstack(All in one)
  2. python3下urlopen解析中文url编码错误
  3. Python&gt;&gt;&gt;使用Python和Pygame创建画板
  4. long l=88;这个表达式是正确的,因为long比int类型大,会发生自动转换
  5. Socket accept failed
  6. 详解SpringMVC请求的时候是如何找到正确的Controller[附带源码分析]
  7. Java:数组
  8. 20145305 《Java程序设计》第8周学习总结
  9. java中volatitle关键字的作用
  10. Android:主题(Theme)
  11. winsock编程IOCP模型实现代码
  12. Oracle GoldenGate中HANDLECOLLISIONS参数使用详解
  13. Angular JS中的路由
  14. HNOI2017 单旋
  15. mysql windows 安装 错误
  16. MySQL-连表查询联系
  17. mysql命名规范
  18. selenium+java二元素定位
  19. NPOI处理Word文本中上下角标
  20. 批量ping测试的脚本

热门文章

  1. EMMC架构
  2. C#网络编程(异步传输字符串) - Part.3
  3. bzoj 3295 (洛谷3157、3193) [Cqoi2011]动态逆序对——树套树 / CDQ分治
  4. SQL Server数据库优化经验总结
  5. MyBatis缓存结构
  6. mysql5.7不支持0000-00-00 00:00:00的默认时间设置
  7. 好用的一个object c 宏
  8. windows服务器审核失败消息:事件ID: 861 进程标识符:904
  9. ansible安装配置zabbix客户端
  10. 代码说明call和apply方法的区别 (咱们这方面讲解的少,这样的题有变式,需要举例讲解一下)