You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Controller如何分别获取查询参数与URL编码请求体

问题描述

处理HTML注册表单的POST控制器,表单以application/x-www-form-urlencoded格式提交,包含username、password等字段;同时请求URI中带有client_id、redirect_uri及用于预填输入框的username查询参数。

使用UserSignUpFormModel(POJO)绑定请求体时,若POST请求同时存在查询参数username=bar和请求体username=foo,调用user.getUsername()会返回foo,bar(两者被逗号拼接),但实际需要仅获取请求体中的foo,并单独获取查询参数的bar。

控制器代码:

@PostController
public String performSignUp(UserSignUpFormModel user) {
     // username = "foo,bar" 但期望仅为请求体中的"foo"
     String username = user.getUsername();
     // ...业务逻辑
}

模型代码:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
@JsonIgnoreProperties(ignoreUnknown = true)
public class UserSignUpFormModel {

    private String email;
    private String username;
    private String password;
    private String passwordConfirmation;
    private Boolean hasAcceptedTerms;

}
问题原因

Spring MVC中,控制器方法的POJO参数默认会被当作@ModelAttribute处理,它会从所有请求参数源(查询参数、表单参数、路径变量等)收集同名参数,当多个源存在同名参数时,会自动将它们用逗号拼接成字符串赋值给POJO字段。

解决方案

方案1:分别绑定查询参数与请求体(推荐)

通过@RequestParam单独获取查询参数中的username,同时用@RequestBody绑定请求体到POJO(需指定请求类型为application/x-www-form-urlencoded),这样POJO只会读取请求体的内容,避免参数合并。

修改后的控制器代码:

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;

@PostController
public class SignUpController {

    @PostMapping(value = "/sign-up", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    public String performSignUp(
            // 获取查询参数中的username(非必填,因为可能不存在)
            @RequestParam(value = "username", required = false) String queryUsername,
            // 仅绑定请求体中的表单参数到POJO
            @RequestBody UserSignUpFormModel user) {
        
        String formUsername = user.getUsername(); // 来自请求体的"foo"
        String prefilledUsername = queryUsername; // 来自查询参数的"bar"(若存在)
        
        // ...业务逻辑处理
        return "redirect:/success";
    }
}

方案2:手动解析请求体参数

若不想使用@RequestBody,可通过HttpServletRequest直接读取并解析请求体的表单参数,同时用@RequestParam获取查询参数:

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.beans.BeanUtils;

@PostController
public class SignUpController {

    @PostMapping("/sign-up")
    public String performSignUp(
            @RequestParam(value = "username", required = false) String queryUsername,
            HttpServletRequest request) throws IOException {
        
        // 读取并解析请求体的表单参数
        String requestBody = request.getReader().lines().collect(Collectors.joining());
        Map<String, String> formParams = Arrays.stream(requestBody.split("&"))
                .map(param -> param.split("="))
                .collect(Collectors.toMap(
                        arr -> URLDecoder.decode(arr[0], StandardCharsets.UTF_8),
                        arr -> arr.length > 1 ? URLDecoder.decode(arr[1], StandardCharsets.UTF_8) : ""
                ));
        
        // 将表单参数映射到POJO
        UserSignUpFormModel user = new UserSignUpFormModel();
        BeanUtils.populate(user, formParams);
        
        String formUsername = user.getUsername(); // 请求体中的"foo"
        // ...业务逻辑处理
        return "redirect:/success";
    }
}

方案3:自定义参数绑定规则(复杂场景)

通过@InitBinder自定义WebDataBinder,限制参数仅从请求体中获取。这种方式需要自定义绑定逻辑,适合复杂的参数处理场景:

import org.springframework.web.bind.InitBinder;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.PostController;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.mvc.method.annotation.ServletRequestDataBinder;

@PostController
public class SignUpController {

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        if (binder instanceof ServletRequestDataBinder servletBinder) {
            // 自定义参数获取逻辑,仅从请求体读取表单参数,忽略查询参数
            servletBinder.setServletRequestParameterNameFilter(paramName -> {
                // 可根据需求过滤参数来源,实现逻辑需结合HttpServletRequest区分参数源
                // 注:该方式需深入Spring绑定逻辑,推荐优先使用方案1
                return true;
            });
        }
    }

    @PostMapping("/sign-up")
    public String performSignUp(
            @RequestParam(value = "username", required = false) String queryUsername,
            UserSignUpFormModel user) {
        String formUsername = user.getUsername();
        // ...业务逻辑处理
        return "redirect:/success";
    }
}
总结

推荐使用方案1,它利用Spring原生注解机制,代码简洁且易于维护,能直接区分查询参数与请求体参数,避免自动合并的问题。

内容的提问来源于stack exchange,提问作者Maxime Esnol

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 16:40:30