SpringMVC 进阶:核心组件详解与参数绑定全攻略

引言

上一篇我们通过 HelloWorld 案例入门了 SpringMVC,但实际开发中,你可能会遇到 "如何接收复杂参数?""不同组件有哪些实现选择?" 等问题。本文将带你深入 SpringMVC 的核心组件,拆解其底层逻辑,并详细讲解 "参数绑定" 这一高频开发场景,帮你解决 80% 的日常开发需求。

一、SpringMVC 核心组件深度解析

上一篇提到 5 个核心组件,本节将聚焦 "最关键的 3 个组件",讲解其实现类、配置方式及适用场景。

1. HandlerMapping:请求与 Controller 的 "桥梁"

HandlerMapping的作用是 "根据请求 URL 找到对应的 Controller 方法",SpringMVC 提供了多种实现,最常用的有 2 种:

(1)RequestMappingHandlerMapping(推荐,注解驱动)

基于@RequestMapping注解实现,支持 URL、请求方法(GET/POST)、请求参数等多维度匹配,是 SpringMVC 3.1 + 的默认选择。无需手动配置 :只要在spring-mvc.xml中开启 "注解驱动"(如下),Spring 会自动注册该组件:

xml

复制代码
<!-- 开启SpringMVC注解驱动(替代手动配置HandlerMapping和HandlerAdapter) -->
<mvc:annotation-driven/>
(2)BeanNameUrlHandlerMapping(传统,XML 配置)

基于 "Bean 名称" 映射请求,例如:

xml

复制代码
<!-- 1. 配置HandlerMapping -->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>

<!-- 2. 配置Controller Bean,名称以/开头 -->
<bean name="/user/list" class="com.example.controller.UserController"/>

适用场景:老项目维护,新项目优先用注解驱动。

2. HandlerAdapter:Controller 的 "执行者"

HandlerAdapter负责 "调用 Controller 方法",并处理 "参数绑定、类型转换" 等前置工作。与HandlerMapping对应,常用实现类也分 2 种:

(1)RequestMappingHandlerAdapter(注解驱动配套)

RequestMappingHandlerMapping配对使用,支持@RequestMapping注解的 Controller 方法,自动处理参数绑定(如@RequestParam)、返回值解析(如ModelAndView)。无需手动配置 :开启mvc:annotation-driven后自动注册。

(2)SimpleControllerHandlerAdapter(传统)

适配实现Controller接口的传统 Controller,例如:

java

运行

复制代码
// 传统Controller(需实现Controller接口)
public class UserController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg", "传统Controller");
        mv.setViewName("user/list");
        return mv;
    }
}

缺点:一个 Controller 只能处理一个请求,灵活性低,新项目不推荐。

3. ViewResolver:视图的 "定位器"

ViewResolver负责 "将视图名解析为实际视图对象",常用实现类有 3 种,覆盖 JSP、Thymeleaf 等主流视图技术:

(1)InternalResourceViewResolver(JSP 视图,最常用)

解析 JSP 文件,配置方式如下(上一篇已用过):

xml

复制代码
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/"/> <!-- 视图目录 -->
    <property name="suffix" value=".jsp"/> <!-- 视图后缀 -->
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <!-- 支持JSTL -->
</bean>

逻辑 :若 Controller 返回视图名"user/list",则解析为/WEB-INF/views/user/list.jsp

(2)ThymeleafViewResolver(Thymeleaf 视图)

若项目用 Thymeleaf 替代 JSP,需配置该解析器(需导入 Thymeleaf-Spring 依赖):

xml

复制代码
<!-- 1. 导入Thymeleaf依赖(pom.xml) -->
<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
    <version>3.1.2.RELEASE</version>
</dependency>

<!-- 2. 配置Thymeleaf视图解析器 -->
<bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
    <property name="templateEngine" ref="templateEngine"/>
    <property name="order" value="1"/> <!-- 解析顺序,越小越优先 -->
</bean>
<bean id="templateEngine" class="org.thymeleaf.spring5.SpringTemplateEngine">
    <property name="templateResolver" ref="templateResolver"/>
</bean>
<bean id="templateResolver" class="org.thymeleaf.templateresolver.ServletContextTemplateResolver">
    <property name="prefix" value="/WEB-INF/templates/"/> <!-- Thymeleaf文件目录 -->
    <property name="suffix" value=".html"/> <!-- 后缀 -->
    <property name="templateMode" value="HTML5"/>
</bean>
(3)FreeMarkerViewResolver(FreeMarker 视图)

类似 Thymeleaf,适用于 FreeMarker 模板引擎,配置逻辑一致,此处不展开。

二、参数绑定全攻略:接收请求参数的 9 种场景

"参数绑定" 是 SpringMVC 接收前端请求参数的核心能力,支持基本类型、对象、集合等多种场景,以下按 "常用程度" 排序讲解。

1. 场景 1:基本类型参数(int、String、boolean 等)

直接在 Controller 方法中声明参数,SpringMVC 会自动匹配请求参数名与方法参数名(大小写敏感)。

示例 1:接收单个参数
  • 请求 URL:http://localhost:8080/springmvc/user/query?id=1
  • Controller 方法:

java

运行

复制代码
@RequestMapping("/user/query")
public String queryUser(int id) { // 参数名id与请求参数id一致
    System.out.println("查询用户ID:" + id);
    return "user/info";
}
示例 2:用 @RequestParam 指定参数名(参数名不一致时)

若请求参数名是userId,但方法参数名是id,需用@RequestParam映射:

java

运行

复制代码
@RequestMapping("/user/query")
// required=true(默认):表示该参数必须传;defaultValue:默认值
public String queryUser(@RequestParam(name = "userId", required = true) int id) {
    System.out.println("查询用户ID:" + id);
    return "user/info";
}

2. 场景 2:POJO 对象参数(实体类参数)

若请求参数较多(如用户注册:姓名、年龄、邮箱),可将参数封装为 POJO 类,SpringMVC 会自动将请求参数注入到 POJO 的属性中(要求:POJO 属性名与请求参数名一致)。

示例:接收用户注册参数
    1. 定义 POJO 类User

java

运行

复制代码
public class User {
    private String username;
    private Integer age;
    private String email;
    // 必须有默认无参构造器(SpringMVC反射创建对象)
    // Getter和Setter方法(必须,SpringMVC通过Setter注入参数)
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    // 其他Getter/Setter省略...
}
    1. Controller 方法:

java

运行

复制代码
@RequestMapping("/user/register")
public String register(User user) { // 直接接收User对象
    System.out.println("用户名:" + user.getUsername());
    System.out.println("年龄:" + user.getAge());
    return "success";
}
    1. 前端请求(表单示例):

html

预览

复制代码
<form action="/springmvc/user/register" method="post">
    用户名:<input type="text" name="username"><br> <!-- 与User属性名一致 -->
    年龄:<input type="text" name="age"><br>
    邮箱:<input type="text" name="email"><br>
    <button type="submit">注册</button>
</form>

3. 场景 3:嵌套 POJO 参数(如用户 - 地址)

若 POJO 中包含另一个 POJO(如User包含Address),请求参数名需用 "." 分隔层级。

示例:
    1. 定义Address类:

java

运行

复制代码
public class Address {
    private String province;
    private String city;
    // Getter/Setter省略
}
    1. 改造User类,添加Address属性:

java

运行

复制代码
public class User {
    private String username;
    private Address address; // 嵌套POJO
    // Getter/Setter省略
}
    1. 前端表单参数名:

html

预览

复制代码
<!-- 参数名用address.province,对应User.address.province -->
地址-省份:<input type="text" name="address.province"><br>
地址-城市:<input type="text" name="address.city"><br>

4. 场景 4:集合参数(List、Map)

接收多个同类型参数(如批量删除用户 ID),需将集合封装到 POJO 中(直接接收 List 需特殊处理)。

示例 1:List 参数(批量删除)
    1. 定义 POJOUserList

java

运行

复制代码
public class UserList {
    private List<Integer> ids; // 存储多个用户ID
    // Getter/Setter省略
}
    1. Controller 方法:

java

运行

复制代码
@RequestMapping("/user/batchDelete")
public String batchDelete(UserList userList) {
    System.out.println("批量删除ID:" + userList.getIds()); // 如[1,2,3]
    return "success";
}
    1. 前端表单(参数名用ids[0]ids[1]):

html

预览

复制代码
<!-- 批量选择用户ID -->
<input type="checkbox" name="ids[0]" value="1">用户1<br>
<input type="checkbox" name="ids[1]" value="2">用户2<br>
<input type="checkbox" name="ids[2]" value="3">用户3<br>
示例 2:Map 参数(灵活键值对)
    1. 定义 POJOUserMap

java

运行

复制代码
public class UserMap {
    private Map<String, String> userInfo; // key=参数名,value=参数值
    // Getter/Setter省略
}
    1. 前端表单参数名(用userInfo['key']):

html

预览

复制代码
<input type="text" name="userInfo['username']" value="张三"><br>
<input type="text" name="userInfo['age']" value="20"><br>

5. 场景 5:@PathVariable(URL 路径参数)

用于接收 URL 路径中的参数(如 RESTful 风格的/user/1中的1),示例:

  • 请求 URL:http://localhost:8080/springmvc/user/1
  • Controller 方法:

java

运行

复制代码
// @PathVariable("id"):将URL中的{id}绑定到方法参数id
@RequestMapping("/user/{id}")
public String getUserById(@PathVariable("id") Integer id) {
    System.out.println("RESTful风格:查询用户ID=" + id);
    return "user/info";
}

6. 场景 6:@RequestBody(接收 JSON 参数)

前后端分离项目中,前端常发送 JSON 格式的请求体,需用@RequestBody接收(需开启mvc:annotation-driven,SpringMVC 会自动用 Jackson 解析 JSON)。

示例:
    1. 导入 Jackson 依赖(pom.xml):

xml

复制代码
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.2</version>
</dependency>
    1. Controller 方法:

    // @RequestBody:将请求体的JSON解析为User对象
    @RequestMapping(value = "/user/add", method = RequestMethod.POST)
    @ResponseBody // 表示返回JSON(下一篇讲)
    public String addUser(@RequestBody User user) {
    System.out.println("新增用户:" + user.getUsername());
    return "success";
    }

    1. 前端发送 JSON 请求(Axios 示例):

    axios.post('/springmvc/user/add', {
    username: '李四',
    age: 25,
    email: 'lisi@xxx.com'
    }).then(res => {
    console.log(res.data);
    });

7. 特殊场景:日期类型参数绑定

SpringMVC 默认不支持java.util.Date类型的参数绑定,需自定义类型转换器或用@DateTimeFormat注解。

示例:用 @DateTimeFormat 指定日期格式
复制代码
@RequestMapping("/order/query")
public String queryOrder(@DateTimeFormat(pattern = "yyyy-MM-dd") Date orderDate) {
    System.out.println("订单日期:" + orderDate);
    return "order/list";
}
  • 请求参数:http://localhost:8080/springmvc/order/query?orderDate=2024-05-20

三、参数绑定常见问题与解决方案

  1. 中文乱码问题 :原因:Tomcat 默认编码是 ISO-8859-1,无法处理中文;解决方案:在web.xml中配置编码过滤器(必须放在所有过滤器之前):

    复制代码
    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value> <!-- 强制响应编码为UTF-8 -->
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
  2. 参数类型不匹配(400 错误) :原因:请求参数类型与方法参数类型不匹配(如传字符串 "abc" 给 int 类型);解决方案:前端确保参数类型正确,或后端用@RequestParam(required = false)允许参数为空,并添加参数校验。

  3. List 参数无法直接接收 :原因:SpringMVC 不支持直接将请求参数绑定到 List(需封装到 POJO);解决方案:参考 "场景 4",将 List 封装到 POJO 中,或用@RequestBody接收 JSON 格式的 List。

四、进阶小结

  1. 核心组件选择:新项目优先用mvc:annotation-driven开启注解驱动,无需手动配置 HandlerMapping 和 HandlerAdapter;
  2. 参数绑定口诀:
    • 简单参数用@RequestParam,路径参数用@PathVariable
    • 多参数用 POJO,嵌套参数用 ".";
    • JSON 参数用@RequestBody,日期参数加@DateTimeFormat
  3. 下一篇预告:SpringMVC 实战整合 MyBatis,实现完整的 CRUD 功能,带你打通 "前端 - Controller-Service-DAO - 数据库" 全链路。
相关推荐
天黑请闭眼3 小时前
Java:Maven下载依赖后,删除pom依赖坐标无法去除依赖
maven
zl9798993 小时前
SpringBoot-数据访问之MyBatis与Redis
java·spring boot·spring
杨DaB5 小时前
【SpringCloud】回顾微服务
spring·spring cloud·微服务
lang201509286 小时前
Spring Boot集成Spring Integration全解析
spring boot·后端·spring
大G的笔记本7 小时前
Spring IOC和AOP
java·后端·spring
hello 早上好8 小时前
解析 Prompt 工程中的四大核心角色
spring
siriuuus11 小时前
Maven 核心概念及生命周期
java·maven
兮动人12 小时前
Spring中@Configuration注解的proxyBeanMethods属性详解
java·后端·spring
猫头虎12 小时前
解决升级IDEA2025.2后,每次打开Maven项目爆红的问题:Windows和Mac解决方案
java·ide·macos·maven·intellij-idea·idea·intellij idea