請(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

請(qǐng)求順序.png

4 版本

4.1 maven依賴

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

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.2.9.RELEASE</version>
</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.pdd.module.lanjie.controller;

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

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

@RestController
@RequestMapping("/my")
public class MyController {
    
    @RequestMapping("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 request, ServletResponse response, FilterChain chain) throws IOException, ServletException;

5.2 定義

(1) 定義Filter

package com.pdd.module.lanjie.filter;

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

/**
 * 計(jì)算執(zhí)行時(shí)間Filter
 */
public class TimerFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        long begin = new Date().getTime();
        System.out.println("[Filter-Time]:進(jìn)入Filter");
        // 執(zhí)行servlet方法(如攔截請(qǐng)求,不執(zhí)行Servlet磷雇,可不執(zhí)行此方法)
        chain.doFilter(request, response);
        long end = new Date().getTime();
        System.out.println("[Filter-Time]:結(jié)束Filter偿警,共" + (end - begin) + "毫秒");
    }
}

(2)配置

package com.pdd.module.lanjie.filter.config;

import com.pdd.module.lanjie.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;

@Configuration
public class WebFilterConfig {
    @Bean
    public FilterRegistrationBean timerFilter() {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        // 設(shè)置:實(shí)現(xiàn)類
        registrationBean.setFilter(new TimerFilter());
        // 設(shè)置:UrlPatterns
        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=張三&age=23

(2)輸出結(jié)果

[Filter-Time]:進(jìn)入Filter
[Controller Action]:userName=張三;age=23
[Filter-Time]:結(jié)束Filter唯笙,共90毫秒

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)核心方法

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

// 調(diào)用Controller方法之后(不拋出異常)
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable ModelAndView modelAndView) throws Exception;

// 調(diào)用Controller方法之后(無論是否拋出異常)
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable Exception ex) throws Exception;

6.2 定義

(1)定義Interceptor

package com.pdd.module.lanjie.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)Interceptor
 */
@Component
public class AuthInterceptor implements HandlerInterceptor {
    
    @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;
    }
    
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("[Interceptor-auth]:postHandle ModelAndView=" + JSONObject.toJSONString(modelAndView));
    }
    
    @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.pdd.module.lanjie.interceptor.config;

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;

import javax.annotation.Resource;

/**
 * WEB統(tǒng)一配置
 */
@Component
public class GlobalWebMvcConfigurer implements WebMvcConfigurer {
    
    @Resource
    private HandlerInterceptor authInterceptor;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 可按照順序定義多個(gè)
        registry.addInterceptor(authInterceptor).addPathPatterns("/**");
        
        // 支持定義多個(gè)PathPattern和excludePathPatterns
        //registry.addInterceptor(xxxInterceptor).addPathPatterns("/xxx","/**").excludePathPatterns("/yyy","/zzz");
    }
}

6.3 測(cè)試

6.3.1 正向測(cè)試

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

http://localhost:8080/my/test?userName=張三&age=23

請(qǐng)求head:sign=123456

(2)輸出結(jié)果

  • 控制臺(tái)輸出
[Interceptor-auth]:進(jìn)入preHandle
[Interceptor-auth]:訪問信息=com.pdd.module.lanjie.controller.MyController#test[2 args]
[Interceptor-auth]:----------鑒權(quán)通過----------
[Interceptor-auth]:結(jié)束preHandle
[Controller Action]:userName=張三迹缀;age=23
[Interceptor-auth]:postHandle ModelAndView=null
[Interceptor-auth]:afterCompletion Exception=null
  • 請(qǐng)求結(jié)果
{
    "success": true,
    "message": "[Controller Action]:userName=張三肮帐;age=23"
}
6.3.2 逆向測(cè)試

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

http://localhost:8080/my/test?userName=張三&age=23

請(qǐng)求head:無

(2)輸出結(jié)果

  • 控制臺(tái)輸出
[Interceptor-auth]:進(jìn)入preHandle
[Interceptor-auth]:訪問信息=com.pdd.module.lanjie.controller.MyController#test[2 args]
[Interceptor-auth]:----------鑒權(quán)不通過----------
[Interceptor-auth]:結(jié)束preHandle
  • 請(qǐng)求結(jié)果
{
    "success": false,
    "message": "鑒權(quán)失敗"
}

6.4 配置順序

public void addInterceptors(InterceptorRegistry registry) {
    // 可按照順序定義多個(gè)
    registry.addInterceptor(xxxInterceptor).addPathPatterns(xxx);
    registry.addInterceptor(yyyInterceptor).addPathPatterns(yyy);
}

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

7.1 說明

相關(guān)注解

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 定義

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;

@Component
@Order(1)
@Aspect
public class LogAop {
    
    @Pointcut("execution(public * com.pdd..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=張三&age=23

(2)后臺(tái)日志

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

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

{"success":true,"message":"[Controller Action]:userName=張三曹鸠;age=23"}

7.4 配置順序

通過spring注解org.springframework.core.annotation.Order惩坑,來定義順序愤诱。

@Component
@Order(1)
@Aspect
public class LogAop {
    //......
}

8 匯總測(cè)試

同時(shí)打開上述的Filter顷蟀,Interceptor,AOP绍赛,一起來攔截請(qǐng)求蔓纠。

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

http://localhost:8080/my/test?userName=張三&age=23

請(qǐng)求head:sign=123456

(2)輸出結(jié)果

  • 控制臺(tái)輸出
[Filter-Time]:進(jìn)入Filter
[Interceptor-auth]:進(jìn)入preHandle
[Interceptor-auth]:訪問信息=com.pdd.module.lanjie.controller.MyController#test[2 args]
[Interceptor-auth]:----------鑒權(quán)通過----------
[Interceptor-auth]:結(jié)束preHandle
[AOP-log]:Around-進(jìn)入
[AOP-log]:Around-請(qǐng)求參數(shù)=["張三","23"]
[AOP-log]:Before
[Controller Action]:userName=張三;age=23
[AOP-log]:After
[AOP-log]:Around-執(zhí)行結(jié)果={"success":true,"message":"[Controller Action]:userName=張三吗蚌;age=23"}
[AOP-log]:Around-結(jié)束
[Interceptor-auth]:postHandle ModelAndView=null
[Interceptor-auth]:afterCompletion Exception=null
[Filter-Time]:結(jié)束Filter腿倚,共326毫秒

將輸出內(nèi)容,進(jìn)行標(biāo)注蚯妇,和3 請(qǐng)求順序敷燎,部分講解的一致。

1615296824839.png
  • 請(qǐng)求結(jié)果
{
    "success": true,
    "message": "[Controller Action]:userName=張三箩言;age=23"
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末硬贯,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子陨收,更是在濱河造成了極大的恐慌饭豹,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件务漩,死亡現(xiàn)場(chǎng)離奇詭異墨状,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)菲饼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來列赎,“玉大人宏悦,你說我怎么就攤上這事“撸” “怎么了饼煞?”我有些...
    開封第一講書人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)诗越。 經(jīng)常有香客問我砖瞧,道長(zhǎng),這世上最難降的妖魔是什么嚷狞? 我笑而不...
    開封第一講書人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任块促,我火速辦了婚禮荣堰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘竭翠。我一直安慰自己振坚,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開白布斋扰。 她就那樣靜靜地躺著渡八,像睡著了一般。 火紅的嫁衣襯著肌膚如雪传货。 梳的紋絲不亂的頭發(fā)上屎鳍,一...
    開封第一講書人閱讀 49,821評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音问裕,去河邊找鬼逮壁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛僻澎,可吹牛的內(nèi)容都是我干的貌踏。 我是一名探鬼主播,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼窟勃,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼祖乳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起秉氧,我...
    開封第一講書人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤眷昆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后汁咏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體亚斋,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年攘滩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了帅刊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡漂问,死狀恐怖赖瞒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蚤假,我是刑警寧澤栏饮,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站磷仰,受9級(jí)特大地震影響袍嬉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜灶平,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一伺通、第九天 我趴在偏房一處隱蔽的房頂上張望箍土。 院中可真熱鬧,春花似錦泵殴、人聲如沸涮帘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽调缨。三九已至,卻和暖如春吆你,著一層夾襖步出監(jiān)牢的瞬間弦叶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工妇多, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留伤哺,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓者祖,卻偏偏與公主長(zhǎng)得像立莉,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子七问,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

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