最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot自定義RestTemplate的攔截器鏈的實(shí)戰(zhàn)指南

 更新時(shí)間:2025年07月15日 08:25:36   作者:風(fēng)象南  
在項(xiàng)目開發(fā)中,RestTemplate作為Spring提供的HTTP客戶端工具,經(jīng)常用于訪問內(nèi)部或三方服務(wù),但在實(shí)際項(xiàng)目中,我們往往需要對(duì)請(qǐng)求進(jìn)行統(tǒng)一處理,所以本文給大家介紹了SpringBoot自定義RestTemplate的攔截器鏈的實(shí)戰(zhàn)指南,需要的朋友可以參考下

引言

在項(xiàng)目開發(fā)中,RestTemplate作為Spring提供的HTTP客戶端工具,經(jīng)常用于訪問內(nèi)部或三方服務(wù)。

但在實(shí)際項(xiàng)目中,我們往往需要對(duì)請(qǐng)求進(jìn)行統(tǒng)一處理:比如添加認(rèn)證Token、記錄請(qǐng)求日志、設(shè)置超時(shí)時(shí)間、實(shí)現(xiàn)失敗重試等。

如果每個(gè)接口調(diào)用都手動(dòng)處理這些邏輯,不僅代碼冗余,還容易出錯(cuò)。

一、為什么需要攔截器鏈?

先看一個(gè)場(chǎng)景:假設(shè)我們的支付服務(wù)需要調(diào)用第三方支付接口,每次請(qǐng)求都要做這些事

  1. 添加Authorization請(qǐng)求頭(認(rèn)證Token)
  2. 記錄請(qǐng)求URL、參數(shù)、耗時(shí)(日志審計(jì))
  3. 設(shè)置3秒超時(shí)時(shí)間(防止阻塞)
  4. 失敗時(shí)重試2次(網(wǎng)絡(luò)抖動(dòng)處理)

如果沒有攔截器,代碼會(huì)寫成這樣:

// 調(diào)用第三方支付接口
public String createOrder(String orderId) {
    // 1. 創(chuàng)建RestTemplate(重復(fù)10次)
    RestTemplate restTemplate = new RestTemplate();
    
    // 2. 設(shè)置超時(shí)(重復(fù)10次)
    SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
    factory.setConnectTimeout(3000);
    factory.setReadTimeout(3000);
    restTemplate.setRequestFactory(factory);
    
    // 3. 添加認(rèn)證頭(重復(fù)10次)
    HttpHeaders headers = new HttpHeaders();
    headers.set("Authorization", "Bearer " + getToken());
    HttpEntity<String> entity = new HttpEntity<>(headers);
    
    // 4. 記錄日志(重復(fù)10次)
    log.info("請(qǐng)求支付接口: {}", orderId);
    try {
        String result = restTemplate.postForObject(PAY_URL, entity, String.class);
        log.info("請(qǐng)求成功: {}", result);
        return result;
    } catch (Exception e) {
        log.error("請(qǐng)求失敗", e);
        // 5. 重試(重復(fù)10次)
        for (int i = 0; i < 2; i++) {
            try {
                return restTemplate.postForObject(PAY_URL, entity, String.class);
            } catch (Exception ex) { /* 重試邏輯 */ }
        }
        throw e;
    }
}

這段代碼的問題很明顯:重復(fù)邏輯散落在各個(gè)接口調(diào)用中,維護(hù)成本極高。如果有10個(gè)接口需要調(diào)用第三方服務(wù),就要寫10遍相同的代碼。

而攔截器鏈能把這些通用邏輯抽離成獨(dú)立組件,按順序串聯(lián)執(zhí)行,實(shí)現(xiàn)“一次定義,處處復(fù)用”。

// 重構(gòu)后:一行搞定所有通用邏輯
return restTemplate.postForObject(PAY_URL, entity, String.class);

二、RestTemplate攔截器基礎(chǔ)

2.1 核心接口:ClientHttpRequestInterceptor

Spring提供了ClientHttpRequestInterceptor接口,所有自定義攔截器都要實(shí)現(xiàn)它

public interface ClientHttpRequestInterceptor {
    // request:請(qǐng)求對(duì)象(可修改請(qǐng)求頭、參數(shù))
    // body:請(qǐng)求體字節(jié)數(shù)組
    // execution:執(zhí)行器(用于調(diào)用下一個(gè)攔截器)
    ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException;
}

攔截器工作流程

  1. 攔截器可以修改請(qǐng)求(如添加頭信息、修改參數(shù))
  2. 通過execution.execute(request, body)調(diào)用下一個(gè)攔截器
  3. 最終執(zhí)行器會(huì)發(fā)送實(shí)際HTTP請(qǐng)求,返回響應(yīng)
  4. 攔截器可以處理響應(yīng)(如記錄日志、解析響應(yīng)體)

2.2 第一個(gè)攔截器:日志攔截器

先實(shí)現(xiàn)一個(gè)打印請(qǐng)求響應(yīng)日志的攔截器,這是項(xiàng)目中最常用的功能:

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

@Slf4j
public class LoggingInterceptor implements ClientHttpRequestInterceptor {
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        // 1. 記錄請(qǐng)求信息
        long start = System.currentTimeMillis();
        log.info("===== 請(qǐng)求開始 =====");
        log.info("URL: {}", request.getURI());
        log.info("Method: {}", request.getMethod());
        log.info("Headers: {}", request.getHeaders());
        log.info("Body: {}", new String(body, StandardCharsets.UTF_8));

        // 2. 執(zhí)行下一個(gè)攔截器(關(guān)鍵!不調(diào)用會(huì)中斷鏈條)
        ClientHttpResponse response = execution.execute(request, body);

        // 3. 記錄響應(yīng)信息
        String responseBody = StreamUtils.copyToString(response.getBody(), StandardCharsets.UTF_8);
        log.info("===== 請(qǐng)求結(jié)束 =====");
        log.info("Status: {}", response.getStatusCode());
        log.info("Headers: {}", response.getHeaders());
        log.info("Body: {}", responseBody);
        log.info("耗時(shí): {}ms", System.currentTimeMillis() - start);

        return response;
    }
}

關(guān)鍵點(diǎn)

  • execution.execute(...)是調(diào)用鏈的核心,必須調(diào)用才能繼續(xù)執(zhí)行
  • 響應(yīng)流response.getBody()只能讀取一次,如需多次處理需緩存(后面會(huì)講解決方案)

三、攔截器鏈實(shí)戰(zhàn):從單攔截器到多攔截器協(xié)同

3.1 注冊(cè)攔截器鏈

SpringBoot中通過RestTemplate.setInterceptors()注冊(cè)多個(gè)攔截器,形成調(diào)用鏈:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.List;

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate customRestTemplate() {
        // 1. 創(chuàng)建攔截器列表(順序很重要?。?
        List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
        interceptors.add(new AuthInterceptor());       // 1. 認(rèn)證攔截器(先加請(qǐng)求頭)
        interceptors.add(new LoggingInterceptor());    // 2. 日志攔截器(記錄完整請(qǐng)求)
        interceptors.add(new RetryInterceptor(2));     // 3. 重試攔截器(最后處理失?。?

        // 2. 創(chuàng)建RestTemplate并設(shè)置攔截器
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setInterceptors(interceptors);

        // 3. 解決響應(yīng)流只能讀取一次的問題(關(guān)鍵配置?。?
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setConnectTimeout(3000);  // 連接超時(shí)3秒
        factory.setReadTimeout(3000);     // 讀取超時(shí)3秒
        restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(factory));

        return restTemplate;
    }
}

攔截器執(zhí)行順序:按添加順序執(zhí)行(像排隊(duì)一樣),上述示例的執(zhí)行流程是:

AuthInterceptor → LoggingInterceptor → RetryInterceptor → 實(shí)際請(qǐng)求 → RetryInterceptor → LoggingInterceptor → AuthInterceptor

3.2 核心攔截器實(shí)現(xiàn)

(1)認(rèn)證攔截器:自動(dòng)添加Token

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;

import java.io.IOException;

public class AuthInterceptor implements ClientHttpRequestInterceptor {
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        // 1. 從緩存獲取Token(實(shí)際項(xiàng)目可能從Redis或配置中心獲?。?
        String token = getToken();
        
        // 2. 添加Authorization請(qǐng)求頭
        HttpHeaders headers = request.getHeaders();
        headers.set("Authorization", "Bearer " + token);  // Bearer認(rèn)證格式
        
        // 3. 傳遞給下一個(gè)攔截器
        return execution.execute(request, body);
    }

    // 模擬從緩存獲取Token
    private String getToken() {
        return "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";  // 實(shí)際項(xiàng)目是真實(shí)Token
    }
}

(2)重試攔截器:失敗自動(dòng)重試

package com.example.rest;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.HttpStatusCodeException;

import java.io.IOException;

@Slf4j
public class RetryInterceptor implements ClientHttpRequestInterceptor {
    private final int maxRetries;  // 最大重試次數(shù)

    public RetryInterceptor(int maxRetries) {
        this.maxRetries = maxRetries;
    }

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        int retryCount = 0;
        while (true) {
            try {
                // 執(zhí)行請(qǐng)求(調(diào)用下一個(gè)攔截器或?qū)嶋H請(qǐng)求)
                return execution.execute(request, body);
            } catch (HttpStatusCodeException e) {
                // 只對(duì)5xx服務(wù)器錯(cuò)誤重試(業(yè)務(wù)錯(cuò)誤不重試)
                if (e.getStatusCode().is5xxServerError() && retryCount < maxRetries) {
                    retryCount++;
                    log.warn("服務(wù)器錯(cuò)誤,開始第{}次重試,狀態(tài)碼:{}", retryCount, e.getStatusCode());
                    continue;
                }
                throw e;  // 非5xx錯(cuò)誤或達(dá)到最大重試次數(shù)
            } catch (IOException e) {
                // 網(wǎng)絡(luò)異常重試(如連接超時(shí)、DNS解析失敗)
                if (retryCount < maxRetries) {
                    retryCount++;
                    log.warn("網(wǎng)絡(luò)異常,開始第{}次重試", retryCount, e);
                    continue;
                }
                throw e;
            }
        }
    }
}

四、實(shí)戰(zhàn)踩坑指南

4.1 響應(yīng)流只能讀取一次問題

現(xiàn)象:日志攔截器讀取響應(yīng)體后,后續(xù)攔截器再讀會(huì)讀取到空數(shù)據(jù)。
原因:響應(yīng)流默認(rèn)是一次性的,讀完就關(guān)閉了。
解決方案:用BufferingClientHttpRequestFactory包裝請(qǐng)求工廠,緩存響應(yīng)流:

// 創(chuàng)建支持響應(yīng)緩存的工廠(已在3.1節(jié)配置中體現(xiàn))
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
BufferingClientHttpRequestFactory bufferingFactory = new BufferingClientHttpRequestFactory(factory);
restTemplate.setRequestFactory(bufferingFactory);

4.2 攔截器順序不當(dāng)導(dǎo)致功能異常

反例:把LoggingInterceptor放在AuthInterceptor前面,日志中會(huì)看不到Authorization頭,因?yàn)槿罩緮r截器先執(zhí)行時(shí)還沒添加認(rèn)證頭。

正確順序(按職責(zé)劃分):

  • 1. 前置處理攔截器:認(rèn)證、加密、參數(shù)修改
  • 2. 日志攔截器:記錄完整請(qǐng)求
  • 3. 重試/降級(jí)攔截器:處理異常情況

4.3 線程安全問題

RestTemplate是線程安全的,但攔截器若有成員變量,需確保線程安全

錯(cuò)誤示例

public class BadInterceptor implements ClientHttpRequestInterceptor {
    private int count = 0;  // ? 多線程共享變量,會(huì)導(dǎo)致計(jì)數(shù)錯(cuò)誤
    
    @Override
    public ClientHttpResponse intercept(...) {
        count++;  // 危險(xiǎn)!多線程下count值不準(zhǔn)
        // ...
    }
}

解決方案

  • 攔截器避免定義可變成員變量
  • 必須使用時(shí),用ThreadLocal隔離線程狀態(tài)

五、測(cè)試示例

5.1 測(cè)試接口

package com.example.rest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class TestController {

    @Autowired
    private RestTemplate customRestTemplate;  // 注入自定義的RestTemplate

    @GetMapping("/call-api")
    public String callThirdApi() {
        // 調(diào)用第三方API,攔截器自動(dòng)處理認(rèn)證、日志、重試
        return customRestTemplate.getForObject("http://localhost:8080/mock-third-api", String.class);
    }

    // 模擬一個(gè)三方接口
    @GetMapping("/mock-third-api")
    public String mockThirdApi() {
        return "hello";
    }
}

5.2 依賴與配置

只需基礎(chǔ)的Spring Boot Starter Web:

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

六、總結(jié)與擴(kuò)展

通過自定義RestTemplate的攔截器鏈,我們可以將請(qǐng)求處理的通用邏輯(認(rèn)證、日志、重試等)抽離成獨(dú)立組件,實(shí)現(xiàn)代碼復(fù)用和統(tǒng)一維護(hù)。

核心要點(diǎn)

  • 1. 攔截器鏈順序:按“前置處理→日志→重試”順序注冊(cè),確保功能正確
  • 2. 響應(yīng)流處理:使用BufferingClientHttpRequestFactory解決流只能讀取一次問題
  • 3. 線程安全:攔截器避免定義可變成員變量,必要時(shí)使用ThreadLocal
  • 4. 異常處理:重試攔截器需明確重試條件(如只對(duì)5xx錯(cuò)誤重試)

以上就是SpringBoot自定義RestTemplate的攔截器鏈的實(shí)戰(zhàn)指南的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot RestTemplate攔截器鏈的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java concurrency之Condition條件_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    Java concurrency之Condition條件_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    Condition的作用是對(duì)鎖進(jìn)行更精確的控制。下面通過本文給大家分享Java concurrency之Condition條件的相關(guān)知識(shí),非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧
    2017-06-06
  • 實(shí)例解析Java程序中正則表達(dá)式的貪婪模式匹配

    實(shí)例解析Java程序中正則表達(dá)式的貪婪模式匹配

    貪婪模式又叫最大匹配,X?、X*、X+、X{n,}都是最大匹配,例如你要用“<.+>”去匹配“a<tr>aava </tr>abb”,也許你所期待的結(jié)果是想匹配“<tr>”,但是實(shí)際結(jié)果卻會(huì)匹配到“<tr>aava </tr>”,下面我們就來看具體看一下貪婪模式的使用.
    2016-05-05
  • 淺談標(biāo)簽和JLabel類構(gòu)造方法

    淺談標(biāo)簽和JLabel類構(gòu)造方法

    這篇文章主要介紹了標(biāo)簽和JLabel類構(gòu)造方法,具有一定參考價(jià)值,需要的朋友可以參考下。
    2017-09-09
  • IDEA生成標(biāo)準(zhǔn)JavaBean的幾種方法總結(jié)

    IDEA生成標(biāo)準(zhǔn)JavaBean的幾種方法總結(jié)

    標(biāo)準(zhǔn)javaBean是定義一個(gè)類的標(biāo)準(zhǔn)結(jié)構(gòu),下面這篇文章主要給大家總結(jié)介紹了關(guān)于IDEA生成標(biāo)準(zhǔn)JavaBean的幾種方法,文中通過圖文以及代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-03-03
  • Java?Spring的兩種事務(wù)你知道嗎

    Java?Spring的兩種事務(wù)你知道嗎

    這篇文章主要為大家詳細(xì)介紹了Java?Spring的兩種事務(wù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-03-03
  • java創(chuàng)建txt文件并存入內(nèi)容

    java創(chuàng)建txt文件并存入內(nèi)容

    這篇文章主要為大家詳細(xì)介紹了java創(chuàng)建txt文件并存入內(nèi)容,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-08-08
  • 一次學(xué)會(huì)JWT之Java登錄、驗(yàn)證與權(quán)限控制完整流程

    一次學(xué)會(huì)JWT之Java登錄、驗(yàn)證與權(quán)限控制完整流程

    JWT(JSON Web Token)是一種開放標(biāo)準(zhǔn)(RFC 7519),用于在網(wǎng)絡(luò)應(yīng)用環(huán)境間以緊湊的方式安全地傳遞信息,這篇文章主要介紹了JWT Java登錄、驗(yàn)證與權(quán)限控制的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2025-09-09
  • Java數(shù)組常見應(yīng)用詳解【創(chuàng)建、遍歷、排序、查找】

    Java數(shù)組常見應(yīng)用詳解【創(chuàng)建、遍歷、排序、查找】

    這篇文章主要介紹了Java數(shù)組常見應(yīng)用,結(jié)合實(shí)例形式詳細(xì)分析了java數(shù)組的基本定義、創(chuàng)建、遍歷、排序、查找等相關(guān)操作技巧與使用注意事項(xiàng),需要的朋友可以參考下
    2020-02-02
  • Java Poi 在Excel中輸出特殊符號(hào)的實(shí)現(xiàn)方法

    Java Poi 在Excel中輸出特殊符號(hào)的實(shí)現(xiàn)方法

    這篇文章主要介紹了Java Poi 在Excel中輸出特殊符號(hào)的實(shí)現(xiàn)方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-07-07
  • JAVA中4種解析XML文件的方法

    JAVA中4種解析XML文件的方法

    這篇文章主要介紹了JAVA中4種解析XML文件的方法,文中示例代碼非常詳細(xì),幫助大家更好的了解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-06-06

最新評(píng)論

和政县| 余姚市| 阳谷县| 临湘市| 东海县| 米脂县| 京山县| 贵州省| 富锦市| 克什克腾旗| 舟山市| 巴林左旗| 武乡县| 铜梁县| 芜湖市| 东平县| 宽城| 洛浦县| 怀来县| 赞皇县| 五指山市| 惠东县| 六安市| 海南省| 会理县| 尚志市| 榆树市| 正镶白旗| 青州市| 舒城县| 刚察县| 溧水县| 栾川县| 桐梓县| 故城县| 彭泽县| 墨竹工卡县| 同仁县| 察隅县| 绿春县| 蕲春县|