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

SpringBoot手寫RestTemplate攔截器鏈,掌控HTTP請求實(shí)踐

 更新時間:2025年12月08日 09:33:53   作者:小馬不敲代碼  
本文介紹了在Spring項(xiàng)目中使用RestTemplate時遇到的問題及解決方案,通過自定義攔截器鏈來實(shí)現(xiàn)請求的統(tǒng)一處理,包括添加認(rèn)證Token、記錄請求日志、設(shè)置超時時間、實(shí)現(xiàn)失敗重試等,關(guān)鍵點(diǎn)在于攔截器鏈的順序和響應(yīng)流的處理,以及確保攔截器的線程安全

01 前言

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

但實(shí)際開發(fā)時,我們常常需要對請求做統(tǒng)一處理,比如添加認(rèn)證Token、記錄請求日志、設(shè)置超時時間、實(shí)現(xiàn)失敗重試等。

要是每個接口調(diào)用都手動處理這些邏輯,不僅代碼冗余,還容易出錯。

02 為什么需要攔截器鏈?

先看一個場景:假設(shè)支付服務(wù)需要調(diào)用第三方支付接口,每次請求都得做一系列操作。

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

public String createOrder(String orderId) {
    // 創(chuàng)建RestTemplate實(shí)例
    RestTemplate restTemplate = new RestTemplate();
    
    // 設(shè)置請求超時時間
    SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
    factory.setConnectTimeout(3000); // 連接超時3秒
    factory.setReadTimeout(3000); // 讀取超時3秒
    restTemplate.setRequestFactory(factory);
    
    // 設(shè)置請求頭(添加認(rèn)證信息)
    HttpHeaders headers = new HttpHeaders();
    headers.set("Authorization", "Bearer " + getToken()); // 拼接認(rèn)證Token
    HttpEntity<String> entity = new HttpEntity<>(headers);
    
    // 記錄請求日志
    log.info("請求支付接口: {}", orderId);
    try {
        // 發(fā)送請求
        String result = restTemplate.postForObject(PAY_URL, entity, String.class);
        log.info("請求成功: {}", result);
        return result;
    } catch (Exception e) {
        log.error("請求失敗", e);
        
        // 失敗重試邏輯
        for (int i = 0; i < 2; i++) {
            try {
                return restTemplate.postForObject(PAY_URL, entity, String.class);
            } catch (Exception ex) {  }
        }
        throw e;
    }
}

這段代碼的問題很明顯:重復(fù)邏輯散落在各個接口調(diào)用中,維護(hù)成本極高。

如果有10個接口需要調(diào)用第三方服務(wù),就要寫10遍相同的代碼。

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

核心調(diào)用代碼可以簡化為:

return restTemplate.postForObject(PAY_URL, entity, String.class);

03 RestTemplate攔截器基礎(chǔ)

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

public interface ClientHttpRequestInterceptor {
    // 攔截方法:處理請求后通過execution繼續(xù)執(zhí)行調(diào)用鏈
    ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException;
}

攔截器工作流程

我們先實(shí)現(xiàn)一個打印請求響應(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 {
        // 記錄請求開始時間
        long start = System.currentTimeMillis();
        
        // 打印請求信息
        log.info("===== 請求開始 =====");
        log.info("URL: {}", request.getURI()); // 請求地址
        log.info("Method: {}", request.getMethod()); // 請求方法(GET/POST等)
        log.info("Headers: {}", request.getHeaders()); // 請求頭
        log.info("Body: {}", new String(body, StandardCharsets.UTF_8)); // 請求體
        
        // 執(zhí)行后續(xù)攔截器或?qū)嶋H請求
        ClientHttpResponse response = execution.execute(request, body);
        
        // 讀取響應(yīng)體(注意:默認(rèn)流只能讀一次)
        String responseBody = StreamUtils.copyToString(response.getBody(), StandardCharsets.UTF_8);
        
        // 打印響應(yīng)信息
        log.info("===== 請求結(jié)束 =====");
        log.info("Status: {}", response.getStatusCode()); // 響應(yīng)狀態(tài)碼
        log.info("Headers: {}", response.getHeaders()); // 響應(yīng)頭
        log.info("Body: {}", responseBody); // 響應(yīng)體
        log.info("耗時: {}ms", System.currentTimeMillis() - start); // 計算請求耗時
        
        return response;
    }
}

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

  • execution.execute(…)是調(diào)用鏈的核心,必須調(diào)用才能繼續(xù)執(zhí)行后續(xù)攔截器或發(fā)送實(shí)際請求。
  • 響應(yīng)流response.getBody()默認(rèn)只能讀取一次,如需多次處理需緩存(后面會講解決方案)。

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

SpringBoot中通過RestTemplate.setInterceptors()注冊多個攔截器,形成調(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 // 標(biāo)記為配置類,讓Spring掃描加載
public class RestTemplateConfig {

    @Bean // 將RestTemplate實(shí)例注入Spring容器
    public RestTemplate customRestTemplate() {
        // 創(chuàng)建攔截器列表
        List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
        interceptors.add(new AuthInterceptor()); // 1. 認(rèn)證攔截器
        interceptors.add(new LoggingInterceptor()); // 2. 日志攔截器
        interceptors.add(new RetryInterceptor(2)); // 3. 重試攔截器(最多重試2次)
        
        // 創(chuàng)建RestTemplate并設(shè)置攔截器
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setInterceptors(interceptors);
        
        // 配置請求工廠(支持響應(yīng)流緩存)
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setConnectTimeout(3000); // 連接超時3秒
        factory.setReadTimeout(3000); // 讀取超時3秒
        // 用BufferingClientHttpRequestFactory包裝,解決響應(yīng)流只能讀一次的問題
        restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(factory));
        
        return restTemplate;
    }
}

攔截器執(zhí)行順序

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

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

認(rèn)證攔截器示例

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 {
        // 獲取認(rèn)證Token(實(shí)際項(xiàng)目中可能從緩存或配置中心獲?。?
        String token = getToken();
        
        // 往請求頭添加認(rèn)證信息
        HttpHeaders headers = request.getHeaders();
        headers.set("Authorization", "Bearer " + token); // 標(biāo)準(zhǔn)Bearer認(rèn)證格式
        
        // 繼續(xù)執(zhí)行后續(xù)攔截器
        return execution.execute(request, body);
    }

    // 模擬獲取Token的方法
    private String getToken() {
        return "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."; // 實(shí)際為真實(shí)Token字符串
    }
}

重試攔截器示例

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ù)

    // 構(gòu)造方法指定最大重試次數(shù)
    public RetryInterceptor(int maxRetries) {
        this.maxRetries = maxRetries;
    }

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        int retryCount = 0; // 當(dāng)前重試次數(shù)
        while (true) {
            try {
                // 執(zhí)行請求,若成功直接返回響應(yīng)
                return execution.execute(request, body);
            } catch (HttpStatusCodeException e) {
                // 處理HTTP狀態(tài)碼異常(如5xx服務(wù)器錯誤)
                if (e.getStatusCode().is5xxServerError() && retryCount < maxRetries) {
                    retryCount++;
                    log.warn("服務(wù)器錯誤,開始第{}次重試,狀態(tài)碼:{}", retryCount, e.getStatusCode());
                    continue; // 繼續(xù)重試
                }
                // 不滿足重試條件,拋出異常
                throw e;
            } catch (IOException e) {
                // 處理網(wǎng)絡(luò)異常(如連接超時)
                if (retryCount < maxRetries) {
                    retryCount++;
                    log.warn("網(wǎng)絡(luò)異常,開始第{}次重試", retryCount, e);
                    continue; // 繼續(xù)重試
                }
                // 重試次數(shù)耗盡,拋出異常
                throw e;
            }
        }
    }
}

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

響應(yīng)流讀取問題

現(xiàn)象:日志攔截器讀取響應(yīng)體后,后續(xù)攔截器再讀會讀取到空數(shù)據(jù)。

原因:響應(yīng)流默認(rèn)是一次性的,讀完就關(guān)閉了。

解決方案:用BufferingClientHttpRequestFactory包裝請求工廠,緩存響應(yīng)流:

// 創(chuàng)建基礎(chǔ)請求工廠
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
// 包裝成支持響應(yīng)緩存的工廠
BufferingClientHttpRequestFactory bufferingFactory = new BufferingClientHttpRequestFactory(factory);
// 設(shè)置到RestTemplate
restTemplate.setRequestFactory(bufferingFactory);
(上述代碼已在04節(jié)的配置類中體現(xiàn))

攔截器順序問題

反例:把LoggingInterceptor放在AuthInterceptor前面,日志中會看不到Authorization頭。

因?yàn)槿罩緮r截器先執(zhí)行時,認(rèn)證攔截器還沒添加認(rèn)證頭。

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

  1. 前置處理攔截器:認(rèn)證、加密、參數(shù)修改
  2. 日志攔截器:記錄完整請求(包含前置處理的結(jié)果)
  3. 重試/降級攔截器:處理異常情況(放在最后,確保重試時能重新執(zhí)行所有前置邏輯)

線程安全問題

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

錯誤示例:

public class BadInterceptor implements ClientHttpRequestInterceptor {
    private int count = 0; // 非線程安全的成員變量

    @Override
    public ClientHttpResponse intercept(...) {
        count++; // 多線程環(huán)境下會出現(xiàn)計數(shù)錯誤
    }
}

解決方案:

  • 攔截器避免定義可變成員變量
  • 必須使用時,用ThreadLocal隔離線程狀態(tài)(每個線程獨(dú)立維護(hù)變量副本)

06 測試示例

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 {

    // 注入自定義的RestTemplate(已包含攔截器鏈)
    @Autowired
    private RestTemplate customRestTemplate;

    // 測試接口:調(diào)用第三方服務(wù)
    @GetMapping("/call-third")
    public String callThirdApi() {
        // 直接調(diào)用,攔截器會自動處理認(rèn)證、日志、重試等邏輯
        return customRestTemplate.getForObject("http://localhost:8080/mock-third-api", String.class);
    }

    // 模擬第三方接口
    @GetMapping("/mock-third-api")
    public String mockThirdApi() {
        return "hello from third party";
    }
}

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

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

07 總結(jié)與擴(kuò)展

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

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

以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • IDEA中啟動多個SpringBoot服務(wù)的實(shí)現(xiàn)示例

    IDEA中啟動多個SpringBoot服務(wù)的實(shí)現(xiàn)示例

    本文主要介紹了IDEA中啟動多個SpringBoot服務(wù)的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-08-08
  • Java Spring Boot 集成Zookeeper

    Java Spring Boot 集成Zookeeper

    這篇文章主要介紹了Java Spring Boot 集成Zookeeper,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-08-08
  • IDEA下使用Spring Boot熱加載的實(shí)現(xiàn)

    IDEA下使用Spring Boot熱加載的實(shí)現(xiàn)

    本文主要介紹了IDEA下使用Spring Boot熱加載的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-06-06
  • Spring?Boot如何接入Security權(quán)限認(rèn)證服務(wù)

    Spring?Boot如何接入Security權(quán)限認(rèn)證服務(wù)

    Spring Security?是一個高度可定制的身份驗(yàn)證和訪問控制的框架,提供了完善的認(rèn)證機(jī)制和方法級的授權(quán)功能,本文通過案例將Spring Security整合到SpringBoot中,要實(shí)現(xiàn)的功能就是在認(rèn)證服務(wù)器上登錄,然后獲取Token,再訪問資源服務(wù)器中的資源,感興趣的朋友一起看看吧
    2024-07-07
  • 關(guān)于Rabbitmq死信隊列及延時隊列的實(shí)現(xiàn)

    關(guān)于Rabbitmq死信隊列及延時隊列的實(shí)現(xiàn)

    這篇文章主要介紹了關(guān)于Rabbitmq死信隊列及延時隊列的實(shí)現(xiàn),TTL就是消息或者隊列的過期功能,當(dāng)消息過期就會進(jìn)到死信隊列,死信隊列和普通隊列沒啥區(qū)別,然后我們只需要配置一個消費(fèi)者來消費(fèi)死信隊列里面的消息就可以了,需要的朋友可以參考下
    2023-08-08
  • SpringBoot @Retryable注解使用

    SpringBoot @Retryable注解使用

    SpringBoot提供的@Retryable注解可以方便地實(shí)現(xiàn)方法的重試機(jī)制,可以在不侵入原有邏輯代碼的方式下,優(yōu)雅地實(shí)現(xiàn)重處理功能
    2024-12-12
  • Java中編譯期異常和運(yùn)行期異常的區(qū)別解析

    Java中編譯期異常和運(yùn)行期異常的區(qū)別解析

    Java中的異常分為運(yùn)行期異常(RuntimeException)和編譯期異常(CheckedException),前者不強(qiáng)制處理,后者必須顯式處理,本文介紹Java中編譯期異常和運(yùn)行期異常的區(qū)別,感興趣的朋友一起看看吧
    2025-02-02
  • SpringBoot項(xiàng)目從18.18M瘦身到0.18M的實(shí)現(xiàn)

    SpringBoot項(xiàng)目從18.18M瘦身到0.18M的實(shí)現(xiàn)

    本文主要介紹了SpringBoot項(xiàng)目從18.18M瘦身到0.18M的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-01-01
  • Spring Boot LocalDateTime格式化處理的示例詳解

    Spring Boot LocalDateTime格式化處理的示例詳解

    這篇文章主要介紹了Spring Boot LocalDateTime格式化處理的示例詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-10-10
  • springboot集成kafka消費(fèi)手動啟動停止操作

    springboot集成kafka消費(fèi)手動啟動停止操作

    這篇文章主要介紹了springboot集成kafka消費(fèi)手動啟動停止操作,本文給大家介紹項(xiàng)目場景及解決分析,結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-09-09

最新評論

吴江市| 库车县| 纳雍县| 昌图县| 泾阳县| 宁河县| 洛川县| 寿阳县| 盐池县| 龙里县| 綦江县| 安丘市| 灵丘县| 琼结县| 沁阳市| 铁岭县| 太保市| 衡东县| 孝感市| 左权县| 宝鸡市| 四会市| 新平| 同德县| 丹凤县| 崇义县| 晋城| 咸宁市| 静安区| 贵定县| 邯郸县| 武穴市| 金沙县| 伊宁县| 友谊县| 新津县| 扎赉特旗| 高雄县| 集安市| 汝阳县| 宾阳县|