第19章 SpringBoot拦截器

Spring Interceptor 拦截器主要用来拦截用户的请求,执行预定的业务逻辑,例如判断用户是否已经登录。在 SpringMVC 中,当用户请求发送到 Controller 时,在被 Controller 处理之前,它会经过Interceptor拦截器。Spring Interceptor是一个非常类似于Servlet Filter 的概念 。过滤器 Filter 是依赖于Servlet容器,属于Servlet规范的一部分。过滤器是在请求进入Web容器(Tomcat)后,但请求进入servlet之前进行预处理的。

接下来,我们创建一个“SpringBootInterceptorDemo”工程

然后我们修改编码格式以及Maven仓库地址,我们省略这个过程了。

接下来,我们修改 “pom.xml” 文件,添加SpringBoot和Web依赖,如下所示

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.demo</groupId>
    <artifactId>SpringBootInterceptorDemo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.13</version>
        <relativePath/>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.7.18</version>
            </plugin>
        </plugins>
    </build>
    
</project>

接下来,我们创建 Appliaction 入口类文件

package com.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {

        SpringApplication.run(Application.class, args);
    }
}

接下来,我们创建 TestController 控制器

package com.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Map;

@Controller
@RequestMapping("/test")
public class TestController {

    @GetMapping("/hello")
    public String hello(Map map){

        map.put("message", "hello, world!");
        return "test";
    }

}

接下来,我们创建 “resources\static\index.html” 入口文件

<!doctype html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>index</title>
</head>
<body>

<a href="/test/hello">/test/hello</a>
<br />

</body>
</html>

接着,我们继续需要创建 “resources\templates\test.html” 文件

<!doctype html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>test</title>
</head>
<body>

<p th:text="${message}"></p>

</body>
</html>

我们运行查看一下效果

接下来,我们创建一个Spring拦截器,我们需要实现 org.springframework.web.servlet.HandlerInterceptor接口 或继承 org.springframework.web.servlet.handler.HandlerInterceptorAdapter类。

package com.demo.interceptor;

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

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

public class MyCustomerInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, 
    						HttpServletResponse response, 
    						Object handler) throws Exception {
    	return false;
    }

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

    }

    @Override
    public void afterCompletion(HttpServletRequest request, 
                                HttpServletResponse response, 
                                Object handler, 
                                Exception ex) throws Exception {

    }
}

重写下面下面三个方法

第一,preHandler 方法在请求处理之前被调用,返回Boolean值决定是否执行后续操作。
第二,postHandler 方法在当前请求处理完成之后,但是视图还没有解析。
第三,afterCompletion 方法会在整个请求结束之后执行,可获取响应数据及异常信息。

在这三个方法中有两个重要的参数,HttpServletRequest request 和 HttpServletResponse response 两个对象。他们分别代表用户请求和响应,通过他们可以做很多操作,这里不做详细介绍了。

package com.demo.interceptor;

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

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

public class MyCustomerInterceptor implements HandlerInterceptor {

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

        System.out.println("第一,拦截器preHandle方法执行");
        return true; // 让 Controller 继续执行
    }

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

        System.out.println("第二,拦截器postHandle方法执行");
        // 修改视图或数据
        modelAndView.addObject("message", "hello, interceptor");
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler,
                                Exception ex) throws Exception {
        System.out.println("第三,拦截器afterCompletion方法执行");
    }
}

请注意,我们在第二个 “postHandle” 方法中对视图做了一个修改。

接下来,我们要注册这个 MyCustomerInterceptor 拦截器。

package com.demo.config;

import com.demo.interceptor.MyCustomerInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(new MyCustomerInterceptor()).addPathPatterns("/test/**");
    }

}

这里我们增加了一个Spring配置类,使其继承 WebMvcConfigurer 用于Web应用的定制,然后重写addInterceptors方法来添加我们刚刚创建的 MyCustomerInterceptor 拦截器。那么,这个拦截器到底拦截哪些请求呢?后面的 addPathPatterns() 方法指定要拦截哪些请求,也可以通过excludePathPatterns() 指定不拦截哪些请求。这里,我们拦截 “/test/***” 的请求,两个 “**” 星号代表我们可以匹配类似“/test/aaa/bbb”的请求路径。另外,addPathPatterns和excludePathPatterns可以接受多个请求连接参数。

接下来,我们重新运行测试一下吧

视图数据被我们修改了,我们去控制台查看日志

我们看到三个方法依次执行了。

我们总结一下拦截器执行过程,添加拦截器后,执行Controller的方法之前,请求会先被拦截器拦截住,执行 preHandle 方法。这个方法需要返回个布尔类型的值,如果返回true,就表示放行本次操作,继续访问controller中的方法。如果返回false,则不会放行(controller中的方法也不会执行)。如果返回true放行的话,controller当中的方法执行完毕后,再回过来执行 postHandle 这个方法,最后执行 afterCompletion 方法,执行完毕之后,最终给浏览器返回响应数据。

如果我们想要实现检查用户登录的操作的话,最快捷的办法就是在preHandle方法中抛出自定义异常,然后通过全局异常处理器来返回给客户端信息。我们首先自定义一个登陆异常 “LoginException” 类

package com.demo.exception;

public class LoginException extends RuntimeException {

    protected final String message;

    // 构造方法
    public LoginException(String message) {
        this.message = message;
    }

    @Override
    public String getMessage() {
        return message;
    }
}

然后再定义一个 全局异常处理器 “GlobalExceptionHandler” 类。

package com.demo.exception;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(LoginException.class)
    public ModelAndView handler(LoginException e) {

        e.printStackTrace();

        ModelAndView mv = new ModelAndView();
        mv.setViewName("exception");
        mv.addObject("message", e.getMessage());
        return mv;
    }

}

接下来,我们需要创建 “resources\templates\exception.html” 文件

<!doctype html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>exception</title>
</head>
<body>

<p th:text="${message}"></p>

</body>
</html>

关于异常的处理完毕了,我们创建一个 “LoginCheckInterceptor ” 拦截器。

package com.demo.interceptor;

import com.demo.exception.LoginException;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LoginCheckInterceptor implements HandlerInterceptor {

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

        String token = request.getParameter("token");
        if(token == null || token.length() <= 0){
            throw new LoginException("没有登录token,无法访问该页面!");
        }
        return true;
    }
}

这里,我们直接抛出了LoginException异常,不需要返回 false 了。

接下来,我们注册刚刚创建的拦截器

package com.demo.config;

import com.demo.interceptor.LoginCheckInterceptor;
import com.demo.interceptor.MyCustomerInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(new MyCustomerInterceptor()).addPathPatterns("/test/**");
        registry.addInterceptor(new LoginCheckInterceptor()).addPathPatterns("/test/**");
    }

}

我们可以定义多个拦截器组成一个拦截器链。我们可以在适配器中注入多个拦截器。多个拦截器的话,先注册的先执行,也可以通过Order方法来设置执行的顺序(值越小越先执行)。

接下来,我们修改 “resources\static\index.html” 入口文件

<!doctype html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>index</title>
</head>
<body>

<a href="/test/hello">/test/hello</a>
<br />
<a href="/test/hello?token=abc">/test/hello?token=abc</a>
<br />

</body>
</html>

接下来,我们重新运行测试一下

没有token参数的话,就无法正常访问页面。

有token参数的话,就可以正常访问页面了。

如果需要返回 Json 格式数据的话,可以使用 HttpServletResponse response 对象和 com.alibaba.fastjson2 来完成。

<dependency>
	<groupId>com.alibaba.fastjson2</groupId>
	<artifactId>fastjson2</artifactId>
	<version>2.0.52</version>
</dependency>

增加依赖库之后,修改 LoginCheckInterceptor 拦截器

package com.demo.interceptor;

import com.alibaba.fastjson2.JSON;
import com.demo.exception.LoginException;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;

public class LoginCheckInterceptor implements HandlerInterceptor {

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

        String token = request.getParameter("token");
        if(token == null || token.length() <= 0){

            //throw new LoginException("没有登录token,无法访问该页面!");

            response.setContentType("application/json;charset=UTF-8");
            Writer writer = response.getWriter();
            Map resultMap = new HashMap();
            resultMap.put("code", "500");
            resultMap.put("msg", "没有登录token,无法访问该页面!");
            String result = JSON.toJSONString(resultMap);
            writer.write(result);
            writer.flush();

            return false; // 停止 controller 的执行
        }
        return true;
    }
}

我们再来测试一下啊

如果在拦截器中获取到用户请求参数并记录日志中,但是会发现在 controller 中的 @RequestBody 注解获取不到参数了(流是一次性的,数据会被消耗掉,无法重复读取)。Spring 中的 request.getInputStream()和 request.getReader()只能被读取一次,而 @RequestBody 注解底层也是通过流来请求数据,所以需要把拦截器中的数据流保存下来让 controller 层可以读取的数据。

本工程完整代码下载: https://download.youkuaiyun.com/download/richieandndsc/89953254

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值