将HTML表单数据作为JSON请求体传入Java Spring控制器的最佳实践
Spring Boot 表单数据转JSON请求体原生实现方案
首先明确:HTML原生表单默认不支持application/json格式的提交,仅支持application/x-www-form-urlencoded和multipart/form-data两种内容类型。如果你不想使用前端AJAX代码,完全基于Spring官方原生能力处理表单数据,有以下两种成熟的最佳实践:
方案1:使用@ModelAttribute自动绑定表单参数(最推荐轻量方案)
Spring原生提供@ModelAttribute注解,可以直接将表单提交的键值对自动映射到Java对象属性中,无需额外配置,你如果需要JSON格式数据直接用Spring内置的Jackson序列化即可。
示例代码
- 定义表单数据传输DTO
// 必须提供无参构造、对应属性的getter/setter public class UserFormDTO { private String username; private String email; private Integer age; // 无参构造 public UserFormDTO() {} // getter和setter省略 }
- 控制器接收逻辑
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import com.fasterxml.jackson.databind.ObjectMapper; @RestController public class FormController { // Spring内置的ObjectMapper可以直接注入,不需要自己new private final ObjectMapper objectMapper; public FormController(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } @PostMapping("/submit/form") public String handleFormSubmit(@ModelAttribute UserFormDTO formDTO) throws Exception { // 直接将绑定好的DTO序列化为JSON字符串 String formJson = objectMapper.writeValueAsString(formDTO); // 后续业务逻辑处理 return "提交成功,JSON数据:" + formJson; } }
适用场景
- 不需要强制请求体为JSON格式,仅需要服务端拿到JSON结构数据
- 项目业务简单,不想增加额外过滤器配置
- 兼容传统表单提交的所有场景,包括文件上传
方案2:自定义过滤器统一转换表单请求为JSON请求体
如果你需要让控制器直接通过@RequestBody接收JSON格式的表单数据,和接口接收JSON参数的逻辑完全统一,可以自定义Spring全局过滤器,自动将表单提交的参数转换为JSON请求体,无需修改任何前端代码。
示例代码
- 定义全局转换过滤器
import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import com.fasterxml.jackson.databind.ObjectMapper; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.stream.Collectors; @Component public class FormToJsonConverterFilter extends OncePerRequestFilter { private final ObjectMapper objectMapper; public FormToJsonConverterFilter(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 仅处理表单提交的请求 if (MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType())) { // 读取所有表单参数 var paramMap = request.getParameterMap(); // 转换为普通JSON结构,单值参数直接存值,多值参数存数组 var jsonMap = paramMap.entrySet().stream() .collect(Collectors.toMap( Map.Entry::getKey, entry -> entry.getValue().length == 1 ? entry.getValue()[0] : entry.getValue() )); String jsonBody = objectMapper.writeValueAsString(jsonMap); // 包装请求,替换请求体和Content-Type HttpServletRequest wrappedRequest = new HttpServletRequestWrapper(request) { @Override public ServletInputStream getInputStream() { var bis = new ByteArrayInputStream(jsonBody.getBytes(StandardCharsets.UTF_8)); return new ServletInputStream() { @Override public boolean isFinished() { return bis.available() == 0; } @Override public boolean isReady() { return true; } @Override public void setReadListener(ReadListener listener) {} @Override public int read() { return bis.read(); } }; } @Override public String getContentType() { return MediaType.APPLICATION_JSON_VALUE; } }; filterChain.doFilter(wrappedRequest, response); return; } filterChain.doFilter(request, response); } }
- 控制器直接用
@RequestBody接收
@PostMapping("/submit/form") public String handleFormSubmit(@RequestBody UserFormDTO formDTO) throws Exception { // 直接拿到和接口请求一致的DTO对象,不需要额外转换 return "提交成功"; }
适用场景
- 需要统一表单提交和接口JSON提交的处理逻辑,减少重复代码
- 后续计划逐步升级为前后端分离架构,不需要修改控制器逻辑
注意事项
- 两种方案均为Spring官方原生能力支持,不需要引入任何第三方依赖
- 如果需要处理带文件上传的表单,仅方案1的
@ModelAttribute可以直接兼容,方案2需要单独适配multipart/form-data类型的请求 - 所有用到的DTO类必须提供无参构造方法和对应属性的getter/setter,否则Spring无法完成自动绑定
内容的提问来源于stack exchange,提问作者Hunter Yavitz
相关产品推荐
相关产品推荐

