资讯详情

SpringBoot接口传参:查询参数、路径参数与JSON参数详解

📅 2026/9/9 16:21:06 | 华诺云谱 👁 阅读
SpringBoot接口传参:查询参数、路径参数与JSON参数详解
从一次前后端联调的“参数大战”说起。前端同事甩过来一句话“我明明把参数传过去了你后端怎么还说没收到”我一看请求日志参数躺在URL的?号后面而后端接口用RequestBody等着JSON两边压根没对齐。SpringBoot接口传参这件事难倒过不少新手——不是代码语法不会写而是搞不清查询参数、路径参数、JSON参数这三种方式到底各自解决什么问题、什么时候该用哪个、前端传过来的数据到底落在了哪里。这篇就把它们彻底捋一遍从注解原理、代码写法、混用场景到排查思路新手看完基本能少走一个月的弯路。1. 三种参数的本质区别先搞清楚数据到底“放”在了哪里很多新手学参数接收是一路抄代码抄过来的。今天抄了个RequestParam能用明天换成PathVariable就报404后天用RequestBody直接报415整个人都是懵的。本质上这三种参数对应的是数据在HTTP请求中的三种不同位置先把这个位置关系刻在脑子里后面所有问题都好解。查询参数Query Parameter是URL问号后面的键值对比如/api/user?namezhangsanage18数据以明文形式出现在地址栏天生适合表达“筛选条件”“分页信息”“非敏感的附加属性”。路径参数Path Variable嵌在URL路径本身里比如/api/user/1001这里的1001是地址的一部分表达的是“我要操作哪个具体的资源”语义上更像在喊一个资源的门牌号。JSON参数则不同它放在HTTP请求的消息体Body里以{name:zhangsan,age:18}这种结构出现能承载嵌套对象、数组、复杂结构是新增和修改类接口最常用的传参方式。理解了这个底层的“数据位置”再看注解就顺了RequestParam从URL的查询串里取值PathVariable从URL模板的占位符里取值RequestBody把请求体里的JSON字符串反序列化成Java对象。三者处理的是不同位置的数据所以它们天然可以在同一个接口里共存——只要前端分别把数据放在不同的位置后端就能用不同的注解分别接收。这也是后面第五部分混用方案的基石。还有一个新手常踩的概念混淆不写任何注解直接在Controller方法里写一个POJO参数Spring能不能自动绑定这个后面第4小节细说。先把三种参数各自的完整用法吃透再谈组合就轻松多了。2. 查询参数URL问号后面的那串学问2.1 RequestParam 的基本写法和必填控制查询参数最常见的使用场景是GET请求带筛选条件。举个最简单的例子按名字和年龄查用户。RestController RequestMapping(/api/user) public class UserController { GetMapping(/list) public Result listUsers(RequestParam String name, RequestParam Integer age) { // 业务代码省略 return Result.success(); } }这时前端发起的请求是GET /api/user/list?namezhangsanage18Spring会自动把name和age从查询串里解析出来绑定到方法参数上。注意age是Integer类型查询串里的值都是字符串Spring的ConversionService会自动做类型转换转不成功就会抛MethodArgumentTypeMismatchException最后表现为400错误。RequestParam默认required true也就是说前端少传任何一个参数接口直接报400Spring甚至不会进入方法体。这个默认行为经常把新手坑一把明明数据库里很多用户没有年龄前端不传age想查全部结果后端直接400。解决办法是设置非必填GetMapping(/list) public Result listUsers(RequestParam(required false) String name, RequestParam(required false) Integer age) { // 此时 name 和 age 都可能为 null业务层要做空值判断 }2.2 参数重命名、默认值以及 List 接收前端和后端对于同一个参数的命名经常有分歧比如前端叫userName后端习惯叫name。总不能为了这个去跟前端吵一架直接用RequestParam的value属性做映射GetMapping(/list) public Result listUsers(RequestParam(userName) String name) { // 前端传 ?userNamezhangsan绑定到 name 变量 }默认值也是一个被低估的功能。分页接口最常见的写法是GetMapping(/page) public Result pageUsers(RequestParam(value page, defaultValue 1) Integer page, RequestParam(value size, defaultValue 10) Integer size) { // 前端不传page和size时自动使用1和10 }defaultValue和required false的区别在于defaultValue设了之后前端不传参数会被解析成默认值前端传了就用前端传的值。而且required不会因为设了defaultValue就自动变true两者各管各的平时推荐用defaultValue来表达“有默认取值”的参数语义更清晰。还有一种情况是批量操作比如删除多个用户前端会传多个相同的key?ids1ids2ids3。后端接收时不用搞数组直接用ListGetMapping(/batch/delete) public Result batchDelete(RequestParam(ids) ListLong ids) { // ids [1, 2, 3] }Spring可以把查询串里同名的多个值自动组装进List前提是泛型类型能转换成功。这个特性在实现“批量”类接口时非常省事。2.3 不写RequestParam直接用POJO接收如果查询参数特别多比如筛选条件有七八个在方法签名里列七八个RequestParam会非常难看。Spring MVC支持直接用一个POJO来接收查询参数前提是参数名和POJO的属性名对得上Data public class UserQuery { private String name; private Integer age; private String city; private Integer status; } GetMapping(/search) public Result search(UserQuery query) { // GET /api/user/search?namezhangsanage18citybeijingstatus1 // Spring 自动把查询参数绑定到 query 对应属性上 }这里有个非常重要的细节POJO接收时方法参数上不能加RequestBody也不能加RequestParam——如果加了RequestParam UserQuery querySpring会把这个POJO当做一个单一的查询参数去解析结果大概率报错或者拿不到值。最佳实践是不加注解让Spring按数据绑定处理或者在Spring Boot里明确指定ModelAttribute两者效果一致。用POJO接收适合筛选列表这种参数多的场景但要注意属性类型转换失败时依然会400而且不会告诉你具体是哪个字段出了问题排查时只能自己加日志。POJO接收还有一个坑如果查询参数里有query.name这种带点号的怪名默认绑定是处理不了的需要自定义WebDataBinder不过正常工作里基本遇不到知道有这么回事就行。3. 路径参数RESTful 风格里的门牌号3.1 PathVariable 的基础用法路径参数跟查询参数最大的不同是数据直接在URL路径里。典型例子GET /api/user/1001表示获取id为1001的用户DELETE /api/user/1001表示删除这个用户。这种风格是RESTful API设计的核心因为它把操作对象直接嵌在资源路径中语义一目了然。GetMapping(/{id}) public Result getUser(PathVariable Long id) { // GET /api/user/1001 - id 1001 }代码里注意两点一是GetMapping(/{id})里的占位符{id}必须跟方法参数名一致否则要显式指定PathVariable(id) Long userId二是路径参数同样会做类型转换Long接收abc照样400。为什么推荐用路径参数表达资源id因为它把这个id跟“筛选条件”从语义上做了区分。/api/user/1001里1001是“用户”这个资源集合中的一个具体元素而/api/user?status1里的status是“在用户集合上做过滤”。混着用时用户读代码、前端对接、后端维护都能快速判断某个参数到底在描述资源还是过滤资源。3.2 一个接口接收多个路径参数遇到嵌套资源时路径参数就不止一个了。比如查询某个用户下的某个订单资源路径天然呈现层级关系GetMapping(/user/{userId}/order/{orderId}) public Result getOrder(PathVariable Long userId, PathVariable Long orderId) { // GET /api/user/1001/order/8888 // userId 1001, orderId 8888 }多个路径参数的关键在于URL模板设计和参数顺序前端只能按路径顺序传值不存在“少传一个”还能取巧的情况。这种接口往往还伴随一个隐藏的权限校验点要确认orderId确实属于userId否则越权访问。路径参数因为是URL的一部分容易出现在网关日志、浏览器历史里所以不要用路径参数传递敏感信息比如不要搞/api/order/{orderSecret}这种把秘钥放进URL的操作。3.3 路径参数与查询参数混用的标准姿势一个经典的混用场景是用路径参数定位具体资源用查询参数表达附加操作。例如获取用户详情的同时要求返回完整字段还是精简字段GetMapping(/{id}) public Result getUser(PathVariable Long id, RequestParam(value verbose, defaultValue false) Boolean verbose) { // GET /api/user/1001?verbosetrue // id 1001, verbose true }再比如获取某个用户的订单列表用户id是资源路径的一部分分页和状态筛选走查询参数GetMapping(/user/{userId}/orders) public Result listOrders(PathVariable Long userId, RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size, RequestParam(required false) Integer status) { // GET /api/user/1001/orders?page1size10status2 }这种划分非常自然路径参数回答“操作什么”查询参数回答“附带什么条件”。新手最容易犯的错是把所有东西都往路径里塞结果是URL模板管理混乱每个接口的路径规则都不一样前端对接时天天翻文档。4. JSON参数前后端分离时代的核心传参方式4.1 RequestBody 与 Spring Boot 的 JSON 解析原理前两种方式的数据都是扁平的键值对遇到复杂结构就力不从心了。比如创建一个用户要同时传基本信息、地址列表、角色信息查询参数和路径参数能把人写到崩溃。JSON参数就是来解决这个问题的前端把数据组织成JSON字符串放进请求体后端用一个对象接住。PostMapping(/create) public Result createUser(RequestBody UserCreateDTO dto) { // POST /api/user/create // Body: {name:zhangsan,age:18,address:{city:beijing},roles:[admin,user]} }对应的DTOData public class UserCreateDTO { private String name; private Integer age; private AddressDTO address; private ListString roles; }Spring Boot默认使用Jackson作为JSON序列化/反序列化工具。RequestBody的底层逻辑就是Spring读取请求体里的字符串交给ObjectMapper按照目标DTO的字段结构把JSON字符串解析成Java对象。这个过程中有几个默认行为要清楚字段匹配规则JSON里的key必须和DTO的属性名一致区分大小写不一致就不绑定但默认不会报错。未知字段处理JSON里有DTO不存在的字段时Jackson默认忽略不会抛异常所以前端多传字段一般没事。空值处理JSON里某个字段为null时DTO对应属性也是null不会报错。4.2 DTO 校验注解接口人门级的防御直接用RequestBody UserCreateDTO dto接收如果不对参数做任何校验name传null、age传-18接口都会照单全收。严谨的做法是配合Bean Validation做参数校验Data public class UserCreateDTO { NotBlank(message 用户名不能为空) private String name; NotNull(message 年龄不能为空) Min(value 1, message 年龄必须大于0) Max(value 150, message 年龄不能超过150) private Integer age; Email(message 邮箱格式不正确) private String email; }Controller方法上要把Validated或Valid加上PostMapping(/create) public Result createUser(RequestBody Validated UserCreateDTO dto) { // 校验不通过时Spring 抛出 MethodArgumentNotValidException }没有校验注解时非法数据会一路穿透到Service层甚至数据库层最终抛出的异常往往不是参数问题而是别的奇怪错误。用Validated之后框架会在进入方法体之前完成校验不合法直接返回400默认配合全局异常处理器还能输出更友好的错误提示。这里强烈建议每个接口的DTO都做好校验哪怕只有一个NotNull也能挡住大量低级问题。DTO的设计还有一个容易被忽略的习惯不要直接把实体类Entity作为RequestBody的接收对象。实体类的字段结构往往跟数据库表一一对应暴露给前端会泄露不该泄露的字段比如密码、内部状态而且后续数据库表结构一变接口就跟着变耦合太紧。正确做法是为接口单独设计DTO按需定义字段再在Service层完成DTO到实体的转换。4.3 RequestBody 与 RequestParam、PathVariable 的混用很多场景下一个接口既要“定位资源”又要“传修改内容”。比如更新某个用户的资料PutMapping(/user/{id}) public Result updateUser(PathVariable Long id, RequestBody UserUpdateDTO dto) { // PUT /api/user/1001 // Body: {name:lisi,age:20} }再比如分页查询加复杂筛选条件可以把筛选条件放到Body里分页信息放查询参数PostMapping(/search) public Result search(RequestBody SearchDTO search, RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size) { // POST /api/user/search?page1size10 // Body: {name:zhang,status:1} }这里要记住一个硬性规则一个接口只能有一个RequestBody参数。原因很直白——请求体只有一个不能绑给两个对象。想同时接收两个对象时要么把其中一个改放进查询参数要么在两个DTO外面再包一层组合DTO。别试图写两个RequestBodySpring会直接启动报错。另外要提醒的是前端用RequestBody对接时务必确认请求头里带了Content-Type: application/json。很多前端AJAX库比如axios传字符串时不自动设置JSON头导致后端读不到body或解析失败这就是第六部分要讲的415问题。5. 三种传参方式如何选型从资源语义到接口设计5.1 选型决策表什么场景用哪种很多团队接口风格混乱根源就是参数选型没有统一原则。做接口设计时先回答三个问题这个参数描述的是“哪个资源”是“什么操作”还是“怎么过滤和展示”回答完之后用下面这张表做决策参数类型典型场景数据位置适合承载的数据一句话原则查询参数GET列表、筛选、分页、排序URL问号后扁平键值对、筛选条件附加条件和展示控制放这里路径参数获取/删除/更新某个具体资源URL路径中资源唯一标识id、code操作哪个资源放这里JSON参数POST/PUT创建或更新复杂数据请求体嵌套对象、数组、批量数据复杂操作和写入数据放这里落到具体设计上GET /api/users用户列表、GET /api/users?status1page2size20筛选和分页用户、GET /api/users/{id}获取单个用户、POST /api/users创建用户body放用户详细信息、PUT /api/users/{id}更新用户body放更新字段。这四组接口是全行业最通用的REST风格写法照着设计前后端沟通成本能降一半。5.2 组件团队协作时的接口契约接口设计不只是后端的事。后端把PathVariable、RequestParam、RequestBody确定下来其实就是在定契约前端必须严格按这个契约传数。因此接口文档里必须写清每个参数的位置。比如路径参数写在URL路径中如/api/order/{orderId}查询参数URL问号后如?page1size10请求体JSON如{productId:1,count:2}我见过最混乱的项目同一个用户接口更新用POST查询参数获取详情用GET路径参数删除用DELETE请求体一套接口三种风格。维护到后期前端每次联调都要翻代码确认参数位置。所以团队内要约定一个param风格规范资源定位用路径参数条件控制用查询参数复杂写入用JSON参数别混成一锅粥。5.3 面试高频考点这三个注解的区别作为SpringBoot面试题里的常客很多人会背结论但一到场景题就歇菜。面试官最常问的几类问题RequestParam和PathVariable的区别——位置不同一个在问号后一个在路径里。一个接口能有两个RequestBody吗——不能请求体只有一个。RequestParam的required和defaultValue的区别——required管要不要传defaultValue管不传时给什么值。只写POJO参数不写注解Spring怎么处理——按数据绑定处理从查询参数绑定到POJO属性。前端传Content-Type: text/plain后端RequestBody能收到吗——收不到JSON会报415。把原理和场景串起来面试题基本难不倒。特别是“三种数据位置”这个底层模型理解了之后任何变体问题都能现场推出来。6. 新手避坑实录从400到415的完整排查链路6.1 排查链路第一步先看请求长什么样收到“接口报错”的第一反应不应该去翻代码而是打开浏览器的Network面板或者后端日志里打印的请求信息看三件事HTTP方法、URL、请求头和请求体。我处理过的90%的传参问题在这三步里就能定位到根因。一个典型的错误请求长这样POST /api/user/create?namezhangsanage18 Content-Type: application/json后端接口是PostMapping(/create) public Result createUser(RequestBody UserCreateDTO dto)结果后端收到的dto是null或者直接415/400。原因一目了然参数name和age放在了URL的查询串里但后端用RequestBody去请求体里找JSON请求体是空的自然绑不上。这种问题在校验时就是“用户名不能为空”之类的必填校验失败。新手常以为“我传了参数啊”问题是你传的位置和后端接收的位置压根不一致。6.2 415 Unsupported Media TypeContent-Type 不匹配415错误是JSON传参最常见的报错之一场景几乎固定后端用RequestBody接收但前端发请求时Content-Type要么没设置要么设成了text/plain或application/x-www-form-urlencoded导致Spring找不到能解析请求体的消息转换器。排查方法很简单在Network面板看请求头确认Content-Type是否是application/json。如果前端用的是axios常见错误是axios.post(/api/user/create, JSON.stringify(data), { headers: { Content-Type: application/json } });这里必须JSON.stringify(data)且明确指定JSON头。有些封装库不序列化会自动转成form表单格式后端RequestBody一脸懵。后端侧可以在全局异常处理器里把HttpMediaTypeNotSupportedException单独catch住返回更友好的提示“请求Content-Type必须是application/json”。这个处理能省掉大量联调时的扯皮。6.3 400 Bad Request类型不匹配与JSON结构错误在排除了415之后400是第二常见的错误。400通常是参数解析或绑定失败常见诱因有三个。第一个是查询参数类型转换失败后端写RequestParam Integer age前端传?ageabcSpring把abc转成Integer失败直接400。这种在日志里能看到MethodArgumentTypeMismatchException。第二个是路径参数类型转换失败PathVariable Long id请求路径却是/api/user/abc同样400。可以在全局异常处理器对这类异常做统一文案提示“参数类型不正确”。第三个是JSON结构不匹配前端传的JSON里age字段给的是十八这种字符串但DTO里age是IntegerJackson反序列化失败也会400报HttpMessageNotReadableException。这类错误最隐蔽因为前端调试工具里看JSON格式没问题但类型却对应不上。排查时优先在后端日志定位异常类名再用上面的异常类型对照表快速归因异常类型常见原因处理位置MethodArgumentTypeMismatchException查询/路径参数类型转换失败全局异常处理器统一拦截HttpMessageNotReadableExceptionJSON解析失败、字段类型不匹配、JSON格式错误全局异常处理器统一拦截MethodArgumentNotValidExceptionValidated 校验不通过全局异常处理器统一拦截HttpMediaTypeNotSupportedExceptionContent-Type 不支持全局异常处理器统一拦截NoHandlerFoundException路径匹配不上404检查XxxMapping的路径模板6.4 POST请求中文乱码字符编码问题有段时间我的接口接收中文用户名全是乱码排查了很久发现是请求体里的中文在解析时用了错误的字符集。Spring Boot 2.x默使用UTF-8处理请求体但偶尔会遇到老项目里代码手动调用request.getParameter()、或者前端没有指定charsetUTF-8的情况。在新项目里建议直接用server.servlet.encoding.force-responsetrue之类的配置把编码锁死为UTF-8避免不同环境行为不一致。6.5 一个模拟演练
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。