SpringMVC 请求数据绑定与参数映射 详解

目录

[一、使用 @RequestParam 解决URL参数不一致的问题](#一、使用 @RequestParam 解决URL参数不一致的问题)

1.@RequestParam注解介绍:

2.实例:

[二、使用 @RequestHeader 获取Http请求消息头](#二、使用 @RequestHeader 获取Http请求消息头)

[1.@RequestHeader 注解介绍:](#1.@RequestHeader 注解介绍:)

2.实例:

三、表单提交的数据自动封装为JavaBean

1.自动封装的原理:

2.实例:

Δ总结


一、使用 @RequestParam 解决URL参数不一致的问题

1.@RequestParam注解介绍:

在 SpringMVC 的默认机制下,控制器(Handler)方法的 形参名 必须与请求 URL 中的 参数名 完全一致,这样程序才能准确完成数据绑定。但在实际开发中,我们经常会遇到参数名不匹配的情况。例如:前端提交的 URL 是 .../test?user_name=Cyan,而你在后端定义的方法形参是 String username。此时,SpringMVC 无法自动识别,就会导致 username 接收到的值为 null,这当然不是我们希望的结果。

**@RequestParam 注解的作用就是建立起"请求参数"与"方法形参"之间的映射关系。**它主要包含三个重要属性:

1) value / name :指定请求参数的名称。通过设置此属性,我们可以强制将 URL 中的 user_name 绑定到后端的 username 形参上。
2) required :布尔值,默认为 true, 表示请求中必须包含该参数,如果没有,系统就会抛出 400 错误。如果我们手动将其设置为 false,就表示在收到的Http请求中,这个参数可有可无。
3) defaultValue:默认值,意思就是说如果请求中没有携带该参数,则使用给定的默认值,这在处理分页参数(如 pageNo)时很有用。

2.实例:

需求:现在有一个处理 "Phone" 的 PhoneHandler类,类中定义了一个 searchPhone 方法,该方法的形参名是 phoneId, 所以这个方法是用于查询指定 phoneId 的手机。但是如果浏览器访问的 URL 中提交的参数名不是 phoneId,比如 URL 中提交的参数名是 id,我们该如何处理?

PhoneHandler类代码如下:

java 复制代码
package com.cyan.web.requestparam;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * @author : Cyan_RA9
 * @version : 23.0
 */
@RequestMapping(value = "/phone")
@Controller
public class PhoneHandler {

    @GetMapping(value = "/search")
    public String searchPhone(@RequestParam(value = "id", required = false) Integer phoneId) {
        /*
            1) @RequestParam 注解 表示会接收提交的参数;
            2) value = "id", 表示提交的参数名是"id"
            3) required = false, 表示这个参数可以不出现在请求的URL中. 默认是true
         */

        System.out.println("查询 phoneId = " + phoneId + " 的手机~");

        return "success";
    }
}

PhoneHandler 的 searchPhone方法 return 的 "success" 就是我们之前编写的 success.jsp 页面,代码也很简单,如下:

html 复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>原神牛逼</title>
</head>
<body>
    <h2>Operation Successful!</h2>
</body>
</html>

再来编写一个简单的 JSP 页面,requestparam.jsp 代码如下:(通过超链接的形式发出 GET 请求)

html 复制代码
<%--
    User : Cyan_RA9
    Version : 24.0
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>原神不牛逼</title>
</head>
<body>
    <h2>捕获下面这个超链接的请求参数</h2>
    <a href="phone/search?id=141">我是超链接, 快点我</a>
</body>
</html>

测试效果如下 GIF图所示:


二、使用 @RequestHeader 获取Http请求消息头

1.@RequestHeader 注解介绍:

HTTP 请求不仅仅包含 URL 参数和表单数据,它的 "消息头(Request Header)"中还隐藏着丰富的元数据信息,比如 User-Agent:告诉服务器浏览器的类型及操作系统;Accept-Language:告知服务器客户端支持的语言;Cookie:携带本地存储的会话信息。

在传统的 Servlet 开发中,我们需要通过 request.getHeader("HeaderName") 手动获取这些消息头。而在 SpringMVC 中,我们可以直接使用 @RequestHeader 注解,直接在 Handler 方法的形参上声明并获取这些信息。通过 @RequestHeader 注解,我们可以非常方便地实现根据浏览器类型做一些 逻辑跳转、多语言国际化处理 或 权限校验等功能。而且,@RequestHeader 注解同样支持 required 和 defaultValue 属性,以保证程序的健壮性。

2.实例:

需求:在上面的 PhoneHandler类中 新定义一个 searchHeaders 方法,专门用来获取Http请求头。

PhoneHandler类代码如下:

java 复制代码
package com.cyan.web.requestparam;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * @author : Cyan_RA9
 * @version : 23.0
 */
@RequestMapping(value = "/phone")
@Controller
public class PhoneHandler {

    @GetMapping(value = "/search")
    public String searchPhone(@RequestParam(value = "id", required = false) Integer phoneId) {
        /*
            1) @RequestParam 注解 表示会接收提交的参数;
            2) value = "id", 表示提交的参数名是"id"
            3) required = false, 表示这个参数可以不出现在请求的URL中. 默认是true
         */

        System.out.println("查询 phoneId = " + phoneId + " 的手机~");

        return "success";
    }

    @GetMapping(value = "/searchEX")
    public String searchHeaders(@RequestHeader("Host") String host,
                           @RequestHeader("Connection") String connection,
                           @RequestHeader(value = "Content-Type", required = false) String contentType) {

        System.out.println("Host = " + host);
        System.out.println("Connection = " + connection);
        System.out.println("Content-Type = " + contentType);

        return "success";
    }
}

我们可以直接在浏览器地址栏,进行测试,如下图所示:

运行结果如下:


三、表单提交的数据自动封装为JavaBean

1.自动封装的原理:

在 SpringMVC 中,有一个非常强大的特性:POJO(Plain Old Java Object)自动绑定 。 也就是说,我们只需在 Handler 方法的形参上直接定义一个 JavaBean(例如 User 对象),当表单提交时,SpringMVC 底层会自动实例化这个类,并将请求参数填充进去,自动完成数据的封装

但是 SpringMVC 是如何做到这一点的?

1) 反射机制 :SpringMVC 的参数解析器(如 ServletModelAttributeMethodProcessor)首先通过反射调用 JavaBean 的无参构造器 ,在内存中创建一个空的 POJO 实例。
2) 调用 Setter :接着,SpringMVC 会遍历请求中的所有参数,寻找与参数名同名 的属性。它的判断依据并不是成员变量名,而是 JavaBean 的 Setter 方法名 (符合 Java 规范)。比方说:请求参数名为 username,它就会寻找并调用 setUsername() 方法进行赋值。

3) 类型转换:在赋值过程中,SpringMVC 内部的 DataBinder(数据绑定器)会自动完成类型转换(如将字符串 "18" 转换为 int类型的 18)。

在复杂的业务场景下,我们的 JavaBean 可能还嵌套了另一个对象。例如 Buyer 类中有一个 Phone 属性,而 Phone 类里也有自己的字段。这时候的属性注入就是"++级联属性注入++ "。

要实现这种级联属性的自动注入,操作非常简单: 首先是封装的对象 以及 该对象的属性对应的类 都满足JavaBean 规范。然后,在表单的 name 属性中使用 "点(.)" 路径语法即可,示例:<input type="text" name="phone.name">,SpringMVC 在解析时,会先通过 Buyer 类的 getPhone() 获取 Phone对象(如果为 null 则自动创建),然后再调用 phone.setName() 完成注入。

2.实例:

需求:编写一个 Phone 类用于模拟手机类,Buyer 类用于模拟买手机的人。然后在 PhoneHandler 中新增一个 findPhoneBuyer 方法,用来打印 Buyer 类对象。我们的目标是在前端的 form 表单中提交数据,数据会被自动封装到 findPhoneBuyer 方法形参列表定义的 Buyer类对象中,然后我们直接打印对象,查看表单传入的数据是否封装成功。

首先我们来编写 JavaBean 类。up 把这两个 JavaBean 类都放到 pojo包下,如下图所示:

Phone 类代码如下:

java 复制代码
package com.cyan.web.pojo;

/**
 * @author : Cyan_RA9
 * @version : 23.0
 */
public class Phone {
    private Integer id;
    private String name;

    public Phone() {}
    public Phone(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Phone{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

Buyer 类代码如下:(Δ注意 Buyer 类中有一个级联属性)

java 复制代码
package com.cyan.web.pojo;

/**
 * @author : Cyan_RA9
 * @version : 23.0
 */
public class Buyer {
    private Integer id;
    private String name;
    private Phone phone;

    public Buyer() {}
    public Buyer(Integer id, String name, Phone phone) {
        this.id = id;
        this.name = name;
        this.phone = phone;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Phone getPhone() {
        return phone;
    }

    public void setPhone(Phone phone) {
        this.phone = phone;
    }

    @Override
    public String toString() {
        return "Buyer{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", phone=" + phone +
                '}';
    }
}

PhoneHandler类代码如下:(新定义了一个 findPhoneBuyer方法)

java 复制代码
package com.cyan.web.requestparam;

import com.cyan.web.pojo.Buyer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

/**
 * @author : Cyan_RA9
 * @version : 23.0
 */
@RequestMapping(value = "/phone")
@Controller
public class PhoneHandler {

    @GetMapping(value = "/search")
    public String searchPhone(@RequestParam(value = "id", required = false) Integer phoneId) {
        /*
            1) @RequestParam 注解 表示会接收提交的参数;
            2) value = "id", 表示提交的参数名是"id"
            3) required = false, 表示这个参数可以不出现在请求的URL中. 默认是true
         */

        System.out.println("查询 phoneId = " + phoneId + " 的手机~");

        return "success";
    }

    @GetMapping(value = "/searchEX")
    public String searchHeaders(@RequestHeader("Host") String host,
                           @RequestHeader("Connection") String connection,
                           @RequestHeader(value = "Content-Type", required = false) String contentType) {

        System.out.println("Host = " + host);
        System.out.println("Connection = " + connection);
        System.out.println("Content-Type = " + contentType);

        return "success";
    }

    /**
     * 表单提交数据 -> 自动封装为POJO类
     * 1) 只要在方法的形参直接声明对应的类型, SpringMVC 底层会对传入的数据进行自动封装
     *    但是要求传入参数的参数名 必须和 对应对象的字段名保持一致.
     * 2) 匹配失败的字段自动为 null
     */
    @PostMapping(value = "/findBuyer")
    public String findPhoneBuyer(Buyer buyer) {
        System.out.println("买手机的人是:" + buyer);

        return "success";
    }
}

然后,我们就在原来的 requestparam.jsp 页面下,新增一个 POST 表单,用于提交数据,代码如下:

html 复制代码
<%--
    User : Cyan_RA9
    Version : 24.0
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>原神不牛逼</title>
</head>
<body>
    <h2>捕获下面这个超链接的请求参数</h2>
    <a href="phone/search?id=141">我是超链接, 快点我</a>

    <hr>

    <form action="phone/findBuyer" method="post">
        <table style="border: 2px cornflowerblue solid">
            <tr>
                <td>买主id:</td>
                <td><input type="text" name="id"></td>
            </tr>
            <tr>
                <td>买主name:</td>
                <td><input type="text" name="name"></td>
            </tr>
            <tr>
                <td>手机id:</td>
                <td><input type="text" name="phone.id"></td>
            </tr>
            <tr>
                <td>手机name:</td>
                <td><input type="text" name="phone.name"></td>
            </tr>
            <tr>
                <td><input type="submit" value="SUBMIT"/></td>
                <td><input type="reset" value="RESET"/></td>
            </tr>
        </table>
    </form>
</body>
</html>

然后我们提交表单,如下图所示:

后端运行结果如下:


Δ总结

  • 🆗,以上就是本篇博文的全部内容了,感谢阅读!
  • 总的来说,整篇博文的三部分内容 本质上都在做同一件事:将非结构化的 HTTP 请求数据(字符串、键值对、消息头)"映射"到结构化的 Java 世界(形参、对象、POJO)中。 比如,@RequestParam是将 URL/Body 参数 映射到 简单形参;@RequestHeader是将 HTTP 报文头 映射到 方法变量;而 JavaBean 封装:是将 整个表单 映射到 内存对象。
  • 这里顺便再提一嘴,如果想用原生的 servlet api,需要先引入tomcat/lib 下的 servlet-api.jar。你想用吗?
  • 文章整体上都是"理论"+"实战" 的结构,应该还是比较清晰的,大家可以自己动手写写、敲一敲。
相关推荐
逻辑驱动的ken1 小时前
Java高频面试考点场景题20
java·开发语言·深度学习·面试·职场和发展
GISer_Jing1 小时前
AI全栈工程师知识体系全景:从前后端核心架构到落地项目全拆解
前端·人工智能·后端·ai编程
bzmK1DTbd1 小时前
Java游戏服务器:Netty框架的高并发网络通信
java·服务器·游戏
longxibo1 小时前
【Flowable 7.2 源码深度解析与实战-前言】
java·后端·流程图
全栈小刘1 小时前
ChatGPT账号打通OpenClaw?Codex又整了个“电子宠物”,开发者这下真坐不住了
后端
小龙报2 小时前
【Coze-AI智能体平台】低代码省时高效:Coze 应用开发全流程指南
java·人工智能·python·深度学习·低代码·chatgpt·交互
陈随易2 小时前
bun将会支持Bun.image,你怎么看?
前端·后端·程序员
念何架构之路2 小时前
Go Web基础和Http演进
开发语言·后端·golang
勿忘初心12212 小时前
【Java实战】SpringBoot 集成 freemarker 导出 Word 模板
java·spring boot·freemarker·模板引擎·word导出·后端实战