advice 和 拦截器_原创 | Filter、Interceptor和Aspect对请求的拦截,有什么不同?

本文主要是介绍advice 和 拦截器_原创 | Filter、Interceptor和Aspect对请求的拦截,有什么不同?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

原创 | Filter、Interceptor和Aspect对请求的拦截,有什么不同?

林必昭 码农沉思录

在使用Spring MVC开发RESTful API的时候,我们经常会使用Java的拦截机制来处理请求,Filter是Java本身自带拦过滤器,Interceptor则是Spring自带的拦截器,而Aspect切面是Spring AOP一个概念,主要的使用场景有:日志记录、事务控制和异常处理,该篇文章主要说说它们是如何实现的以及他们之间的差别,在这过程中也会探讨全局异常处理机制的原理以及异常处理过程。

Filter

我对Filter过滤器做了以下总结:

介绍:

java的过滤器,依赖于Sevlet,和框架无关的,是所有过滤组件中最外层的,从粒度来说是最大的,它主要是在过滤器中修改字符编码(CharacterEncodingFilter)、过滤掉没用的参数、简单的安全校验(比如登录不登录之类)

实现和配置方式

1.直接实现Filter接口+@Component

2.@Bean+@Configuration(第三方Filter)

3.web.xml配置方式

ca379fecb2473cd8f866a064d10d0d50.png

Filter的实现方式

@Component

public class TimeFilter implements Filter {

@Override

public void init(FilterConfig filterConfig) throws ServletException {

System.out.println("初始化TimeFilter...");

}

@Override

public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {

System.out.println("-------TimeFilter Start--------");

long start = new Date().getTime();

filterChain.doFilter(request, response);

System.out.println("TimeFilter执行耗时:" + (new Date().getTime() - start));

System.out.println("-------TimeFilter End--------");

}

@Override

public void destroy() {

System.out.println("销毁TimeFilter...");

}

}

注意:关于filterChain.doFilter(request,response,filterChain),执行filterChain.doFilter的意思是将请求转发给过滤器链上的下一个对象,如果没有filter那就是你请求的资源。一般filter都是一个链,web.xml 里面配置了几个就有几个。一个一个的连在一起这里指的是下一个Filter, request->filter1->filter2->filter3->...->response。

我们定义完Filter之后,如果我们不使用@Component注解注入,可以使用另一种方式将Filter注入到我们的容器中,这里使用@Bean的形式定义,通过自定义配置类WebConfig实现配置,最后返回registrationBean,这个方法主要有两个好处就是第一我们可以通过registrationBean.setUrlPatterns(urls)来指明filter在哪些路径下起作用,第二我们可以使用该方法去注入第三方的filter,原因的很多地方的filter其实并不是以@Component注入方式(也就是没有标注@Component注解),这时候我们就只能使用第二种方式来实现了。

@Configuration

public class WebConfig extends WebMvcConfigurerAdapter {

@Autowired

TimeInterceptor timeInterceptor;

@Bean

public FilterRegistrationBean charsetFilter(){

FilterRegistrationBean registrationBean = new FilterRegistrationBean();

TimeFilter timeFilter = new TimeFilter();

CharsetFilter charsetFilter = new CharsetFilter();

registrationBean.setFilter(charsetFilter);

registrationBean.setFilter(timeFilter);

//相当于@webFilter的@WebInitParam()注解的作用

Map paramMap = new HashMap<>();

paramMap.put("charset","utf-8");

registrationBean.setInitParameters(paramMap);

//相当于@webFilter的 urlPatterns = "/*"的作用

List urls = new ArrayList<>();

urls.add("/*");

//urls.add("/user/*");

registrationBean.setUrlPatterns(urls);

return registrationBean;

}

我们在controller中定义一个getInfo()方法:

//请求路径的{id}回传到方法里也就是传到(@PathVariable String id)的id里

@RequestMapping(value = "/user/{id:\\d+}",method = RequestMethod.GET)

@JsonView(User.UserDetailView.class) //这里因为UserDetailView继承了UserSimpleView所有会返回username和password

@ApiOperation("获取用户信息")

public User getInfo(@PathVariable Integer id) {

// throw new UserNotExistException(id);

System.out.println("进入getInfo()服务");

User user = new User();

user.setId(1);

user.setUsername("jacklin");

user.setPassword("123");

return user;

}

当我们调用controller中的getInfo()方法的时候,看看请求响应是否成以及控制台的输出:

03e6192c98a7ff0eadbb1ca81366adae.png

GET请求发送成功,返回200,控制台输出如下:

4fcff93c6411941c51181ffdda1ac50d.png

f7ff3206024f31cc48951c883d2b7a64.png

从上述结果,我们可以分析得出,当客户端发送请求,到达Controller方法之前,先执行Filter初始化操作,接着进入Controller的方法体,最后执行完成,通过分析我们明白了Filter的工作原理和方法的执行顺序!

Interceptor

我对**Interceptor**过滤器做了以下总结(导图中加粗部分是重点):

简介:

spring框架的拦截器,主要依赖于Spring MVC框架,它是在 service 或者一个方法调用前,调用一个方法,或者在方法调用后,调用一个方法。

实现和配置方式:

实现HandlerInterceptor接口看,并重写preHandle、postHandle、afterCompletion方法。

解释说明:

SpringMVC中的Interceptor是链式的调用的,在一个应用中或者是在一个请求中可以同时存在多个Interceptor,每个Inteceptor的调用都会按照它的声明顺序依次执行,而且最先执行的Intecptor的preHandler方法,所以可以在这个方法中进行一些前置初始化操作或者是堆当前请求的一个预处理,也可以在这个方法中进行一些判断是否要继续进行下去。

该方法的返回值是Boolean类型的,当它返回为false时,表示请求结束,后续的Interceptor和Controller都不会再执行;

当返回值为true 时就会继续调用下一个Interceptor的preHandle方法,如果已经是最后一个Interceptor的时候就会是调用当前请求的Controller方法。

8b37b3c581b1c571729711c80e62f6cb.png

Interceptor拦截器的实现方式

/**

* @Author 林必昭

* @Date 2019/7/4 13:15

*/

@Component

public class TimeInterceptor implements HandlerInterceptor {

/**

* preHandle方法的返回值是boolean值,当返回的是false时候,不会进入controller里的方法

*/

@Override

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

System.out.println("------->preHandle");

System.out.println("控制器类名:" + ((HandlerMethod) handler).getBean().getClass().getName()); //获取类名

System.out.println("控制器中的对应的方法名:" + ((HandlerMethod) handler).getMethod().getName()); //获取类中方法名

request.setAttribute("startTime", new Date().getTime());

return true;

}

@Override

public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

System.out.println("------->postHandle");

Long start = (Long) request.getAttribute("startTime");

System.out.println("TimeInterceptor 执行耗时:" + " " + (new Date().getTime() - start));

}

@Override

public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) throws Exception {

System.out.println("------->afterCompletion");

Long start = (Long) request.getAttribute("startTime");

System.out.println("TimeInterceptor 执行耗时:" + " " + (new Date().getTime() - start));

System.out.println("Exception is " + e);

}

}

注意:我们使用@Component定义Interceptor之后,还不能起作用,好要进行下一步配置,我们在之前定义的WebConfig配置类继承抽象类WebMvcConfigurerAdapter,将Interceptor注入容器中:

@Configuration

public class WebConfig extends WebMvcConfigurerAdapter {

@Autowired

TimeInterceptor timeInterceptor;

@Override

public void addInterceptors(InterceptorRegistry registry) {

registry.addInterceptor(timeInterceptor);

}

}

这样Interceptor就起作用了,同样,我们通过发送请求,观察控制台的输出,来分析结果:

e0afb7347a553b06e6d6b1cc09dd9901.png

从TimeInterceptor拦截器结果,我们可以分析得出,当客户端发送请求,到达Controller方法之前,先执行Interceptor的preHandler方法,接着进入Controller的方法体,通过Interceptor我们可以获取到对应的Controller和执行的方法名,接着执行postHandler方法,最后执行afterCompletion方法,如何结果出现异常,也会执行afterCompletion,这里没有异常,所以Exception为空。

那么当控制层中抛出异常,如果没有使用全局异常处理,在拦截器上也能捕获到异常信息,我们可以尝试一下,在Controller抛出一个RuntimeException,RuntimeException并没有在全局异常处理中被处理,Controller修改如下:

@RequestMapping(value = "/user/{id:\\d+}",method = RequestMethod.GET)

@JsonView(User.UserDetailView.class) //这里因为UserDetailView继承了UserSimpleView所有会返回username和password

@ApiOperation("获取用户信息")

public User getInfo(@PathVariable Integer id) {

/**

* 当抛出UserNotExistException异常的时候,会跳到ControllerExceptionHandler的handleUserNotExistException方法

* 进行相应的处理

*/

throw new RuntimeException("user not exist!!"); //这里抛出一个RuntimeException

// System.out.println("进入getInfo()服务");

// User user = new User();

// user.setId(1);

// user.setUsername("jacklin");

// user.setPassword("123");

// return user;

}

观察控制台输出:

17c8d7d9577e20dd2a61c1b9ed7996bb.png

结果很明显了,当控制层出现异常的时候,异常没有被全局处理器处理,到达拦截器,拦截器会捕获到异常,这时候只执行了preHandle和afterCompletionn方法,并没有执行postHandle方法,控制台也输出了异常信息。

想想,如果抛出我们自定义异常,而且自定义异常被全局处理器拦截处理,异常还会到达我们的拦截器吗,我们来自定义一个异常UserNotExistException,如下:

public class UserNotExistException extends RuntimeException {

private static final long serialVersionUID = -9136501205369741760L;

private String id;

public UserNotExistException(String id){

super("user is not exist...");

this.id = id;

}

public String getId() {

return id;

}

public void setId(String id) {

this.id = id;

}

}

接着,定义全局异常处理器GlobalExceptionHandler,使用@ControllerAdvice修饰:

/**

* 全局异常处理,负责处理controller抛出的异常

*

* @Author 林必昭

* @Date 2019/7/4 11:31

*/

@ControllerAdvice

public class GlobalExceptionHandler {

@ExceptionHandler(UserNotExistException.class)

@ResponseBody

@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) //服务器内部错误

public Map handleUserNotExistException(UserNotExistException ex) {

Map resultMap = new HashMap<>();

resultMap.put("id", ex.getId());

resultMap.put("message", ex.getMessage());

return resultMap;

}

}

然后,我们再在UserController中抛出我们的自定义异常UserNotExistException,观察控制台的输出,来分析结果:

public User getInfo(@PathVariable Integer id) {

/**

* 当抛出UserNotExistException异常的时候,会跳到ControllerExceptionHandler的handleUserNotExistException方法

* 进行相应的处理

*/

//throw new RuntimeException("user not exist!!");

throw new UserNotExistException("user not exist!!")

}

543ca73274735f933b626e4f0b9a668e.png

从结果看出,异常时空的,证明我们定义的异常处理器已经生效,UserNotExistException在GlobalExceptionHandler已经被处理了,所有异常没有到达我们的拦截器,到这里我们可以得出异常的处理顺相顺序结论了,在文化在那个末尾会给出。

Aspect

我对Aspect过滤器做了以下总结:

f614f22f9330d0889b3d6746abe555f2.png

在使用Spring AOP切面前,我们需要导入pom依赖:

org.springframework.boot

spring-boot-starter-aop

切面拦截的实现方式

@Aspect

@Component

public class TimeAspect {

/**

* 切入点

*/

@Around("execution(* com.lbz.web.controller.UserController.*(..))") //UserController下的任何方法被调用都会执行这个切片

public Object handleControllerMethod(ProceedingJoinPoint point) throws Throwable {

System.out.println("TimeAspect start");

long start = new Date().getTime();

Object object = point.proceed(); //proceed中文意思是继续的意思,也就是切入,相当于filterChain.doFilter()

Object[] args = point.getArgs(); //与Filter和Interceptor的区别是,可以获取到UserController里方法的参数

for (Object arg : args) {

System.out.println("控制层的方法对应参数是:" + arg);

}

System.out.println("TimeAspect执行耗时:" + (new Date().getTime() - start));

System.out.println("TimeAspect end");

return object;

}

}

这里的point.proceed()是继续的意思,也就是切入,相当于filterChain.doFilter(),与Filter和Interceptor不同的是,我们可以通过point.getArgs();拿到对应方法的参数,我们通过遍历把参数打印看一下。

310c945edfc4150ccf13195d4d6e349c.png

从结果看出,我们可以看到我们拿到方法对应的参数,为1,也就是我们请求:http://localhost:8060/user/1 传入的id的值;

总结:

1.过滤器可以拿到原始方法的Http的请求和响应信息,拿不到对应方法的详细信息,拦截器既可以拿到原始方法的Http请求和响应信息,也能拿到对应方法的详细信息,但是拿不到被调用方法对应参数的值,而切面可以拿到被调用方法传递过来参数的值,但却拿不到原始的Http请求和响应对象。

2.Controller方法抛出异常之后,最先捕获到异常的是切片,如果你定义了全局异常处理器并声明了ControllerAdvice,切片捕获到异常往外抛,就轮到全局异常处理器处理,接着到拦截器,再到过滤器,也就是:

拦截作用顺序:Aspect->全局处理器->拦截器->过滤器->Tomcat

最后,我完成了对Filter、Interceptor、Aspect三种拦截方式的实现和过程分析,通过本次的学习,我也掌握了很多的知识,包括拦截器的工作原理,异常被处理的顺序,全局异常处理机制,掌握如何实现请求的拦截和处理,我个人觉得多看不如一写,多写写加以思考总会有收获,看了很多文章但还是觉得自己理解不够深刻,所有才决定将他记录下来,加深理解,我觉得这样值得,晚安!

这篇关于advice 和 拦截器_原创 | Filter、Interceptor和Aspect对请求的拦截,有什么不同?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/577951

相关文章

鸿蒙中Axios数据请求的封装和配置方法

《鸿蒙中Axios数据请求的封装和配置方法》:本文主要介绍鸿蒙中Axios数据请求的封装和配置方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1.配置权限 应用级权限和系统级权限2.配置网络请求的代码3.下载在Entry中 下载AxIOS4.封装Htt

springboot filter实现请求响应全链路拦截

《springbootfilter实现请求响应全链路拦截》这篇文章主要为大家详细介绍了SpringBoot如何结合Filter同时拦截请求和响应,从而实现​​日志采集自动化,感兴趣的小伙伴可以跟随小... 目录一、为什么你需要这个过滤器?​​​二、核心实现:一个Filter搞定双向数据流​​​​三、完整代码

AJAX请求上传下载进度监控实现方式

《AJAX请求上传下载进度监控实现方式》在日常Web开发中,AJAX(AsynchronousJavaScriptandXML)被广泛用于异步请求数据,而无需刷新整个页面,:本文主要介绍AJAX请... 目录1. 前言2. 基于XMLHttpRequest的进度监控2.1 基础版文件上传监控2.2 增强版多

SpringBoot使用OkHttp完成高效网络请求详解

《SpringBoot使用OkHttp完成高效网络请求详解》OkHttp是一个高效的HTTP客户端,支持同步和异步请求,且具备自动处理cookie、缓存和连接池等高级功能,下面我们来看看SpringB... 目录一、OkHttp 简介二、在 Spring Boot 中集成 OkHttp三、封装 OkHttp

MySQL中慢SQL优化的不同方式介绍

《MySQL中慢SQL优化的不同方式介绍》慢SQL的优化,主要从两个方面考虑,SQL语句本身的优化,以及数据库设计的优化,下面小编就来给大家介绍一下有哪些方式可以优化慢SQL吧... 目录避免不必要的列分页优化索引优化JOIN 的优化排序优化UNION 优化慢 SQL 的优化,主要从两个方面考虑,SQL 语

Spring Boot拦截器Interceptor与过滤器Filter详细教程(示例详解)

《SpringBoot拦截器Interceptor与过滤器Filter详细教程(示例详解)》本文详细介绍了SpringBoot中的拦截器(Interceptor)和过滤器(Filter),包括它们的... 目录Spring Boot拦截器(Interceptor)与过滤器(Filter)详细教程1. 概述1

Go语言中最便捷的http请求包resty的使用详解

《Go语言中最便捷的http请求包resty的使用详解》go语言虽然自身就有net/http包,但是说实话用起来没那么好用,resty包是go语言中一个非常受欢迎的http请求处理包,下面我们一起来学... 目录安装一、一个简单的get二、带查询参数三、设置请求头、body四、设置表单数据五、处理响应六、超

dubbo3 filter(过滤器)如何自定义过滤器

《dubbo3filter(过滤器)如何自定义过滤器》dubbo3filter(过滤器)类似于javaweb中的filter和springmvc中的intercaptor,用于在请求发送前或到达前进... 目录dubbo3 filter(过滤器)简介dubbo 过滤器运行时机自定义 filter第一种 @A

Qt实现发送HTTP请求的示例详解

《Qt实现发送HTTP请求的示例详解》这篇文章主要为大家详细介绍了如何通过Qt实现发送HTTP请求,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1、添加network模块2、包含改头文件3、创建网络访问管理器4、创建接口5、创建网络请求对象6、创建一个回复对

SpringBoot项目注入 traceId 追踪整个请求的日志链路(过程详解)

《SpringBoot项目注入traceId追踪整个请求的日志链路(过程详解)》本文介绍了如何在单体SpringBoot项目中通过手动实现过滤器或拦截器来注入traceId,以追踪整个请求的日志链... SpringBoot项目注入 traceId 来追踪整个请求的日志链路,有了 traceId, 我们在排