Spring自定义Filter踩坑

通过继承OncePerRequestFilter,发现无法往Response写入内容

OncePerRequestFilter是什么?

OncePerRequestFilter是SpringBoot提供的一种能够确保在一次请求只通过一次filter,一般情况下我们要自定义Filter都会继承这个类。

What!!! 一次请求还会过滤多次吗?

  1. Filter base class that guarantees to be just executed once per request,on any servlet container.
      过滤器基础类,保证在所有的Servlet容器中只执行一次。
      也就是说不同的Web容器会有不同的处理方式,有些容器真的会过滤不只一次,噢,斯密达。。。

  2. Servlet版本不同,表现也不同。
      如 :servlet2.3与servlet2.4也有一定差异

  • Servlet-2.3中,Filter会过滤一切请求,包括服务器内部使用forward转发请求和<%@ include file=”/index.jsp”%>的情况。
  • Servlet-2.4中Filter默认下只拦截外部提交的请求,forward和include这些内部转发都不会被过滤,但是有时候我们需要 forward的时候也用到Filter。

自定义Filter

  1. 创建MyFilter
@Component
public class MyFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException {
        response.setStatus(200);
        response.setHeader("Content-Type", "application/json");
        response.getWriter().write("{\"age\":12}");
        response.getWriter().flush();
        response.flushBuffer();
    }
  1. 构建Spring Bean
@Configuration
public class FilterConfiguration {

    @Autowired
    private MyFilter myFilter;

    @Bean
    public FilterRegistrationBean authFilterRegistrationBean() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(requestFilter); //设置自定义的Filter
        registration.addUrlPatterns("/**");  //设置过滤路径
        registration.setName("RequestFilter"); //设置过滤器名称
        registration.setOrder(1);   //设置过滤器顺序
        return registration;
    }
}

  这样Filter就生效了,但是高兴的太早了。。。访问接口的返回结果:

headers:
content-length →0
content-type →application/json
date →Mon, 24 Jun 2019 11:07:50 GMT
body:

为什么无法写入Response?和普通的Filter不一样吗?

  1. 来来来,打个断点看一下:
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException {
        response.setStatus(200);
        response.setHeader("Content-Type", "application/json");
        response.getWriter().write("{\"age\":12}");
        response.getWriter().flush();
        // 断点为止
*       response.flushBuffer();
}

// 断点输出
this = {RequestFilter@10416} 
request = {MultiReadRequestWrapper@10417} 
filterChain = {ApplicationFilterChain@10419} 
response = {ContentCachingResponseWrapper@10418} 
  1. ContentCachingResponseWrapper是个什么东东,走走走看一下
  • 貌似和缓存有关系,为什么需要存在这个东西?

HttpServletRequest 中的 InputStream 或 reader 来获取请求的数据,但如果我们直接在这里读取了流或内容,到后面的逻辑将无法进行下去,所以需要实现一个可以缓存的 HttpServletRequest。
官方说明:
HttpServletRequest wrapper that caches all content read from the input stream and reader, and allows this content to be retrieved via a byte array. Used e.g. by AbstractRequestLoggingFilter. Note: As of Spring Framework 5.0, this wrapper is built on the Servlet 3.1 API. HttpServletResponse wrapper that caches all content written to the output stream and writer, and allows this content to be retrieved via a byte array. Used e.g. by ShallowEtagHeaderFilter. Note: As of Spring Framework 5.0, this wrapper is built on the Servlet 3.1 API.
大概就是说,HttpServletRequest包装器,用于缓存从输入流和读取器读取的所有内容,并允许通过字节数组检索此内容。并且HttpServletRequest包装器,用于缓存从输入流和读取器读取的所有内容,并允许通过字节数组检索此内容。然后从Spring Framework 5.0开始,此包装器构建在Servlet 3.1 API上,就是默认就用它了。

  噢,原来是这样,对Stream的操作,只能有一次,所以我们需要一个缓存的东西。同理,Response也是这样的。

  • 找一下具体的方法
public class ContentCachingResponseWrapper extends HttpServletResponseWrapper {
......
	/**
	 * Copy the complete cached body content to the response.
	 * @since 4.2
	 */
	public void copyBodyToResponse() throws IOException {
		copyBodyToResponse(true);
	}

	/**
	 * Copy the cached body content to the response.
	 * @param complete whether to set a corresponding content length
	 * for the complete cached body content
	 * @since 4.2
	 */
	protected void copyBodyToResponse(boolean complete) throws IOException {
		if (this.content.size() > 0) {
			HttpServletResponse rawResponse = (HttpServletResponse) getResponse();
			if ((complete || this.contentLength != null) && !rawResponse.isCommitted()) {
				rawResponse.setContentLength(complete ? this.content.size() : this.contentLength);
				this.contentLength = null;
			}
			this.content.writeTo(rawResponse.getOutputStream());
			this.content.reset();
			if (complete) {
				super.flushBuffer();
			}
		}
	}
	......
}

  哈哈,找到你了,Copy the complete cached body content to the response.

  • 修改下我们之前的代码
        ContentCachingResponseWrapper response = (ContentCachingResponseWrapper) httpServletResponse;
        response.setStatus(200);
        response.setHeader("Content-Type", "application/json");
        response.getWriter().write("{\"age\":12}");
        response.getWriter().flush();
        response.copyBodyToResponse();
        response.flushBuffer();

返回结果:

headers:
content-length →10
content-type →application/json
date →Mon, 24 Jun 2019 11:34:32 GMT

body:
{
    "age": 12
}

OK,打完收工

我的个人博客,有空来坐坐

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值