Spring Boot Web上下文工具類詳解:獲取Request、Response和參數

工具類概述

這個CommonServletUtil工具類提供了在Spring Boot應用中便捷地獲取Web上下文相關對象的方法,包括HttpServletRequest、HttpServletResponse以及從多種來源獲取參數值。

功能特點

  1. 多來源參數獲取:從請求參數、請求頭、Cookie和屬性中獲取值
  2. Web上下文檢測:判斷當前是否處於Web環境中
  3. 異常處理:對非Web上下文情況進行友好錯誤提示
  4. 便捷訪問:靜態方法直接調用,無需注入

完整代碼

package com.mnsn.framework.boot.utils;

import cn.hutool.core.util.ObjectUtil;
import com.mnsn.framework.commons.exception.CheckedException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.Generated;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * Servlet工具類
 * 提供Web上下文相關操作的工具方法
 * 
 * @author mnsn
 * @version 1.0
 * @date 2024-01-15
 */
public class CommonServletUtil {
    @Generated
    private static final Logger log = LoggerFactory.getLogger(CommonServletUtil.class);

    private CommonServletUtil() {
        // 工具類,防止實例化
    }

    /**
     * 從請求中獲取參數值
     * 按照以下順序查找:請求參數 -> 請求頭 -> Cookie -> 請求屬性
     *
     * @param paramName 參數名
     * @return 參數值,如果未找到返回null
     */
    public static String getParamFromRequest(String paramName) {
        HttpServletRequest request = getRequest();
        String paramValue = request.getParameter(paramName);
        
        // 1. 從請求參數中獲取
        if (ObjectUtil.isEmpty(paramValue)) {
            paramValue = request.getHeader(paramName);
        }

        // 2. 從請求頭中獲取
        if (ObjectUtil.isEmpty(paramValue)) {
            Cookie[] cookies = request.getCookies();
            if (ObjectUtil.isNotEmpty(cookies)) {
                // 3. 從Cookie中獲取
                for (Cookie cookie : cookies) {
                    String cookieName = cookie.getName();
                    if (cookieName.equals(paramName)) {
                        return cookie.getValue();
                    }
                }
            }
        }

        // 4. 從請求屬性中獲取
        if (ObjectUtil.isEmpty(paramValue)) {
            paramValue = String.valueOf(request.getAttribute(paramName));
        }

        return paramValue;
    }

    /**
     * 獲取當前請求的HttpServletRequest對象
     *
     * @return HttpServletRequest對象
     * @throws CheckedException 非Web上下文時拋出異常
     */
    public static HttpServletRequest getRequest() {
        ServletRequestAttributes servletRequestAttributes;
        try {
            servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        } catch (Exception var2) {
            log.error(">>> 非Web上下文無法獲取Request:", var2);
            throw new CheckedException("非Web上下文無法獲取Request");
        }

        if (servletRequestAttributes == null) {
            throw new CheckedException("非Web上下文無法獲取Request");
        } else {
            return servletRequestAttributes.getRequest();
        }
    }

    /**
     * 獲取當前請求的HttpServletResponse對象
     *
     * @return HttpServletResponse對象
     * @throws CheckedException 非Web上下文時拋出異常
     */
    public static HttpServletResponse getResponse() {
        ServletRequestAttributes servletRequestAttributes;
        try {
            servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        } catch (Exception var2) {
            log.error(">>> 非Web上下文無法獲取Response:", var2);
            throw new CheckedException("非Web上下文無法獲取Response");
        }

        if (servletRequestAttributes == null) {
            throw new CheckedException("非Web上下文無法獲取Response");
        } else {
            return servletRequestAttributes.getResponse();
        }
    }

    /**
     * 判斷當前是否處於Web上下文中
     *
     * @return true-在Web上下文中, false-不在Web上下文中
     */
    public static boolean isWeb() {
        return RequestContextHolder.getRequestAttributes() != null;
    }
}

Maven依賴配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.0</version>
        <relativePath/>
    </parent>
    
    <groupId>com.mnsn.framework</groupId>
    <artifactId>common-utils</artifactId>
    <version>1.0.0</version>
    
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <hutool.version>5.8.11</hutool.version>
    </properties>
    
    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- Hutool工具包 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-core</artifactId>
            <version>${hutool.version}</version>
        </dependency>
        
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        
        <!-- 測試 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

使用示例

1. 在Controller中使用

@RestController
@RequestMapping("/api/user")
public class UserController {
    
    @GetMapping("/info")
    public ResponseEntity<UserInfo> getUserInfo() {
        // 獲取請求參數
        String userId = CommonServletUtil.getParamFromRequest("userId");
        String token = CommonServletUtil.getParamFromRequest("token");
        
        // 獲取Request對象
        HttpServletRequest request = CommonServletUtil.getRequest();
        String userAgent = request.getHeader("User-Agent");
        
        UserInfo userInfo = userService.getUserInfo(userId, token);
        return ResponseEntity.ok(userInfo);
    }
    
    @PostMapping("/update")
    public ResponseEntity<String> updateUser(@RequestBody User user) {
        // 獲取Response對象設置Cookie
        HttpServletResponse response = CommonServletUtil.getResponse();
        Cookie cookie = new Cookie("lastUpdate", String.valueOf(System.currentTimeMillis()));
        response.addCookie(cookie);
        
        userService.updateUser(user);
        return ResponseEntity.ok("更新成功");
    }
}

2. 在Service中使用

@Service
public class UserService {
    
    public UserInfo getUserInfo(String userId, String token) {
        // 檢查是否在Web上下文中
        if (CommonServletUtil.isWeb()) {
            String clientIp = CommonServletUtil.getRequest().getRemoteAddr();
            log.info("客户端IP: {}", clientIp);
        }
        
        // 業務邏輯...
        return userInfo;
    }
}

3. 在攔截器中使用

@Component
public class AuthInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 使用工具類獲取token
        String token = CommonServletUtil.getParamFromRequest("Authorization");
        
        if (StringUtils.isEmpty(token)) {
            throw new UnauthorizedException("未授權訪問");
        }
        
        // 驗證token邏輯...
        return true;
    }
}

方法詳解

getParamFromRequest(String paramName)

這個方法按照以下優先級從請求中獲取參數值:

  1. 請求參數request.getParameter(paramName)
  2. 請求頭request.getHeader(paramName)
  3. Cookie:遍歷所有Cookie查找匹配的名稱
  4. 請求屬性request.getAttribute(paramName)

getRequest() 和 getResponse()

這兩個方法通過Spring的RequestContextHolder獲取當前線程綁定的請求和響應對象,如果不在Web上下文中會拋出CheckedException

isWeb()

用於判斷當前是否處於Web環境中,在異步任務或定時任務中特別有用。

注意事項

  1. 線程安全性:工具類中的方法都是靜態的且無狀態,可以在多線程環境中安全使用
  2. Web上下文:在非Web環境(如單元測試、定時任務)中調用getRequest()/getResponse()會拋出異常
  3. 性能考慮:頻繁調用getRequest()可能影響性能,建議在需要時緩存引用
  4. 空值處理:getParamFromRequest()方法在找不到參數時返回null,調用方需要處理空值情況

單元測試

@SpringBootTest
class CommonServletUtilTest {
    
    @Test
    void testIsWebInWebContext() {
        assertTrue(CommonServletUtil.isWeb());
    }
    
    @Test
    void testGetRequest() {
        HttpServletRequest request = CommonServletUtil.getRequest();
        assertNotNull(request);
    }
    
    @Test
    void testGetParamFromRequest() {
        // 需要通過Mock HttpServletRequest來測試
        // 具體實現略...
    }
}

這個工具類大大簡化了在Spring Boot應用中處理Web相關操作的工作,提供了統一的方式來訪問請求、響應和參數信息。