SpringBoot接收前端请求的参数方式

在 SpringBoot 中,后端接收前端请求的过程涉及请求类型、参数位置、数据格式等多个维度,核心是通过 Spring MVC 的参数绑定机制,将前端传递的数据自动映射到后端方法的参数中。以下从请求方式、参数位置、数据格式三个角度详细说明接收逻辑,并结合示例分析。

一、先明确前端请求的核心要素

前端发送请求时,后端需要关注的关键信息:

  1. 请求方式:GET、POST、PUT、DELETE 等(决定参数传递的默认位置)。

  2. 参数位置:URL 路径(如 /user/123)、URL 查询参数(如 ?name=xxx)、请求体(Body,如 JSON / 表单数据)。

  3. 数据格式

    • 表单默认格式:application/x-www-form-urlencoded(键值对,如 name=xxx&age=18)。

    • 表单文件格式:multipart/form-data(用于上传文件,可包含键值对 + 文件)。

    • JSON 格式:application/json(主流,适合复杂数据结构)。

      text
      复制代码
      { "username": "zhangsan", "password": "123456", "age": 20 }
    • XML 格式:application/xml(较少用)。

      text
      复制代码
      <class name="一班"> <students> <student> <name>张三</name> <age>18</age> </student> <student> <name>李四</name> <age>19</age> </student> </students> </class>

二、按参数位置分类:接收不同位置的参数

1. 接收 URL 路径中的参数(@PathVariable

适用于 RESTful 风格接口,参数直接嵌入 URL 路径中(如 /user/{id}),需用 @PathVariable 注解绑定。

示例

  • 前端请求:GET /user/123/detail?type=simple(路径中的 123 是参数)

  • 后端接收:

    java
    复制代码
    @GetMapping("/user/{userId}/detail") public String getUserDetail( @PathVariable("userId") Integer id, // 绑定路径中的 userId,映射到参数 id @RequestParam String type // 顺便接收查询参数 type ) { return "用户ID:" + id + ",类型:" + type; }
  • 关键点:

    • 路径中的占位符 {userId} 必须与 @PathVariable("userId") 名称一致,若参数名与占位符相同,可省略括号内的名称(如 @PathVariable Integer userId)。
    • 支持多个路径参数(如 /user/{id}/order/{orderId})。

2. 接收 URL 查询参数(@RequestParam

参数以 ?key=value&key2=value2 形式拼接在 URL 后,常见于 GET 请求,也可用于 POST 等请求。需用 @RequestParam 注解(参数名匹配时可省略)。

示例

  • 前端请求:GET /search?name=张三&page=1&size=10

  • 后端接收:

    java
    复制代码
    @GetMapping("/search") public String search( @RequestParam(required = false) String name, // required=false:可选参数(默认 true) @RequestParam(defaultValue = "1") Integer page, // 默认值 Integer size // 省略 @RequestParam,自动匹配参数名 size ) { return "查询:" + name + ",页码:" + page + ",条数:" + size; }

    @RequestParam: value/name:指定前端传递的参数名(解决前后端参数名不一致问题)。 示例:前端传 user_name,后端参数名是 username

    text
    复制代码
    @GetMapping("/user") public String getUser(@RequestParam("user_name") String username) { return "用户名:" + username; // 正确接收 user_name 的值 }
  • 关键点:

    • 若前端传递的参数名与后端参数名不一致,需用 @RequestParam("前端参数名") 映射(如前端传 user_name,后端用 @RequestParam("user_name") String userName)。
    • 支持数组 / 集合(如前端传 ids=1&ids=2,后端用 @RequestParam List<Integer> ids 接收)。

3. 接收请求体(Body)中的参数

参数放在请求体中,常见于 POST、PUT 等请求,根据数据格式不同,接收方式不同。

(1)JSON 格式(application/json):必须用 @RequestBody

前端传递 JSON 字符串(适合复杂对象、嵌套结构),后端用 @RequestBody 将 JSON 自动映射为 Java 对象。

示例

  • 前端请求:POST /user/save,请求体为 JSON:

    json
    复制代码
    { "name": "张三", "age": 20, "address": { "city": "北京", "street": "长安街" } }
  • 后端定义实体类:

    java
    复制代码
    @Data // Lombok 注解,自动生成 getter/setter class User { private String name; private Integer age; private Address address; // 嵌套对象 } @Data class Address { private String city; private String street; }
  • 后端接口接收:

    java
    复制代码
    @PostMapping("/user/save") public String saveUser(@RequestBody User user) { // 自动映射 JSON 到 User 对象 return "保存用户:" + user.getName() + ",城市:" + user.getAddress().getCity(); }
  • 关键点:

    • JSON 字段名必须与 Java 对象属性名一致(大小写敏感),不一致可通过 @JsonProperty 注解映射(如 JSON 是 user_name,属性用 @JsonProperty("user_name") String userName)。
    • 支持 JSON 数组(如 [1,2,3],后端用 @RequestBody List<Integer> 接收)。
(2)表单格式(application/x-www-form-urlencoded):无需 @RequestBody

前端传递键值对(如表单提交),参数在请求体中但格式为 name=xxx&age=18,后端可直接绑定到参数或对象(无需注解)。

示例

  • 前端请求:POST /user/login,请求体为 username=zhangsan&password=123(Content-Type: application/x-www-form-urlencoded)

  • 后端接收:

    java
    复制代码
    // 方式1:绑定到单个参数 @PostMapping("/user/login") public String login(String username, String password) { return "用户名:" + username + ",密码:" + password; } // 方式2:绑定到对象(属性名与参数名一致) @PostMapping("/user/login") public String login(UserLoginDTO dto) { // dto 包含 username 和 password 属性 return "用户名:" + dto.getUsername(); }
  • 关键点:

    • 若用 @RequestBody 接收该格式,会报错(@RequestBody 仅处理 JSON/XML 等格式)。
(3)文件上传格式(multipart/form-data):@RequestParam + MultipartFile

用于上传文件,同时可包含普通键值对参数,后端用 MultipartFile 接收文件,普通参数用 @RequestParam 接收。

示例

  • 前端请求:POST /file/upload,Content-Type: multipart/form-data,包含:

    • 普通参数:desc=用户头像
    • 文件:file(选择的图片文件)
  • 后端接收:

    java
    复制代码
    @PostMapping("/file/upload") public String uploadFile( @RequestParam String desc, // 普通参数 @RequestParam("file") MultipartFile file // 文件参数 ) throws IOException { String filename = file.getOriginalFilename(); // 获取文件名 file.transferTo(new File("D:/" + filename)); // 保存文件 return "上传成功:" + desc + ",文件名:" + filename; }
  • 关键点:

    • 若上传多个文件,用 List<MultipartFile> 接收(如 @RequestParam List<MultipartFile> files)。

    • 需在配置类中设置文件大小限制(默认有限制):yaml

      yaml
      复制代码
      spring: servlet: multipart: max-file-size: 10MB # 单个文件大小 max-request-size: 100MB # 总请求大小

三、特殊场景:参数绑定的进阶用法

1. 接收请求头参数(@RequestHeader

获取请求头中的信息(如 tokenUser-Agent 等)。

示例

java
复制代码
@GetMapping("/header") public String getHeader( @RequestHeader("token") String token, // 获取 token 头 @RequestHeader("User-Agent") String userAgent ) { return "token:" + token + ",浏览器:" + userAgent; }

2. 接收 Cookie 参数(@CookieValue

获取请求中的 Cookie 值。

示例

java
复制代码
@GetMapping("/cookie") public String getCookie(@CookieValue("sessionId") String sessionId) { return "SessionID:" + sessionId; }

3. 自动绑定复杂对象(含嵌套 + 数组)

对于表单或查询参数中的复杂结构(如数组、嵌套对象),Spring 会自动映射到对应的 Java 对象。

示例

  • 前端请求:GET /query?name=张三&hobbies=篮球&hobbies=足球&address.city=北京

  • 后端实体类:

    java
    复制代码
    @Data class QueryDTO { private String name; private List<String> hobbies; // 数组参数 private Address address; // 嵌套对象(address.city 对应参数) }
  • 后端接口:

    java
    复制代码
    @GetMapping("/query") public String query(QueryDTO dto) { // 自动绑定所有参数 return "爱好:" + dto.getHobbies() + ",城市:" + dto.getAddress().getCity(); }

四、核心原理:Spring 的参数解析机制

SpringBoot 能自动接收参数,本质是通过 HandlerMethodArgumentResolver(方法参数解析器) 完成的:

  1. 当请求到达后端,DispatcherServlet 找到对应的 Controller 方法。
  2. 遍历该方法的参数,根据参数类型、注解(如 @RequestBody@PathVariable)匹配对应的解析器。
  3. 解析器从请求中提取数据(路径、查询参数、请求体等),转换为参数所需的类型(如 JSON → 对象、字符串 → 整数)。
  4. 将转换后的值注入方法参数,执行方法。

总结

后端接收前端请求的核心是明确参数位置和格式,对应使用正确的注解和类型:

  • URL 路径参数@PathVariable
  • URL 查询参数 / 表单键值对@RequestParam(可省略)或直接绑定对象
  • JSON 格式请求体@RequestBody + 实体类
  • 文件上传@RequestParam + MultipartFile
  • 请求头 / Cookie@RequestHeader / @CookieValue
0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
X
作者分享
Day 106 ✅ 今天做了: 1. 背单词√ 2. 面试题:√ 总结: 单词复习: 认识:69,模糊:21,忘记:24,顽固:3 问题: ArrayBlockingQueue 与 LinkedBlockingQueue 在并发性能和锁机制上有何区别?ArrayBlockingQueue 用单锁,生产与消费互斥,适合固定容量、需公平控制的场景;LinkedBlockingQueue 用双锁分离入队/出队,可并行操作,吞吐量更高,但默认无界有 OOM 风险。 线程池内任务调度时 BlockingQueue 的作用是什么?在线程池中,BlockingQueue 用于缓冲待执行任务,并在生产者(提交任务的线程)与消费者(工作线程)间做流量控制与同步。队列满时阻塞提交,空时阻塞取任务,从而避免忙等和资源过载,是线程池任务调度的核心缓冲与协调机制。 使用 put 方法与 offer 方法处理队列满时有何不同表现?put 会无限期阻塞生产者线程直至有空间,offer 则立即返回 false 不阻塞。前者用于强一致、必须入队的场景,后者适用于需快速失败或可降级的流控场景。 自动装箱/拆箱发生的常见场景:1.赋值2.方法调用3.算术运算4.集合操作 自动装箱/拆箱陷阱:NullPointerException、性能开销、== 运算符在用于对象时,比较的是内存地址;而 .equals() 方法(如果被正确重写)比较的是对象的内容(值)。Integer 缓存:默认情况下,对于 -128 到 127 之间的整数,通过自动装箱(或 Integer.valueOf())创建的 Integer 对象会被缓存。如果值在这个范围内,你会得到指向同一个缓存对象的引用。 int与Integer的区别在以下几点:存储的位置,性能,默认值,类型,比较方式,使用场景 默认的 equals() 方法比较的是两个对象的内存地址,即判断它们是否为同一个对象的引用。这被称为“引用相等性” equals() 方法的重写协定:1.自反性:一个对象必须等于它自己。2.对称性3.一致性:只要对象的状态不变,equals() 的结果就不应该改变。4.传递性5.非空性:任何对象都不等于 null。 写了 equals() 方法,那么你必须重写 hashCode() 方法,hashCode() 用于为对象生成一个整数“哈希码”。 - 如果两个对象根据 equals() 相同,hashCode也必须相同 - 如果两个对象根据 equals() 方法是不相等的,它们的 hashCode() 不一定相同,但最好不相同,提高哈希表的性能。 如果没有重写hashCode会导致,本来两个内容一样的对象会导致hashCode不同,在放到hashMap时,会分配到不同的桶,会导致找不到原先的对象。 重写equals:1先==比较两个对象是否是同一个引用,2检查参数是否为 null,以及类型是否匹配,使用 getClass() 而不是 instanceof 可以确保比较的对象是完全相同的类3.将参数转换为正确的类型,对用到的关键字进行比较。 hashCode重写:使用Objects.hash(Object... values),传入的参数是equals里用到的所有字段。 如果你使用的是 Java 14 或更高版本,使用这个public record PersonRecord(String name, int age) {}
3
Day 105(昨天的打卡) ✅ 今天做了: 1. 背单词√ 2. 面试题:√ 总结: 单词复习: 认识:75,模糊:27,忘记:23,顽固:3 问题: 受检测异常:Java编译器检查这类异常,需要在方法签名中使用throws或者使用try-cath捕获,比如IOException 非受检测异常:主要是程序逻辑上的问题,比如类型转换失败。 finally:无论是否发生异常,都保证会被执行。 但存在问题:异常覆盖:catch 块中已经抛出了一个异常,而 finally 块在执行清理时又抛出了一个新的异常,JVM 会丢弃原始的异常,只向上抛出 finally 中的异常。破坏正常的返回逻辑:finally 块中使用了 return 语句,它会直接导致方法结束,并覆盖掉 try 或 catch 块中原本要返回的值或要抛出的异常。并非绝对可靠。 throw:代码中主动抛出异常对象,通常是自己定义的业务逻辑 throws:用于方法签名上,主要是告诉调用者这个方法会抛出的异常,调用者可以try-catch或者继续向上抛。 只捕获你能处理的异常,不要“吞掉”异常,使用 finally 或 try-with-resources 释放资源,为异常提供有意义的信息在抛出异常时,附带清晰的描述性消息,异常链当捕获一个底层异常并抛出一个新的高层异常时,应将原始异常(cause)包装进去。这保留了完整的堆栈跟踪信息,便于排查问题。优先使用非受检异常处理编程错误,不要用异常来控制程序流程 Java的反射机制,就是程序在运行期间,可以像照镜子一样,动态地获取任何一个类的信息(它的属性、方法、构造函数等),并且能够动态地创建对象、调用方法、修改属性。它赋予了Java一种“自我剖析”和“自我操作”的能力。 获取Class对象有三种主要方式: 通过对象获取:anyObject.getClass() 通过类名获取:ClassName.class 通过类的全限定名获取:Class.forName("com.example.MyClass") 反射的优点: 框架设计的灵魂:Spring/SprintBoot的IoC控制反转(不需要自己去new对象由容器去创建对象) 动态代理:在运行时创建一个实现了指定接口的代理类,用来增强原始类的功能(比如添加日志、事务管理),这是AOP(面向切面编程)的基础。 工具和IDE:比如你用的Eclipse/IntelliJ IDEA,它的代码提示、自动补全、调试器等功能,都需要通过反射来分析你写的类。 注解处理器:需要在运行时被读取和处理,这也要靠反射。 缺点:性能开销大,破坏封装、类型不安全、代码可读性差,维护困难。 阻塞队列:生产者生产数据时,没有地方放了会阻塞,消费者拿数据时没有数据会阻塞。BlockingQueue 自动地协调了生产者和消费者之间的速度差异 ArrayBlockingQueue: - 由数组支持的有界阻塞队列。创建时必须指定容量,且容量不可变。 - 公平/非公平:可以通过构造函数 new ArrayBlockingQueue(capacity, fair) 设置公平性。公平模式下,等待的线程按 FIFO 顺序访问队列;非公平模式下,可能存在插队,吞吐量通常更高。 - 性能:内部使用一个锁(ReentrantLock)来控制生产者和消费者的访问,意味着两者不能同时操作。 LinkedBlockingQueue: - 底层结构:由链表支持的阻塞队列。 - 特点: - 可选有界:可以指定容量,也可以不指定。如果不指定,默认容量是 Integer.MAX_VALUE,相当于一个无界队列。 - 无界时的风险:如果生产者速度远快于消费者,可能导致内存耗尽(OOM)。 - 性能:内部使用两个锁(putLock 和 takeLock),一个用于入队,一个用于出队。这种“读写分离”的设计使得生产者和消费者可以并行操作,在高并发场景下吞吐量通常高于 ArrayBlockingQueue。 PriorityBlockingQueue: - 底层结构:一个支持优先级的无界阻塞队列。 - 特点: - 无界 (Unbounded):队列容量没有限制。 - 优先级排序:存入的元素必须实现 Comparable 接口,或者在构造时传入 Comparator。队列会根据元素的优先级进行排序,优先级高的元素先出队。 - 它不保证 FIFO,而是保证每次 take 出来的都是当前队列中优先级最高的元素。 SynchronousQueue: - 底层结构:一个不存储元素的阻塞队列,容量为 0。 - 特点: - 容量为零:它没有任何内部容量来缓存数据。 - 直接传递 (Hand-off):每个 put 操作必须等待一个对应的 take 操作,反之亦然。它更像一个“一手交钱,一手交货”的交易场所,而不是一个仓库。 - 高吞吐量:非常适合传递性场景,因为避免了数据在队列中的存储和管理开销。Java 的 Executors.newCachedThreadPool() 线程池就使用了它。 DelayQueue: - 底层结构:一个支持延时获取元素的无界阻塞队列。 - 特点: - 延时:队列中的元素只有在其指定的延迟时间到了之后,才能被消费者 take 出来。 - 元素类型:存入的元素必须实现 Delayed 接口(该接口又继承了 Comparable)。 - 应用场景:非常适合实现定时任务、缓存过期等功能。 应用场景:线程池,消息中间件,系统日志,消费者生产者
3
Day 104 ✅ 今天做了: 1. 背单词√ 2. 面试题:√ 总结: 单词复习: 认识:48,模糊:36,忘记:26,顽固:11 问题: String线程安全是对于只读取来说,如果A线程修改了str的引用,B线程访问的是修改引用后的str。 字符串常量池依赖不可变性的原因是什么?str1 与str2引用的都是hello,如果是可变的,str1修改为www,str2也是www,本来要用hello结果导致混乱。 EnumSet:要求枚举值来自同一个枚举类 EnumMap:以枚举类型作为key 任何时候,当你需要表示一个固定的、有限的常量集合时,都应该优先考虑使用枚举。 为什么要使用枚举:1.类型安全 2.代码清晰,可读性强 3.功能强大 4.易于维护 5.与集合框架无缝集成 枚举如何实现接口并支持不同常量的独立行为?在常量后跟{}写匿名内部类覆写接口的方法来实现接口,对于每个常量来说都是独立的子类,调用方法的时候按照多态来执行。所以会有不同常量的独立行为。同理抽象方法也是的。
2
Day 103 ✅ 今天做了: 1. 背单词√ 2. 面试题:√ 3. 智能云图库-团队空间管理 总结: 单词复习: 认识:61,模糊:33,忘记:16,顽固:2 问题: 匿名内部类与普通内部类的区别:匿名内部类只能在定义处一次性使用,没办法复用,因为匿名内部类无构造方法,无类名导致的。 匿名内部类没有构造方法也没有可以替代的方法:使用实例初始化块在对象创建时完成字段赋值与初始化逻辑 匿名内部类访问局部变量:要求局部变量为final或事实上是final,因为局部变量在栈中,当用完后会被销毁,但内部类是在堆中,可能内部类还存在,为了能继续引用,JVM 在编译期将变量值拷贝进内部类,并在字节码层面添加合成字段持有该副本。若变量可变,则副本与原始值可能不一致,破坏闭包语义。 什么情况下只能用匿名内部类而无法用 Lambda 表达式?需继承类(含抽象类)、实现多抽象方法接口、内部需定义实例字段或多个方法,或对 this 指向自身实例有强依赖。因为 Lambda 只能针对单抽象方法接口且无状态,this 指向外部类,无法满足这些结构与语义需求。 同一类被不同加载器加载会造成什么问题?同一类被不同类加载器加载会导致JVM视为两个不同类,引发类型不匹配、ClassCastException及逻辑错误。双亲委派模型通过统一委派至父加载器避免此问题,确保类全局唯一性与核心库安全。 Tomcat如何实现不同Web应用间的类库隔离?Tomcat通过为每个Web应用创建独立的WebAppClassLoader,并重写loadCla ss()方法打破Java双亲委派模型,实现类库隔离。其核心是先自加载再委派,让不同应用可各自使用私有版本的依赖,避免版本冲突。 非静态内部类为何不能定义非 final 的静态成员?因为其内部类实例天然绑定外部类实例,而静态成员属于类、与实例无关,两者在设计语义上冲突。Java 为避免“既属类又属实例”的逻辑混乱,直接禁止此类定义,仅允许 static final 编译时常量。仅 static final 编译时常量被允许,因其值在编译期确定并可内联,不触发运行时类加载/初始化矛盾,避免了语义与实现上的不一致。 创建非静态内部类实例时必须依赖外部类实例的原因是什么?非静态内部类对象隐式持有外部类实例引用,创建时必须通过 outerInstance.new Inner(),生命周期与外部实例绑定。
2
Day 102 ✅ 今天做了: 1. 背单词√ 2. 面试题:√ 总结: 单词复习: 认识:50,模糊:25,忘记:35,顽固:5 问题: 浅拷贝与深拷贝:主要区别在于,对象内部的成员,如果存在引用类型,那浅拷贝会导致原对象以及拷贝的对象使用的引用数据是同一份(拷贝的是地址),深拷贝是对这段引用类型的数据也完全拷贝过来。 实现深拷贝的方法:1.拷贝构造函数 2.序列化 3. 重写clone方法 少量修改 -> String 单线程大量修改 -> StringBuilder 多线程大量修改 -> StringBuffer (谨慎使用) 一个类里面,有两个方法,这两个方法都用当前这个类的静态锁,那多线程的时候,是依次还是并发执行? 使用隐式锁即 synchronized 直接加在方法上: - 如果是同一个对象实例:它们使用的是同一把实例锁(this),所以是依次执行(互斥)的。 - 如果是不同的对象实例:每个对象都有自己独立的 this 锁,所以它们之间可以并发执行。 使用的是显式锁(如 ReentrantLock): - 如果是同一个对象实例:两个方法竞争的是同一个 lock 对象,所以是依次执行(互斥)的。 - 如果是不同的对象实例:每个对象都有自己独立的 lock 成员变量,所以它们之间可以并发执行。 使用了特定的静态锁对象: - 无论是不是同一个对象实例,因为锁是 static 的(全局唯一),所以它们始终是依次执行(互斥)的。 耦合与解耦: 耦合 (Coupling):模块之间“绑得有多紧”。 解耦 (Decoupling):把绑得紧的模块“松开”,让它们各自独立。
3
下载 APP