Spring Boot 2 实战:如何自定义 Servlet Filter
1.前言
有些时候我们需要在 Spring Boot Servlet Web 应用中声明一些自定义的 Servlet Filter 来处理一些逻辑。比如简单的权限系统、请求头过滤、防止 XSS 攻击等。本篇将讲解如何在 Spring Boot 应用中声明自定义 Servlet Filter 以及定义它们各自的作用域和顺序。
2. 自定义 Filter
可能有人说声明 Servlet Filter 不就是实现 Filter 接口嘛,没有什么好讲的!是的这个没错,但是很多时候我们并不想我们声明的 Filter 作用于全部的请求。甚至当一个请求经过多个 Filter 时需要按照既定的顺序执行。接下来我会一一讲解如何实现以上的功能。
2.1 Filter 的声明
在 Spring Boot 中 只需要声明一个实现 javax.servlet.Filter
接口的 Spring Bean 就可以了。如下:
@Configuration public class FilterConfig { @Bean public Filter requestFilter() { return (request, response, chain) -> { //todo your business }; } @Bean public Filter responseFilter() { return (request, response, chain) -> { //todo your business }; } }
非常简单不是吗?但是这种方式无法保证顺序,而且作用于所有的请求,即拦截的 Ant 规则为 /*
。所以需要我们改进
2.2 实现 Filter 顺序化
如果需要实现顺序化,可以借助于 Spring 提供的 @Order
注解或者 Ordered
接口。这里有一个坑:如果使用 @Order
注解一定要注解标注到具体的类上。 为了方便 JavaConfig 风格的声明。我们可以实现 OrderedFilter
接口,该接口是 Filter
接口和 Ordered
接口的复合体,最终上面的配置如下
@Configuration public class FilterConfig { @Bean public OrderedFilter responseFilter() { return new ResponseFilter("/foo/*"); } @Bean public OrderedFilter requestFilter() { return new RequestFilter("/foo/*"); } }
Filter 执行的规则是 数字越小越先执行 。跟之前 Bean 实例化的优先级是一致的。
2.3 自定义 Filter 作用域
实现了顺序化之后我们来看看如何实现自定义 Filter 的作用域。我们先说一下思路:
通过
ServletRequest
对象来获取请求的URI
,然后对 URI 进行 ANT 风格匹配,关于 ANT 风格可以参考我的这一篇文章。 匹配通过执行具体的逻辑,否则跳过该 Filter 。
这里非常适合抽象一个基类来把该流程固定下来,留一个抽象方法作为函数钩子,只需要继承基类实现该抽象方法钩子就可以了。 为了保证顺序执行基类我们依然实现了 OrderedFilter
接口,我们来定义基类:
package cn.felord.springboot.filters; import org.springframework.util.AntPathMatcher; import org.springframework.util.CollectionUtils; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.Collections; import java.util.LinkedHashSet; import java.util.Optional; import java.util.Set; /** * The type Abstract filter bean. * * @author Felordcn * @since 11 :19 */ public abstract class AbstractFilterBean implements OrderedFilter { private Set<String> urlPatterns = new LinkedHashSet<>(); public AbstractFilterBean(String... urlPatterns) { Collections.addAll(this.urlPatterns, urlPatterns); } /** * 各自逻辑的函数钩子 * * @param request the request * @param response the response */ public abstract void internalHandler(ServletRequest request, ServletResponse response); @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 进行ant匹配 true 执行具体的拦截逻辑 false 跳过 if (this.antMatch(request)) { this.internalHandler(request, response); } chain.doFilter(request, response); } private boolean antMatch(ServletRequest request) { Set<String> urlPatterns = getUrlPatterns(); if (!CollectionUtils.isEmpty(urlPatterns)) { //进行Ant匹配处理 HttpServletRequest httpServletRequest = (HttpServletRequest) request; String uri = httpServletRequest.getRequestURI(); Optional<String> any = urlPatterns.stream().filter(s -> { AntPathMatcher antPathMatcher = new AntPathMatcher(); return antPathMatcher.match(s, uri); }).findAny(); return any.isPresent(); } // 如果 没有元素 表示全部匹配 return true; } public Set<String> getUrlPatterns() { return urlPatterns; } }
我们来实现一个具体的 Filter 逻辑,打印请求的 URI:
@Slf4j public class RequestFilter extends AbstractFilterBean { public RequestFilter(String... urlPatterns) { super(urlPatterns); } @Override public void internalHandler(ServletRequest request, ServletResponse response) { HttpServletRequest httpServletRequest = (HttpServletRequest) request; log.info("request from {}", httpServletRequest.getRequestURI()); } @Override public int getOrder() { // 定义自己的优先级 return 0; } }
然后定义好其 urlPatterns
并将其注册到 Spring IoC 容器中就行了,如果有多个而且希望按照一定的顺序执行,遵循 2.2 章节 提供的方法就可以了。
3. Spring Boot的机制
以上方式是我们自己造的轮子。其实 Spring Boot 还提供了 Filter 注册机制来实现顺序执行和声明作用域。 我们上面的逻辑可以改为:
package cn.felord.springboot.configuration; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 使用 Spring Boot 提供的注册机制 * * @author Felordcn * @since 14:27 **/ @Configuration @Slf4j public class SpringFilterRegistrationConfig { @Bean public FilterRegistrationBean<Filter> responseFilter() { FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setName("responseFilter"); registrationBean.setOrder(2); registrationBean.setFilter((request, response, chain) -> { HttpServletResponse servletResponse = (HttpServletResponse) response; log.info("response status {}", servletResponse.getStatus()); chain.doFilter(request,response); }); registrationBean.addUrlPatterns("/foo/*"); return registrationBean; } @Bean public FilterRegistrationBean<Filter> requestFilter() { FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setName("requestFilter"); registrationBean.setOrder(1); registrationBean.setFilter((request, response, chain) -> { HttpServletRequest httpServletRequest = (HttpServletRequest) request; log.info("request from {}", httpServletRequest.getRequestURI()); chain.doFilter(request,response); }); registrationBean.addUrlPatterns("/foo/*"); return registrationBean; } }
3.1 要点
FilterRegistrationBean
与Filter
之间是一对一关系。- 如果存在多个
FilterRegistrationBean
需要调用其setName(String name)
为其声明唯一名称,否则只有第一个注册成功的有效。 - 如果需要保证调用顺序可通过调用其
setOrder(int order)
方法进行设置。
4. 总结
我们在本文中通过自定义和 Spring Boot 提供的两种方式实现了使用自定义 Filter ,虽然 Spring Boot 提供的方式更加方便一些,但是自定义的方式更能体现你对面向对象理解和提高你的抽象能力。希望多多关注,与往常一样。通过关注公众号: Felordcn 回复 f01 获取本文 DEMO 。
关注公众号:Felordcn获取更多资讯
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
浅谈JVM - 内存结构(三)- 虚拟机栈
2.1 定义 Java Virtual Machine Stacks(Java虚拟机栈) Java 虚拟机栈描述的是 Java 方法执行的内存模型,用于存储栈帧,是线程私有的,生命周期随着线程启动而产生,线程结束而消亡 线程启动时会创建虚拟机栈,每个方法在执行时会在虚拟机栈中创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态连接、方法返回地址等信息。每个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中的入栈(压栈)到出栈(弹栈)的过程 每个线程只能有一个活动栈帧,对应着正在执行的那个方法 问题辨析 垃圾回收是否涉及栈内存? 不涉及。栈内存无非就是一次次的方法调用产生的栈帧内存,栈帧内存在每一次方法调用后都会被弹出栈,也就是这部分内存会被自动的回收掉,所以并不需要垃圾回收来回收栈内存。 栈内存分配越大越好吗? 不是。 栈内存可以在代码运行时通过一个虚拟机参数来指定其大小 -Xss size。不指定的话,除了windows系统,默认都是1M,windows系统是依据虚拟内存大小分配。 栈内存分配的越大,只是能够进行更多次的方法递归调用,并不会增快运行的效率,...
- 下一篇
未来,仅凭几个前端工程师,就能 hold 住一家企业吗? | 12月30号云栖号夜读
点击订阅云栖夜读日刊,专业的技术干货,不容错过! 阿里专家原创好文 1.未来,仅凭几个前端工程师,就能 hold 住一家企业吗? 微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增加,从一个普通应用演变成一个巨石应用(Frontend Monolith),随之而来的应用不可维护问题。这类问题在企业级 Web 应用中尤为常见。今天,我们就来聊聊拥抱云时代的前端开发架构:微前端。阅读更多》》 2.携程实时智能检测平台建设实践 本次演讲将为大家介绍携程实时智能异常检测平台——Prophet。到目前为止,Prophet基本覆盖了携程所有业务线,监控指标的数量达到10K+,覆盖了携程所有订单、支付等重要的业务指标。Prophet将时间序列的数据作为数据输入,以监控平台作为接入对象,以智能告警实现异常的告警功能,并基于Flink实时计算引擎来实现异常的实时预警,提供一站式异常检测解决方案。阅读更多》》 3.一个 Blink 小白的成长之路 写过blink sql的同学应该都有体会,明明写的时候就很顺滑,小手一抖,洋洋洒洒三百行代码,一气呵成。结果跑的时候,吞吐量就是上不去...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Windows10,CentOS7,CentOS8安装Nodejs环境
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS8安装Docker,最新的服务器搭配容器使用
- 设置Eclipse缩进为4个空格,增强代码规范
- CentOS7安装Docker,走上虚拟化容器引擎之路
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- CentOS7设置SWAP分区,小内存服务器的救世主