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

在Spring中給HttpServletRequest添加自定義Header的完整指南

 更新時(shí)間:2025年10月23日 09:16:30   作者:淦出一番成就  
在Spring應(yīng)用開發(fā)中,我們經(jīng)常需要在請(qǐng)求處理過程中添加自定義的 HTTP Header,由于HttpServletRequest對(duì)象在設(shè)計(jì)上是只讀的,直接添加Header會(huì)遇到困難,本文將詳細(xì)介紹如何通過Filter和RequestWrapper的方式優(yōu)雅地解決這個(gè)問題,需要的朋友可以參考下

引言

在 Spring 應(yīng)用開發(fā)中,我們經(jīng)常需要在請(qǐng)求處理過程中添加自定義的 HTTP Header。由于 HttpServletRequest 對(duì)象在設(shè)計(jì)上是只讀的,直接添加 Header 會(huì)遇到困難。本文將詳細(xì)介紹如何通過 Filter 和 RequestWrapper 的方式優(yōu)雅地解決這個(gè)問題。

問題背景

HttpServletRequest 接口本身不提供修改 Header 的方法,這是因?yàn)椋?/p>

  • Servlet 規(guī)范設(shè)計(jì)請(qǐng)求對(duì)象為只讀
  • 保證請(qǐng)求數(shù)據(jù)的完整性和一致性
  • 避免在多線程環(huán)境下出現(xiàn)數(shù)據(jù)競(jìng)爭(zhēng)

但在實(shí)際業(yè)務(wù)場(chǎng)景中,我們經(jīng)常需要添加一些業(yè)務(wù)相關(guān)的 Header或引用的包中會(huì)依賴header中的某個(gè)值,比如:

  • 租戶 ID(tenantId)
  • 用戶追蹤信息
  • 認(rèn)證令牌
  • 業(yè)務(wù)上下文信息

解決方案:Filter + RequestWrapper

核心思路

通過創(chuàng)建 HttpServletRequestWrapper 子類來包裝原始請(qǐng)求,重寫相關(guān)方法以支持自定義 Header 的添加。

完整代碼實(shí)現(xiàn)

1. 自定義 RequestWrapper

public class RequestWrapper extends HttpServletRequestWrapper {
    
    // 可選,如需對(duì)body數(shù)據(jù)做處理
    private String mBody;
    private Map<String, String> headerMap = new HashMap<>();

    public RequestWrapper(HttpServletRequest request) {
        super(request);
        mBody = getBodyString(request);
    }

    /**
     * 添加自定義 Header
     */
    public void addHeader(String name, String value) {
        headerMap.put(name, value);
    }

    @Override
    public String getHeader(String name) {
        String headerValue = super.getHeader(name);
        if (headerMap.containsKey(name)) {
            headerValue = headerMap.get(name);
        }
        return headerValue;
    }

    @Override
    public Enumeration<String> getHeaderNames() {
        List<String> names = Collections.list(super.getHeaderNames());
        names.addAll(headerMap.keySet());
        return Collections.enumeration(names);
    }

    @Override
    public Enumeration<String> getHeaders(String name) {
        List<String> values = Collections.list(super.getHeaders(name));
        if (headerMap.containsKey(name)) {
            values.add(headerMap.get(name));
        }
        return Collections.enumeration(values);
    }

    /**
     * 獲取請(qǐng)求體內(nèi)容
     */
    private String getBodyString(ServletRequest request) {
        StringBuilder sb = new StringBuilder();
        try (InputStream inputStream = request.getInputStream();
             BufferedReader reader = new BufferedReader(
                 new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
            
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            logger.error("讀取請(qǐng)求體失敗", e);
        }
        return sb.toString();
    }

    // 其他必要的方法重寫...
}

2. 實(shí)現(xiàn)自定義 Filter

@Component
@Order(10000)
@WebFilter(filterName = "HttpServletRequestFilter", urlPatterns = "/*")
public class HttpServletRequestFilter implements Filter {
    
    private static final Set<String> EXCLUDE_PATHS = Set.of(
        "/health", "/actuator/health", "/favicon.ico"
    );
    
    private static final Logger logger = LoggerFactory.getLogger(HttpServletRequestFilter.class);

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, 
                         FilterChain filterChain) throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
        String path = httpRequest.getRequestURI();
        
        // 跳過不需要處理的路徑
        if (shouldSkipProcessing(path)) {
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }
        
        // 包裝請(qǐng)求
        RequestWrapper requestWrapper = new RequestWrapper(httpRequest);
        
        // 設(shè)置自定義 Header,這里可以在別處設(shè)置,比如從token中解析后調(diào)用setCustomHeaders
        setCustomHeaders(requestWrapper);
        
        // 繼續(xù)過濾器鏈
        filterChain.doFilter(requestWrapper, servletResponse);
    }

    /**
     * 判斷是否跳過處理
     */
    private boolean shouldSkipProcessing(String path) {
        return EXCLUDE_PATHS.stream().anyMatch(path::startsWith);
    }

    /**
     * 設(shè)置自定義 Header
     */
    private void setCustomHeaders(HttpServletRequest request) {
        try {
            // 示例:從用戶上下文中獲取租戶ID
            User user = UserContext.getUser();
            if (user != null && user.getTenantId() != null) {
                setHeaderTenantId(request, user.getTenantId());
            }
            
            // 可以添加更多自定義 Header
            // setOtherHeaders(request);
            
        } catch (Exception e) {
            logger.warn("設(shè)置自定義 Header 失敗", e);
        }
    }

    /**
     * 設(shè)置租戶ID Header
     */
    private void setHeaderTenantId(HttpServletRequest request, String tenantId) {
        if (request instanceof StandardMultipartHttpServletRequest) {
            // 處理文件上傳請(qǐng)求
            StandardMultipartHttpServletRequest multipartRequest = 
                (StandardMultipartHttpServletRequest) request;
            HttpServletRequest originalRequest = multipartRequest.getRequest();
            if (originalRequest instanceof RequestWrapper) {
                ((RequestWrapper) originalRequest).addHeader("tenantId", tenantId);
            }
        } else if (request instanceof RequestWrapper) {
            // 處理普通請(qǐng)求
            ((RequestWrapper) request).addHeader("tenantId", tenantId);
        }
        // 其他類型的請(qǐng)求可以根據(jù)需要擴(kuò)展
    }

    @Override
    public void init(FilterConfig filterConfig) {
        logger.info("HttpServletRequestFilter 初始化完成");
    }

    @Override
    public void destroy() {
        logger.info("HttpServletRequestFilter 銷毀");
    }
}

關(guān)鍵技術(shù)點(diǎn)解析

1. HttpServletRequestWrapper 的作用

HttpServletRequestWrapper 是裝飾器模式的實(shí)現(xiàn),它允許我們:

  • 包裝原始請(qǐng)求對(duì)象
  • 選擇性重寫方法
  • 保持其他方法的默認(rèn)行為

2. 必須重寫的方法

要支持自定義 Header,必須重寫以下三個(gè)方法:

  • getHeader(String name) - 獲取單個(gè) Header 值
  • getHeaders(String name) - 獲取 Header 的所有值
  • getHeaderNames() - 獲取所有 Header 名稱

3. 請(qǐng)求體處理

由于 HTTP 請(qǐng)求的輸入流只能讀取一次,我們需要在包裝時(shí)保存請(qǐng)求體內(nèi)容:

private String getBodyString(ServletRequest request) {
    // 讀取并保存請(qǐng)求體,支持重復(fù)讀取
}

4. 特殊請(qǐng)求類型處理

對(duì)于 MultipartHttpServletRequest(文件上傳)等特殊請(qǐng)求類型,需要特殊處理:

if (request instanceof StandardMultipartHttpServletRequest) {
    // 獲取底層請(qǐng)求對(duì)象進(jìn)行處理
}

使用示例

在 Controller 中獲取自定義 Header:

@RestController
public class TestController {
    
    @GetMapping("/api/data")
    public ResponseEntity<?> getData(HttpServletRequest request) {
        // 獲取自定義的 tenantId Header
        String tenantId = request.getHeader("tenantId");
        
        // 使用 tenantId 進(jìn)行業(yè)務(wù)處理
        List<Data> result = dataService.getDataByTenant(tenantId);
        
        return ResponseEntity.ok(result);
    }
}

最佳實(shí)踐建議

1. 性能優(yōu)化

  • 路徑排除:對(duì)健康檢查等不需要處理的路徑進(jìn)行排除
  • 懶加載:只在需要時(shí)讀取請(qǐng)求體
  • 資源清理:及時(shí)關(guān)閉流和清理 ThreadLocal

2. 錯(cuò)誤處理

  • 異常捕獲:妥善處理可能出現(xiàn)的異常
  • 降級(jí)策略:在無法設(shè)置 Header 時(shí)提供合理的降級(jí)方案
  • 日志記錄:記錄關(guān)鍵操作和錯(cuò)誤信息

3. 可維護(hù)性

  • 配置化:將排除路徑等配置外部化
  • 模塊化:將 Header 設(shè)置邏輯分離到獨(dú)立的服務(wù)中
  • 文檔化:記錄自定義 Header 的含義和用途

替代方案比較

1. ThreadLocal 方式

優(yōu)點(diǎn):簡(jiǎn)單高效
缺點(diǎn):需要在同一線程內(nèi)傳遞,對(duì)異步支持不友好

2. Request Attribute 方式

優(yōu)點(diǎn):實(shí)現(xiàn)簡(jiǎn)單
缺點(diǎn):不符合 HTTP Header 的語義

3. Filter + Wrapper 方式(本文方案)

優(yōu)點(diǎn):符合 Servlet 規(guī)范,支持所有請(qǐng)求類型
缺點(diǎn):實(shí)現(xiàn)相對(duì)復(fù)雜

總結(jié)

通過 Filter 和 HttpServletRequestWrapper 的組合,我們可以優(yōu)雅地在 Spring 應(yīng)用中添加自定義 HTTP Header。這種方案:

  1. 符合 Servlet 規(guī)范,不破壞現(xiàn)有框架行為
  2. 支持所有請(qǐng)求類型,包括文件上傳等特殊場(chǎng)景
  3. 具有良好的擴(kuò)展性,可以方便地添加更多自定義 Header
  4. 保持代碼整潔,業(yè)務(wù)代碼無需關(guān)心 Header 的設(shè)置細(xì)節(jié)

在實(shí)際項(xiàng)目中,建議根據(jù)具體需求選擇合適的方案,并遵循本文提到的最佳實(shí)踐,以確保代碼的健壯性和可維護(hù)性。

以上就是在Spring中給HttpServletRequest添加自定義Header的完整指南的詳細(xì)內(nèi)容,更多關(guān)于Spring給HttpServletRequest添加Header的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java流程控制語句之If選擇結(jié)構(gòu)

    Java流程控制語句之If選擇結(jié)構(gòu)

    今天繼續(xù)帶大家復(fù)習(xí)Java流程控制語句的相關(guān)知識(shí),本文對(duì)If選擇結(jié)構(gòu)作了非常詳細(xì)的介紹及代碼示例,對(duì)正在學(xué)習(xí)的小伙伴們很有幫助,需要的朋友可以參考下
    2021-06-06
  • IDEA中Git的配置及其使用過程

    IDEA中Git的配置及其使用過程

    本文介紹了如何在IntelliJ IDEA中配置和使用Git,包括配置Git路徑、克隆項(xiàng)目、更新代碼、提交和推送代碼、合并分支、管理分支以及將本地項(xiàng)目推送到GitHub
    2025-12-12
  • Hibernate處理多對(duì)多關(guān)系的實(shí)現(xiàn)示例

    Hibernate處理多對(duì)多關(guān)系的實(shí)現(xiàn)示例

    本文介紹了Hibernate中實(shí)現(xiàn)多對(duì)多關(guān)系的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2026-01-01
  • Intellij IDEA如何去掉@Autowired 注入警告的方法

    Intellij IDEA如何去掉@Autowired 注入警告的方法

    這篇文章主要介紹了Intellij IDEA如何去掉@Autowired 注入警告的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-04-04
  • 詳解Spring Boot 配置加載順序及屬性加載順序

    詳解Spring Boot 配置加載順序及屬性加載順序

    這篇文章主要介紹了詳解Spring Boot 配置加載順序及屬性加載順序,本章內(nèi)容基于 Spring Boot 2.0 進(jìn)行詳解,感興趣的朋友跟隨腳本之家小編一起看看吧
    2018-08-08
  • 解決springboot啟動(dòng)Logback報(bào)錯(cuò)ERROR in ch.qos.logback.classic.joran.action.ContextNameAction - Failed to rena

    解決springboot啟動(dòng)Logback報(bào)錯(cuò)ERROR in ch.qos.logback.cla

    這篇文章主要介紹了解決springboot啟動(dòng)Logback報(bào)錯(cuò)ERROR in ch.qos.logback.classic.joran.action.ContextNameAction - Failed to rena問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-04-04
  • springboot實(shí)用配置詳細(xì)圖文教程

    springboot實(shí)用配置詳細(xì)圖文教程

    SpringBoot從本質(zhì)上來說就是Spring,它通過了一些自己的特性幫助我們簡(jiǎn)化了Spring應(yīng)用程序的開發(fā),下面這篇文章主要給大家介紹了關(guān)于springboot實(shí)用配置的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2023-05-05
  • Java實(shí)現(xiàn)駝峰和下劃線互相轉(zhuǎn)換的示例代碼

    Java實(shí)現(xiàn)駝峰和下劃線互相轉(zhuǎn)換的示例代碼

    Java對(duì)各種變量、方法和類等要素命名時(shí)使用的字符序列稱為標(biāo)識(shí)符,凡是自己可以起名字的地方都叫標(biāo)識(shí)符。本文為大家分享了Java中如何實(shí)現(xiàn)駝峰命名與下劃線命名的互轉(zhuǎn),感興趣的可以了解一下
    2022-05-05
  • Java  Option用法詳解

    Java  Option用法詳解

    Optional類是Java8為了解決null值判斷問題,借鑒google guava類庫(kù)的Optional類而引入的一個(gè)同名Optional類,使用Optional類可以避免顯式的null值判斷,避免null導(dǎo)致的NPE,下面以一些典型場(chǎng)景為例,列出Optional API常用接口的用法,并附上相應(yīng)代碼,感興趣的朋友一起看看吧
    2024-01-01
  • Mybatis-plus配置之日期時(shí)間自動(dòng)填充實(shí)踐

    Mybatis-plus配置之日期時(shí)間自動(dòng)填充實(shí)踐

    本文介紹如何使用MyBatis-Plus的MetaObjectHandler接口實(shí)現(xiàn)新增和更新時(shí)間的自動(dòng)填充,通過繼承抽象類、添加注解及處理版本兼容性,簡(jiǎn)化開發(fā)流程并減少手動(dòng)操作
    2025-08-08

最新評(píng)論

天长市| 内丘县| 井陉县| 凉山| 华安县| 济宁市| 十堰市| 恩平市| 恩平市| 武邑县| 武川县| 海阳市| 乌兰浩特市| 塔城市| 虞城县| 石河子市| 双峰县| 太谷县| 琼海市| 鄂伦春自治旗| 黔东| 准格尔旗| 秦安县| 镇宁| 加查县| 从江县| 科技| 芮城县| 澄城县| 峨边| 昭苏县| 色达县| 阿拉善盟| 民和| 江源县| 惠州市| 五华县| 库尔勒市| 进贤县| 鄯善县| 江津市|