SpringBoot实现过滤器、拦截器与切片源码分析
过滤器Filter
过滤器概念
Filter是J2E中来的,可以看做是Servlet的一种“加强版”,它主要用于对用户请求进行预处理和后处理,拥有一个典型的处理链。Filter也可以对用户请求生成响应,这一点与Servlet相同,但实际上很少会使用Filter向用户请求生成响应。使用Filter完整的流程是:Filter对用户请求进行预处理,接着将请求交给Servlet进行预处理并生成响应,最后Filter再对服务器响应进行后处理。
过滤器作用
在JavaDoc中给出了几种过滤器的作用
* Examples that have been identified for this design are* 1) Authentication Filters, 即用户访问权限过滤* 2) Logging and Auditing Filters, 日志过滤,可以记录特殊用户的特殊请求的记录等* 3) Image conversion Filters* 4) Data compression Filters <br>* 5) Encryption Filters <br>* 6) Tokenizing Filters <br>* 7) Filters that trigger resource access events <br>* 8) XSL/T filters <br>* 9) Mime-type chain Filter <br>
对于第一条,即使用Filter作权限过滤,其可以这么实现:定义一个Filter,获取每个客户端发起的请求URL,与当前用户无权限访问的URL列表(可以是从DB中取出)作对比,起到权限过滤的作用。
过滤器实现方式
自定义的过滤器都必须实现javax.Servlet.Filter接口,并重写接口中定义的三个方法:
void init(FilterConfig config):用于完成Filter的初始化。
void destory():用于Filter销毁前,完成某些资源的回收。
void doFilter(ServletRequest request,ServletResponse response,FilterChain chain):实现过滤功能,即对每个请求及响应增加的额外的预处理和后处理。执行该方法之前,即对用户请求进行预处理;执行该方法之后,即对服务器响应进行后处理。值得注意的是,chain.doFilter()方法执行之前为预处理阶段,该方法执行结束即代表用户的请求已经得到控制器处理。因此,如果在doFilter中忘记调用chain.doFilter()方法,则用户的请求将得不到处理。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;// 必须添加注解,springmvc通过web.xml配置
@Component
public class TimeFilter implements Filter {private static final Logger LOG = LoggerFactory.getLogger(TimeFilter.class);@Overridepublic void init(FilterConfig filterConfig) throws ServletException {LOG.info("初始化过滤器:{}", filterConfig.getFilterName());}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {LOG.info("start to doFilter");long startTime = System.currentTimeMillis();chain.doFilter(request, response);long endTime = System.currentTimeMillis();LOG.info("the request of {} consumes {}ms.", getUrlFrom(request), (endTime - startTime));LOG.info("end to doFilter");}@Overridepublic void destroy() {LOG.info("销毁过滤器");}private String getUrlFrom(ServletRequest servletRequest){if (servletRequest instanceof HttpServletRequest){return ((HttpServletRequest) servletRequest).getRequestURL().toString();}return "";}
}
从代码中可看出,类Filter是在javax.servlet.*中,因此可以看出,过滤器的一个很大的局限性在于,其不能够知道当前用户的请求是被哪个控制器(Controller)处理的,因为后者是spring框架中定义的。
在SpringBoot中注册第三方过滤器
对于SpringMVC,可以通过在web.xml中注册过滤器。但在SpringBoot中不存在web.xml,此时如果引用的某个jar包中的过滤器,且这个过滤器在实现时没有使用@Component标识为Spring Bean,则这个过滤器将不会生效。此时需要通过java代码去注册这个过滤器。以上面定义的TimeFilter为例,当去掉类注解@Component时,注册方式为:
@Configuration
public class WebConfig {/*** 注册第三方过滤器* 功能与spring mvc中通过配置web.xml相同* @return*/@Beanpublic FilterRegistrationBean thirdFilter(){ThirdPartFilter thirdPartFilter = new ThirdPartFilter();FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean() ;filterRegistrationBean.setFilter(thirdPartFilter);List<String > urls = new ArrayList<>();// 匹配所有请求路径urls.add("/*");filterRegistrationBean.setUrlPatterns(urls);return filterRegistrationBean;}
}
相比使用@Component注解,这种配置方式有个优点,即可以自由配置拦截的URL。
拦截器Interceptor
拦截器概念
拦截器,在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截,然后在之前或之后加入某些操作。拦截是AOP的一种实现策略。
拦截器作用
日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等
权限检查:如登录检测,进入处理器检测检测是否登录
性能监控:通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间。(反向代理,如apache也可以自动记录);
通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个处理器都需要的即可使用拦截器实现。
拦截器实现
通过实现HandlerInterceptor接口,并重写该接口的三个方法来实现拦截器的自定义:
preHandler(HttpServletRequest request, HttpServletResponse response, Object handler):方法将在请求处理之前进行调用。SpringMVC中的Interceptor同Filter一样都是链式调用。每个Interceptor的调用会依据它的声明顺序依次执行,而且最先执行的都是Interceptor中的preHandle方法,所以可以在这个方法中进行一些前置初始化操作或者是对当前请求的一个预处理,也可以在这个方法中进行一些判断来决定请求是否要继续进行下去。该方法的返回值是布尔值Boolean 类型的,当它返回为false时,表示请求结束,后续的Interceptor和Controller都不会再执行;当返回值为true时就会继续调用下一个Interceptor 的preHandle 方法,如果已经是最后一个Interceptor 的时候就会是调用当前请求的Controller 方法。
postHandler(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView):在当前请求进行处理之后,也就是Controller 方法调用之后执行,但是它会在DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个方法中对Controller 处理之后的ModelAndView 对象进行操作。
afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex):该方法也是需要当前对应的Interceptor的preHandle方法的返回值为true时才会执行。顾名思义,该方法将在整个请求结束之后,也就是在DispatcherServlet 渲染了对应的视图之后执行。这个方法的主要作用是用于进行资源清理工作的。
@Component
public class TimeInterceptor implements HandlerInterceptor {private static final Logger LOG = LoggerFactory.getLogger(TimeInterceptor.class);@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {LOG.info("在请求处理之前进行调用(Controller方法调用之前)");request.setAttribute("startTime", System.currentTimeMillis());HandlerMethod handlerMethod = (HandlerMethod) handler;LOG.info("controller object is {}", handlerMethod.getBean().getClass().getName());LOG.info("controller method is {}", handlerMethod.getMethod());// 需要返回true,否则请求不会被控制器处理return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {LOG.info("请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后),如果异常发生,则该方法不会被调用");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {LOG.info("在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作)");long startTime = (long) request.getAttribute("startTime");LOG.info("time consume is {}", System.currentTimeMillis() - startTime);}
与过滤器不同的是,拦截器使用@Component修饰后,还需要通过实现WebMvcConfigurer手动注册:
// java配置类
@Configuration
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate TimeInterceptor timeInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry){registry.addInterceptor(timeInterceptor);}
}
切片Aspect
切片概述
相比过滤器,拦截器能够知道用户发出的请求最终被哪个控制器处理,但是拦截器还有一个明显的不足,即不能够获取request的参数以及控制器处理之后的response。所以就有了切片的用武之地了。
切片实现
切片的实现需要注意@Aspect,@Component以及@Around这三个注解的使用,详细查看官方文档:传送门
@Aspect
@Component
public class TimeAspect {private static final Logger LOG = LoggerFactory.getLogger(TimeAspect.class);@Around("execution(* me.ifight.controller.*.*(..))")public Object handleControllerMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{LOG.info("切片开始。。。");long startTime = System.currentTimeMillis();// 获取请求入参Object[] args = proceedingJoinPoint.getArgs();Arrays.stream(args).forEach(arg -> LOG.info("arg is {}", arg));// 获取响应Object response = proceedingJoinPoint.proceed();long endTime = System.currentTimeMillis();LOG.info("请求:{}, 耗时{}ms", proceedingJoinPoint.getSignature(), (endTime - startTime));LOG.info("切片结束。。。");return null;}
}
过滤器、拦截器以及切片的调用顺序
如下图,展示了三者的调用顺序Filter->Interceptor->Aspect->Controller。相反的是,当Controller抛出的异常的处理顺序则是从内到外的。因此我们总是定义一个注解@ControllerAdvice去统一处理控制器抛出的异常。如果一旦异常被@ControllerAdvice处理了,则调用拦截器的afterCompletion方法的参数Exception ex就为空了。
实际执行的调用栈也说明了这一点:
而对于过滤器和拦截器详细的调用顺序如下图:
过滤器和拦截器的区别
最后有必要再说说过滤器和拦截器二者之间的区别:
Filter | Interceptor | |
实现方式 | 基于函数回调 | 基于Java的反射机制的 |
规范 | Servlet规范 | Spring规范 |
作用范围 | 对几乎所有的请求起作用 | 只对action请求起作用 |
除此之外,相比过滤器,拦截器能够“看到”用户的请求具体是被Spring框架的哪个控制器所处理。
SpringBoot实现过滤器、拦截器与切片源码分析相关推荐
- SpringBoot的过滤器拦截器AOP和异常处理器
目录 前言 过滤器 创建过滤器 拦截器 创建拦截器 配置拦截器 AOP 创建AOP 异常处理器 创建异常处理器 测试请求的执行过程 创建接口 发送请求 前言 过滤器.拦截器.AOP.异常处理器是搭建系 ...
- Hadoop MapReduce Splits 切片源码分析及切片机制
本文从Job提交,逐步分析Splits相关源码. 数据块:Block是HDFS物理上把数据分成一块一块的. 数据切片:数据切片只是在物理上输入进行分片,并不会在磁盘上将其分成片进行存储. 文件路径 o ...
- 大数据之-Hadoop3.x_MapReduce_切片源码分析---大数据之hadoop3.x工作笔记0104
然后我们开始来看看,切片的源码,看看到底是怎么切片的 然后我们继续调试,我们快速走就可以了,上节,已经调试一遍了. 然后进入job提交方法
- 十六、FileInputFormat介绍,切片源码分析
一.InputFormat介绍 InputFormat,从单词意思解读分为输入.格式,也就是数据来源与加载数据的方式是决定MR编程的map阶段的任务并行度. 数据来源划分:其实也就是他的子类,由于我目 ...
- SpringBoot之Interceptor拦截器注入使用
相关文章: SpringBoot 之AOP切面的使用 SpringBoot之Listener注册到Spring容器中的多种方法 SpringBoot之Filter过滤器的实现及排序问题 SpringB ...
- SpringBoot中配置拦截器时,跨域失效
SpringBoot中配置拦截器时,跨域失效 前后段分离的项目,配置了跨域后,访问正常,但是配置了拦截器以后,有的访问正常,有的出现跨域问题,发现出现跨域问题的都是拦截器里面没有放行的请求. @Con ...
- springboot 自定义注解拦截器
springboot 自定义注解拦截器 最近在工作中,发现自定义注解拦截使用起来特别方便,现在来写出来给大家看看 环境springboot 首先写一个自定义注解 package com.study.c ...
- 2022-2028年全球与中国固体拦截器行业发展趋势及投资战略分析
本文研究全球与中国市场固体拦截器的发展现状及未来发展趋势,分别从生产和消费的角度分析固体拦截器的主要生产地区.主要消费地区以及主要的生产商.重点分析全球与中国市场的主要厂商产品特点.产品规格.不同规格 ...
- FileInputFormat切片源码解析
文章目录 FileInputFormat切片源码解析 1.MapTask并行度决定机制 2.源码步骤 3.FileInputFormat切片机制 3.1 源代码中计算切片大小的公式 3.2 获取切片信 ...
最新文章
- 用一维数组统计五个人的成绩中的最大值最小值平均值_昨天为了整理这份教程,我放弃了参加一个几亿人的大项目。...
- Apache与IIS的优劣对比点点评
- 自己服务器上部署APP应用(安卓和IOS版),下载页面的代码,以及IOS的xxx.plist文件的建立
- 8 Redis 持久化RDB
- 蓝桥杯 试题 基础练习 十六进制转十进制——5行代码AC
- Web前端开发笔记——第二章 HTML语言 第十节 画布标签、音视频标签
- mysql创建的是拉丁_将MySQL数据库从拉丁转换为UTF-8
- 【文章】一副对联,便写尽了人生
- [转]Android截屏及图片解析
- 数字图像相关基础知识
- nodejs的moment操作时间
- 物理课上该怎样使用计算机,物理课堂教学中怎样使用演示课件.doc
- java对象创建、对象内存布局、对象的访问定位、句柄池、直接指针
- 为什么要选择Linux
- 谷歌浏览器开发者工具network_关于Chrome谷歌浏览器开发者工具网络Network中返回无数据的问题...
- 【解决方案】IGCCTray.exe参数错误
- Gimp 将图片中的颜色更改
- nexus 4/5/6/7/9/10 安卓5.1 LMY47D root教程
- Adobe认证证书(ACCD/ACPE)说明
- java.lang.IllegalArgumentException: Can not set java.lang.Integer field com.pojo.Fruit.price to java