如何优雅的实现一个过滤器
上一篇文章中我们学了了Servlet的概念,这篇我们学习一下过滤器Filter, 正如它的名字,过滤器是介于Servlet之前,可拦截过滤浏览器对于Servlet的请求,也可以改变Servlet对于浏览器的响应。
那要如何实现一个Filter呢,这里我又把Filter的各子类关系图给大家放出来了,从图中可以看出,要想实现一个过滤器,可以直接实现Filter接口,也可以继承他的子类,对于Http请求,则可以直接继承HttpFilter。
从图中可以看出Filter主要有3个要实现的方法: init(), doFilter()和destroy().其中关键的就是这个doFilter()方法,该方法类似于Servlet接口的service()方法。当请求到达容器时,如果容器发现调用Servlet的service()方法前,可以应用某个过滤器, 就会调用该过滤器的doFilter()方法。可以在doFilter()中执行service()的前置处理,并且决定是否调用FilterChain的doFilter()方法。 调用FilterChain的doFilter()方法后,就用运行下一个Filter,如果没有下一个Filter了,就会调用Servlet的service()方法。整理过程会以调用堆栈的顺序返回, 所以在FilterChain的doFiler()方法之后,我们有可以对Servlet的service()方法做后置处理。整个处理过程的图示如下:
接下来我们就来看看如何实现一个过滤器:
方式一:使用@WebFilter注解 和 @ServletComponentScan注解
方式二:使用@Component注解
方式三:使用FilterRegistrationBean
接下来我们来写一个contoller, 来验证一下我的三种方式实现的filter都生效了。
我们调用上面的请求, 会看看再我们的console下出现下面的运行结果,正如我们的预期。
需求一: 指定Filter的运行顺序
如果在程序中想定义多个filter,并且为不同的Filter指定顺序,则只能使用@Component注解和FilterRegistrationBean的方式。具体的使用请参见下图:
需求二: 在Filter中校验失败,抛出异常
在实际项目中,我们通常会定义一个统一的返回信息格式,如果抛出异常,会通过全局统一的异常处理器,使得前端接收到的返回结果都是同一种格式,具体示例如下:
自定义的返回类型
全局异常处理
当我分别在controller和Filter中抛出异常后,得到的结果如下:
Controller中异常处理结果
Filter中异常处理结果
我们会看到在Filter中的异常并没有被我们的全局异常处理器捕捉到,所以也没有被处理成我们想要的统一格式,这是因为Filter并不在我们的Spring上下文中, 要想处理成我们统一定义的格式, 只能我们自己重写,具体示例如下面的代码所示:
不同实现方式的对比
说了这么多那我该用那种方式来实现呢? 这里我总结了几点,供大家参考。
使用@Component方式最简单, 但是这种方式会过滤所有的请求。其余两种方式都可以针对指定的路由进行过滤
使用FilterRegistrationBean的方式实现,无法通过注入的方式获取Spring上下文的其他Bean。
使用@WebFilter的方式实现,无法指定顺序。
spring中提供的Filter
上面介绍了Filter是什么,以及如何实现一个Filter, 不知道大家发现没有,其实我们在实际项目中早就使用了Filter,比如我们常用的跨域处理filter, 具体示例如下所示,其实CorsFilter的本质就是一个Filter, 感兴趣的小伙伴可以去看看源码, 其本质就是实现了Filter接口。
这张图是Spring给我们提供的所有Filter, 我们在自己定义Filter时其实可以通过实现OncePerRequestFilter. 具体它都实现了什么,以及每个Filter都有怎样的用处,我们会在下一篇文章中详细阐述。
版权声明: 本文为 InfoQ 作者【双儿么么哒】的原创文章。
原文链接:【http://xie.infoq.cn/article/42ae7522e41ff5a0d0e1c32c1】。文章转载请联系作者。
评论