在使用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. day13 memcache,redis上篇

    memcache memcache简介 Memcached是一个自由开源的,高性能,分布式内存对象缓存系统. Memcached是以LiveJournal旗下Danga Interactive公司的B ...

  2. iOS 9 学习系列:Split Screen Multitasking

    http://www.cocoachina.com/ios/20151010/13601.html iOS 9 的一个重大变化就是增加了多任务,这个多任务允许用户在屏幕上同时运行多个 app.有两种形 ...

  3. docker-ce 安装和卸载

    一.按照官网给的安装方法进行Ubuntu16.04 docker-ce 的安装,步骤如下: 1.由于apt官方库里的docker版本可能比较旧,所以先卸载可能存在的旧版本: sudo apt-get ...

  4. Leetcode783.Minimum Distance Between BST Nodes二叉搜索树结点最小距离

    给定一个二叉搜索树的根结点 root, 返回树中任意两节点的差的最小值. 示例: 输入: root = [4,2,6,1,3,null,null] 输出: 1 解释: 注意,root是树结点对象(Tr ...

  5. docker+jenkins的实现方式(ps.使用dockerfile的方式)!

    继http://www.cnblogs.com/guilty/p/4747993.html之后. 前两天朋友问的,docker+jenkins整合. 我也没搞过,但是正好最近有空,我也很有兴趣,就搞一 ...

  6. select引起的服务端程序崩溃问题

    现象: 某个线上的服务最近频繁崩溃.该服务使用C++编写,是个网络服务端程序.作为TCP服务端,接收和转发客户端发来的消息,并给客户端发送消息.该服务跑在CentOS上,8G内存.线上环境中,与客户端 ...

  7. 【Leetcode链表】环形链表 II(142)

    题目 给定一个链表,返回链表开始入环的第一个节点. 如果链表无环,则返回 null. 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始). 如果 pos ...

  8. Python 常量

  9. @hdu - 5960@ Subsequence

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 给定如下计算序列权值的函数: 对于一个由三元组 (cost0, ...

  10. Python类型模块:types

    types模块中定义了Python中所有的类型,包括NoneType,  TypeType,  IntType,  FloatType,  BooleanType,  BufferType,  Bui ...