SpringBoot實(shí)現(xiàn)攔截Http請(qǐng)求并設(shè)置請(qǐng)求頭
ClientHttpRequestInterceptor攔截器
在Spring Boot中,你可以通過攔截RestTemplate請(qǐng)求并在發(fā)送前設(shè)置請(qǐng)求頭,通常有以下兩種方式實(shí)現(xiàn):
1. 使用ClientHttpRequestInterceptor
通過自定義攔截器ClientHttpRequestInterceptor,在intercept方法中修改請(qǐng)求頭,然后將攔截器添加到RestTemplate實(shí)例中。
步驟:
- 創(chuàng)建攔截器類實(shí)現(xiàn)
ClientHttpRequestInterceptor接口 - 在
intercept方法中添加或修改請(qǐng)求頭 - 將攔截器注冊(cè)到
RestTemplate
示例代碼:
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.RestTemplate;
import java.io.IOException;
import java.util.Collections;
public class HeaderInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
// 添加請(qǐng)求頭
request.getHeaders().add("Authorization", "Bearer your-token");
request.getHeaders().add("Custom-Header", "custom-value");
return execution.execute(request, body);
}
}
// 配置RestTemplate
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setInterceptors(Collections.singletonList(new HeaderInterceptor()));
return restTemplate;
}
2. 使用RestTemplateBuilder(推薦)
通過RestTemplateBuilder可以更方便地添加攔截器,支持鏈?zhǔn)秸{(diào)用。
示例代碼:
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.additionalInterceptors(new HeaderInterceptor())
.build();
}
}
3. 動(dòng)態(tài)設(shè)置請(qǐng)求頭(針對(duì)特定請(qǐng)求)
如果某些請(qǐng)求頭需要?jiǎng)討B(tài)設(shè)置(如從上下文獲取token),可以在攔截器中加入邏輯:
public class DynamicHeaderInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
// 從安全上下文或其他來源動(dòng)態(tài)獲取值
String token = SecurityContextHolder.getContext().getAuthentication().getCredentials().toString();
request.getHeaders().add("Authorization", "Bearer " + token);
return execution.execute(request, body);
}
}
注意事項(xiàng)
- 攔截器會(huì)應(yīng)用于所有通過該
RestTemplate發(fā)起的請(qǐng)求 - 如果需要為不同請(qǐng)求設(shè)置不同請(qǐng)求頭,可在攔截器中根據(jù)
request.getURI()判斷路徑 - 考慮使用
ThreadLocal存儲(chǔ)線程安全的上下文信息(如請(qǐng)求ID)
如果需要更細(xì)粒度的控制,可以考慮使用RestTemplate的exchange()方法直接傳入HttpEntity(包含頭部信息),但這種方式需要在每次調(diào)用時(shí)顯式設(shè)置。
統(tǒng)一攔截所有HTTP請(qǐng)求
要統(tǒng)一攔截所有HTTP請(qǐng)求(包括RestTemplate、WebClient、Feign等),可以通過以下幾種方式實(shí)現(xiàn):
1. 使用Spring的ClientHttpRequestInterceptor(僅適用于RestTemplate)
適用于所有通過RestTemplate發(fā)起的請(qǐng)求,但對(duì)其他客戶端無效。
2. 使用Servlet Filter(最通用)
Filter可以攔截所有進(jìn)入容器的HTTP請(qǐng)求,包括:
- RestTemplate發(fā)起的請(qǐng)求
- WebClient發(fā)起的請(qǐng)求
- Feign客戶端發(fā)起的請(qǐng)求
- 任何其他HTTP客戶端發(fā)起的請(qǐng)求
示例代碼:
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@Component
@Order(1) // 執(zhí)行順序
public class GlobalHttpFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// 添加全局請(qǐng)求頭
if (httpRequest.getHeader("Authorization") == null) {
// 這里可以設(shè)置默認(rèn)認(rèn)證頭或其他通用頭
}
// 記錄請(qǐng)求日志
System.out.println("Request URL: " + httpRequest.getRequestURL());
System.out.println("Method: " + httpRequest.getMethod());
chain.doFilter(request, response); // 繼續(xù)執(zhí)行過濾器鏈
}
}
3. 使用AOP面向切面編程
通過AOP攔截所有HTTP客戶端調(diào)用方法:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class HttpRequestAspect {
@Around("execution(* org.springframework.web.client.RestTemplate.*(..)) || " +
"execution(* org.springframework.web.reactive.function.client.WebClient.*(..))")
public Object aroundHttpRequest(ProceedingJoinPoint joinPoint) throws Throwable {
// 在方法執(zhí)行前添加請(qǐng)求頭
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof HttpEntity) {
HttpEntity<?> entity = (HttpEntity<?>) args[i];
HttpHeaders headers = new HttpHeaders();
headers.putAll(entity.getHeaders());
headers.add("Global-Header", "common-value");
// 替換為新的HttpEntity
args[i] = new HttpEntity<>(entity.getBody(), headers);
}
}
return joinPoint.proceed(args);
}
}
4. 使用OkHttp的Interceptor(如果使用OkHttp作為底層客戶端)
import okhttp3.Interceptor;
import okhttp3.Response;
import java.io.IOException;
public class GlobalOkHttpInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
return chain.proceed(chain.request().newBuilder()
.addHeader("Authorization", "Bearer token")
.addHeader("X-Request-ID", UUID.randomUUID().toString())
.build());
}
}
5. 推薦方案:組合使用
| 攔截方式 | 適用范圍 | 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|---|---|
| Servlet Filter | 所有HTTP請(qǐng)求 | 最通用,覆蓋全面 | 無法區(qū)分內(nèi)部/外部請(qǐng)求 |
| AOP | 方法級(jí)別攔截 | 精確控制,可區(qū)分業(yè)務(wù) | 配置相對(duì)復(fù)雜 |
| ClientHttpRequestInterceptor | 僅RestTemplate | 簡單易用 | 局限性大 |
| OkHttp Interceptor | 使用OkHttp的客戶端 | 性能好 | 需要統(tǒng)一HTTP客戶端 |
最佳實(shí)踐建議:
- 使用Servlet Filter處理全局通用的請(qǐng)求頭(如追蹤ID、基礎(chǔ)認(rèn)證)
- 使用AOP處理業(yè)務(wù)特定的攔截邏輯
- 根據(jù)項(xiàng)目使用的HTTP客戶端選擇相應(yīng)的攔截機(jī)制
根據(jù)你的需求,Servlet Filter通常是最合適的統(tǒng)一攔截方案。
手動(dòng)攔截并設(shè)置(反射):
public void setRequestHeader(String key, String value) {
if (StringUtils.isEmpty(key) || StringUtils.isEmpty(value)) {
return;
}
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (null == attributes) {
return;
}
HttpServletRequest request = attributes.getRequest();
Class<? extends HttpServletRequest> requestClass = request.getClass();
try {
// 反射獲取request屬性
Field headerField = requestClass.getDeclaredField("headerMap");
headerField.setAccessible(true);
Map<String, String> headerMap = (Map<String, String>) headerField.get(request);
headerMap.put(key, value);
} catch (Exception e) {
log.error("set header error: ", e);
}
}
ClientHttpRequestInterceptor 會(huì)在 每次 通過配置了該攔截器的 RestTemplate 實(shí)例發(fā)起 HTTP 調(diào)用之前執(zhí)行。
工作原理
當(dāng)你調(diào)用 RestTemplate 的任何方法(如 getForObject, postForEntity, exchange 等)時(shí),Spring 會(huì)按以下順序處理:
- 你調(diào)用
restTemplate.getForObject(...) - Spring 開始構(gòu)建實(shí)際的 HTTP 請(qǐng)求
- 在執(zhí)行網(wǎng)絡(luò)調(diào)用之前,Spring 會(huì)遍歷所有已注冊(cè)的
ClientHttpRequestInterceptor - 按順序調(diào)用每個(gè)攔截器的
intercept方法 - 在你的
intercept方法中,你可以檢查和修改請(qǐng)求(HttpRequest)和請(qǐng)求體(byte[] body) - 所有攔截器執(zhí)行完畢后,最終調(diào)用
execution.execute(request, body)來真正地發(fā)送網(wǎng)絡(luò)請(qǐng)求
關(guān)鍵特性
- 每次調(diào)用都執(zhí)行:無論這個(gè)
RestTemplate實(shí)例被調(diào)用多少次,攔截器都會(huì)在每次請(qǐng)求前工作。 - 適用于所有方法:它不區(qū)分
get,post,put,delete等方法,對(duì)所有通過該RestTemplate發(fā)起的請(qǐng)求一視同仁。 - 可設(shè)置多個(gè)攔截器:你可以為一個(gè)
RestTemplate添加多個(gè)攔截器,它們會(huì)按照添加的順序依次執(zhí)行。
示例場(chǎng)景驗(yàn)證
假設(shè)你有以下配置和代碼:
// 1. 定義一個(gè)打印日志的攔截器
public class LoggingInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
System.out.println("【攔截器執(zhí)行】準(zhǔn)備發(fā)送請(qǐng)求至: " + request.getURI());
// ... 可以在這里添加請(qǐng)求頭
return execution.execute(request, body);
}
}
// 2. 將攔截器添加到 RestTemplate
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setInterceptors(Collections.singletonList(new LoggingInterceptor()));
return restTemplate;
}
// 3. 在服務(wù)中多次使用 RestTemplate
@Service
public class MyService {
@Autowired
private RestTemplate restTemplate;
public void fetchData() {
// 第一次調(diào)用
String result1 = restTemplate.getForObject("https://api.example.com/data/1", String.class);
// 第二次調(diào)用
String result2 = restTemplate.getForObject("https://api.example.com/data/2", String.class);
}
}
當(dāng)你調(diào)用 myService.fetchData() 時(shí),控制臺(tái)會(huì)輸出:
【攔截器執(zhí)行】準(zhǔn)備發(fā)送請(qǐng)求至: https://api.example.com/data/1
【攔截器執(zhí)行】準(zhǔn)備發(fā)送請(qǐng)求至: https://api.example.com/data/2
這清楚地證明了攔截器為每次請(qǐng)求都執(zhí)行了一次。
總結(jié)
你可以放心地依賴 ClientHttpRequestInterceptor 來執(zhí)行諸如添加認(rèn)證令牌、設(shè)置公共請(qǐng)求頭、記錄請(qǐng)求日志、重試邏輯等需要在每個(gè)請(qǐng)求發(fā)出前完成的統(tǒng)一操作。它的設(shè)計(jì)初衷就是為此類場(chǎng)景服務(wù)的。
Attribute和Header
- Attribute(屬性) 是服務(wù)端內(nèi)部的“便簽”,用于在一次請(qǐng)求的生命周期內(nèi),在服務(wù)器內(nèi)部的各個(gè)組件(如Controller、Service、Interceptor、Filter、View等)之間傳遞數(shù)據(jù)。客戶端(如瀏覽器)完全不知道它的存在。
- Header(頭信息) 是客戶端和服務(wù)器之間正式通信協(xié)議(HTTP)的一部分,用于傳遞元數(shù)據(jù)(如內(nèi)容類型、認(rèn)證信息、緩存控制等)。它對(duì)客戶端是可見的,并且通常會(huì)影響請(qǐng)求/響應(yīng)的處理方式。
下面是一個(gè)詳細(xì)的對(duì)比表格,幫助你更清晰地理解:
| 特性 | Attribute(屬性) | Header(頭信息) |
|---|---|---|
| 作用域 | 服務(wù)器端的一次請(qǐng)求生命周期內(nèi)(Request Scope)** | HTTP協(xié)議本身,在客戶端和服務(wù)器之間傳輸 |
| 可見性 | 僅服務(wù)器內(nèi)部可見(如Filter, Interceptor, Controller, Service, JSP/Thymeleaf等) | 客戶端和服務(wù)器都可見,可通過瀏覽器開發(fā)者工具或抓包工具查看 |
| 用途 | 在服務(wù)器處理請(qǐng)求的過程中,在不同組件間傳遞數(shù)據(jù)。例如: - Filter校驗(yàn)用戶后,將用戶信息存入attribute供Controller使用 - Interceptor計(jì)算耗時(shí)后,將耗時(shí)結(jié)果存入attribute | 傳遞HTTP協(xié)議的元數(shù)據(jù)。例如: - Content-Type: 聲明請(qǐng)求體/響應(yīng)體的格式(如application/json) - Authorization: 傳遞認(rèn)證憑證(如Bearer token) - User-Agent: 聲明客戶端類型 - Custom-Header: 自定義業(yè)務(wù)頭(如X-Request-ID用于全鏈路追蹤) |
| 生命周期 | 從請(qǐng)求進(jìn)入服務(wù)器開始,到服務(wù)器返回響應(yīng)結(jié)束。請(qǐng)求結(jié)束即銷毀。 | 請(qǐng)求頭(Request Header) 隨請(qǐng)求從客戶端發(fā)往服務(wù)器。 響應(yīng)頭(Response Header) 隨響應(yīng)從服務(wù)器發(fā)往客戶端。 |
| 數(shù)據(jù)類型 | 可以是任何Java對(duì)象(Object) | 只能是字符串(String)或字符串?dāng)?shù)組(String[]) |
| 操作方式 (Java) | request.setAttribute("key", value); Object value = request.getAttribute("key"); | 獲取請(qǐng)求頭: String value = request.getHeader("Header-Name"); 設(shè)置響應(yīng)頭: response.setHeader("Header-Name", "value"); response.addHeader("Header-Name", "value"); |
類比幫助理解
你可以把它們想象成寄送一個(gè)實(shí)體包裹:
- Header 就像是包裹外面的快遞單。上面寫明了收件人地址、寄件人信息、包裹類型(易碎品)、是否需要代收款等。快遞員和收發(fā)貨雙方都能看到這個(gè)單子,并根據(jù)上面的信息來處理包裹。
- Attribute 就像是包裹里面的一張內(nèi)部紙條,是發(fā)貨人寫給倉庫分揀員的,比如“這是VIP客戶的加急訂單,優(yōu)先處理”。只有打開包裹的內(nèi)部人員(服務(wù)器)才能看到這張紙條,收貨人(客戶端)根本不知道它的存在。
代碼示例
1. 操作 Header (在Servlet Filter或Controller中)
// 從客戶端發(fā)來的請(qǐng)求中獲取頭信息
String authHeader = httpRequest.getHeader("Authorization");
String userAgent = httpRequest.getHeader("User-Agent");
// 向客戶端發(fā)送響應(yīng)時(shí)設(shè)置頭信息
httpResponse.setHeader("Content-Type", "application/json");
httpResponse.addHeader("X-Custom-Header", "MyValue");
2. 操作 Attribute (在Filter, Interceptor, Controller中)
// 在Filter中設(shè)置屬性
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// 認(rèn)證成功后,將用戶信息存入attribute
User user = authenticateUser(httpRequest);
httpRequest.setAttribute("currentUser", user); // 存入任何對(duì)象
chain.doFilter(request, response);
}
// 在Controller中獲取和使用屬性
@GetMapping("/api/profile")
public ResponseEntity getUserProfile(HttpServletRequest request) {
// 從attribute中取出之前存入的對(duì)象
User currentUser = (User) request.getAttribute("currentUser");
// ... 使用user對(duì)象處理業(yè)務(wù)
return ResponseEntity.ok(currentUser.getProfile());
}
總結(jié)
記住這個(gè)最簡單的原則:
- 需要讓客戶端知曉或受其影響的信息,用 Header(如認(rèn)證、緩存、內(nèi)容協(xié)商)。
- 只在服務(wù)器內(nèi)部處理過程中需要臨時(shí)攜帶的數(shù)據(jù),用 Attribute(如用戶身份、計(jì)算中間結(jié)果、頁面渲染數(shù)據(jù))。
到此這篇關(guān)于SpringBoot實(shí)現(xiàn)攔截Http請(qǐng)求并設(shè)置請(qǐng)求頭的文章就介紹到這了,更多相關(guān)SpringBoot攔截Http請(qǐng)求內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Spring Cloud負(fù)載均衡重要組件Ribbon中重要類的用法
本篇文章主要介紹了詳解Spring Cloud負(fù)載均衡重要組件Ribbon中重要類的用法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03
詳解Java?ThreadPoolExecutor的拒絕策略
這篇文章主要介紹了Java?ThreadPoolExecutor的拒絕策略,本文對(duì)于線程的池的幾種策略進(jìn)行詳細(xì)的講解,在實(shí)際的生產(chǎn)中需要集合相關(guān)的場(chǎng)景來選擇合適的拒絕策略,需要的朋友可以參考下2022-08-08
MybatisPlus使用@TableId主鍵id自增長無效的解決
本文主要介紹了MybatisPlus使用@TableId主鍵id自增長無效的解決,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04
springboot使JUL實(shí)現(xiàn)日志管理功能
這篇文章主要介紹了springboot使JUL實(shí)現(xiàn)日志管理功能,本文分步驟給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09
Mybatis基于注解形式的sql語句生成實(shí)例代碼
這篇文章主要介紹了 Mybatis基于注解形式的sql語句生成實(shí)例代碼,需要的朋友可以參考下2017-09-09
Mybatis錯(cuò)誤引起的程序啟動(dòng)卡死問題及解決
這篇文章主要介紹了Mybatis錯(cuò)誤引起的程序啟動(dòng)卡死問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02

