深入理解SpringMVC开发-拦截器

前言

我们之前谈到过当请求来到DispatcherServlet时,他会根据HanlderMapping的机制找到处理器,这样就会返回一个HandlerExecutionChain对象,这个对象包含处理器和拦截器.这里的拦截器会对处理器进行拦截,这样通过拦截器就可以增强处理器的功能,这节的内容就是对他的使用.

1. 拦截器设计

首先所有的拦截器都需要实现HnadlerInterceptor接口,该接口定义如下:


package org.springframework.web.servlet;
public interface HandlerInterceptor {

	//处理器执行前方法
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }
	//处理器执行后方法
    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
    }
//处理器完成后方法
    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
    }
}

上面的中文注释都是我加入的.所以除了需要知道拦截器各个方法的作用之外,还需要知道这些方法执行的流程,如图
在这里插入图片描述
从图中可以看出,其流程描述如下:

  • 执行preHandle方法,该方法会返回一个布尔值.如果是false,则结束所有流程;如果为true,则执行下一步.
  • 执行处理器逻辑,它包括控制器的功能
  • 执行postHandle方法
  • 执行视图解析和视图渲染
  • 执行afterCompletion方法
    因为这个接口是Java8的接口,所以3个方法都被声明为default,并且提供空实现.当我们需要自己定义方法的时候,只需要实现HandlerInterceptor,覆盖对应的方法即可.

2. 开发拦截器

从上一节的论述中知道只需要实现Interceptor接口即可.下面先实现一个简单的拦截器,代码如下:

package cn.hctech2006.boot.bootmvc.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Interceptor1 extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("处理器前方法");
        //返回true,不会拦截后续的处理
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("处理器后方法");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("处理器完成方法");
    }
}

这里的代码实现了HandlerInterceptor,然后按照自己的需要重写了三个具体的方法.在这些方法中都打印了一些信息,这样就可以定位拦截器方法的执行顺序了.其中这里的preHandle方法,返回的是true,后续测试时,有兴趣的读者kepi把它改成false,在观察其执行顺序.有了这个拦截器,Spring MVC并不会发现他,它还需要注册才能让能够拦截处理器,代码如下:

package cn.hctech2006.boot.bootmvc;
//定制扫描路径
@SpringBootApplication
//扫描MyBatis的DAO接口
@MapperScan(basePackages = "cn.hctech2006.boot.bootmvc",
        annotationClass = Repository.class)
public class BootMvcApplication implements WebMvcConfigurer {

    public static void main(String[] args) {
        SpringApplication.run(BootMvcApplication.class, args);
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册拦截器到Spring MVC机制,然后他会返回一个拦截器注册
        InterceptorRegistration ir = registry.addInterceptor(new Interceptor1());
        //指定拦截匹配模式,限制拦截器请求.
        ir.addPathPatterns("/interceptor/*");
    }
}

这里通过实现WebMvcConfiguration接口,重写其中的addInterceptors方法,进而加入自定义拦截器-Interceptor1,然后为期指定拦截的模式,所以它只会拦截与正则表达式"/interceptor/*"匹配的请求.这里还需要创建对应的请求方法,为此新建控制器来实现

@Controller
@RequestMapping("/interceptor")
public class InterceptorController {
    @GetMapping("start")
    public String start(){
        System.out.println("执行处理器逻辑");
        return "/role/welcome";
    }
}

这个控制器的start方法只是打开一个欢迎页面,十分简单,同时他自定义了拦截"/interceptor/start",而这个请求显然会被锁创建的拦截器拦截,所以汉字需要请求这个方法,请求就会被我们的拦截器拦截.为了更好的测试这个拦截器,我们在欢迎页面上也打印一下后台的信息,这个页面如下:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" http-equiv="Content-Type" content="text/html">
    <title>深入理解Spring MVC</title>
</head>
<body>
    我不会
</body>
</html>

下面是我请求之后后台打印的日志

处理器前方法
执行处理器逻辑
处理器后方法
视图渲染
处理器完成方法

其中视图渲染是我自己家的,因为没法用JSP测试
显然处理器被拦截器拦截了,这里需要注意的是拦截器方法的执行顺序.有兴趣的读者可以把拦截器的preHandle方法修改为false,或者让控制器抛出异常,然后重新测试,从而进一步掌握整个拦截器的流程.这些都非常容易做到,不再赘述

3. 多个拦截器的顺序

上一节讨论了拦截器,而实际拦截器可能还不止一个.那么在多个拦截器环境中,他的各个方法执行的顺序是怎么样的呢?为了探讨这个问题,我们先创建三个拦截器,代码如下

package cn.hctech2006.boot.bootmvc.interceptor;

public class MultiInterceptor1 extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("["+this.getClass().getSimpleName()+"]处理器前方法");
        //返回true,不会拦截后续的处理
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("["+this.getClass().getSimpleName()+"]处理器后方法");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("["+this.getClass().getSimpleName()+"]处理器完成方法");
    }
}

package cn.hctech2006.boot.bootmvc.interceptor;

public class MultiInterceptor2 extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("["+this.getClass().getSimpleName()+"]处理器前方法");
        //返回true,不会拦截后续的处理
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("["+this.getClass().getSimpleName()+"]处理器后方法");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("["+this.getClass().getSimpleName()+"]处理器完成方法");
    }
}

package cn.hctech2006.boot.bootmvc.interceptor;

public class MultiInterceptor3 extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("["+this.getClass().getSimpleName()+"]处理器前方法");
        //返回true,不会拦截后续的处理
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("["+this.getClass().getSimpleName()+"]处理器后方法");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("["+this.getClass().getSimpleName()+"]处理器完成方法");
    }
}

然后注册拦截的方法来注册以上三个拦截器,代码如下

package cn.hctech2006.boot.bootmvc;

//定制扫描路径
@SpringBootApplication
//扫描MyBatis的DAO接口
@MapperScan(basePackages = "cn.hctech2006.boot.bootmvc",
        annotationClass = Repository.class)
public class BootMvcApplication implements WebMvcConfigurer {

    public static void main(String[] args) {
        SpringApplication.run(BootMvcApplication.class, args);
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册拦截器到Spring MVC机制,然后他会返回一个拦截器注册
        InterceptorRegistration ir = registry.addInterceptor(new Interceptor1());
        //指定拦截匹配模式,限制拦截器请求.
        ir.addPathPatterns("/interceptor/*");
        //注册拦截器到Spring MVC机制,然后他会返回一个拦截器注册
        InterceptorRegistration ir1 = registry.addInterceptor(new MultiInterceptor1());
        //指定拦截匹配模式,限制拦截器请求.
        ir.addPathPatterns("/interceptor/*");
        //注册拦截器到Spring MVC机制,然后他会返回一个拦截器注册
        InterceptorRegistration ir2 = registry.addInterceptor(new MultiInterceptor2());
        //指定拦截匹配模式,限制拦截器请求.
        ir.addPathPatterns("/interceptor/*");
        //注册拦截器到Spring MVC机制,然后他会返回一个拦截器注册
        InterceptorRegistration ir3 = registry.addInterceptor(new MultiInterceptor3());
        //指定拦截匹配模式,限制拦截器请求.
        ir.addPathPatterns("/interceptor/*");
    }
}

这样这些拦截器都会拦截与"/interceptor/*"匹配的请求.这里使用浏览器再次请求代码中的start方法,于是日志如下

[MultiInterceptor1]处理器前方法
[MultiInterceptor2]处理器前方法
[MultiInterceptor3]处理器前方法
执行处理器逻辑
[MultiInterceptor3]处理器后方法
[MultiInterceptor2]处理器后方法
[MultiInterceptor1]处理器后方法
视图渲染
[MultiInterceptor3]处理器完成方法
[MultiInterceptor2]处理器完成方法
[MultiInterceptor1]处理器完成方法

这个结果是一个典型的责任链模式的规则,对于处理器前方法采用先注册先执行,而处理后方法和完成方法则是先注册后执行的规则.只是上述仅测试了处理器前(preHandle)方法返回为true的场景,在某些时候还可能返回为false,这个时候又会怎么样呢?为此,可将MultiInterceptor的preHandle方法修改为false,然后在进行测试,日志如下


[MultiInterceptor1]处理器前方法
[MultiInterceptor2]处理器前方法
[MultiInterceptor1]处理器完成方法

从上面的日志可以看出,处理器前(preHandle)方法会执行,但是一旦返回false,则后续的拦截器,处理器和所有拦截器的处理器后方法(postHandle)都不会执行.完成方法afterCompletion则不一样,它只会执行返回true的拦截器的完成方法,而且顺序是先注册后执行.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值