SpringBoot 请求拦截--filter、interceptor、aop

1.场景

web程序中,对用户请求,经常会对请求进行拦截处理,常用的处理方式如下:

  • Filter
  • Interceptor
  • AOP

在此基于SpringBoot的web程序,进行这三种拦截方式的说明。

2.区别

三种拦截方式的区别如下:

依赖 Servlet容器 Spring Web Spring
基于实现 回调机制 反射机制(AOP思想) 动态代理
类别 Filter Interceptor AOP
实现方式 实现接口Filter 实现接口HandlerInterceptor 注解@Aspect
作用范围 所有URL请求(可过滤) 所有Controller的action 包括自己定义的和其他组件定义的 spring的bean(可过滤)
可操作数据 原始Http请求信息: ServletRequest request, ServletResponse response (1)Http请求信息: HttpServletRequest request, HttpServletResponse response, (2)springMvc执行的方法信息: HandlerMethod handlerMethod (3)返回结果(执行Action方法后,不报错): ModelAndView modelAndView (4)异常信息(执行Action方法后): Exception ex 请求参数 返回结果 异常信息
不可操作数据 执行方法相关信息 ResponseBody的返回结果 http请求信息
相关方法 doFilter preHandle postHandle afterCompletion@ @Aspect @Pointcut @Before @After @Around
用途 字符编码, 鉴权操作, 防重复提交 记录执行时间, 脱敏信息、 过滤敏感词、 多租户切换 ...... 字符编码 鉴权操作 防重复提交 异常记录 ...... 日志记录 异常记录 数据源切换 请求埋点 ......

3.请求顺序

基于SpringBoot的web程序,Filter、Interceptor、Aop的请求顺序如下:

Filter -> Interceptor ->AOP -> Controller

image-20210512172914548.png

4.版本

4.1 maven依赖

Filter和Interceptor有spring-boot-starter-web依赖即可:

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

AOP依赖的aspectJ需要额外的maven依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>2.2.9.RELEASE</version>
</dependency>

4.2 测试Controller

package com.wangcp.intercept.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
* 测试类
* @author wangcp
* @date 2021/05/12 15:45
**/
@RestController
@RequestMapping(value = "/my")
public class MyController {

    @GetMapping(value = "/test")
    public Map<String,Object> test(String userName , String age){
        String message = "[Controller Action]:userName=" + userName + ";age=" + age;
        System.out.println(message);
        Map<String,Object> map = new HashMap<>();
        map.put("success",true);
        map.put("message",message);
        return map;
    }

}

5.Filter代码实现

5.1 说明

1.实现接口

实现接口:javax.servlet.Filter

2.核心方法

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {}

5.2 定义

1.定义Filter

package com.wangcp.intercept.filter;


import javax.servlet.*;
import java.io.IOException;
import java.util.Date;

/**
* 计算执行时间Filter
* @author wangcp
* @date 2021/05/12 15:50
**/
public class TimerFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        long begin = new Date().getTime();
        System.out.println("[Filter-Time]:进入Filter");
        // 执行servlet方法(如拦截请求,不执行servlet,可不执行此方法)
        filterChain.doFilter(servletRequest , servletResponse);
        long end = new Date().getTime();
        System.out.println("[Filter-Time]:结束Filter,共" + (end - begin) + "毫秒");
    }
}

2.配置

package com.wangcp.intercept.filter.config;

import com.wangcp.intercept.filter.TimerFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Arrays;

/**
* filter配置类
* @author wangcp
* @date 2021/05/12 15:54
**/
@Configuration
public class WebFilterConfig {

    @Bean
    public FilterRegistrationBean timerFilter(){
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        // 设置:实现类
        registrationBean.setFilter(new TimerFilter());
        // 设置:UrlPatterns (要拦截的url)
        registrationBean.setUrlPatterns(Arrays.asList("/*"));
        // 设置:优先级
        registrationBean.setOrder(1);
        return registrationBean;
    }

}

5.3 测试

1.测试请求

http://localhost:8080/my/test?userName=wangcp&age=18

2.输出结果

[Filter-Time]:进入Filter
[Controller Action]:userName=wangcp;age=18
[Filter-Time]:结束Filter,共40毫秒

5.4 配置顺序

// 设置:优先级
registrationBean.setOrder(1);

6.HandlerInterceptor 代码实现

6.1 说明

1.实现接口

实现接口:org.springframework.web.servlet.HandlerInterceptor

2.核心方法

各方法详细介绍可查看: https://blog.csdn.net/weixin_41767154/article/details/84648873

// 调用Controller方法之前
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

// 在当前这个Interceptor的preHandle方法返回值为true的时候才会执行
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable ModelAndView modelAndView) throws Exception;

// 也是需要当前对应的Interceptor的preHandle方法的返回值为true时才会执行
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable Exception ex) throws Exception;

6.2 定义

1.定义Interceptor

package com.wangcp.intercept.interceptor;

import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;

/**
* 鉴权拦截器
* @author wangcp
* @date 2021/05/12 16:05
 * https://blog.csdn.net/weixin_41767154/article/details/84648873
**/
@Component
public class AuthInterceptor implements HandlerInterceptor {

    /**
    * 调用Controller方法之前
    * @author wangcp
    * @date 2021/05/12 16:06
     * @param request
     * @param response
     * @param handler
    * @return boolean
    */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("[Interceptor-auth]:进入preHandle");
        if(handler instanceof HandlerMethod){
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            System.out.println("[Interceptor-auth]:访问信息=" + handlerMethod.getShortLogMessage());
            // 获取head鉴权信息
            String sign = request.getHeader("sign");
            if (!"123456".equals(sign)) {
                // 鉴权不通过
                response.setCharacterEncoding("utf-8");
                response.setContentType("application/json; charset=utf-8");
                PrintWriter writer = response.getWriter();
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("success", false);
                jsonObject.put("message", "鉴权失败");
                writer.write(jsonObject.toJSONString());
                writer.flush();
                writer.close();
                System.out.println("[Interceptor-auth]:----------鉴权不通过----------");
                System.out.println("[Interceptor-auth]:结束preHandle");
                return false;
            } else {
                // 鉴权通过
                System.out.println("[Interceptor-auth]:----------鉴权通过----------");
                System.out.println("[Interceptor-auth]:结束preHandle");
                return true;
            }
        }
        System.out.println("[Interceptor-auth]:结束preHandle");
        // 返回true为通过校验,返回false为不通过校验
        return true;
    }

    /**
    * 在当前这个Interceptor的preHandle方法返回值为true的时候才会执行
     * 执行时机:在DispatcherServlet进行视图的渲染之前执行,也就是说在这个方法中你可以对ModelAndView进行操作
    * @author wangcp
    * @date 2021/05/12 16:07
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
    * @return void
    */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("[Interceptor-auth]:postHandle ModelAndView=" + JSONObject.toJSONString(modelAndView));
    }

    /**
     * 也是需要当前对应的Interceptor的preHandle方法的返回值为true时才会执行
     * 执行时机:该方法将在整个请求完成之后,也就是DispatcherServlet渲染了视图执行
     * (这个方法的主要作用是用于清理资源的)
    * @author wangcp
    * @date 2021/05/12 16:07
     * @param request
     * @param response
     * @param handler
     * @param ex
    * @return void
    */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("[Interceptor-auth]:afterCompletion Exception=" + JSONObject.toJSONString(ex));
    }
}

2.配置

package com.wangcp.intercept.interceptor.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
* WEB统一配置
* @author wangcp
* @date 2021/05/12 16:20
**/
@Component
public class GlobalWebMvcConfigurer implements WebMvcConfigurer {

    @Autowired
    private HandlerInterceptor authInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor).addPathPatterns("/**");

        // 支持定义多个PathPattern和excludePathPatterns
//        registry.addInterceptor(authInterceptor).addPathPatterns("/xxx","/**").excludePathPatterns("/yyy","/zzz");
    }
}

6.3 测试

1.正向测试

请求:

http://localhost:8080/my/test?userName=wangcp&age=18
请求head:sign=123456

输出结果:

[Interceptor-auth]:进入preHandle
[Interceptor-auth]:访问信息=com.wangcp.intercept.controller.MyController#test[2 args]
[Interceptor-auth]:----------鉴权通过----------
[Interceptor-auth]:结束preHandle
[Controller Action]:userName=wangcp;age=18
[Interceptor-auth]:postHandle ModelAndView=null
[Interceptor-auth]:afterCompletion Exception=null

请求结果:

{
    "success": true,
    "message": "[Controller Action]:userName=wangcp;age=18"
}

2.逆向测试

请求:

http://localhost:8080/my/test?userName=wangcp&age=18
请求head:sign=8888

输出结果:

[Interceptor-auth]:进入preHandle
[Interceptor-auth]:访问信息=com.wangcp.intercept.controller.MyController#test[2 args]
[Interceptor-auth]:----------鉴权不通过----------
[Interceptor-auth]:结束preHandle

请求结果:

{
    "success": false,
    "message": "鉴权失败"
}

7.AOP代码实现

7.1 说明

相关注解

各注解详细介绍可参考:https://blog.csdn.net/qq_45515432/article/details/104187326

org.aspectj.lang.annotation.Aspect
org.aspectj.lang.annotation.Pointcut
org.aspectj.lang.annotation.Before
org.aspectj.lang.annotation.After
org.aspectj.lang.annotation.Around

7.2 定义

package com.wangcp.intercept.aop;

import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
* AOP实现拦截请求
* @author wangcp
* @date 2021/05/12 16:34
 * https://blog.csdn.net/qq_45515432/article/details/104187326
**/
@Component
@Order(1)
@Aspect
public class LogApp {

    @Pointcut("execution(public * com.wangcp..controller..*(..))")
    public void log() {
        
    }

    @Before("log()")
    public void doBefore(JoinPoint joinPoint) {
        System.out.println("[AOP-log]:Before");
    }

    @After("log()")
    public void doAfter(JoinPoint joinPoint) {
        System.out.println("[AOP-log]:After");
    }

    @Around("log()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("[AOP-log]:Around-进入");
        // 请求参数
        System.out.println("[AOP-log]:Around-请求参数="+ JSONObject.toJSONString(joinPoint.getArgs()));

        // 执行切面方法
        Object object = joinPoint.proceed();

        // 执行结果
        System.out.println("[AOP-log]:Around-执行结果="+ JSONObject.toJSONString(object));
        System.out.println("[AOP-log]:Around-结束");
        return object;
    }
}

7.3 测试

1.测试请求

http://localhost:8080/my/test?userName=wangcp&age=18

2.输出结果

[AOP-log]:Around-进入
[AOP-log]:Around-请求参数=["wangcp","18"]
[AOP-log]:Before
[Controller Action]:userName=wangcp;age=18
[AOP-log]:After
[AOP-log]:Around-执行结果={"success":true,"message":"[Controller Action]:userName=wangcp;age=18"}
[AOP-log]:Around-结束

3.请求结果

{"success":true,"message":"[Controller Action]:userName=wangcp;age=18"}

8.汇总测试

同时打开上述的Filter,Interceptor,AOP,一起来拦截请求。

1.测试请求

http://localhost:8080/my/test?userName=wangcp&age=18
请求head:sign=123456

2.输出结果

[Filter-Time]:进入Filter
[Interceptor-auth]:进入preHandle
[Interceptor-auth]:访问信息=com.wangcp.intercept.controller.MyController#test[2 args]
[Interceptor-auth]:----------鉴权通过----------
[Interceptor-auth]:结束preHandle
[AOP-log]:Around-进入
[AOP-log]:Around-请求参数=["wangcp","18"]
[AOP-log]:Before
[Controller Action]:userName=wangcp;age=18
[AOP-log]:After
[AOP-log]:Around-执行结果={"success":true,"message":"[Controller Action]:userName=wangcp;age=18"}
[AOP-log]:Around-结束
[Interceptor-auth]:postHandle ModelAndView=null
[Interceptor-auth]:afterCompletion Exception=null
[Filter-Time]:结束Filter,共1毫秒

输出顺序与文章开头请求顺序讲解一致。

3.请求结果

{
    "success": true,
    "message": "[Controller Action]:userName=wangcp;age=18"
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,313评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,369评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,916评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,333评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,425评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,481评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,491评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,268评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,719评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,004评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,179评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,832评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,510评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,153评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,402评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,045评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,071评论 2 352

推荐阅读更多精彩内容