在使用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配置方式

Filter的实现方式

@Componentpublic 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<String,String> paramMap = new HashMap<>();
paramMap.put("charset","utf-8");
registrationBean.setInitParameters(paramMap);
//相当于@webFilter的 urlPatterns = "/*"的作用
List<String> 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()方法的时候,看看请求响应是否成以及控制台的输出:

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

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

Interceptor

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

  • 简介:

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

  • 实现和配置方式:

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

  • 解释说明:

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

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

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

Interceptor拦截器的实现方式

/** * @Author 林必昭 * @Date 2019/7/4 13:15 */
@Componentpublic 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注入容器中:

@Configurationpublic class WebConfig extends WebMvcConfigurerAdapter {
@Autowired
TimeInterceptor timeInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(timeInterceptor);
}}

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

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

那么当控制层中抛出异常,如果没有使用全局异常处理,在拦截器上也能捕获到异常信息,我们可以尝试一下,在Controller抛出一个RuntimeExceptionRuntimeException并没有在全局异常处理中被处理,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;
}

观察控制台输出:

结果很明显了,当控制层出现异常的时候,异常没有被全局处理器处理,到达拦截器,拦截器会捕获到异常,这时候只执行了preHandleafterCompletionn方法,并没有执行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 */
@ControllerAdvicepublic class GlobalExceptionHandler {
@ExceptionHandler(UserNotExistException.class) @ResponseBody @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
//服务器内部错误
public Map<String, Object> handleUserNotExistException(UserNotExistException ex) { Map<String, Object> 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!!")
}

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

Aspect

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

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

 <!-- 切面 --><dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-aop</artifactId></dependency>

切面拦截的实现方式

@Aspect@Componentpublic 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();拿到对应方法的参数,我们通过遍历把参数打印看一下。

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

总结:

1.过滤器可以拿到原始方法的Http的请求和响应信息,拿不到对应方法的详细信息,拦截器既可以拿到原始方法的Http请求和响应信息,也能拿到对应方法的详细信息,但是拿不到被调用方法对应参数的值,而切面可以拿到被调用方法传递过来参数的值,但却拿不到原始的Http请求和响应对象。2.Controller方法抛出异常之后,最先捕获到异常的是切片,如果你定义了全局异常处理器并声明了ControllerAdvice,切片捕获到异常往外抛,就轮到全局异常处理器处理,接着到拦截器,再到过滤器,也就是:拦截作用顺序:Aspect->全局处理器->拦截器->过滤器->Tomcat

Filter、Intercepter、AOP的区别的更多相关文章

  1. spring boot: filter/interceptor/aop在获取request/method参数上的区别(spring boot 2.3.1)

    一,filter/interceptor/aop在获取参数上有什么区别? 1,filter可以修改HttpServletRequest的参数(doFilter方法的功能), interceptor/a ...

  2. Filter ,Interceptor,AOP

    一.Filter: Filter也称之为过滤器,它是Servlet技术中比较激动人心的技术,WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态 ...

  3. 过滤器和拦截器filter和Interceptor的区别

    1.创建一个Filter过滤器只需两个步骤 创建Filter处理类 web.xml文件中配置Filter 2.Servlet中的过滤器Filter是实现了javax.servlet.Filter接口的 ...

  4. Filter与Servlet的区别与联系

    Filter与Servlet的区别与联系 转自 http://blog.csdn.net/gaibian0823/article/details/51027495 在我们写代码时,在web.xml中总 ...

  5. filter和interceptor的区别

    前言 最近在面试的时候,被问到了这个问题,觉得答得不是很好,在此进行整理和记录,供自己学习,也希望能帮助到大家. 什么是Filter 在java的javax.servlet下有一个接口Filter.任 ...

  6. every();some();filter();map();forEach()各自区别:

    every();some();filter();map();forEach()各自区别: (1)every()方法:(返回值为boolean类型) 对数组每一项都执行测试函数,知道获得对指定的函数返回 ...

  7. filter listener interceptor的区别

    转自: http://www.cnblogs.com/shangxiaofei/p/5328377.html https://www.cnblogs.com/jinb/p/6915351.html 一 ...

  8. java---servlet与filter的联系与区别

    filter是一个可以复用的代码片段,可以用来转换HTTP请求.响应和头信息.Filter不像Servlet,它不能产生一个请求或者响应,它只是修改对某一资源的请求,或者修改从某一的响应. 最近使用插 ...

  9. 【jQuery】【转】jQuery中filter()和find()的区别

    Precondition: 现在有一个页面,里面HTML代码为: <div class="css"> <p class="rain">测 ...

随机推荐

  1. jquery.dataTables的探索之路-服务端分页配置

    最近闲来无事想研究下数据表格,因为之前接触过layui和bootstrap的数据表格,本着能学多少学多少的学习态度,学习下dataTables的服务端分页配置.特与同学们一块分享下从中遇到的问题和解决 ...

  2. 大数据技术之Hadoop(MapReduce)

    第1章 MapReduce概述 1.1 MapReduce定义 1.2 MapReduce优缺点 1.2.1 优点 1.2.2 缺点 1.3 MapReduce核心思想 MapReduce核心编程思想 ...

  3. Mybatis自查询递归查找子菜单

    之前写过 java从数据库读取菜单,递归生成菜单树 今天才发现mybatis也可以递归查询子菜单 先看一下数据库 主键id,名称name,父id,和url 设计菜单类 public class Men ...

  4. Directx11教程36 纹理映射(6)

    原文:Directx11教程36 纹理映射(6)    本章主要是整理代码,做以下两件事情: 1.把世界坐标矩阵的计算,放在GraphicsClass的渲染函数中,之前放在D3DClass中,而且只是 ...

  5. 使用curl指令实现restful接口操作

    curl 是很方便的Rest客戶端,可以很方便的完成許多Rest API測試的需求,甚至,如果是需要先登入或認證的rest api,也可以進行測試,利用curl指令,可以送出HTTP GET, POS ...

  6. 总结 ESP8266 RTOS 开发环境搭建

    总结 ESP8266 RTOS 开发环境搭建 仔细看官方文档. 必须一步一步操作. 不要想当然,以为 make 就可以. 忽略编译警告,除非是错误. 工具链必须使用官方提供的. 多看看 Issues ...

  7. Leetcode830.Positions of Large Groups较大分组的位置

    在一个由小写字母构成的字符串 S 中,包含由一些连续的相同字符所构成的分组. 例如,在字符串 S = "abbxxxxzyy" 中,就含有 "a", " ...

  8. SFINAE and enable_if

    There's an interesting issue one has to consider when mixing function overloading with templates in ...

  9. Qt qmake报错(TypeError: Property 'asciify' of object Core::Internal::UtilsJsExtension)

    问题如题. 解决方案: 第一种 用下管理员权限来打开qt creator,再创建工程.有可能是没权限创建出源码工程目录 第二种 打开qt左边的项目上,可以看到这个项目的编译路径,修改成绝对路径,或者设 ...

  10. 《C程序设计语言》笔记(二)

    四:函数与程序结构 1:函数之间的通信可以通过参数.函数返回值以及外部变量进行. 2:如果函数定义中省略了返回值类型,则默认为int类型.如果没有函数原型,则函数将在第一次出现的表达式中被隐式声明,比 ...