1、什么是MVC
- MVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范。
- 是将业务逻辑、数据、显示分离的方法来组织代码。
- MVC主要作用是降低了视图与业务逻辑间的双向偶合。
- MVC不是一种设计模式,MVC是一种架构模式。当然不同的MVC存在差异。
Model(模型):数据模型,提供要展示的数据,因此包含数据和行为,可以认为是领域模型或JavaBean组件(包含数据和行为),不过现在一般都分离开来:Value Object(数据Dao)和服务层(行为Service)。也就是模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。
View(视图):负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西。
Controller(控制器):接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。也就是说控制器做了个调度员的工作。
最典型的MVC就是JSP + servlet + javabean的模式。
Spring MVC = Spring 框架的"Web 模块"
Spring 是一个非常庞大的"家族"(也叫 Spring 生态),而 Spring 框架(Spring Framework) 是这个家族的基石。这个基石本身又是由多个模块组成的。
- Spring Core (核心) :负责管理对象(IoC)和依赖注入(DI),是整个 Spring 的基石。
- Spring AOP (面向切面) :负责处理日志、事务等公共逻辑。
- Spring JDBC / ORM:负责数据库操作。
- ...
- Spring MVC :专门负责 Web 层 的模块。
我们为什么要学习SpringMVC呢?
Spring MVC的特点:
- 轻量级,简单易学
- 高效,基于请求响应的MVC框架
- 与Spring兼容性好,无缝结合
- 约定优于配置
- 功能强大:RESTful、数据验证、格式化、本地化、主题等
- 简洁灵活
Spring的web框架围绕DispatcherServlet [ 调度Servlet ] 设计。
DispatcherServlet的作用是将请求分发到不同的处理器。从Spring 2.5开始,使用Java 5或者以上版本的用户可以采用基于注解形式进行开发,十分简洁;
正因为SpringMVC好,简单,便捷,易学,天生和Spring无缝集成(使用SpringIoC和Aop),使用约定优于配置.能够进行简单的junit测试.支持Restful风格 .异常处理,本地化,国际化,数据验证,类型转换,拦截器 等等......所以我们要学习.
2、回顾Servlet
1、添加相关依赖
xml
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>7.0.6</version>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/jakarta.servlet/jakarta.servlet-api -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.1.0</version>
<scope>provided</scope>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/jakarta.servlet.jsp/jakarta.servlet.jsp-api -->
<dependency>
<groupId>jakarta.servlet.jsp</groupId>
<artifactId>jakarta.servlet.jsp-api</artifactId>
<version>4.0.0</version>
<scope>compile</scope>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/jakarta.servlet.jsp.jstl/jakarta.servlet.jsp.jstl-api -->
<dependency>
<groupId>jakarta.servlet.jsp.jstl</groupId>
<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
<version>3.0.2</version>
<scope>compile</scope>
</dependency>
- 如何把一个普通模块添加web支持

2、编写Servlet
java
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取前端参数
String method = req.getParameter("method");
if (method.equals("add")) {
req.getSession().setAttribute("msg","add方法");
}
if(method.equals("delete")) {
req.getSession().setAttribute("msg","delete方法");
}
// 调用业务层(因为暂时没有业务所以没写)
// 视图转发或者重定向
req.getRequestDispatcher("/WEB-INF/jsp/test.jsp").forward(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
jsp页面
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%--从 JSP 的四大作用域(Page、Request、Session、Application)中,
按顺序查找名为 msg 的属性,找到后将其值输出到 HTML 页面上--%>
${msg}
</body>
</html>
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/hello" method="post">
<input type="text" name="method">
<input type="submit">
</form>
</body>
</html>
web.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.zhang.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>form.jsp</welcome-file>
</welcome-file-list>
</web-app>
web.xml除了可以注册Servlet,还可以干嘛
1. 配置项目首页(欢迎文件列表)
当用户访问根路径(如 http://localhost:8080/app/)时,服务器按顺序查找文件并返回第一个存在的页面。
xml
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>login.html</welcome-file>
</welcome-file-list>
2. 配置过滤器(Filter)
过滤器可以在请求到达 Servlet 之前,或响应返回客户端之前,执行一些预处理或后处理逻辑(例如:编码过滤、登录校验、日志记录)。
xml
<filter>
<filter-name>encodingFilter</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>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern> <!-- 拦截所有请求 -->
</filter-mapping>
3. 配置监听器(Listener)
监听器可以监听 Servlet 上下文(应用启动/销毁)、Session(会话创建/销毁)、Request 等事件,常用于在项目启动时加载资源或初始化数据。
xml
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
4. 配置会话超时时间
可以设置用户 Session 的有效期,防止用户无限期占用服务器资源。
xml
<session-config>
<!-- 单位:分钟,30分钟后Session自动失效 -->
<session-timeout>30</session-timeout>
</session-config>
5. 配置错误页面
可以根据不同的 HTTP 错误码(如 404, 500)或 Java 异常类型,跳转到自定义的友好提示页面,提升用户体验。
xml
<error-page>
<error-code>404</error-code>
<location>/error/404.jsp</location>
</error-page>
<error-page>
<exception-type>java.lang.NullPointerException</exception-type>
<location>/error/exception.jsp</location>
</error-page>
3、配置Tomcat
效果如下:

3、HelloMVC
1、配置DispatcherServlet
DispatcherServlet 是什么?
DispatcherServlet 是 Spring MVC 的核心(前端控制器) ,它是一个 Servlet,负责接收所有 HTTP 请求,并将请求分发给相应的处理器(Controller)进行处理,最后返回响应结果。
可以把它理解为 Spring MVC 的 "总指挥" 或 "交通警察" 。
这段配置是 Spring MVC 框架的核心配置,定义在 web.xml 文件中。它的作用是:
将所有的请求(/)都交给 Spring MVC 的前端控制器 DispatcherServlet 来处理。
xml
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 绑定spring配置文件
指定 DispatcherServlet 启动时要加载的 Spring MVC 配置文件位置。-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<!-- 配置启动级别
控制 Servlet 是在 Web 容器启动时立即创建,还是在第一次访问时才创建。
数字越小,启动优先级越高
0 或负数是懒加载,第一次收到请求时才创建-->
<load-on-startup>1</load-on-startup>
</servlet>
<!--- `/`:匹配所有请求,但不会拦截 JSP
`/*` :匹配所有请求,包括 JSP
-->
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
'/' 和 '/*' 匹配有什么区别?
-
/:匹配所有请求,但不会拦截 JSP -
/*:匹配所有请求,包括 JSP- 使用
/映射时,DispatcherServlet 只处理像/test这样的普通请求,而.jsp请求会被 Tomcat 内置的 JspServlet 直接处理,因此 JSP 页面能正常显示且不会去 Controller 中查找匹配的方法;但是其他的静态资源比如css也会被匹配
- 使用

- 但使用
/*映射时,所有请求(包括.jsp和 JSP 内部的include、转发)都会被强制交给 DispatcherServlet,导致 JSP 请求也被当作普通 Controller 映射去查找@RequestMapping("/login.jsp"),由于找不到对应方法,就会返回 404 错误,甚至 JSP 页面内的子页面也会因拦截而无法正常加载。
最佳实践:DispatcherServlet永远使用 /,不要用 /*!
2、配置springmvc配置文件
| 组件 | 作用 | 类比 |
|---|---|---|
| 处理器映射器 | 根据 URL 找 Controller | 前台根据房间号找人 |
| 处理器适配器 | 执行 Controller 方法 | 让人去做具体工作 |
| 视图解析器 | 把逻辑视图名转成真实路径 | 把"餐厅"转成"3楼西餐厅" |
xml
<!--处理器映射器-->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<!-- 处理器适配器-->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
<!-- 视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
<!-- 前缀-->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!-- 后缀-->
<property name="suffix" value=".jsp"/>
</bean>
这三个配置是 Spring MVC 中最经典、最基础 的三大组件配置,它们共同完成请求的完整处理流程。现在虽然很少直接这样配置(通常用 <mvc:annotation-driven/> 代替),但理解它们能帮助你深刻理解 Spring MVC 的核心工作流程
3、HelloController
java
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jspecify.annotations.Nullable;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
//实现mvc控制器接口是传统的编写方式,注解在有@ResponseBody下有了更灵活的选择,这个只适合理解mvc
public class HelloController implements Controller {
@Override
public @Nullable ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView modelAndView = new ModelAndView();
// 下面写业务代码
String result="Hello SpringMVC";
modelAndView.addObject("msg" ,result);
// 下面跳转视图
modelAndView.setViewName("test");//这个会通过视图解析器,然后变为/WEB-INF/jsp/test.jsp
return modelAndView;
}
}
4、测试
依赖的 JAR 包没有同步部署而引发的 404 错误
- 项目部署后,依赖的 JAR 包没有复制到 WEB-INF/lib 目录下,导致运行时找不到类,从而引发 404 错误,IDEA不会自动运行这个

- 解决方法
把 Available Elements 中的依赖包(如 spring-webmvc、spring-core 等)Put into Output Root ,这些 JAR 包会被复制到输出目录的 WEB-INF/lib 下 

4、使用注解开发
1、新的spring-mvc.xml配置
- 首先依旧配置web.xml

xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.zhang.controller"/>
<mvc:default-servlet-handler/>
<mvc:annotation-driven/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
-
<context:component-scan>:让 Spring 自动扫描指定包及其子包下的类,寻找带有特定注解(@Controller)的类,并将它们自动注册为 Spring 容器中的 Bean。- 替代了旧版中 手动在 XML 里写每一个
<bean>来声明 Controller 的工作
- 替代了旧版中 手动在 XML 里写每一个
-
mvc:annotation-driven/>它让 Spring MVC 全面支持基于注解的开发方式。启用 Spring MVC 的注解驱动功能 ,自动注册处理@RequestMapping、@RestController等注解所必需的组件。<mvc:annotation-driven/>- 替代了
BeanNameUrlHandlerMapping+SimpleControllerHandlerAdapter并且提供了更多功能(参数绑定、JSON 转换、数据验证等)
- 替代了
-
<mvc:default-servlet-handler/>是 Spring MVC 中用于处理静态资源 的配置。它解决了一个核心问题:让动态请求和静态资源请求能共存 。让 Spring MVC 能够正确处理静态资源(HTML、CSS、JS、图片等),而不会被 DispatcherServlet "拦截" 导致 404。/也会拦截静态资源 ,只是不拦截jsp,jsp有Tomcat(或其他容器),内部有一个 JSP Servlet ,它专门处理*.jsp和*.jspx/*会拦截所有资源(包括 JSP),更糟糕- 所以才需要
<mvc:default-servlet-handler/>来解决/带来的静态资源问题
2、新的HelloController
- 还是新建一个jsp来做视图转发目的地

- 通过注解编写控制器
java
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
//@RequestMapping("/test")
public class HelloController {
@RequestMapping("/hello")
public String hello(Model model) {
model.addAttribute("msg", "Hello Spring MVC");
return "hello";
//返回 `hello` 时:
// Spring MVC 会自动执行 `prefix + 返回值 + suffix`拼接操作
}
}
@RequestMapping 用于将特定的 HTTP 请求(如 URL、方法等)映射到控制器类或方法上,从而让 Spring MVC 知道当收到什么请求时,由哪个类的哪个方法来处理。
- 这种写法完全等同于
java
public ModelAndView hello() {
ModelAndView mv = new ModelAndView();
// 设置模型数据
mv.addObject("msg", "Hello Spring MVC");
// 设置视图逻辑名
mv.setViewName("hello");
return mv;
}
3、测试

4、小结
实现步骤其实非常的简单:
- 新建一个web项目
- 导入相关jar包
- 编写web.xml,注册DispatcherServlet
- 编写springmvc配置文件
- 接下来就是去创建对应的控制类,controller
- 最后完善前端视图和controller之间的对应
- 测试运行调试
使用springMVC必须配置的三大件:
处理器映射器、处理器适配器、视图解析器
通常,我们只需要手动配置视图解析器,而处理器映射器和处理器适配器只需要开启注解驱动即可,而省去了大段的xml配置
5、RestFul风格
RESTful 是一种软件架构风格 ,不是强制标准,而是一套设计 Web API 的约定和最佳实践 。它的核心思想是:用 HTTP 协议本身来表达对资源的操作。

1、传统写法
java
@RequestMapping("/add")
public String add(int a, int b,Model model ) {
int sum = a + b;
model.addAttribute("msg", sum);
return "hello";
}
这里是我模仿老师的写法得到的,但不知为何我的500了,可能是spring无法识别参数,ds建议说用 (@RequestParam("a") int a, @RequestParam("b") int b, Model model)
http://localhost:8080/springMVC03_annotation_Web_exploded/add?a=1&b=2
这条 URL 的本质是:通过 HTTP GET 请求,向服务器传递 a=1, b=2 两个参数,请求执行 /add 这个计算逻辑,并返回结果页面。 这是传统风格的URL表示
2、RESTful写法
java
@Controller
public class RESTfulController {
@RequestMapping("/add/{a}/{b}")
public String add(@PathVariable int a, @PathVariable int b, Model model ) {
int sum = a + b;
model.addAttribute("msg", sum);
return "hello";
}
}
@PathVariable 的作用
一句话概括:将 URL 路径中的占位符参数,绑定到控制器方法的参数上。@PathVariable 的作用就是从 URL 路径中"挖"出动态的值,填到方法参数里。 它是实现简洁、规范的 RESTful API 的关键工具。

http://localhost:8080/springMVC03_annotation_Web_exploded/add/10/20
- 它把"操作"和"参数"从请求的"角落"移到了 URL 的"主干"上,让 URL 本身成为一个清晰的名词短语。
- RESTful 的核心思想是将一切视为资源,并用 HTTP 的方法(GET, POST, PUT, DELETE)来表达操作。
- 这就像你去图书馆找一本叫《add/10/20》的书(获取资源),而不是对图书管理员喊"帮我把10和20加起来"(执行操作)。
6、重定向与转发
1、Controller与Servlet
- 因为 Spring MVC 的
@Controller本质上就是基于 Servlet API 构建的。HttpServletRequest和HttpServletResponse参数可由 Spring 在底层调用原生 Servlet 时传递进来的。
java
@Controller
public class ModelTest {
@RequestMapping("/test1")
public String test1(HttpServletRequest request, HttpServletResponse response) {
HttpSession session = request.getSession();
System.out.println(session.getId());
return "hello";
}
}
Spring MVC 的 @Controller 方法中,只要通过方法参数声明,就能拿到 Servlet API 中的几乎所有核心对象,然后调用它们的全部方法。
| 对象 | 创建者 | 作用域 | 获取方式 |
|---|---|---|---|
HttpServletRequest |
Servlet容器 | 一次请求 | 方法参数 |
HttpServletResponse |
Servlet容器 | 一次请求 | 方法参数 |
HttpSession |
Servlet容器 | 一个用户会话 | request.getSession() |
ServletContext |
Servlet容器 | 整个应用 | request.getServletContext() 或 getServletContext() |
2、原生MVC实现转发(不用视图解析器)
在上个小节的例子里,用的就是基于视图处理器才能实现的转发,如果没有视图处理器要怎么做?
- 直接指定视图目录,是 Spring MVC 中最经典的转发实现方式
java
@RequestMapping("/test1")
public String test1(Model model) {
model.addAttribute("msg", "zhang");
return "/WEB-INF/jsp/hello.jsp";
//或者"forward:/WEB-INF/jsp/hello.jsp"
}
diff
- 因为是转发,所以可以看到实际url没有变

- 重定向 redirect
java
@RequestMapping("/test1")
public String test1(Model model) {
model.addAttribute("msg", "zhang");
return "redirect:test.jsp";
}
diff
- 我们可以看到url到了一个新的目录,并且数据没有被传递过来,
这是因为重定向被视为一个新的请求,与转发有本质区别

redirect: 和 forward: 前缀会被 Spring 的 InternalResourceViewResolver 直接识别并跳过 ,不会进行任何路径拼接。
7、与前端数据传递与处理
1、 前端传递了同名数据
java
@RequestMapping("/t1")
public String test01(String name, Model model) {
model.addAttribute("msg", name);
return "hello";
}

2、 前端传递参数名与后端不同
java
@RequestMapping("/t1")
public String test01(@RequestParam("username") String name, Model model) {
model.addAttribute("msg", name);
return "hello";
}

@RequestParam和@PathVariable有什么区别?
1、 编码问题
-
@PathVariable:路径参数,Tomcat 默认会解码(需注意中文)- URL格式:
/user/张三/18
- URL格式:
-
@RequestParam:查询参数,自动处理 URL 编码- URL格式:
/user?name=张三&age=18 - 可用 Map 接收所有参数
- URL格式:
java
@GetMapping("/params")
public String getAllParams(@RequestParam Map<String, String> allParams) {
return allParams.toString();
}
// 请求:GET /params?name=张三&age=18&city=北京
// 结果:{name=张三, age=18, city=北京}
- 性能考虑
@PathVariable:路径参数,URL 更短,某些场景下性能略好@RequestParam:查询参数,URL 更长,但更灵活
- 缓存影响
- 相同路径参数的不同值会被视为不同 URL
- 查询参数的顺序不影响 URL 的唯一性(
?a=1&b=2和?b=2&a=1被视为相同)
@RequestParam 问号后面,@PathVariable 斜杠之间!
3、 前端传递个对象
controller会自动匹配前端数据名与对象字段相同的赋值
java
@RequestMapping("/t2")
public String test02(User user,Model model) {
model.addAttribute("msg", user.toString());
return "hello";
}

8、乱码问题
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/e" method="post">
<input type="text" name="name">
<input type="submit">
</form>
</body>
</html>
在Web项目中,任何内部路径(表单action、超链接href、重定向地址等)都不要写死,一律使用 ${pageContext.request.contextPath} 作为前缀,这样项目无论部署在根目录还是任意子路径下,路径都能自动适配,避免404错误。
java
@PostMapping("/e")
public String test1(String name, Model model){
model.addAttribute("msg", name);
return "hello";
}

- 可以看到英文输出没有问题,而中文有可能会出现下面的乱码

- MVC给出的解决方案,在web配置中添加过滤器
xml
<filter>
<filter-name>encoding</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> <!-- 强制编码,包括响应 -->
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern> <!-- 改为/*,拦截所有请求和转发 ,包括jsp-->
</filter-mapping>
- 有时控制器返回的内容不经过视图没有响应头,那还是可能会乱码,mvc添加配置为下列可解决
xml
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg value="UTF-8"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
- 你也可以像servlet里学的那样自定义filter代替MVC的,不过也要是" /* "
java
package com.zhang.filter;
import jakarta.servlet.*;
import java.io.IOException;
public class CharacterEncodingFilter implements Filter {
@Override
// 初始化方法,在web服务器启动时就调用,随时监听程序
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("CharacterEncodingFilter init");
}
//过滤特定请求
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
// 将当前请求传递给过滤器链(Filter Chain)中的下一个组件。没有这个就会拦截在这里
// 就像是当前安检关卡说:"检查通过,去下一个关卡(或最终目的地)
chain.doFilter(request, response);
}
// 销毁:在服务器关闭时销毁
@Override
public void destroy() {
System.out.println("CharacterEncodingFilter destroy");
}
}
xml
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>com.zhang.filter.CharacterEncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<!-- 在指定url下的请求会全部进入过滤-->
<url-pattern>/*</url-pattern>
</filter-mapping>
- 有的乱码可能是Tomcat的配置问题
- 检查Tomcat conf/server.xml
xml
<!-- 问题配置(会乱码)-->
<Connector port="8080" protocol="HTTP/1.1"/>
<!-- 正确配置 -->
<Connector port="8080" protocol="HTTP/1.1" URIEncoding="UTF-8"/>
9、JSON
1、什么是JSON?
- JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式,目前使用特别广泛。
- 采用完全独立于编程语言的文本格式来存储和表示数据。
- 简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。
- 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。
在 JavaScript 语言中,一切都是对象。因此,任何JavaScript 支持的类型都可以通过 JSON 来表示,例如字符串、数字、对象、数组等。看看他的要求和语法格式:
- 对象表示为键值对,数据由逗号分隔
- 花括号保存对象
- 方括号保存数组
JSON 键值对是用来保存 JavaScript 对象的一种方式,和 JavaScript 对象的写法也大同小异,键/值对组合中的键名写在前面并用双引号 "" 包裹,使用冒号 : 分隔,然后紧接着值:
js
{"name": "QinJiang"}
{"age": "3"}
{"sex": "男"}
JSON对象与JavaScript对象互相转换
js
<script>
let obj={
name:"zhanglei",
age:18
}
console.log("输出对象:")
console.log(obj);
let json=JSON.stringify(obj);
console.log("对象转换为字符串为");
console.log(json);
let obj2=JSON.parse(json);
console.log("字符串转换为对象:");
console.log(obj2);
</script>

2、Controller返回JSON
1、@RequestMapping与@RestController
java
@RequestMapping("/j1")
@ResponseBody//用了这个注解,返回值就不会被视图解析器解析了,会直接返回字符串
public String json1(){
User user = new User("张磊", 18, "男");
return user.toString();
}
同样,@RestController代替掉@Controller也可以实现阻止视图,只返回字符串的效果
2、如何转换数据为JSON格式数据
Jackson Databind
xml
<!-- Source: https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.21.2</version>
<scope>compile</scope>
</dependency>
在上面可以实现把字符串返回回去,JSON对java来说其实也只是字符串, 这个 Jackson Databind 是Java中最常用的 JSON处理库 ,主要功能是在 Java对象 和 JSON格式 之间相互转换。
java
@RequestMapping("/j1")
@ResponseBody//用了这个注解,返回值就不会被视图解析器解析了,会直接返回字符串
public String json1() throws JsonProcessingException {
User user = new User("张磊", 18, "男");
ObjectMapper objectMapper = new ObjectMapper();
String s = objectMapper.writeValueAsString(user);
return s;
}
得到了JSON格式字符串

3、JSON字符串 → Java对象(反序列化)
java
String json = "{"name":"李四","age":20,"sex":"女"}";
ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue(json, User.class);
// 结果:User对象,属性被自动填充
4、自动转换
- 导入依赖 +
<mvc:annotation-driven/>= 自动转换 - 缺少任意一个,都不会自动转换
- 返回类型必须是对象(不是String),才会触发JSON转换
java
@Controller
public class UserController {
// 返回对象时,Spring自动用Jackson转成JSON
@RequestMapping("/getUser")
@ResponseBody
public User getUser() {
User user = new User("张磊", 18, "男");
return user; // 自动变成 {"name":"张磊","age":18,"sex":"男"}
}
// 接收JSON数据,自动转成Java对象
@PostMapping("/saveUser")
@ResponseBody
public String saveUser(@RequestBody User user) {
System.out.println(user.getName()); // 直接使用对象
return "success";
}
}
Spring Boot 内置了这个功能,这也是它能成为主流框架的重要原因之一
国内有一个同类工具,Fastjson2 是由 阿里巴巴 开源的一款高性能 JSON 处理库
xml
<!-- Source: https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.61</version>
<scope>compile</scope>
</dependency>
- ✍️ 基本用法示例
- ✅ 对象 → JSON
java
import com.alibaba.fastjson2.JSON;
User user = new User("Alice", 20);
String json = JSON.toJSONString(user);
System.out.println(json);
// {"name":"Alice","age":20}
- ✅ JSON → 对象
java
String json = "{"name":"Alice","age":20}";
User user = JSON.parseObject(json, User.class);
- ✅ 使用 JSONB
- 把原本的 JSON 文本,编译成一种更高效的二进制格式,更快更小
java
byte[] bytes = JSON.toJSONBytes(user);
User u = JSON.parseObject(bytes, User.class);
10、整合SSM
sql
CREATE DATABASE `ssmbuild`;
USE `ssmbuild`;
DROP TABLE IF EXISTS `books`;
CREATE TABLE `books` (
`bookID` INT(10) NOT NULL AUTO_INCREMENT COMMENT '书id',
`bookName` VARCHAR(100) NOT NULL COMMENT '书名',
`bookCounts` INT(11) NOT NULL COMMENT '数量',
`detail` VARCHAR(200) NOT NULL COMMENT '描述',
PRIMARY KEY (`bookID`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `books` (`bookID`, `bookName`, `bookCounts`, `detail`) VALUES
(1, 'Java', 1, '从入门到放弃'),
(2, 'MySQL', 10, '从删库到跑路'),
(3, 'Linux', 5, '从进门到进牢');
一、MyBatis 配置(数据持久层)
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--给实体类起"别名"-->
<typeAliases>
<package name="com.zhang.pojo"/>
</typeAliases>
<!-- 注册 SQL 映射文件-->
<mappers>
<mapper resource="com/zhang/dao/BookMapper.xml"/>
</mappers>
</configuration>
二、Spring 配置(核心容器)
管:Service + DAO + 数据源 + MyBatis
spring-dao.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--加载数据库配置(用户名、密码等)-->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 创建数据库连接池(连接复用)-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driver}"/>
<property name="jdbcUrl" value="${url}"/>
<property name="user" value="${username}"/>
<property name="password" value="${password}"/>
<!-- c3p0连接池的私有属性 -->
<property name="maxPoolSize" value="30"/>
<property name="minPoolSize" value="10"/>
<!-- 关闭连接后不自动commit -->
<property name="autoCommitOnClose" value="false"/>
<!-- 获取连接超时时间 -->
<property name="checkoutTimeout" value="10000"/>
<!-- 当获取连接失败重试次数 -->
<property name="acquireRetryAttempts" value="2"/>
</bean>
<!--让 Spring 帮忙创建并管理 MyBatis 的核心对象 SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 用哪个数据库-->
<property name="dataSource" ref="dataSource"/>
<!-- 加载 MyBatis 配置-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>
<!--自动创建 Mapper 接口的代理对象-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 去容器里找名叫这个sqlSessionFactory的 Bean
SqlSessionFactory 和扫描器共同生产一个mapper的制造机器,
把 这个制造机器注册到容器,再取的时候是这个机器生产的mapper-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="com.zhang.dao"/>
</bean>
</beans>
spring-service.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--Service 扫描,自动识别:@Service-->
<context:component-scan base-package="com.zhang.service"/>
<bean id="BookServiceImpl" class="com.zhang.service.BookServiceImpl">
<property name="bookMapper" ref="bookMapper" />
</bean>
<!-- 管理数据库事务(提交 / 回滚)-->
<bean id="TransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--让 @Transactional 生效-->
<tx:annotation-driven/>
</beans>
applicationContext.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--整合 Spring 配置(统一入口)-->
<import resource="classpath:spring-dao.xml"/>
<import resource="classpath:spring-service.xml"/>
</beans>
三、SpringMVC 配置(Web 层)
spring-mvc.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 让静态资源(css/js)可以访问-->
<mvc:default-servlet-handler/>
<!-- 开启:@RequestMapping,@ResponseBody参数绑定-->
<mvc:annotation-driven/>
<!--把 Controller 返回的字符串 → JSP 路径-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 扫描 @Controller-->
<context:component-scan base-package="com.zhang.controller"/>
</beans>
web.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>springmvc</servlet-name>
<!-- SpringMVC 的核心控制器(前端控制器)-->
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 绑定spring配置文件
指定 DispatcherServlet 启动时要加载的 Spring MVC 配置文件位置。-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<!-- 配置启动级别
控制 Servlet 是在 Web 容器启动时立即创建,还是在第一次访问时才创建。
数字越小,启动优先级越高
0 或负数是懒加载,第一次收到请求时才创建-->
<load-on-startup>1</load-on-startup>
</servlet>
<!--- `/`:匹配所有请求,但不会拦截 JSP
`/*` :匹配所有请求,包括 JSP
-->
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<context-param>
<!-- 告诉 Spring:主配置文件在哪-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--在 Tomcat 启动时:
1️⃣ 读取 contextConfigLocation
2️⃣ 创建 Spring 容器(ApplicationContext)
3️⃣ 加载所有 Bean(Service / DAO / MyBatis)-->
<!-- ContextLoaderListener 的作用是创建 Spring 根容器,
使业务层(Service、DAO)与 Web 层(Controller)解耦。在简单项目中可以省略,
但在标准 SSM 架构中是推荐配置。-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
四、小结
SSM 本质就是:
- Spring:管理对象(IoC + 事务)
- SpringMVC:处理请求(Web层)
- MyBatis:操作数据库(持久层)
SSM 的核心不是配置,而是三层解耦:
Controller → Service → DAO
javascript
浏览器请求
↓
DispatcherServlet(SpringMVC)
↓
Controller(控制层)
↓
Service(业务层,Spring管理)
↓
Mapper(持久层,MyBatis)
↓
数据库
↓
返回结果 → JSP / JSON
11、Ajax技术
- AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。
- AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。
- Ajax 不是一种新的编程语言,而是一种用于创建更好更快以及交互性更强的Web应用程序的技术。
- 在 2005 年,Google 通过其 Google Suggest 使 AJAX 变得流行起来。Google Suggest能够自动帮你完成搜索单词。
- Google Suggest 使用 AJAX 创造出动态性极强的 web 界面:当您在谷歌的搜索框输入关键字时,JavaScript 会把这些字符发送到服务器,然后服务器会返回一个搜索建议的列表。
- 就和国内百度的搜索框一样:
1、iframe体验
<iframe>(内联框架)是 HTML 中一个非常经典且实用的标签,用于在当前网页中嵌入另一个独立的 HTML 页面。
你可以把它理解为"网页中的网页"或"一个窗口"。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>iframe</title>
</head>
<script>
function test(){
let va=document.getElementById("url").value;
document.getElementById("iframe").src=va;
}
</script>
<body>
<div>
<p>
<input type="text" id="url">
<input type="button" value="提交" id="btn" onclick="test()">
</p>
</div>
<div>
<iframe id="iframe" style="width: 100%; height: 500px;" >
</iframe>
</div>
</body>
</html>
iframe 和 Ajax 在理念上有相似之处(都是实现局部刷新、按需加载),但在技术实现和体验上是完全不同的两条技术路线。
Ajax (Asynchronous JavaScript and XML) :是 "无刷新数据交互" 。浏览器在后台静默地请求数据(通常是 JSON/XML),然后 JavaScript 拿到数据后,动态修改当前页面的 DOM(文档对象模型)。用户感觉页面没动,但内容变了
2、失去焦点的Ajax
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<%-- 引入 jQuery 库,方便进行 AJAX 操作--%>
<script src="${pageContext.request.contextPath}/static/js/jquery-4.0.0.min.js"></script>
<script>
function checkUsername(){
// 当用户输入框失去焦点时,这个函数会被触发
$.ajax({
// 请求的服务器地址,对应的后端处理器
url:"${pageContext.request.contextPath}/a1",
// 要发送到服务器的数据(键值对),`$()` 是jQuery 库的核心函数,通常被称为 jQuery 选择器
data:{"name":$("#username").val()},
// 请求成功后的回调函数
// 回调函数是只要这个ajax触发后就执行一遍,他的data是后端处理后又放回前端的data
// 只要后端在前端写东西的行为都是返回的data
//,如response.getWriter().print(),@ResponseBody,@RestController,返回对象(自动转 JSON)
success:function(data){
alert(data);
}
})
}
</script>
</head>
<body>
<%--onblur="checkUsername()":当输入框失去焦点时调用函数--%>
用户名:<input type="text" id="username" onblur="checkUsername()">
</body>
</html>
java
@RequestMapping("/a1")
public void a1(@RequestParam("name") String name, HttpServletResponse response) throws IOException {
if(name.equals("zhanglei")){
response.getWriter().print("ture");
}
else{
response.getWriter().print(false);
}
}
这段代码展示了一个典型的 AJAX 异步验证模式:
- 触发时机 :失去焦点事件(
onblur) - 数据传输:通过 AJAX 异步发送
- 结果展示 :使用弹窗(
alert)显示 - 核心价值:无需刷新页面即可与服务器交互
1. 基本语法结构
javascript
$.ajax({
// 必需参数
url: "请求地址", // 如:"/api/user"
type: "请求方式", // GET、POST、PUT、DELETE 等
// 可选参数
data: {}, // 发送的数据
dataType: "json", // 期望返回的数据类型
success: function(data){}, // 成功回调
error: function(){}, // 失败回调
timeout: 5000, // 超时时间(毫秒)
async: true // 是否异步(默认true)
});
2. 常用简写方法
javascript
// GET 请求
$.get(url, data, successCallback);
// POST 请求
$.post(url, data, successCallback);
// GET 请求(返回 JSON)
$.getJSON(url, data, successCallback);
// GET 请求(加载 HTML)
$.load(url, data, callback);
// 简写
$.get("/api/user", { "id": 1 }, function(data) {
console.log(data);
}, "json");
下面的例子展示了另一个ajax的运用,实时验证用户名
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<%-- 引入 jQuery 库,方便进行 AJAX 操作--%>
<script src="${pageContext.request.contextPath}/static/js/jquery-4.0.0.min.js"></script>
<script>
function checkUsername(){
$.ajax({
url:"${pageContext.request.contextPath}/a3",
data: {"name":$("#username").val()},
success: function(data){
if(data.toString()==="ok"){
$("#userIfo").html("ok")
$("#userIfo").css({"background-color":"#95ec93"});
}
}
})
}
</script>
</head>
<body>
<%--onblur="checkUsername()":当输入框失去焦点时调用函数--%>
用户名:<input type="text" id="username" onblur="checkUsername()">
<span id="userIfo" ></span>
</body>
</html>
java
@RequestMapping("/a3")
public String a3(@RequestParam("name") String name, HttpServletResponse response) throws IOException {
if(name.equals("zhanglei")){
return "ok";
}
else{
return "error";
}
}

12、拦截器
SpringMVC的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。开发者可以自己定义一些拦截器来实现特定的功能。
过滤器与拦截器的区别:拦截器是AOP思想的具体应用。
过滤器
- servlet规范中的一部分,任何java web工程都可以使用
- 在url-pattern中配置了/*之后,可以对所有要访问的资源进行拦截
拦截器
- 拦截器是SpringMVC框架自己的,只有使用了SpringMVC框架的工程才能使用
- 拦截器只会拦截访问的控制器方法,如果访问的是jsp/html/css/image/js是不会进行拦截的
1、自定义拦截器
这段代码是 Spring MVC 拦截器(HandlerInterceptor) 的一个标准实现
java
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jspecify.annotations.Nullable;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle");
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
System.out.println("afterCompletion");
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
System.out.println("postHandle");
}
}
xml
<mvc:interceptors>
<mvc:interceptor>
<!-- "/**"表示拦截所有请求,包括这个请求下的所有请求,比如admin/user1
Spring 的路径匹配中 /* 只匹配一层,必须用 /** 才能匹配多层-->
<mvc:mapping path="/**"/>
<bean class="com.zhang.config.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>

preHandle - 前置处理
返回值含义:
-
true:继续执行(调用 Controller) -
false:中断请求(不调用 Controller,通常返回错误信息) -
主要用途:
- ✅ 登录检查(是否已登录)
- ✅ 权限验证(是否有权限访问)
java
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String uri = request.getRequestURI();
// 只拦截需要权限的资源
if (uri.startsWith("/admin/") || uri.startsWith("/user/")) {
String username = (String) request.getSession().getAttribute("username");
if (!"admin".equals(username)) {
// 未登录或不是admin,重定向到登录页
response.sendRedirect("/login.jsp");
return false;
}
}
return true; // 登录页、静态资源等都放行
}
diff
- ✅ 参数预处理(校验、格式化)
- ✅ 记录请求日志
- ✅ 限流控制
postHandle - 后置处理
-
执行时机 :Controller 执行之后 ,视图渲染之前
-
主要用途:
- ✅ 修改 ModelAndView(添加公共数据)
- ✅ 记录 Controller 执行时间
- ✅ 响应数据加密/压缩
- ✅ 操作日志记录
afterCompletion - 完成处理
-
执行时机 :整个请求完全结束之后(视图渲染完成)
-
主要用途:
- ✅ 资源清理(关闭文件流、数据库连接)
- ✅ 释放内存(清理 ThreadLocal)
- ✅ 记录最终响应结果
- ✅ 异常处理日志
13、文件上传与下载
文件上传是项目开发中最常见的功能之一,springMVC 可以很好的支持文件上传,但是 SpringMVC上下文中默认没有装配MultipartResolver,因此默认情况下其不能处理文件上传工作。如果想使用Spring的文件上传功能,则需要在上下文中配置MultipartResolver。
前端表单要求:为了能上传文件,必须将表单的method设置为POST,并将enctype设置为multipart/form-data 只有在这样的情况下,浏览器才会把用户选择的文件以二进制数据发送给服务器;
对表单中的 enctype 属性做个详细的说明:
- application/x-www=form-urlencoded:默认方式,只处理表单域中的 value 属性值,采用这种编码方式的表单会将表单域中的值处理成 URL 编码方式。
- multipart/form-data:这种编码方式会以二进制流的方式来处理表单数据,这种编码方式会把文件域指定文件的内容也封装到请求参数中,不会对字符编码。
- text/plain:除了把空格转换为 "+" 号外,其他字符都不做编码处理,这种方式适用直接通过表单发送邮件。
xml
<!-- Source: https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.6.0</version>
<scope>compile</scope>
</dependency>
文件上传
- 首先一个表单
enctype 是 HTML 表单中的一个属性,用于指定表单数据在发送到服务器之前应该如何编码。
| 特性 | application/x-www-form-urlencoded |
multipart/form-data |
text/plain |
|---|---|---|---|
| 是否默认 | ✅ 是(浏览器默认) 不写enctype就是它 | ❌ 否 必须手动指定 | ❌ 否 几乎不手动指定 |
| 适用场景 | • 登录/注册表单 • 搜索框 • 评论提交 • 设置页面 • 任何只有文本的表单 | • 上传头像/照片 • 发送附件邮件 • 上传文档/压缩包 • 提交带文件的表单 • 批量文件上传 | • 调试测试 • 查看原始数据 • 简单的纯文本传输 • ⚠️ 生产环境几乎不用 |
| 二进制数据 | ❌ 不支持 二进制数据会被破坏 只能传文本内容 | ✅ 完全支持 保持原始二进制格式 图片/视频/PDF都能传 | ❌ 不支持 二进制数据会乱码 无法恢复原始文件 |
| 数据大小限制 | ⚠️ 较小 • POST理论上可大 • 但服务器常限制(2-8MB) • URL编码后体积增大约20% • 不适合大文件 | ✅ 很大 • 可达GB级别 • 分块传输,内存友好 • 主要受服务器配置限制 • 适合大文件上传 | ⚠️ 中等 • 无特殊限制 • 但不适合二进制 • 实际很少用 |
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" value="Submit" />
</form>
</body>
</html>
- 控制器
MultipartFile 是 Spring 框架提供的文件上传抽象接口,用于接收 HTTP 请求中上传的文件。它是 Spring 处理文件上传的核心类。
java
@RequestMapping("/upload")
public String fileUpload(@RequestParam("file") MultipartFile file, HttpServletRequest request) throws IOException {
// 获取文件名
String uploadFileName = file.getOriginalFilename();
// 如果文件名为空,直接回到首页
if (uploadFileName == null || "".equals(uploadFileName)){
return "redirect:/index.jsp";
}
System.out.println("上传文件名:" + uploadFileName);
// 上传路径保存设置,输出在out下
//上传文件名:copy.png
//上传文件保存地址:D:\code\JAVA\IDEA\SpringMVC\out\artifacts\spring_05_file_Web_exploded\upload
String path = request.getServletContext().getRealPath("/upload");
// 如果路径不存在,创建一个
File realPath = new File(path);
if (!realPath.exists()){
realPath.mkdirs(); // 建议使用 mkdirs()
}
System.out.println("上传文件保存地址:" + realPath);
// 方式1:使用 MultipartFile.transferTo() 最简单(推荐)
//`transferTo()` 是 Spring 提供的最便捷的文件保存方法,它的作用就是直接将上传的文件保存到磁盘上的目标位置**。
File targetFile = new File(realPath, uploadFileName);
file.transferTo(targetFile);
// 方式2:手动读写流(如果你需要特殊处理)
// InputStream is = file.getInputStream();
// OutputStream os = new FileOutputStream(new File(realPath, uploadFileName));
// byte[] buffer = new byte[1024];
// int len;
// while ((len = is.read(buffer)) != -1){
// os.write(buffer, 0, len);
// }
// os.close();
// is.close();
return "redirect:/index.jsp";
}


文件下载
ResponseEntity 是 Spring 提供的用于完整控制 HTTP 响应的类 ,尖括号里的 <byte[]> 表示响应体的数据类型是字节数组。
它让你可以完全自定义 HTTP 响应的状态码、响应头和响应体。
java
@RequestMapping(value = "/download")
public ResponseEntity<byte[]> download(HttpServletRequest request) throws Exception {
// 要下载的文件路径:src/upload/copy.png
String path = request.getServletContext().getRealPath("/upload");
String fileName = "copy.png";
File file = new File(path, fileName);
// 检查文件是否存在
if (!file.exists()) {
return ResponseEntity.notFound().build();
}
// 读取文件内容
byte[] fileBytes = java.nio.file.Files.readAllBytes(file.toPath());
// 设置响应头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); // 二进制流
headers.setContentDispositionFormData("attachment",
new String(fileName.getBytes("UTF-8"), "ISO-8859-1")); // 解决中文文件名乱码
return ResponseEntity.ok() // 1. 设置状态码为 200 OK
.headers(headers) // 2. 设置响应头
.contentLength(file.length()) // 3. 设置内容长度
.body(fileBytes); // 4. 设置响应体(文件内容)
}
