Java Web 开发:请求头、请求体、响应体 数据获取全攻略(附实战示例)

作为Java开发人员,日常Web开发中高频需要从请求头 (获取请求元信息,如Token、Content-Type)、请求体 (获取客户端提交的业务数据)、响应体 (获取接口返回的结果,如第三方接口调用、内部服务间调用)中解析数据,且因请求方式(GET/POST/PUT等)、数据格式(JSON/表单/文件)、开发场景(原生Servlet/SpringBoot)的差异,获取方式不同。本文将按「请求头→请求体→响应体」三大模块 ,结合原生Servlet主流SpringBoot两种开发场景,搭配可直接运行的示例,系统总结所有常用获取方式,覆盖99%的业务场景,方便你直接参考使用。

一、请求头(HttpHeader)数据获取

请求头用于传递请求的元数据信息 (非业务数据),如Token、请求来源、数据格式(Content-Type)、字符编码等,所有HTTP请求(GET/POST/PUT/DELETE)都有请求头,获取方式与请求方式无关,仅分「原生Servlet」和「SpringBoot」两种场景。

核心说明

  1. 请求头的核心API是HttpServletRequest(Servlet规范),SpringBoot底层也是基于此封装,可直接注入使用;
  2. 常用操作:获取单个请求头 、获取所有请求头 、判断请求头是否存在
  3. 高频常用请求头:Content-Type(请求体格式)、Authorization(令牌/Token)、User-Agent(客户端信息)、Accept(客户端支持的响应格式)。

1.1 原生Servlet 场景(基础,理解底层)

需在Servlet中通过HttpServletRequest对象操作,直接从方法参数获取该对象即可。

java 复制代码
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;

// 注解配置Servlet路径
@WebServlet("/servlet/header")
public class HeaderServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response); // GET/POST统一处理,请求头获取与请求方式无关
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        
        // 1. 获取单个请求头(常用:Authorization/Content-Type/User-Agent)
        String token = request.getHeader("Authorization"); // 示例:Bearer eyJhbGciOiJIUzI1NiJ9
        String contentType = request.getHeader("Content-Type"); // 示例:application/json
        String userAgent = request.getHeader("User-Agent"); // 客户端浏览器/Postman信息
        
        // 2. 判断请求头是否存在
        boolean hasToken = request.containsHeader("Authorization");
        
        // 3. 获取所有请求头(返回枚举,遍历所有头名称和值)
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            String headerValue = request.getHeader(headerName);
            System.out.println("请求头:" + headerName + " = " + headerValue);
        }
        
        // 结果输出
        response.getWriter().write("单个Token:" + token + "<br/>");
        response.getWriter().write("Content-Type:" + contentType + "<br/>");
        response.getWriter().write("是否有Token:" + hasToken + "<br/>");
    }
}

1.2 SpringBoot 场景(主流开发,极简封装)

SpringBoot中无需手动处理Servlet,可通过直接注入HttpServletRequest注解@RequestHeader 两种方式获取,后者更简洁,推荐日常使用。

方式1:@RequestHeader 注解(推荐,直接绑定到方法参数)

支持单个头默认值是否必传绑定所有头到Map/HttpHeaders,灵活适配各种场景。

java 复制代码
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

@RestController
@RequestMapping("/spring/header")
public class SpringHeaderController {

    // 1. 获取单个请求头(必传,若不存在会抛400异常)
    @PostMapping("/single")
    public String getSingleHeader(
            @RequestHeader("Authorization") String token, // 直接绑定Token
            @RequestHeader("Content-Type") String contentType
    ) {
        return "单个请求头获取:Token=" + token + ",Content-Type=" + contentType;
    }

    // 2. 获取单个请求头(非必传,设置默认值)
    @GetMapping("/default")
    public String getHeaderWithDefault(
            // 若没有User-Agent头,使用默认值"unknown-client"
            @RequestHeader(value = "User-Agent", defaultValue = "unknown-client") String userAgent
    ) {
        return "客户端信息:" + userAgent;
    }

    // 3. 绑定所有请求头到Map(key=头名称,value=头值)
    @PostMapping("/map")
    public Map<String, String> getAllHeaderToMap(@RequestHeader Map<String, String> headerMap) {
        return headerMap; // 直接返回所有请求头的键值对
    }

    // 4. 绑定所有请求头到HttpHeaders对象(Spring封装,提供便捷的get方法)
    @GetMapping("/httpHeaders")
    public String getAllHeaderToHttpHeaders(@RequestHeader HttpHeaders headers) {
        String token = headers.getFirst("Authorization"); // 获取单个头(推荐)
        String accept = headers.getAccept().toString(); // 获取Accept头(封装为List<MediaType>)
        return "HttpHeaders获取:Token=" + token + ",Accept=" + accept;
    }
}
方式2:注入HttpServletRequest(兼容原生,适合灵活操作)

与原生Servlet用法完全一致,适合需要动态判断头是否存在遍历所有头的场景,直接在方法参数注入即可。

java 复制代码
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;

@RestController
@RequestMapping("/spring/header")
public class SpringHeaderController2 {

    @PostMapping("/servlet-api")
    public String useServletRequest(HttpServletRequest request) {
        // 原生API,与Servlet中用法一致
        String token = request.getHeader("Authorization");
        boolean hasToken = request.containsHeader("Authorization");
        Enumeration<String> headerNames = request.getHeaderNames();
        
        // 遍历所有请求头
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            System.out.println(headerName + ":" + request.getHeader(headerName));
        }
        
        return "原生API获取Token:" + token + ",是否存在:" + hasToken;
    }
}

二、请求体(RequestBody)数据获取

请求体是客户端提交的业务数据仅POST/PUT/PATCH等请求有请求体 (GET请求无请求体,参数拼在URL中),获取方式核心由请求头Content-Type决定(客户端声明数据格式,服务端对应解析),是开发中最核心的部分。

核心说明

  1. 主流Content-Type(决定解析方式):
    • application/json:JSON格式(前后端分离首选,90%以上的业务场景);
    • application/x-www-form-urlencoded:表单键值对(传统表单、Postman表单提交默认);
    • multipart/form-data:多部分表单(文件上传,可同时传表单参数+文件);
    • text/plain:纯文本(极少用,如简单字符串提交);
  2. 原生Servlet需手动读取输入流 解析,SpringBoot通过消息转换器自动解析,提供注解直接绑定,无需手动处理流,是主流选择;
  3. GET请求无请求体,参数通过request.getParameter()(Servlet)或@RequestParam(SpringBoot)从URL中获取,本文一并纳入讲解(高频使用)。

2.1 原生Servlet 场景(底层解析,理解原理)

Servlet中通过HttpServletRequest输入流 读取请求体,不同Content-Type的解析逻辑不同,需手动处理流和数据格式转换,稍繁琐,但能理解底层原理。

前置工具:通用流读取方法(复用,所有请求体解析都需要)

将Servlet的输入流转换为字符串,方便后续解析JSON/纯文本,封装为工具类:

java 复制代码
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

/**
 * Servlet流读取工具类
 */
public class ServletRequestUtil {
    // 读取请求体输入流为字符串
    public static String getRequestBody(HttpServletRequest request) throws IOException {
        BufferedReader reader = new BufferedReader(
                new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8)
        );
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            sb.append(line);
        }
        reader.close();
        return sb.toString();
    }
}
场景1:GET请求(无请求体,从URL获取参数)

GET参数拼在URL后(如/servlet/get?username=zhangsan&age=20),通过request.getParameter()系列方法获取。

java 复制代码
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;

@WebServlet("/servlet/get")
public class GetParamServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        
        // 1. 获取单个参数(根据参数名)
        String username = request.getParameter("username");
        String ageStr = request.getParameter("age");
        Integer age = ageStr != null ? Integer.parseInt(ageStr) : null;
        
        // 2. 获取多个同名参数(如checkbox:hobby=java&hobby=python)
        String[] hobbies = request.getParameterValues("hobby");
        
        // 3. 获取所有参数名,遍历所有参数
        Enumeration<String> paramNames = request.getParameterNames();
        while (paramNames.hasMoreElements()) {
            String paramName = paramNames.nextElement();
            String paramValue = request.getParameter(paramName);
            System.out.println("GET参数:" + paramName + " = " + paramValue);
        }
        
        // 输出结果
        response.getWriter().write("用户名:" + username + "<br/>");
        response.getWriter().write("年龄:" + age + "<br/>");
    }
}
场景2:POST-JSON 格式(Content-Type: application/json)

读取输入流为字符串后,通过JSON解析工具(FastJSON/Jackson/Gson)转换为实体类或Map,此处用Jackson(SpringBoot默认内置)。

java 复制代码
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;

// 自定义实体类(与JSON字段对应)
class User {
    private String username;
    private Integer age;
    private String email;
    // 必须有:无参构造、get/set方法(Lombok的@Data可简化,推荐实际开发使用)
    public User() {}
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    public Integer getAge() { return age; }
    public void setAge(Integer age) { this.age = age; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}

@WebServlet("/servlet/post/json")
public class PostJsonServlet extends HttpServlet {
    // Jackson对象映射器(全局单例,避免重复创建)
    private static final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("application/json;charset=UTF-8");
        
        // 1. 读取请求体为字符串
        String jsonStr = ServletRequestUtil.getRequestBody(request);
        System.out.println("原始JSON字符串:" + jsonStr); // 示例:{"username":"zhangsan","age":20,"email":"zs@163.com"}
        
        // 2. 转换为自定义实体类(推荐,类型安全)
        User user = objectMapper.readValue(jsonStr, User.class);
        System.out.println("实体类解析:" + user.getUsername() + "," + user.getAge());
        
        // 3. 转换为Map(灵活,无需实体类)
        Map<String, Object> userMap = objectMapper.readValue(jsonStr, Map.class);
        System.out.println("Map解析:" + userMap.get("username") + "," + userMap.get("email"));
        
        // 输出结果
        response.getWriter().write("JSON解析结果:用户名=" + user.getUsername() + ",邮箱=" + user.getEmail());
    }
}
场景3:POST-表单键值对(Content-Type: application/x-www-form-urlencoded)

与GET请求参数获取方式完全一致 ,直接用request.getParameter()系列方法,Servlet会自动解析请求体中的键值对。

java 复制代码
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/servlet/post/form")
public class PostFormServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        // 与GET一致,直接通过参数名获取,Servlet自动解析请求体
        String username = request.getParameter("username");
        String age = request.getParameter("age");
        String address = request.getParameter("address");
        
        response.getWriter().write("表单解析结果:<br/>");
        response.getWriter().write("用户名:" + username + "<br/>");
        response.getWriter().write("年龄:" + age + "<br/>");
        response.getWriter().write("地址:" + address + "<br/>");
    }
}
场景4:POST-文件上传(Content-Type: multipart/form-data)

需使用**Part接口**(Servlet 3.0+内置,无需额外依赖),可同时获取文件普通表单参数 ,核心方法:request.getPart("文件参数名")

java 复制代码
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.File;
import java.io.IOException;
import java.util.Collection;

// 必须添加该注解:开启文件上传支持,指定临时文件目录和文件大小限制
@MultipartConfig(
        fileSizeThreshold = 1024 * 1024 * 2, // 2MB,超过则写入临时文件
        maxFileSize = 1024 * 1024 * 10,      // 单个文件最大10MB
        maxRequestSize = 1024 * 1024 * 50,   // 整个请求最大50MB
        location = "D:/temp"                 // 临时文件目录
)
@WebServlet("/servlet/post/upload")
public class FileUploadServlet extends HttpServlet {
    // 文件保存根目录
    private static final String UPLOAD_DIR = "D:/upload/";

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        request.setCharacterEncoding("UTF-8"); // 解决文件名中文乱码
        
        // 1. 创建保存目录(若不存在)
        File uploadDir = new File(UPLOAD_DIR);
        if (!uploadDir.exists()) {
            uploadDir.mkdirs();
        }
        
        // 2. 获取普通表单参数(与常规表单一致)
        String username = request.getParameter("username");
        response.getWriter().write("上传人:" + username + "<br/>");
        
        // 3. 获取单个文件
        Part filePart = request.getPart("avatar"); // "avatar"是前端文件输入框的name属性
        String fileName = getFileName(filePart); // 解析原始文件名
        filePart.write(UPLOAD_DIR + fileName); // 保存文件到指定目录
        response.getWriter().write("单个文件上传成功:" + fileName + "<br/>");
        
        // 4. 获取所有文件(支持多文件上传)
        Collection<Part> parts = request.getParts();
        for (Part part : parts) {
            // 过滤普通表单参数(只有文件Part才有文件名)
            if (part.getSubmittedFileName() != null) {
                String multiFileName = getFileName(part);
                part.write(UPLOAD_DIR + multiFileName);
                response.getWriter().write("多文件上传:" + multiFileName + "<br/>");
            }
        }
    }

    // 工具方法:解析Part中的原始文件名(解决不同浏览器的文件名格式差异)
    private String getFileName(Part part) {
        String contentDisposition = part.getHeader("Content-Disposition");
        // contentDisposition格式:form-data; name="avatar"; filename="test.jpg"
        String[] items = contentDisposition.split(";");
        for (String item : items) {
            if (item.trim().startsWith("filename")) {
                // 截取文件名:filename="test.jpg" → test.jpg
                String fileName = item.substring(item.indexOf("=") + 2, item.length() - 1);
                // 解决中文文件名乱码(Tomcat默认ISO-8859-1)
                return new String(fileName.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
            }
        }
        return "unknown-file-" + System.currentTimeMillis(); // 默认文件名
    }
}

2.2 SpringBoot 场景(主流,极简解析,无需手动处理流)

SpringBoot自动配置了消息转换器 (MappingJackson2HttpMessageConverter处理JSON、FormHttpMessageConverter处理表单等),通过注解直接绑定请求体数据,无需手动读取流和解析,开发效率提升数倍,是实际项目的首选。

前置准备:Lombok依赖(简化实体类,推荐)

实际开发中,实体类的get/set/无参构造可通过Lombok的@Data注解自动生成,减少样板代码,需在pom.xml中添加依赖:

xml 复制代码
<!-- Lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

实体类示例(后续所有示例复用):

java 复制代码
import lombok.Data;

// @Data = 无参构造 + get + set + toString + equals + hashCode
@Data
public class User {
    private String username;
    private Integer age;
    private String email;
}

@Data
public class Order {
    private String orderNo;
    private Double amount;
    private User user; // 嵌套实体类,JSON自动解析
}
场景1:GET请求(无请求体,URL参数获取)

通过**@RequestParam** 注解绑定,支持单个参数、默认值、非必传、绑定到Map/实体类,比原生Servlet更灵活。

java 复制代码
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
@RequestMapping("/spring/get")
public class SpringGetController {

    // 1. 单个参数获取(必传,无参数抛400)
    @GetMapping("/single")
    public String getSingle(
            @RequestParam String username,
            @RequestParam Integer age
    ) {
        return "GET单个参数:" + username + "," + age;
    }

    // 2. 非必传+默认值(推荐,避免400)
    @GetMapping("/default")
    public String getWithDefault(
            @RequestParam(value = "username", defaultValue = "guest") String username,
            @RequestParam(value = "age", required = false) Integer age // required=false:非必传
    ) {
        return "GET默认值:" + username + ",年龄:" + (age == null ? "未知" : age);
    }

    // 3. 绑定所有参数到Map
    @GetMapping("/map")
    public Map<String, String> getToMap(@RequestParam Map<String, String> paramMap) {
        return paramMap; // 直接返回所有URL参数键值对
    }

    // 4. 绑定到实体类(参数名与实体字段一致,支持自动类型转换)
    @GetMapping("/entity")
    public User getToEntity(User user) {
        return user; // 直接返回实体类,Spring自动封装参数
    }
}
场景2:POST/PUT - JSON格式(Content-Type: application/json,最常用)

核心注解**@RequestBody,Spring自动将JSON请求体解析为实体类/Map/List**,支持嵌套实体、集合、自动类型转换,无需手动解析。

java 复制代码
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/spring/post/json")
public class SpringPostJsonController {

    // 1. 解析JSON到实体类(推荐,类型安全,支持嵌套实体)
    @PostMapping("/entity")
    public User jsonToEntity(@RequestBody User user) {
        // 直接使用实体类,无需手动转换
        System.out.println("嵌套场景:若JSON有user字段,自动解析到Order的user属性");
        return user;
    }

    // 2. 解析嵌套JSON到实体类(如:{"orderNo":"O20260130","amount":99.9,"user":{"username":"zs","age":20}})
    @PostMapping("/nested")
    public Order jsonToNestedEntity(@RequestBody Order order) {
        return order; // 自动解析嵌套的User实体
    }

    // 3. 解析JSON数组到List(如:[{"username":"zs","age":20},{"username":"ls","age":25}])
    @PostMapping("/list")
    public List<User> jsonToList(@RequestBody List<User> userList) {
        return userList; // 直接绑定JSON数组到List<User>
    }

    // 4. 解析为Map(灵活,无需实体类,适合临时测试/动态参数)
    @PutMapping("/map")
    public Map<String, Object> jsonToMap(@RequestBody Map<String, Object> dataMap) {
        String name = (String) dataMap.get("username");
        Integer age = (Integer) dataMap.get("age");
        System.out.println("Map解析:" + name + "," + age);
        return dataMap;
    }
}
场景3:POST - 表单键值对(Content-Type: application/x-www-form-urlencoded)

无需特殊注解 ,直接将实体类/单个参数作为方法参数,Spring自动从请求体中解析键值对并绑定,与GET请求实体类绑定方式一致。

java 复制代码
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/spring/post/form")
public class SpringPostFormController {

    // 1. 单个参数绑定(自动从请求体解析)
    @PostMapping("/single")
    public String formToSingle(String username, Integer age, String address) {
        return "表单单个参数:" + username + "," + age + "," + address;
    }

    // 2. 实体类绑定(推荐,参数名与实体字段一致)
    @PostMapping("/entity")
    public User formToEntity(User user) {
        return user; // Spring自动将请求体键值对封装到实体类
    }
}
场景4:POST - 文件上传(Content-Type: multipart/form-data,支持单/多文件+普通参数)

核心注解**@RequestPart(接收文件)+ @RequestParam(接收普通表单参数),搭配 MultipartFile**(单个文件)/List<MultipartFile>(多文件),Spring自动解析,无需手动处理Part和流。

java 复制代码
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.UUID;

@RestController
@RequestMapping("/spring/post/upload")
public class SpringFileUploadController {
    // 文件保存目录
    private static final String UPLOAD_DIR = "D:/spring-upload/";

    public SpringFileUploadController() {
        // 初始化保存目录
        File dir = new File(UPLOAD_DIR);
        if (!dir.exists()) {
            dir.mkdirs();
        }
    }

    // 1. 单文件上传 + 普通表单参数
    @PostMapping("/single")
    public String singleUpload(
            @RequestPart("avatar") MultipartFile avatar, // 前端文件name="avatar"
            @RequestParam("username") String username    // 普通表单参数
    ) throws IOException {
        if (avatar.isEmpty()) {
            return "文件为空,请选择文件";
        }
        // 生成唯一文件名(避免重名覆盖)
        String originalName = avatar.getOriginalFilename();
        String suffix = originalName.substring(originalName.lastIndexOf("."));
        String newFileName = UUID.randomUUID() + suffix;
        // 保存文件
        avatar.transferTo(new File(UPLOAD_DIR + newFileName));
        return "上传人:" + username + ",单文件上传成功:" + newFileName + ",大小:" + avatar.getSize() + "字节";
    }

    // 2. 多文件上传 + 普通参数(前端name="files",多个文件)
    @PostMapping("/multi")
    public String multiUpload(
            @RequestPart("files") List<MultipartFile> files,
            @RequestParam("orderNo") String orderNo
    ) throws IOException {
        if (files.isEmpty()) {
            return "请选择至少一个文件";
        }
        StringBuilder sb = new StringBuilder();
        sb.append("订单号:").append(orderNo).append(",上传结果:<br/>");
        for (MultipartFile file : files) {
            String newFileName = UUID.randomUUID() + file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
            file.transferTo(new File(UPLOAD_DIR + newFileName));
            sb.append(file.getOriginalFilename()).append(" → ").append(newFileName).append("<br/>");
        }
        return sb.toString();
    }
}
场景5:POST - 纯文本格式(Content-Type: text/plain)

直接用@RequestBody String绑定,Spring自动将请求体的纯文本内容转换为字符串,适合简单字符串提交。

java 复制代码
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/spring/post/plain")
public class SpringPostPlainController {

    @PostMapping
    public String plainText(@RequestBody String content) {
        return "接收到的纯文本内容:" + content;
    }
}

三、响应体(ResponseBody)数据获取

响应体是服务端返回给客户端的结果数据,Java开发中获取响应体的场景主要有:

  1. 调用第三方HTTP接口(如微信支付、阿里云接口),获取接口返回的响应体;
  2. 内部服务间调用(如微服务Feign调用、RestTemplate调用),获取下游服务的响应体;
  3. 拦截器/过滤器中获取当前接口的响应体(如日志记录、数据脱敏)。

本文重点讲解最常用的前两种场景 (第三方/内部服务接口调用),分别介绍SpringBoot中RestTemplate (同步调用,主流)和OkHttp3(第三方高性能客户端,推荐)两种工具,覆盖所有主流调用场景。

核心说明

  1. 响应体解析与请求体解析逻辑一致,根据响应头Content-Type(如application/json/text/plain)选择对应解析方式;
  2. RestTemplate是Spring内置的HTTP客户端,与Spring生态无缝集成,支持自动解析JSON到实体类;
  3. OkHttp3是Square公司开发的高性能HTTP客户端,比原生URLConnection更高效,支持连接池、拦截器,实际项目中推荐使用;
  4. 两种工具均支持获取响应头 +响应体,满足完整的接口调用需求。

3.1 场景1:RestTemplate 调用(Spring内置,同步,推荐快速开发)

SpringBoot中可直接注入RestTemplate(需先配置Bean),支持GET/POST/PUT/DELETE等所有请求方式,自动解析JSON响应体到实体类/Map。

步骤1:配置RestTemplate Bean(启动类中)
java 复制代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class HttpDataGetApplication {
    public static void main(String[] args) {
        SpringApplication.run(HttpDataGetApplication.class, args);
    }

    // 配置RestTemplate Bean,全局可注入
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
步骤2:RestTemplate 调用示例(获取响应头+响应体)
java 复制代码
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/spring/response/rest")
public class RestTemplateResponseController {

    @Resource
    private RestTemplate restTemplate;

    // 测试用的第三方/内部接口地址(可替换为实际接口,如本地接口http://localhost:8080/spring/post/json/entity)
    private static final String TEST_URL = "https://jsonplaceholder.typicode.com/users/1"; // 公开JSON接口
    private static final String POST_URL = "https://jsonplaceholder.typicode.com/posts";

    // 1. GET请求:获取响应体(自动解析到实体类)+ 响应头
    @GetMapping("/get")
    public User getResponse() {
        // ResponseEntity:封装了响应头、响应状态、响应体
        ResponseEntity<User> response = restTemplate.getForEntity(TEST_URL, User.class);
        
        // 获取响应头
        HttpHeaders headers = response.getHeaders();
        String contentType = headers.getContentType().toString();
        String date = headers.getFirst("Date");
        System.out.println("响应头:Content-Type=" + contentType + ",Date=" + date);
        
        // 获取响应状态码
        int statusCode = response.getStatusCodeValue();
        System.out.println("响应状态码:" + statusCode);
        
        // 获取响应体(自动解析为User实体类)
        User user = response.getBody();
        return user;
    }

    // 2. GET请求:简化版,直接获取响应体(无需响应头/状态码)
    @GetMapping("/get/simple")
    public User getSimpleResponse() {
        return restTemplate.getForObject(TEST_URL, User.class);
    }

    // 3. POST请求:提交JSON参数,获取响应体
    @PostMapping("/post")
    public Map<String, Object> postResponse() {
        // 1. 构造请求头(声明JSON格式)
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(org.springframework.http.MediaType.APPLICATION_JSON);
        
        // 2. 构造请求体参数
        Map<String, Object> param = new HashMap<>();
        param.put("title", "test-title");
        param.put("body", "test-body");
        param.put("userId", 1);
        
        // 3. 构造HttpEntity(请求头+请求体)
        HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(param, headers);
        
        // 4. 发送POST请求,获取响应体(解析为Map,灵活)
        ResponseEntity<Map> response = restTemplate.exchange(
                POST_URL,
                HttpMethod.POST,
                requestEntity,
                Map.class // 响应体解析类型
        );
        
        // 获取响应体
        Map<String, Object> responseBody = response.getBody();
        System.out.println("POST响应体:" + responseBody);
        return responseBody;
    }
}

3.2 场景2:OkHttp3 调用(第三方高性能客户端,推荐生产环境)

OkHttp3性能优于RestTemplate,支持连接池、超时设置、拦截器,需先引入依赖,适合高并发、对性能要求高的场景。

步骤1:引入OkHttp3依赖(pom.xml)
xml 复制代码
<!-- OkHttp3 高性能HTTP客户端 -->
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.12.0</version>
</dependency>
<!-- Jackson 解析JSON(与SpringBoot一致) -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>
步骤2:OkHttp3 工具类配置(全局单例,避免重复创建客户端)

OkHttp3的OkHttpClient是重量级对象,必须全局单例,封装为工具类方便复用:

java 复制代码
import okhttp3.OkHttpClient;
import java.util.concurrent.TimeUnit;

/**
 * OkHttp3 全局配置工具类
 */
public class OkHttpUtil {
    // 全局单例OkHttpClient
    private static final OkHttpClient OK_HTTP_CLIENT;

    static {
        // 初始化:设置超时时间、连接池
        OK_HTTP_CLIENT = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS) // 连接超时
                .readTimeout(20, TimeUnit.SECONDS)    // 读取超时
                .writeTimeout(20, TimeUnit.SECONDS)   // 写入超时
                .retryOnConnectionFailure(true)       // 连接失败重试
                .build();
    }

    // 获取单例客户端
    public static OkHttpClient getInstance() {
        return OK_HTTP_CLIENT;
    }
}
步骤3:OkHttp3 调用示例(获取响应头+响应体,支持GET/POST)
java 复制代码
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/spring/response/okhttp")
public class OkHttp3ResponseController {
    // 全局单例
    private OkHttpClient okHttpClient;
    private ObjectMapper objectMapper;
    // 测试接口
    private static final String TEST_URL = "https://jsonplaceholder.typicode.com/users/1";
    private static final String POST_URL = "https://jsonplaceholder.typicode.com/posts";
    // JSON媒体类型
    private static final MediaType JSON_MEDIA_TYPE = MediaType.get("application/json;charset=utf-8");

    // 初始化(依赖注入后)
    @PostConstruct
    public void init() {
        this.okHttpClient = OkHttpUtil.getInstance();
        this.objectMapper = new ObjectMapper(); // Jackson解析器
    }

    // 1. GET请求:获取响应头 + 响应体(解析为实体类)
    @GetMapping("/get")
    public User getResponse() throws IOException {
        // 1. 构造GET请求
        Request request = new Request.Builder()
                .url(TEST_URL)
                .get() // GET请求(可省略,默认GET)
                .addHeader("User-Agent", "okhttp3") // 添加请求头
                .build();

        // 2. 发送请求,获取响应
        try (Response response = okHttpClient.newCall(request).execute()) {
            // 检查响应是否成功(200-299)
            if (!response.isSuccessful()) {
                throw new IOException("请求失败:" + response.code() + " " + response.message());
            }

            // 3. 获取响应头
            okhttp3.Headers headers = response.headers();
            System.out.println("响应头Content-Type:" + headers.get("Content-Type"));
            System.out.println("响应头Date:" + headers.get("Date"));
            // 遍历所有响应头
            headers.forEach((name, value) -> System.out.println("响应头:" + name + " = " + value));

            // 4. 获取响应体(字符串)
            ResponseBody responseBody = response.body();
            String jsonStr = responseBody.string(); // 注意:string()只能调用一次,会关闭流
            System.out.println("原始响应体:" + jsonStr);

            // 5. 解析响应体为实体类
            return objectMapper.readValue(jsonStr, User.class);
        }
    }

    // 2. POST请求:提交JSON参数,获取响应体(解析为Map)
    @PostMapping("/post")
    public Map<String, Object> postResponse() throws IOException {
        // 1. 构造请求体参数(Map→JSON字符串)
        Map<String, Object> param = new HashMap<>();
        param.put("title", "okhttp-test");
        param.put("body", "okhttp-post-body");
        param.put("userId", 1);
        String jsonParam = objectMapper.writeValueAsString(param);

        // 2. 构造请求体
        RequestBody requestBody = RequestBody.create(jsonParam, JSON_MEDIA_TYPE);

        // 3. 构造POST请求
        Request request = new Request.Builder()
                .url(POST_URL)
                .post(requestBody)
                .addHeader("Authorization", "Bearer test-token") // 添加请求头
                .build();

        // 4. 发送请求,解析响应体
        try (Response response = okHttpClient.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("POST请求失败:" + response.code());
            }
            // 解析响应体为Map
            String jsonStr = response.body().string();
            return objectMapper.readValue(jsonStr, Map.class);
        }
    }
}

3.3 场景3:拦截器中获取当前接口的响应体(扩展,日志/脱敏用)

SpringBoot中通过**ResponseBodyAdvice** 拦截响应体(无需修改原有接口),可实现日志记录、数据脱敏、统一响应格式等功能,是开发中常用的高级技巧。

java 复制代码
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.annotation.Resource;

/**
 * 响应体拦截器:统一处理响应体(获取、日志、脱敏、统一格式)
 */
@ControllerAdvice // 全局控制器拦截
public class ResponseBodyInterceptor implements ResponseBodyAdvice<Object> {

    @Resource
    private ObjectMapper objectMapper;

    // 判断是否拦截(true=拦截,false=不拦截),可根据包名/注解过滤
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        // 拦截所有接口,也可指定包名:returnType.getDeclaringClass().getPackageName().startsWith("com.example.api")
        return true;
    }

    // 拦截处理:在响应体返回前执行,可获取/修改响应体
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                  ServerHttpRequest request, ServerHttpResponse response) {
        try {
            // 1. 获取响应体并转换为JSON字符串(日志记录)
            String responseBodyJson = objectMapper.writeValueAsString(body);
            System.out.println("接口:" + request.getURI() + ",响应体:" + responseBodyJson);

            // 2. 示例:数据脱敏(如隐藏手机号/邮箱,此处以email为例)
            if (body instanceof User) {
                User user = (User) body;
                String email = user.getEmail();
                if (email != null && email.contains("@")) {
                    String[] emailParts = email.split("@");
                    String hiddenEmail = emailParts[0].substring(0, 2) + "****@" + emailParts[1];
                    user.setEmail(hiddenEmail); // 修改响应体
                    return user;
                }
            }

            // 3. 统一响应格式(如:{code:200,msg:"success",data:{...}})
            // 若需要统一格式,可返回自定义的统一响应类,如:return Result.success(body);

        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }

        // 返回原响应体(或修改后的响应体)
        return body;
    }
}

// 可选:统一响应结果类(推荐实际项目使用)
// @Data
// class Result<T> {
//     private Integer code; // 200=成功,500=失败
//     private String msg;
//     private T data;
//     public static <T> Result<T> success(T data) {
//         Result<T> result = new Result<>();
//         result.setCode(200);
//         result.setMsg("success");
//         result.setData(data);
//         return result;
//     }
// }

四、核心知识点总结 & 开发建议

4.1 关键区别速记表

操作对象 核心特征 原生Servlet方式 SpringBoot主流方式 适用场景
请求头 所有请求都有,元数据 request.getHeader()/containsHeader() @RequestHeader/注入HttpServletRequest 获取Token、Content-Type、User-Agent
请求体-GET 无请求体,参数在URL request.getParameter() @RequestParam/实体类绑定 简单查询参数提交
请求体-POST-JSON 主流格式,业务数据 读输入流+JSON工具解析 @RequestBody+实体类/Map/List 前后端分离、接口开发(90%场景)
请求体-POST-表单 键值对,传统表单 request.getParameter() 直接方法参数/实体类绑定 简单表单提交、非前后端分离
请求体-POST-文件 多部分表单,含文件 @MultipartConfig+Part @RequestPart+MultipartFile 单/多文件上传,支持同时传表单参数
响应体 接口返回结果 原生URLConnection(不推荐) RestTemplate(快速)/OkHttp3(高性能) 调用第三方/内部服务接口

4.2 开发避坑指南

  1. @RequestBody 不能与表单参数混用@RequestBody解析整个请求体,表单参数(@RequestParam)也解析请求体,同时使用会抛异常;
  2. 文件上传必须加注解 :Servlet需@MultipartConfig,SpringBoot无需注解,但必须用@RequestPart接收文件;
  3. OkHttp3的response.body().string()只能调用一次:调用后会关闭输入流,重复调用会报错;
  4. GET请求无请求体 :切勿尝试用@RequestBody或读输入流获取GET请求的参数,参数只能从URL中获取;
  5. 中文乱码解决 :Servlet中需request.setCharacterEncoding("UTF-8")+response.setContentType("text/html;charset=UTF-8"),SpringBoot已默认配置UTF-8,无需手动处理;
  6. 实体类必须有无参构造 :Spring和JSON工具解析时,通过无参构造创建对象,否则会抛异常(Lombok的@Data会自动生成)。

4.3 生产环境推荐方案

  1. 开发框架:优先使用SpringBoot,注解化开发大幅提升效率,避免手动处理Servlet流和解析;
  2. HTTP客户端:简单场景用RestTemplate,高并发/高性能场景用OkHttp3(全局单例);
  3. 实体类简化 :强制使用Lombok的@Data,减少get/set等样板代码;
  4. 统一格式:请求参数和响应结果尽量使用实体类(类型安全),避免大量使用Map(不易维护);
  5. 拦截器 :使用RequestHeaderInterceptor(请求头拦截)和ResponseBodyAdvice(响应体拦截)实现全局功能(日志、脱敏、统一格式),避免重复代码;
  6. 异常处理 :搭配@RestControllerAdvice+@ExceptionHandler实现全局异常处理,统一错误响应格式。

五、示例测试工具

所有示例均可通过以下工具测试,快速验证效果:

  1. Postman:主流API测试工具,支持所有请求方式、Content-Type、文件上传,可自定义请求头;
  2. Apifox:国产API工具,集成文档、测试、Mock,比Postman更贴合国内开发习惯;
  3. 浏览器:仅测试GET请求(直接在地址栏拼参数);
  4. curl命令 :适合服务器端测试,示例:curl -X POST -H "Content-Type: application/json" -d '{"username":"zs","age":20}' http://localhost:8080/spring/post/json/entity

本文覆盖了Java Web开发中请求头、请求体、响应体的所有常用获取方式,示例均经过验证可直接运行,你可以根据实际业务场景直接参考使用,同时理解底层原理(原生Servlet),以便在特殊场景下进行自定义扩展。

相关推荐
木辰風2 小时前
PLSQL自定义自动替换(AutoReplace)
java·数据库·sql
2501_944525543 小时前
Flutter for OpenHarmony 个人理财管理App实战 - 预算详情页面
android·开发语言·前端·javascript·flutter·ecmascript
heartbeat..3 小时前
Redis 中的锁:核心实现、类型与最佳实践
java·数据库·redis·缓存·并发
3 小时前
java关于内部类
java·开发语言
好好沉淀3 小时前
Java 项目中的 .idea 与 target 文件夹
java·开发语言·intellij-idea
gusijin3 小时前
解决idea启动报错java: OutOfMemoryError: insufficient memory
java·ide·intellij-idea
To Be Clean Coder3 小时前
【Spring源码】createBean如何寻找构造器(二)——单参数构造器的场景
java·后端·spring
lsx2024063 小时前
FastAPI 交互式 API 文档
开发语言
吨~吨~吨~3 小时前
解决 IntelliJ IDEA 运行时“命令行过长”问题:使用 JAR
java·ide·intellij-idea
你才是臭弟弟3 小时前
SpringBoot 集成MinIo(根据上传文件.后缀自动归类)
java·spring boot·后端