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

相关文章

2. c#从不同cs的文件调用函数

1.文件目录如下: 2. Program.cs文件的主函数如下 using System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;using System.Windows.Forms;namespace datasAnalysis{internal static

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

uva 10061 How many zero's and how many digits ?(不同进制阶乘末尾几个0)+poj 1401

题意是求在base进制下的 n!的结果有几位数,末尾有几个0。 想起刚开始的时候做的一道10进制下的n阶乘末尾有几个零,以及之前有做过的一道n阶乘的位数。 当时都是在10进制下的。 10进制下的做法是: 1. n阶位数:直接 lg(n!)就是得数的位数。 2. n阶末尾0的个数:由于2 * 5 将会在得数中以0的形式存在,所以计算2或者计算5,由于因子中出现5必然出现2,所以直接一

速了解MySQL 数据库不同存储引擎

快速了解MySQL 数据库不同存储引擎 MySQL 提供了多种存储引擎,每种存储引擎都有其特定的特性和适用场景。了解这些存储引擎的特性,有助于在设计数据库时做出合理的选择。以下是 MySQL 中几种常用存储引擎的详细介绍。 1. InnoDB 特点: 事务支持:InnoDB 是一个支持 ACID(原子性、一致性、隔离性、持久性)事务的存储引擎。行级锁:使用行级锁来提高并发性,减少锁竞争

MyBatis 切换不同的类型数据库方案

下属案例例当前结合SpringBoot 配置进行讲解。 背景: 实现一个工程里面在部署阶段支持切换不同类型数据库支持。 方案一 数据源配置 关键代码(是什么数据库,该怎么配就怎么配) spring:datasource:name: test# 使用druid数据源type: com.alibaba.druid.pool.DruidDataSource# @需要修改 数据库连接及驱动u

linux中使用rust语言在不同进程之间通信

第一种:使用mmap映射相同文件 fn main() {let pid = std::process::id();println!(

Java http请求示例

使用HttpURLConnection public static String httpGet(String host) {HttpURLConnection connection = null;try {URL url = new URL(host);connection = (HttpURLConnection) url.openConnection();connection.setReq

10 Source-Get-Post-JsonP 网络请求

划重点 使用vue-resource.js库 进行网络请求操作POST : this.$http.post ( … )GET : this.$http.get ( … ) 小鸡炖蘑菇 <!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-w

Hbase Filter+Scan 查询效率优化

Hbase Filter+Scan 查询效率问题 众所周知,Hbase利用filter过滤器查询时候会进行全表扫描,查询效率低下,如果没有二级索引,在项目中很多情况需要利用filter,下面针对这种情况尝试了几种优化的方案,仅供参考,欢迎交流。 根据业务要求,作者需要根据时间范围搜索所需要的数据,所以作者设计的rowKey是以时间戳为起始字符串的。 正确尝试: 1.scan 设置 开始行和结

智能工厂程序设计 之1 智能工厂都本俱的方面(Facet,Aspect和Respect)即智能依赖的基底Substrate 之1

Q1、昨天分别给出了三个智能工厂的 “面face”(里面inter-face,外面outer-face和表面surface) 以及每个“面face” 各自使用的“方”(StringProcessor,CaseFilter和ModeAdapter)  。今天我们将继续说说三个智能工厂的“方面” 。在展开之前先看一下三个单词:面向facing,取向oriented,朝向toword。理解这三个词 和