SpringMVC核心知识点实操笔记

SpringMVC核心知识点实操笔记(参数绑定+返回值+文件上传+拦截器+类型转换)

本篇基于SpringMVC入门实操代码整理,覆盖日常开发最核心的五大模块:请求参数绑定、Controller返回值处理、文件上传三种实现、拦截器执行流程、自定义类型转换器。所有知识点均配套对应代码示例,适合入门学习与面试复习。

一、请求参数绑定全解

SpringMVC的核心能力之一就是自动参数绑定:框架会自动将HTTP请求中的参数,按照规则映射到Controller方法的入参上,支持多种数据类型与注解扩展。

1.1 基本数据类型与字符串

直接在方法形参上声明对应类型,请求参数名与形参名保持一致即可自动注入。

java 复制代码
@RequestMapping("/save1")
public String save(String username, Integer age, Date birthday) {
    System.out.println("姓名:" + username);
    System.out.println("年龄:" + age);
    System.out.println("生日:" + birthday);
    return "suc";
}

注意:基本数据类型(如int)要求参数必须传值,否则空指针报500;包装类型(如Integer)允许为null,更推荐使用。

1.2 JavaBean实体类型

请求参数名与实体类的属性名一一对应时,框架会自动反射封装成完整的Java对象。

java 复制代码
@RequestMapping("/save2")
public String save2(User user){
    System.out.println("user对象:"+user);
    return "suc";
}

支持级联属性自动封装,例如user.address.city这种嵌套对象属性也能自动赋值。

1.3 集合类型(List/Map)

当实体类中包含List、Map集合时,通过特定的参数命名规则即可完成绑定:

  • List集合:list[0].namelist[1].name
  • Map集合:map['key1'].namemap['key2'].name
java 复制代码
@RequestMapping("/save4")
public String save4(User user) {
    System.out.println("user对象:" + user);
    return "suc";
}

1.4 原生Servlet API注入

直接在方法形参上声明HttpServletRequestHttpServletResponseHttpSession,SpringMVC会自动注入原生对象。

java 复制代码
@RequestMapping("/save6")
public String save6(HttpServletRequest request, HttpServletResponse response) {
    System.out.println("request: " + request);
    HttpSession session = request.getSession();
    System.out.println("session: " + session);
    return "suc";
}

1.5 核心参数注解

@RequestParam

解决形参名与请求参数名不一致的问题,同时可设置是否必填、默认值。

java 复制代码
@RequestMapping("/save7")
public String save7(@RequestParam(value = "username", required = false, defaultValue = "abc") String name){
    System.out.println("姓名:"+name);
    return "suc";
}
  • value:请求参数的名称
  • required:是否必须传该参数,默认true
  • defaultValue:参数不传时的默认值
@RequestBody

用于接收请求体中的JSON数据,自动反序列化为Java对象,仅支持POST请求

java 复制代码
@RequestMapping("/save8")
public String save8(@RequestBody User user) {
    System.out.println("请求体内容:" + user);
    return "suc";
}

前提:需要导入jackson等JSON转换依赖,SpringMVC自动配置消息转换器完成序列化/反序列化。

@PathVariable

RESTful风格开发必备,从URL路径中提取占位符变量值。

java 复制代码
@RequestMapping(path = "/emp/{id}", method = RequestMethod.GET)
public String findById(@PathVariable(value = "id") Integer id){
    System.out.println("通过id查询员工..."+id);
    return "suc";
}
@RequestHeader / @CookieValue
  • @RequestHeader:获取指定名称的请求头的值
  • @CookieValue:获取指定名称的Cookie值
java 复制代码
// 获取Accept请求头
public String save9(@RequestHeader(value = "Accept") String header)
// 获取JSESSIONID的Cookie值
public String save10(@CookieValue(value = "JSESSIONID") String cookie)

1.6 @RequestMapping进阶属性

  • params:限定请求必须携带指定参数,否则无法访问接口
  • method:限定允许的请求方式(GET/POST/PUT/DELETE等)
java 复制代码
// 必须同时携带username和age参数才能访问
@RequestMapping(path = "/hello1", params = {"username","age"})
// 只允许POST请求访问
@RequestMapping(path = "/hello2", method = RequestMethod.POST)

1.7 RESTful风格接口示例

通过请求方式区分增删改查,同一URL对应不同业务操作:

java 复制代码
// 新增员工:POST请求
@RequestMapping(path = "/emp", method = RequestMethod.POST)
// 查询全部员工:GET请求
@RequestMapping(path = "/emp", method = RequestMethod.GET)
// 根据id查询:GET请求 + 路径变量
@RequestMapping(path = "/emp/{id}", method = RequestMethod.GET)

二、Controller返回值与页面跳转

2.1 返回String(最常用)

返回字符串作为逻辑视图名,由视图解析器拼接前缀和后缀,找到真实JSP页面,默认采用请求转发

java 复制代码
@RequestMapping("/save1")
public String save1(){
    System.out.println("执行了...");
    return "suc"; // 拼接后:/WEB-INF/pages/suc.jsp
}

2.2 返回void

默认会根据请求路径自动拼接视图地址,找不到页面就报404。正确的打开方式有三种:

  1. 直接通过response输出响应内容,不走视图解析
  2. 手动调用request进行请求转发
  3. 手动调用response进行重定向
java 复制代码
@RequestMapping("/save2")
public void save2(HttpServletRequest request, HttpServletResponse response) throws IOException {
    System.out.println("执行了...");
    // 直接输出数据,SpringMVC不会再走视图解析流程
    response.getWriter().print("hello");
}

2.3 返回ModelAndView

SpringMVC原生返回方式,同时封装模型数据视图信息

java 复制代码
@RequestMapping("/save3")
public ModelAndView save3(){
    ModelAndView mv = new ModelAndView();
    // 存入数据,相当于存到request域中
    mv.addObject("msg","用户名或者密码已经存在");
    // 设置逻辑视图名称
    mv.setViewName("suc");
    return mv;
}

2.4 转发与重定向的核心区别(面试高频)

基础语法
java 复制代码
// 请求转发
return "forward:/WEB-INF/pages/suc.jsp";
// 重定向
return "redirect:/user/save1";
8大核心区别对比
对比维度 请求转发 forward 重定向 redirect
地址栏URL 保持不变,显示原接口地址 发生变化,更新为跳转后的新地址
request域数据 共享,同一次请求,数据可传递 不共享,两次独立请求,数据丢失
请求次数 1次,服务器内部完成跳转 2次,浏览器收到302后重新发起
跳转范围 仅能访问当前项目内部资源 可跳转外部网站、第三方域名
表单重复提交 刷新页面会重复提交表单 刷新页面只会重新查询,不会重复提交
性能 更高,仅一次网络往返 稍低,两次网络往返
HTTP状态码 返回200正常状态 返回302临时重定向状态
适用场景 查询页面跳转、需传递大量数据 增删改操作后、跳转外部地址

开发规范:所有新增、修改、删除操作完成后,必须使用重定向跳转到查询页,避免用户刷新页面造成表单重复提交。

2.5 @ResponseBody响应JSON

加在方法上后,返回值会直接作为响应体输出,不再走视图解析器。返回Java对象时,框架自动序列化为JSON字符串。

java 复制代码
@PostMapping("/save6")
@ResponseBody
public User save6(@RequestBody User user){
    System.out.println(user);
    user.setUsername("hello");
    user.setAge(100);
    // 自动转为JSON字符串响应给前端
    return user;
}

三、文件上传三种实现方式

3.1 文件上传前置条件

  1. 表单提交方式必须为 method="post"
  2. 表单编码类型必须设置为 enctype="multipart/form-data"(默认编码无法传输二进制文件)
  3. 后端需要对应的文件解析组件

3.2 方式一:原生commons-fileupload

Servlet 2.x时代的通用方案,依赖Apache的commons-fileuploadcommons-io,手动解析请求中的表单项。

java 复制代码
// 核心执行流程
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setSizeThreshold(10240); // 10KB以下存内存
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setFileSizeMax(5 * 1024 * 1024); // 单文件最大5MB

// 解析请求得到所有表单项
List<FileItem> items = upload.parseRequest(request);
for (FileItem item : items) {
    if (item.isFormField()) {
        // 普通表单字段,获取name和value
    } else {
        // 文件字段,写入磁盘
        item.write(savedFile);
        item.delete(); // 清理临时文件
    }
}
  • 优点:底层可控,兼容所有Servlet版本
  • 缺点:代码冗余,需手动处理文件名、临时文件、大小限制

3.3 方式二:Servlet 3.0 Part API

Servlet 3.0(Tomcat 7+)原生支持,无需第三方依赖,通过@MultipartConfig注解配置参数。

java 复制代码
@MultipartConfig(
    fileSizeThreshold = 10240,
    maxFileSize = 5 * 1024 * 1024,    // 单文件最大5MB
    maxRequestSize = 10 * 1024 * 1024 // 总请求最大10MB
)
public class PartUploadServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) {
        Part filePart = request.getPart("uploadFile");
        String fileName = filePart.getSubmittedFileName();
        // 保存文件,参数必须是绝对路径
        filePart.write(absolutePath);
    }
}
  • 优点:零第三方依赖,API简洁,容器原生支持
  • 缺点:仅支持Servlet 3.0及以上环境

3.4 方式三:SpringMVC MultipartFile(企业开发首选)

SpringMVC封装的标准方案,也是实际开发中最常用的方式。需先配置文件上传解析器,Controller直接声明形参即可接收。

第一步:配置文件上传解析器

XML配置方式(id必须固定为multipartResolver):

xml 复制代码
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="defaultEncoding" value="utf-8"/>
    <property name="maxFileSize" value="5242880"/>   <!-- 单文件最大5MB -->
    <property name="maxUploadSize" value="10485760"/><!-- 总请求最大10MB -->
</bean>
第二步:单文件上传
java 复制代码
@RequestMapping("/fileupload")
public String upload(MultipartFile upload, HttpServletRequest request) throws IOException {
    // 获取项目内uploads目录的真实路径
    String realPath = request.getSession().getServletContext().getRealPath("/uploads");
    File dir = new File(realPath);
    if(!dir.exists()) dir.mkdirs();

    // 获取原始文件名
    String filename = upload.getOriginalFilename();
    // UUID重命名,防止文件名重复覆盖
    String uuid = UUID.randomUUID().toString().replace("-", "").toUpperCase();
    filename = uuid + "_" + filename;

    // 核心方法:将上传文件保存到目标位置
    upload.transferTo(new File(dir, filename));
    return "suc";
}
第三步:批量文件上传

用数组接收多个同名文件,循环逐个保存:

java 复制代码
@RequestMapping("/batchFileupload")
public String batchUpload(MultipartFile[] uploads, HttpServletRequest request) throws IOException {
    String realPath = request.getSession().getServletContext().getRealPath("/uploads");
    File dir = new File(realPath);
    if (!dir.exists()) dir.mkdirs();

    for (MultipartFile upload : uploads) {
        if (upload.isEmpty()) continue; // 跳过空文件
        String originalName = upload.getOriginalFilename();
        String uuid = UUID.randomUUID().toString().replace("-", "").toUpperCase();
        String finalName = uuid + "_" + originalName;
        upload.transferTo(new File(dir, finalName));
    }
    return "suc";
}
  • 优点:代码极简,与SpringMVC无缝集成,支持普通参数与文件混传
  • 企业开发中90%以上的文件上传场景都采用此方案

3.5 拓展:大文件分片上传原理

针对GB级大文件,解决HTTP超时、内存溢出、网络中断无法续传的问题。

核心五步流程
  1. 前端切片 :通过File.slice()将文件切成1~10MB的分片,携带fileId、分片序号、总分片数
  2. 逐片上传:可开启3~6个并发提升传输速度
  3. 后端存片 :每个分片存为临时文件 {fileId}_{chunkIndex}.part
  4. 合并文件:全部上传完成后,按分片序号顺序合并成完整文件
  5. 完整性校验:通过MD5校验文件是否完整
进阶能力
  • 秒传(去重):上传前先校验文件MD5,服务端已存在相同文件则直接返回地址,跳过整个上传流程
  • 断点续传:上传前查询已上传的分片列表,跳过已传输完成的分片,从中断处继续

四、拦截器Interceptor

4.1 拦截器定义

实现HandlerInterceptor接口,用于在Controller方法执行前后进行拦截,常用于权限校验、日志记录、统一编码处理、性能监控等场景。

java 复制代码
public class MyInterceptor implements HandlerInterceptor{
    /**
     * Controller方法执行前执行
     * return true 放行,执行后续逻辑;return false 拦截请求
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        System.out.println("preHandle方法执行了...");
        return true;
    }

    /**
     * Controller方法执行后,视图渲染前执行
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        System.out.println("postHandle方法执行了...");
    }

    /**
     * 视图渲染完成后最后执行,用于资源清理、异常记录
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        System.out.println("afterCompletion方法执行了...");
    }
}

4.2 执行顺序

  • 单个拦截器:preHandle → Controller方法 → postHandle → 页面渲染 → afterCompletion
  • 多个拦截器:preHandle按配置顺序正序 执行,postHandle和afterCompletion按配置顺序倒序执行

4.3 拦截器 vs 过滤器

  • 过滤器是Servlet规范定义的,能拦截所有Web请求;拦截器是SpringMVC组件,只拦截Controller方法
  • 过滤器执行时机早于拦截器
  • 拦截器可以注入Spring容器中的Bean,过滤器不行

五、自定义类型转换器

5.1 为什么需要自定义转换器

SpringMVC自带的类型转换无法覆盖所有场景,比如前端传入yyyy-MM-dd格式的日期字符串,默认无法直接转为Date对象,此时就需要自定义转换规则。

5.2 实现方式

实现Converter<S, T>接口,S是源类型,T是目标类型。

java 复制代码
public class StringToDate implements Converter<String, Date>{
    @Override
    public Date convert(String s) {
        if (s == null) {
            throw new RuntimeException("请输入日期内容");
        }
        // 支持多种日期格式兼容
        String[] patterns = {"yyyy-MM-dd", "yyyy/MM/dd", "yyyy-MM-d", "yyyy/MM/d"};
        for (String pattern : patterns) {
            try {
                return new SimpleDateFormat(pattern).parse(s);
            } catch (ParseException ignored) {
            }
        }
        throw new RuntimeException("日期格式不合法,支持: yyyy-MM-dd 或 yyyy/MM/dd");
    }
}

5.3 注册生效

编写完成后,需要将自定义转换器注册到SpringMVC的类型转换服务中,配置完成后才会生效。


六、全局统一异常处理

6.1 为什么需要全局异常处理

在日常开发中,如果在每个 Controller 方法内部都通过 try-catch 捕获异常,会造成大量重复代码,业务逻辑与异常处理高度耦合,也不利于统一错误提示和日志排查。

Spring MVC 提供了全局异常处理机制,可以统一拦截 Controller 层抛出的所有异常,集中进行日志记录、友好提示封装和页面跳转,实现业务代码与异常处理逻辑的解耦。

6.2 自定义业务异常类

系统中的异常通常分为两类:

  • 业务异常:预期内的异常,由用户违规操作、业务校验不通过等引起,需要携带明确的提示信息返回给用户
  • 系统异常:预期外的异常,如空指针、数据库连接失败、网络超时等,属于系统底层错误,不应将详细信息暴露给用户

我们通过自定义异常类来封装业务异常,便于全局处理器识别和处理:

java 复制代码
package com.qcby.exception;

/**
 * 自定义业务异常类
 */
public class SysException extends Exception{

    // 异常提示消息
    private String message;

    @Override
    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public SysException(String message) {
        this.message = message;
    }
}

6.3 全局异常处理器实现

Spring MVC 提供了 HandlerExceptionResolver 接口,实现该接口并交由 Spring 容器管理后,即可成为全局异常处理器,自动拦截所有 Controller 抛出的异常。

java 复制代码
package com.qcby.exception;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 全局异常处理器
 */
public class SysExceptionResolver implements HandlerExceptionResolver {

    /**
     * 程序抛出异常后,自动执行该方法处理异常
     * @param request 当前请求对象
     * @param response 当前响应对象
     * @param handler 异常发生的处理器(Controller方法)
     * @param e 抛出的异常对象
     * @return 异常处理后的视图与模型数据
     */
    @Override
    public ModelAndView resolveException(HttpServletRequest request, 
                                         HttpServletResponse response, 
                                         Object handler, 
                                         Exception e) {
        // 1. 打印异常堆栈,便于后台排查问题
        e.printStackTrace();

        SysException exception;
        // 2. 区分异常类型,封装友好提示
        if(e instanceof SysException){
            // 业务异常:直接取出预设的提示信息
            exception = (SysException) e;
        }else{
            // 系统异常:封装通用友好提示,隐藏底层错误细节
            exception = new SysException("系统正在维护,请联系管理员");
        }

        // 3. 封装模型数据与视图,跳转到统一错误页面
        ModelAndView mv = new ModelAndView();
        mv.addObject("errorMsg", exception.getMessage());
        mv.setViewName("error");
        return mv;
    }
}

注意:最终存入模型的应当是封装后的 exception.getMessage(),而非原始异常 e.getMessage(),避免系统异常的底层错误信息直接暴露给前端用户,造成安全风险。

6.4 配置生效

自定义的异常处理器必须交由 Spring IoC 容器管理才能生效,有两种常用配置方式:

  1. XML 配置方式(传统SSM项目)
xml 复制代码
<!-- 注册全局异常处理器,id无强制要求 -->
<bean class="com.qcby.exception.SysExceptionResolver"/>
  1. 注解配置方式 (全注解开发项目)
    SysExceptionResolver 类上添加 @Component 注解,配合组件扫描自动注入容器。

6.5 异常处理执行流程

  1. Controller 方法执行业务逻辑,出现异常时向上抛出
  2. DispatcherServlet 捕获到异常,查找并委托给全局异常处理器
  3. 异常处理器执行 resolveException 方法,完成异常日志记录、信息封装
  4. 返回 ModelAndView 对象,Spring MVC 按正常视图解析流程渲染统一错误页面

6.6 Spring MVC 四种异常处理方案对比

除了实现 HandlerExceptionResolver 接口,Spring MVC 还提供了另外三种常用的异常处理方式,适用于不同场景:

实现方式 作用范围 优点 缺点 适用场景
@ExceptionHandler 局部异常 当前Controller类内部 无需额外配置,灵活度高 只能作用于单个Controller,代码冗余 单个Controller专属异常处理
@ControllerAdvice + @ExceptionHandler 全局 注解驱动、支持按异常类型精准匹配、代码简洁 Spring 3.2 以上版本支持 主流项目首选,前后端分离、异步请求场景
实现 HandlerExceptionResolver 接口 全局 底层可控,兼容所有版本 异常类型判断需手动实现,代码稍繁琐 传统SSM项目、需要深度定制异常处理逻辑
SimpleMappingExceptionResolver 全局 纯配置、零代码 无法携带自定义业务数据,灵活性差 简单项目、仅需跳转错误页面的场景

小结(更新版)

以上就是 Spring MVC 从入门到进阶的核心实操知识点,覆盖请求参数绑定、Controller 返回值与页面跳转、三种文件上传实现方案、拦截器执行机制、自定义类型转换器、全局异常处理六大核心模块,基本覆盖了传统 SSM 项目日常开发的绝大多数场景。理解底层执行原理、结合代码多动手实操,就能快速掌握 Spring MVC 的核心用法与常见面试考点。

相关推荐
依然范特东3 小时前
强化学习笔记1--基础概念
笔记
晓梦林5 小时前
[ACTF2020 新生赛]BackupFile学习笔记
android·笔记·学习
xian_wwq6 小时前
【学习笔记】解剖 Claude Code —— Anthropic 的 Harness 参考实现-09/15
人工智能·笔记·学习
晓梦林6 小时前
Tools靶场学习笔记
笔记·学习
xian_wwq7 小时前
【学习笔记】什么是Harness Engineering- 01/15
人工智能·笔记·学习
笨笨饿7 小时前
#102_Codex无在VSCold无法打开
java·c语言·数据库·笔记
xian_wwq7 小时前
【学习笔记】Agent 安全护栏 —— 当 Agent DROP TABLE 了你的生产数据库-07/15
笔记·学习·安全
AOwhisky8 小时前
Linux(CentOS)系统管理入门笔记(第十四期)——计划任务与进程调度管理:atcron 与 nicechrt
linux·运维·笔记·centos·云计算·进程调度·计划任务
玖玥拾9 小时前
LeetCode 13 罗马数字转整数
笔记·算法·leetcode