SpringBoot 請(qǐng)求攔截--filter慈参、interceptor、aop

1.場(chǎng)景

web程序中刮萌,對(duì)用戶請(qǐng)求驮配,經(jīng)常會(huì)對(duì)請(qǐng)求進(jìn)行攔截處理,常用的處理方式如下:

  • Filter
  • Interceptor
  • AOP

在此基于SpringBoot的web程序着茸,進(jìn)行這三種攔截方式的說明僧凤。

2.區(qū)別

三種攔截方式的區(qū)別如下:

依賴 Servlet容器 Spring Web Spring
基于實(shí)現(xiàn) 回調(diào)機(jī)制 反射機(jī)制(AOP思想) 動(dòng)態(tài)代理
類別 Filter Interceptor AOP
實(shí)現(xiàn)方式 實(shí)現(xiàn)接口Filter 實(shí)現(xiàn)接口HandlerInterceptor 注解@Aspect
作用范圍 所有URL請(qǐng)求(可過濾) 所有Controller的action 包括自己定義的和其他組件定義的 spring的bean(可過濾)
可操作數(shù)據(jù) 原始Http請(qǐng)求信息: ServletRequest request, ServletResponse response (1)Http請(qǐng)求信息: HttpServletRequest request, HttpServletResponse response, (2)springMvc執(zhí)行的方法信息: HandlerMethod handlerMethod (3)返回結(jié)果(執(zhí)行Action方法后,不報(bào)錯(cuò)): ModelAndView modelAndView (4)異常信息(執(zhí)行Action方法后): Exception ex 請(qǐng)求參數(shù) 返回結(jié)果 異常信息
不可操作數(shù)據(jù) 執(zhí)行方法相關(guān)信息 ResponseBody的返回結(jié)果 http請(qǐng)求信息
相關(guān)方法 doFilter preHandle postHandle afterCompletion@ @Aspect @Pointcut @Before @After @Around
用途 字符編碼元扔, 鑒權(quán)操作, 防重復(fù)提交 記錄執(zhí)行時(shí)間旋膳, 脫敏信息澎语、 過濾敏感詞、 多租戶切換 ...... 字符編碼 鑒權(quán)操作 防重復(fù)提交 異常記錄 ...... 日志記錄 異常記錄 數(shù)據(jù)源切換 請(qǐng)求埋點(diǎn) ......

3.請(qǐng)求順序

基于SpringBoot的web程序,F(xiàn)ilter擅羞、Interceptor尸变、Aop的請(qǐng)求順序如下:

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 測(cè)試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;

/**
* 測(cè)試類
* @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代碼實(shí)現(xiàn)

5.1 說明

1.實(shí)現(xiàn)接口

實(shí)現(xiàn)接口: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;

/**
* 計(jì)算執(zhí)行時(shí)間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]:進(jìn)入Filter");
        // 執(zhí)行servlet方法(如攔截請(qǐng)求减俏,不執(zhí)行servlet召烂,可不執(zhí)行此方法)
        filterChain.doFilter(servletRequest , servletResponse);
        long end = new Date().getTime();
        System.out.println("[Filter-Time]:結(jié)束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();
        // 設(shè)置:實(shí)現(xiàn)類
        registrationBean.setFilter(new TimerFilter());
        // 設(shè)置:UrlPatterns (要攔截的url)
        registrationBean.setUrlPatterns(Arrays.asList("/*"));
        // 設(shè)置:優(yōu)先級(jí)
        registrationBean.setOrder(1);
        return registrationBean;
    }

}

5.3 測(cè)試

1.測(cè)試請(qǐng)求

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

2.輸出結(jié)果

[Filter-Time]:進(jìn)入Filter
[Controller Action]:userName=wangcp娃承;age=18
[Filter-Time]:結(jié)束Filter奏夫,共40毫秒

5.4 配置順序

// 設(shè)置:優(yōu)先級(jí)
registrationBean.setOrder(1);

6.HandlerInterceptor 代碼實(shí)現(xiàn)

6.1 說明

1.實(shí)現(xiàn)接口

實(shí)現(xiàn)接口:org.springframework.web.servlet.HandlerInterceptor

2.核心方法

各方法詳細(xì)介紹可查看: https://blog.csdn.net/weixin_41767154/article/details/84648873

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

// 在當(dāng)前這個(gè)Interceptor的preHandle方法返回值為true的時(shí)候才會(huì)執(zhí)行
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable ModelAndView modelAndView) throws Exception;

// 也是需要當(dāng)前對(duì)應(yīng)的Interceptor的preHandle方法的返回值為true時(shí)才會(huì)執(zhí)行
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;

/**
* 鑒權(quán)攔截器
* @author wangcp
* @date 2021/05/12 16:05
 * https://blog.csdn.net/weixin_41767154/article/details/84648873
**/
@Component
public class AuthInterceptor implements HandlerInterceptor {

    /**
    * 調(diào)用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]:進(jìn)入preHandle");
        if(handler instanceof HandlerMethod){
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            System.out.println("[Interceptor-auth]:訪問信息=" + handlerMethod.getShortLogMessage());
            // 獲取head鑒權(quán)信息
            String sign = request.getHeader("sign");
            if (!"123456".equals(sign)) {
                // 鑒權(quán)不通過
                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", "鑒權(quán)失敗");
                writer.write(jsonObject.toJSONString());
                writer.flush();
                writer.close();
                System.out.println("[Interceptor-auth]:----------鑒權(quán)不通過----------");
                System.out.println("[Interceptor-auth]:結(jié)束preHandle");
                return false;
            } else {
                // 鑒權(quán)通過
                System.out.println("[Interceptor-auth]:----------鑒權(quán)通過----------");
                System.out.println("[Interceptor-auth]:結(jié)束preHandle");
                return true;
            }
        }
        System.out.println("[Interceptor-auth]:結(jié)束preHandle");
        // 返回true為通過校驗(yàn),返回false為不通過校驗(yàn)
        return true;
    }

    /**
    * 在當(dāng)前這個(gè)Interceptor的preHandle方法返回值為true的時(shí)候才會(huì)執(zhí)行
     * 執(zhí)行時(shí)機(jī):在DispatcherServlet進(jìn)行視圖的渲染之前執(zhí)行历筝,也就是說在這個(gè)方法中你可以對(duì)ModelAndView進(jìn)行操作
    * @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));
    }

    /**
     * 也是需要當(dāng)前對(duì)應(yīng)的Interceptor的preHandle方法的返回值為true時(shí)才會(huì)執(zhí)行
     * 執(zhí)行時(shí)機(jī):該方法將在整個(gè)請(qǐng)求完成之后酗昼,也就是DispatcherServlet渲染了視圖執(zhí)行
     * (這個(gè)方法的主要作用是用于清理資源的)
    * @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統(tǒng)一配置
* @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("/**");

        // 支持定義多個(gè)PathPattern和excludePathPatterns
//        registry.addInterceptor(authInterceptor).addPathPatterns("/xxx","/**").excludePathPatterns("/yyy","/zzz");
    }
}

6.3 測(cè)試

1.正向測(cè)試

請(qǐng)求:

http://localhost:8080/my/test?userName=wangcp&age=18
請(qǐng)求head:sign=123456

輸出結(jié)果:

[Interceptor-auth]:進(jìn)入preHandle
[Interceptor-auth]:訪問信息=com.wangcp.intercept.controller.MyController#test[2 args]
[Interceptor-auth]:----------鑒權(quán)通過----------
[Interceptor-auth]:結(jié)束preHandle
[Controller Action]:userName=wangcp;age=18
[Interceptor-auth]:postHandle ModelAndView=null
[Interceptor-auth]:afterCompletion Exception=null

請(qǐng)求結(jié)果:

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

2.逆向測(cè)試

請(qǐng)求:

http://localhost:8080/my/test?userName=wangcp&age=18
請(qǐng)求head:sign=8888

輸出結(jié)果:

[Interceptor-auth]:進(jìn)入preHandle
[Interceptor-auth]:訪問信息=com.wangcp.intercept.controller.MyController#test[2 args]
[Interceptor-auth]:----------鑒權(quán)不通過----------
[Interceptor-auth]:結(jié)束preHandle

請(qǐng)求結(jié)果:

{
    "success": false,
    "message": "鑒權(quán)失敗"
}

7.AOP代碼實(shí)現(xiàn)

7.1 說明

相關(guān)注解

各注解詳細(xì)介紹可參考: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實(shí)現(xiàn)攔截請(qǐng)求
* @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-進(jìn)入");
        // 請(qǐng)求參數(shù)
        System.out.println("[AOP-log]:Around-請(qǐng)求參數(shù)="+ JSONObject.toJSONString(joinPoint.getArgs()));

        // 執(zhí)行切面方法
        Object object = joinPoint.proceed();

        // 執(zhí)行結(jié)果
        System.out.println("[AOP-log]:Around-執(zhí)行結(jié)果="+ JSONObject.toJSONString(object));
        System.out.println("[AOP-log]:Around-結(jié)束");
        return object;
    }
}

7.3 測(cè)試

1.測(cè)試請(qǐng)求

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

2.輸出結(jié)果

[AOP-log]:Around-進(jìn)入
[AOP-log]:Around-請(qǐng)求參數(shù)=["wangcp","18"]
[AOP-log]:Before
[Controller Action]:userName=wangcp麻削;age=18
[AOP-log]:After
[AOP-log]:Around-執(zhí)行結(jié)果={"success":true,"message":"[Controller Action]:userName=wangcp;age=18"}
[AOP-log]:Around-結(jié)束

3.請(qǐng)求結(jié)果

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

8.匯總測(cè)試

同時(shí)打開上述的Filter呛哟,Interceptor,AOP匿沛,一起來攔截請(qǐng)求扫责。

1.測(cè)試請(qǐng)求

http://localhost:8080/my/test?userName=wangcp&age=18
請(qǐng)求head:sign=123456

2.輸出結(jié)果

[Filter-Time]:進(jìn)入Filter
[Interceptor-auth]:進(jìn)入preHandle
[Interceptor-auth]:訪問信息=com.wangcp.intercept.controller.MyController#test[2 args]
[Interceptor-auth]:----------鑒權(quán)通過----------
[Interceptor-auth]:結(jié)束preHandle
[AOP-log]:Around-進(jìn)入
[AOP-log]:Around-請(qǐng)求參數(shù)=["wangcp","18"]
[AOP-log]:Before
[Controller Action]:userName=wangcp;age=18
[AOP-log]:After
[AOP-log]:Around-執(zhí)行結(jié)果={"success":true,"message":"[Controller Action]:userName=wangcp俺祠;age=18"}
[AOP-log]:Around-結(jié)束
[Interceptor-auth]:postHandle ModelAndView=null
[Interceptor-auth]:afterCompletion Exception=null
[Filter-Time]:結(jié)束Filter公给,共1毫秒

輸出順序與文章開頭請(qǐng)求順序講解一致。

3.請(qǐng)求結(jié)果

{
    "success": true,
    "message": "[Controller Action]:userName=wangcp蜘渣;age=18"
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末淌铐,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蔫缸,更是在濱河造成了極大的恐慌腿准,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拾碌,死亡現(xiàn)場(chǎng)離奇詭異吐葱,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)校翔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門弟跑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人防症,你說我怎么就攤上這事孟辑“ゼ祝” “怎么了?”我有些...
    開封第一講書人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵饲嗽,是天一觀的道長(zhǎng)炭玫。 經(jīng)常有香客問我,道長(zhǎng)貌虾,這世上最難降的妖魔是什么吞加? 我笑而不...
    開封第一講書人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮尽狠,結(jié)果婚禮上衔憨,老公的妹妹穿的比我還像新娘。我一直安慰自己晚唇,他們只是感情好巫财,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著哩陕,像睡著了一般平项。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上悍及,一...
    開封第一講書人閱讀 49,816評(píng)論 1 290
  • 那天闽瓢,我揣著相機(jī)與錄音,去河邊找鬼心赶。 笑死扣讼,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的缨叫。 我是一名探鬼主播椭符,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼耻姥!你這毒婦竟也來了销钝?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤琐簇,失蹤者是張志新(化名)和其女友劉穎蒸健,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體婉商,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡似忧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了丈秩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盯捌。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蘑秽,靈堂內(nèi)的尸體忽然破棺而出饺著,到底是詐尸還是另有隱情滤祖,我是刑警寧澤,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布瓶籽,位于F島的核電站,受9級(jí)特大地震影響埂材,放射性物質(zhì)發(fā)生泄漏塑顺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一俏险、第九天 我趴在偏房一處隱蔽的房頂上張望严拒。 院中可真熱鬧,春花似錦竖独、人聲如沸裤唠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽种蘸。三九已至,卻和暖如春竞膳,著一層夾襖步出監(jiān)牢的瞬間航瞭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工坦辟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留刊侯,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓锉走,卻偏偏與公主長(zhǎng)得像滨彻,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子挪蹭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容