SpringMVC
约 7916 字大约 26 分钟
2025-01-31
1.SpringMVC 简介
1.1 定义
基于 java 的实现MVC 设计模式的请求驱动类型的轻量级 Web 框架,通过注解,无需实现任何接口,处理请求,支持 restful。
- 三层结构:表现层、业务层、持久层
- 设计模式:Model(模型)、View(视图)、Controller(控制器)
1.2 主要组件
前端控制器 DispatcherServlet(不需要程序员开发)
作用:接收请求,处理结果,相当于转发器,有了 DispatcherServlet 就减少了其它组件之间的耦合度。
==处理器映射器 HandleMapping (==不需要程序员开发)
作用:根据 url 找到 handler
处理器适配器 HandlerAdapter(适配器模式)
注意:在编写 Handler 的时候要按照 HandlerAdapter 要求的规则去编写,这样适配器 HandlerAdapter 才可以 正确的去执行 Handler。
处理器 Handler(需要程序员开发)
作用:处理请求的具体过程。
视图解析器 ViewResolver(不需要程序员开发)
作用:进行视图的解析,根据视图逻辑名解析成真正的视图(view)
视图 View(需要程序员开发 jsp)
View 是一个接口, 它的实现类支持不同的视图类型(jsp,freemarker,pdf 等等)
1.3 工作流程
1.3.1 简要流程

- 用户发送请求至前端控制器 DispatcherServlet;
- DispatcherServlet 收到请求后,调用 HandlerMapping 处理器映射器,请求获取 Handle;
- 处理器映射器根据请求 url 找到具体的处理器,生成处理器对象及处理器拦截器如果有则生成)一并返回给 DispatcherServlet;
- DispatcherServlet 调用 HandlerAdapter 处理器适配器;
- HandlerAdapter 经过适配调用 具体处理器(Handler,也叫后端控制器);
- Handler 执行完成返回 ModelAndView;
- HandlerAdapter 将 Handler 执行结果 ModelAndView 返回给 DispatcherServlet;
- DispatcherServlet 将 ModelAndView 传给 ViewResolver 视图解析器进行解析;
- ViewResolver 解析后返回具体 View;
- DispatcherServlet 对 View 进行渲染视图(即将模型数据填充至视图中)
- DispatcherServlet 响应用户。
1.3.2 详细流程
- 用户向服务器发送请求,请求被 SpringMVC 前端控制器 DispatcherServlet 捕获。
- DispatcherServlet 对请求 URL 进行解析,得到请求资源标识符(URI),判断请求 URI 对应的映射:
a) 不存在
i. 再判断是否配置了 mvc:default-servlet-handler
ii. 如果没配置,则控制台报映射查找不到,客户端展示 404 错误

iii. 如果有配置,则访问目标资源(一般为静态资源,如:JS,CSS,HTML),找不到客户端也会展示 404 错误

b) 存在则执行下面的流程
- 根据该 URI,调用 HandlerMapping 获得该 Handler 配置的所有相关的对象(包括 Handler 对象以及 Handler 对象对应的拦截器),在 doDispatch 方法开头先创建了一个 HandlerExecutionChain 执行链,最后以 HandlerExecutionChain 执行链对象的形式返回。
- DispatcherServlet 根据获得的 Handler,选择一个合适的 HandlerAdapter。
- 如果成功获得 HandlerAdapter,此时将开始执行拦截器的 preHandler(…)方法【正向】
- 提取 Request 中的模型数据,填充 Handler 入参,开始执行 Handler(Controller)方法,处理请求。在填充 Handler 的入参过程中,根据你的配置,Spring 将帮你做一些额外的工作:
a) HttpMessageConveter: 将请求消息(如 Json、xml 等数据)转换成一个对象,将对象转换为指定的响应信息
b) 数据转换:对请求消息进行数据转换。如 String 转换成 Integer、Double 等
c) 数据格式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
d) 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到 BindingResult 或 Error
5.Handler 执行完成后,向 DispatcherServlet 返回一个 ModelAndView 对象。
6.此时将开始执行拦截器的 postHandle(…)方法【逆向】。
7.根据返回的 ModelAndView(此时会判断是否存在异常:如果存在异常,则执行 HandlerExceptionResolver 进行异常处理)选择一个适合的 ViewResolver 进行视图解析,根据 Model 和 View,来渲染视图。
8.渲染视图完毕执行拦截器的 afterCompletion(…)方法【逆向】。
9.将渲染结果返回给客户端。
1.4 优缺点
优点:
- 可以支持各种视图技术,而不仅仅局限于 JSP;
- 与 Spring 框架集成(如 IoC 容器、AOP 等);
- 清晰的角色分配:
- 前端控制器(dispatcherServlet)
- 请求到处理器映射(handlerMapping)
- 处理器适配器(HandlerAdapter)
- 视图解析器(ViewResolver)。
- 支持各种请求资源的映射策略。
2.常用注解
@Controller : 用于定义控制类
@RequestMapping : 用来处理请求地址映射的注解,可以作用于类和方法上,属性如下:
- value: 指定请求的实际地址,指定的地址可以是 URI Template 模式
- method: 指定请求的 method 类型, GET、POST、PUT、DELETE 等
- consumes: 指定处理请求的提交内容类型(Content-Type),例如 application/json, text/html;
- produces: 指定返回的内容类型,仅当 request 请求头中的(Accept)类型中包含该指定类型才返回;
- params: 指定 request 中必须包含某些参数值是,才让该方法处理
- headers: 指定 request 中必须包含某些指定的 header 值,才能让该方法处理请求。
@ResponseBody : 该注解用于将 Controller 的方法返回的对象,通过适当的 HttpMessageConverter 转换为指定格式后,写入到 Response 对象的 body 数据区。使用时机:返回的数据不是 html 标签的页面,而是其他==某种格式的数据时(如 json、xml 等)==使用;
@RequestParam : 用于在 SpringMVC 后台控制层获取参数,类似一种是 request.getParameter(“name”),它有三个常用参数:defaultValue = “0”, required = false, value = “isApp”;defaultValue 表示设置默认值,required 通过 boolean 设置是否是必须要传入的参数,value 值表示接受的传入的参数类型。
@PathVariable : 用于将请求 URL 中的模板变量映射到功能处理方法的参数上,即取出 uri 模板中的变量作为参数。
@ModelAttribute 和 @SessionAttributes :代表的是:该 Controller 的所有方法在调用前,先执行此@ModelAttribute 方法,可用于注解和方法参数中,可以把这个@ModelAttribute 特性,应用在 BaseController 当中,所有的 Controller 继承 BaseController,即可实现在调用 Controller 时,先执行@ModelAttribute 方法。
@SessionAttributes 即将值放到 session 作用域中,写在 class 上面。
3.获取请求参数
- SpringMVC 中一共有三种方式可以获取请求参数
- 通过 HttpServletRequest 对象获取请求参数
- 通过控制器方法的形参获取参数
- 请求路径的参数名和方法的参数名相同(直接赋值)
- 请求路径的参数名和方法的参数名不相同(使用@RequestParm(value1) String value2)—>将请求路径的参数值 value1 赋值给方法的参数值 value2
- 通过 POJO(对象)获取请求参数
3.1 HttpServletRequest
- 登录表单
<form action="${pageContext.request.contextPath}/login/test1" method="get">
用户名: <input type="text" name="username" /> <br />
密 码: <input type="password" name="password" /> <br />
<input type="submit" />
</form>- 控制层
@Controller
@RequestMapping("/login")
public class MyController {
@RequestMapping(value = "/?est1")
public String handler1(HttpServletRequest request) {
System.out.println("处理器1");
// 通过 HttpServletRequest 获取请求参数
String username= request.getParameter("username");
String password = request.getParameter("password");
System.out.println("username:" + username + " password:" + password);
return "success";
}3.2 控制器方法的形参
3.2.1 请求路径参数与方法形参一致
<input type="text" name="username"/>
<input type="password" name="password"/>
public String handler1(String username, String password)注意:控制器方法的形参必须和前端 name 的属性值一致,如控制器方法的形参 username 和 password 要和 input 标签中的 name 属性值 username 和 password 一致
- 举例
<form action="${pageContext.request.contextPath}/login/test1" method="get">
用户名: <input type="text" name="username" /> <br />
密 码: <input type="password" name="password" /> <br />
<input type="submit" />
</form>- 控制层
@Controller
@RequestMapping("/login")
public class MyController {
@RequestMapping(value = "/?est1")
// 通过 形参 获取请求参数
public String handler1(String username, String password) {
System.out.println("处理器1");
System.out.println("username:" + username + " password:" + password);
return "success";
}
}3.2.2 请求路径参数与方法形参不一致
@RequestParam 注解用于将请求参数的数据映射到 Handler 方法(控制器方法)的形参上,相当于给请求参数重命名。如有时有一些特殊情况,前端的 name 属性值与后端 Handler 方法中的形参不一致,这个时候就可以通过 @RequestParam 注解来解决。
- 语法:@RequestParam(value=”参数名”,required=”true|false”,defaultValue=””)
- value:请求中传入参数的名称
- required:该参数是否为必传项,默认为 true,表示该请求路径中必须包含该参数,如果不包含就报错;若设置为 false,则表示该请求路径中不必包含该参数,若没有传输该参数,则注解所标识的形参的值为 null
- defaultValue:设置默认参数值,如果设置了该值,required=true 将失效,自动为 false,如果没有传该参数,就使用默认值
3.3 通过 POJO 获取请求参数(重点)
- POJO 全称“Plain Old Java Object”,意思是“简单 Java 对象”。POJO 的内在含义是指那些没有从任何类继承、也没有实现任何接口,更没有被其它框架侵入的 Java 对象。
- 可以在控制器方法的形参位置设置一个实体类类型的形参,若浏览器传输的请求参数的参数名和实体类中的属性名一致,那么请求参数就会为此属性赋值。
3.3.1 举例:User
- 测试案例:
- // 普通数据:private String username;
- // 对象:private UserInfo userInfo;
- // 数组:private String hobbys[];
- // 列表:private ListString titles;
- // Map:private MapString, Company companys
(1)表单
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/login/test1" method="get">
用户名: <input type="text" name="username" value="赵飞燕" /> <br />
年龄: <input type="text" name="userInfo.age" value="18" /> <br />
身高: <input type="text" name="userInfo.height" value="168" /> <br />
爱好:
<input type="checkbox" name="hobbys" value="追剧" checked="checked" />追剧
<input type="checkbox" name="hobbys" value="画画" checked="checked" />画画
<input
type="checkbox"
name="hobbys"
value="健身"
checked="checked"
/>健身<br />
头衔1: <input type="text" name="titles[0]" value="智慧女神" /> <br />
头衔2: <input type="text" name="titles[1]" value="幸运之神" /> <br />
公司1名称:
<input type="text" name="companys['公司1'].companyName" value="肯德基" />
<br />
公司1市值:
<input type="text" name="companys['公司1'].values" value="12亿" /> <br />
公司2名称:
<input type="text" name="companys['公司2'].companyName" value="黑马" />
<br />
公司2市值:
<input type="text" name="companys['公司2'].values" value="15亿" /> <br />
<input type="submit" />
</form>
</body>
</html>(2)控制层
@Controller
@RequestMapping("/login")
public class MyController {
@RequestMapping(value = "/?est1")
// 通过 形参 获取请求参数
public String handler1(User user) {
String username = user.getUsername();
int age = user.getUserInfo().getAge();
int height = user.getUserInfo().getHeight();
String hobbys[] = user.getHobbys();
List<String> titles = user.getTitles();
Map<String, Company> companys = user.getCompanys();
// 普通参数
System.out.println("用户姓名:" + username);
// 对象
System.out.println("用户年龄:" + age);
System.out.println("用户身高:" + height);
// 数组
System.out.print("用户爱好:");
for(String hobby : hobbys) {
System.out.print(" " + hobby);
}
System.out.println();
// List
System.out.print("称号:");
for(String title : titles) {
System.out.print(" " + title);
}
System.out.println();
// Map
Set<Map.Entry<String, Company>> entries = companys.entrySet();
for (Map.Entry<String, Company> entry : entries) {
System.out.println(entry.getKey() + ":" + entry.getValue().getCompanyName() + "市值:" + entry.getValue().getValues());
}
return "success";
}
}(3)Company 类
public class Company {
private String companyName;
private String values;
// setter、getter 方法省略
}(4)UserInfo 类
public class UserInfo {
private int age;
private int height;
// setter、getter 方法省略
}4.域对象共享数据
在 SpringMVC 中常用的域有以下三个:
- request:数据在当前请求有效,请求转发后有效,重定向无效
- session:数据在关闭浏览器前有效,中途关闭服务器,数据钝化(还在),重启浏览器数据又会活化(还能用)
- application:数据在关闭服务器前有效(关闭浏览器数据还在)
- 注意:pageContext 是用在 jsp 文件中的,但是 jsp 这种文件现在好像过时了,所以呢,就不用这个了
4.1 四种共享 request 域数据
4.1.1 ServletAPI 方式
- 控制层
@Controller
public class DomainController {
/*
* 使用ServletAPI向request域对象共享数据
* */
@RequestMapping("/testRequestByServletAPI")
public String testRequestByServletAPI(HttpServletRequest request){
request.setAttribute("testRequestScope","Hello ServletAPI");
return "success";
}
}- success.html:展示 request 域中对象数据:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>成功页面</title>
</head>
<body>
<h1>跳转成功</h1>
<!--使用thymeleaf模块后,可以直接用${域对象存储的键的名称}取出里面的内容-->
<p th:text="${testRequestScope}" />
</body>
</html>4.1.2 ModelAndView 方式
ModelAndView 有 Model 和 View 的功能
- Model 主要用于向请求域共享数据
- View 主要用于设置视图,实现页面跳转。
举例
@RequestMapping("/testModelAndView") //使用这种方法必须返回ModelAndView对象 public ModelAndView testModelAndView(){ ModelAndView mav = new ModelAndView(); //处理模型数据(向请求域request共享数据) mav.addObject("testRequestScope","Hello ModelAndView"); //设置视图名称(跳转到哪里) mav.setViewName("success"); return mav; }
4.1.3 Model 方式
@RequestMapping("/testModel")
public String testModel(Model model){
model.addAttribute("testRequestScope", "Hello Model");
return "success";
}4.1.4 Map 方式
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map){
map.put("testRequestScope","Hello Map");
return "success";
}4.1.5 ModelMap 方式
@RequestMapping("/testModelMap")
public String testModelMap(ModelMap modelMap){
modelMap.addAttribute("testRequestScope", "Hello ModelMap");
return "success";
}4.2 辨别 Model、ModelMap、Map 的异同
4.2.1 三者本质上都是 BindingAwareModelMap 类型
- 测试输出类名:
@RequestMapping("/testModel")
public String testModel(Model model){
model.addAttribute("testRequestScope", "Hello Model");
System.out.println("Model:"+model.getClass().getName());
return "success";
}
/*
* 使用Map这种就更厉害了,给一个map的形参对象
* 用法类似于之前的Model方法
* 主要通过map对象的每一次put,将数据共享到request域中
* */
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map){
map.put("testRequestScope","Hello Map");
System.out.println("Map:"+map.getClass().getName());
return "success";
}
@RequestMapping("/testModelMap")
public String testModelMap(ModelMap modelMap){
modelMap.addAttribute("testRequestScope", "Hello ModelMap");
System.out.println("ModelMap:"+modelMap.getClass().getName());
return "success";
}- 输出结果:

4.2.2 三者都会返回一个 ModelAndView 对象

4.3 向 session 域共享数据

5.拦截器
5.1 概述
5.1.1 定义
SpringMVC 的处理器—>拦截器,类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。
5.1.2 过滤器和拦截器区别
过滤器:
- 依赖于servlet 容器
- 在实现上基于函数回调,可以对几乎所有请求进行过滤,
- 缺点:一个过滤器实例只能在容器初始化时调用一次。
- 目的:做一些过滤操作,比如:在过滤器中修改字符编码;在过滤器中修改 HttpServletRequest 的一些参数,包括:过滤低俗文字、危险字符等。
拦截器:
- 依赖于web 框架
- 在实现上基于 Java 的反射机制,属于==面向切面编程(AOP)==的一种运用。
- 由于拦截器是基于 web 框架的调用,因此可以使用 Spring 的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个 controller 生命周期之内可以多次调用。
5.2 拦截器使用
5.2.1 自定义拦截器
public class MyHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("MyHandlerInterceptor->preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("MyHandlerInterceptor->postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("MyHandlerInterceptor->afterCompletion");
}
}- 拦截器一个有 3 个回调方法
preHandle:预处理回调方法,实现处理器的预处理(如登录检查),第三个参数为响应的处理器返回值:true 表示继续流程(如调用下一个拦截器或处理器);false 表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过 response 来产生响应;postHandle:后处理回调方法,实现处理器的后处理(但==在渲染视图之前==),此时我们可以通过 modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView 也可能为 null。afterCompletion:整个请求处理完毕回调方法,即在视图渲染完毕时回调,如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于 try-catch-finally 中的 finally,但仅调用处理器执行链中 preHandle 返回 true 的拦截器才会执行 afterCompletion。
5.2.2 controller 层
@Controller
@RequestMapping("/index")
public class LoginControl {
@RequestMapping(value = "/login")
public String login(){
System.out.println("LoginControl->login");
return "login";
}
@RequestMapping(value = "/test")
@ResponseBody
public String test(){
System.out.println("LoginControl->test");
return "test";
}
}5.2.3 配置拦截器
- 拦截所有 Controller 类里的所有处理方法
(1)基于 xml 配置
- 在 Spring 的配置文件中添加如下配置
<!-- 配置拦截器:-->
<mvc:interceptors>
<!-- 会拦截所有Controller类里的所有处理方法 -->
<bean class="com.lucas.controller.MyHandlerInterceptor"></bean>
</mvc:interceptors>- 拦截指定请求路径
<!-- 配置拦截器:-->
<mvc:interceptors>
<!-- 可以配置多个拦截器 也可以配置bean 拦截器 拦截所有请求 -->
<mvc:interceptor>
<!-- 只拦截该路径 -->
<mvc:mapping path="/**/login"/>
<!-- 会拦截所有Controller类里的所有处理方法 -->
<bean class="com.lucas.controller.MyHandlerInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>(2)基于注解配置
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//创建自定义的拦截器
MyHandlerInterceptor interceptor = new MyHandlerInterceptor();
//添加拦截器
registry.addInterceptor(MyHandlerInterceptor)
//添加需要拦截的路径
.addPathPatterns("");
}
}5.2.4 测试
- 请求路径:

- 测试结果:

6.异常处理
Spring MVC 有以下 3 种处理异常的方式:
- 使用 Spring MVC 提供的简单异常处理器 SimpleMappingExceptionResolver。
- 实现 Spring 的异常处理接口 HandlerExceptionResolver,自定义自己的异常处理器。
- 使用 @ExceptionHandler 注解实现局部异常处理
6.1 @ExceptionHandler 实现局部异常处理
局部异常处理仅能处理指定 Controller 中的异常。
注意:@ExceptionHandler 不是加在产生异常的方法上,而是加在处理异常的方法上。
@ExceptionHandler 注解定义的方法优先级问题:例如发生的是 NullPointerException,但是声明的异常有 RuntimeException 和 Exception,这时候会根据异常的最近继承关系==找到继承深度最浅的那个@ExceptionHandler 注解方法==,即标记了 RuntimeException 的方法。
例子:
定义一个处理过程中可能会存在异常情况的 submit 方法,当 i=0 时会产生算术运算异常,在同一个类中定义处理异常的方法 controllerExceptionHandler,捕获运算异常。
@Controller @RequestMapping public class ExceptionController { @RequestMapping("/submit") // 抛错方法 public String submit(HttpServletRequest req, HttpServletResponse resp) throws Exception { String num = req.getParameter("num"); System.out.println(10 / Integer.valueOf(num)); return "success"; } @ExceptionHandler({ArithmeticException.class}) //捕获运算异常 public String controllerExceptionHandler(Exception e) { System.out.println("打印错误信息 ===> ArithmeticException:" + e); // 跳转到指定页面 return "error"; } }
6.2 HandlerExceptionResolver——处理全局异常
Spring MVC 通过 HandlerExceptionResolver 处理程序异常,包括处理器异常、数据绑定异常以及控制器执行时发生的异常。HandlerExceptionResolver 仅有一个接口方法,源码如下。
public interface HandlerExceptionResolver {
ModelAndView resolveException(HttpServletRequest var1,
HttpServletResponse var2,
Object var3,
Exception var4);
}发生异常时,Spring MVC 会调用 resolveException() 方法,并转到 ModelAndView 对应的视图中,返回一个异常报告页面反馈给用户。
创建一个 HandlerExceptionResolver 接口的实现类 MyExceptionHandler,代码如下
@Component public class MyExceptionHandler implements HandlerExceptionResolver { public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) { Map<String, Object> model = new HashMap<String, Object>(); model.put("errorMessage", "程序运行出错"); //根据不同错误转向不同页面(统一处理),即异常与View的对应关系 if (e instanceof ArithmeticException) { return new ModelAndView("error", model); } return new ModelAndView("other_error", model); } }
6.3 SimpleMappingExceptionResolver——处理全局异常
全局异常处理可使用 SimpleMappingExceptionResolver 来实现。它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常。
6.3.1 基于 xml 配置
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!-- 定义默认的异常处理页面,当该异常类型注册时使用 -->
<property name="defaultErrorView" value="other_error"></property>
<!-- 定义异常处理页面用来获取异常信息的变量名,默认名为exception -->
<property name="exceptionAttribute" value="errorMessage"></property>
<!-- 定义需要特殊处理的异常,用类名或完全路径名作为key,异常页名作为值 -->
<property name="exceptionMappings">
<props>
<prop key="ArithmeticException">error</prop>
<!-- 在这里还可以继续扩展对不同异常类型的处理 -->
</props>
</property>
</bean>6.4 @ControllerAdvice + @ExceptionHandler
使用@ControllerAdvice 和@ExceptionHandler 可以全局控制异常,使业务逻辑和异常处理分隔开。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MyException.class)
@ResponseBody
public ResultBean handleMyException(MyException e){
System.out.println("handleMyException....");
return new ResultBean(e.getErrorEnum().getCode(),e.getErrorEnum().getMsg());
}
}
public class MyException extends RuntimeException {
private ErrorEnum errorEnum;
public MyException(ErrorEnum errorEnum){
this.errorEnum = errorEnum;
}
public ErrorEnum getErrorEnum() {
return errorEnum;
}
}7.上传/下载文件
SpringMVC 上下文中默认没有装配 MultipartResolver,因此默认情况下其不能处理文件上传工作。如果想使用 Spring 的文件上传功能,则需要在上下文中配置 MultipartResolver。
7.1 前端表单
前端表单要求:为了能上传文件,必须将表单的method设置为POST,并将enctype设置为multipart/form-data。只有在这样的情况下,浏览器才会把用户选择的文件以二进制数据发送给服务器。
<form action="" enctype="multipart/form-data" method="post">
<input type="file" name="file" />
<input type="submit" />
</form>表单中 enctype 属性的详细说明:
application/x-www=form-urlencoded:默认方式,只处理表单域中的 value 属性值,采用这种编码方式的表单会将表单域中的值处理成 URL 编码方式。multipart/form-data:这种编码方式会以二进制流的方式来处理表单数据,这种编码方式会把文件域指定文件的内容也封装到请求参数中,不会对字符编码。text/plain:除了把空格转换为 “+” 号外,其他字符都不做编码处理,这种方式适用直接通过表单发送邮件。
一旦设置了enctype 为 multipart/form-data,浏览器即会采用二进制流的方式来处理表单数据,而对于文件上传的处理则涉及在服务器端解析原始的 HTTP 响应。在 2003 年,Apache Software Foundation 发布了开源的 Commons FileUpload 组件,其很快成为 Servlet/JSP 程序员上传文件的最佳选择。
- Servlet3.0 规范已经提供方法来处理文件上传,但这种上传需要在 Servlet 中完成。而 Spring MVC 则提供了更简单的封装。
- Spring MVC 为文件上传提供了直接的支持,这种支持是用即插即用的 MultipartResolver 实现的。
- Spring MVC 使用 Apache Commons FileUpload 技术实现了一个 MultipartResolver 实现类:CommonsMultipartResolver。因此,SpringMVC 的文件上传还需要依赖 Apache Commons FileUpload 的组件。
7.2 文件上传
【MultipartResolver】用于处理文件上传。当收到请求时,DispatcherServlet 的 checkMultipart() 方法会调用 MultipartResolver 的 isMultipart() 方法判断请求中【是否包含文件】。如果请求数据中包含文件,则调用 MultipartResolver 的 resolveMultipart() 方法对请求的数据进行解析,然后==将文件数据解析成 MultipartFile 并封装在 MultipartHttpServletRequest (继承了 HttpServletRequest) 对象==中,最后传递给 Controller。
DispatcherServlet 的核心方法中第一句就是如下的代码:
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
...注意: MultipartResolver 默认不开启,需要手动开启。
7.2.1 前端页面
<form action="/upload" enctype="multipart/form-data" method="post">
<input type="file" name="file" />
<input type="submit" value="upload" />
</form>7.2.2 导入依赖
- 注意:导入这个【commons-fileupload】jar 包,Maven 会自动帮我们导入它的依赖包【commons-io】
<!--文件上传-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>7.2.3 配置 bean:multipartResolver
注意: 这个 bena 的 id 必须为:multipartResolver , 否则上传文件会报 400 的错误!
<!--文件上传配置-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 请求的编码格式,必须和jSP的pageEncoding属性一致,以便正确读取表单的内容,默认为ISO-8859-1 -->
<property name="defaultEncoding" value="utf-8"/>
<!-- 上传文件大小上限,单位为字节(10485760=10M) -->
<property name="maxUploadSize" value="10485760"/>
<property name="maxInMemorySize" value="40960"/>
</bean>- CommonsMultipartFile 的常用方法:
String getOriginalFilename():获取上传文件的原名InputStream getInputStream():获取文件流void transferTo(File dest):将上传文件保存到一个目录文件中
7.2.4 controller 层
(1)测试类
package com.wang.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
@Controller
public class FileController {
//@RequestParam("file") 将name=file控件得到的文件封装成CommonsMultipartFile 对象
//批量上传CommonsMultipartFile则为数组即可
@RequestMapping("/upload")
public String fileUpload(@RequestParam("file") CommonsMultipartFile file , HttpServletRequest request) throws IOException {
//获取文件名 : file.getOriginalFilename();
String uploadFileName = file.getOriginalFilename();
//如果文件名为空,直接回到首页!
if ("".equals(uploadFileName)){
return "redirect:/index.jsp";
}
System.out.println("上传文件名 : "+uploadFileName);
//上传路径保存设置
String path = request.getServletContext().getRealPath("/upload");
//如果路径不存在,创建一个
File realPath = new File(path);
if (!realPath.exists()){
realPath.mkdir();
}
System.out.println("上传文件保存地址:"+realPath);
InputStream is = file.getInputStream(); //文件输入流
OutputStream os = new FileOutputStream(new File(realPath,uploadFileName)); //文件输出流
//读取写出
int len=0;
byte[] buffer = new byte[1024];
while ((len=is.read(buffer))!=-1){
os.write(buffer,0,len);
os.flush();
}
os.close();
is.close();
return "redirect:/index.jsp";
}
}(2)保存文件
采用 file.Transto 来保存上传的文件
/*
* 采用file.Transto 来保存上传的文件
*/
@RequestMapping("/upload2")
public String fileUpload2(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException {
//上传路径保存设置
String path = request.getServletContext().getRealPath("/upload");
File realPath = new File(path);
if (!realPath.exists()){
realPath.mkdir();
}
//上传文件地址
System.out.println("上传文件保存地址:"+realPath);
//通过CommonsMultipartFile的方法直接写文件(注意这个时候)
file.transferTo(new File(realPath +"/"+ file.getOriginalFilename()));
return "redirect:/index.jsp";
}7.3 文件下载
一共有两种文件下载方法:
- 直接向 response 的输出流中写入对应的文件流
- 使用 ResponseEntity<byte[]>来向前端返回文件
7.3.1 传统方式
- 设置 response 响应头
- 读取文件 — InputStream
- 写出文件 — OutputStream
- 执行操作
- 关闭流 (先开后关)
@GetMapping("/download1")
@ResponseBody
public R download1(HttpServletResponse response){
FileInputStream fileInputStream = null;
ServletOutputStream outputStream = null;
try {
// 这个文件名是前端传给你的要下载的图片的id
// 然后根据id去数据库查询出对应的文件的相关信息,包括url,文件名等
String fileName = "wang.jpg";
//1、设置response 响应头,处理中文名字乱码问题
response.reset(); //设置页面不缓存,清空buffer
response.setCharacterEncoding("UTF-8"); //字符编码
response.setContentType("multipart/form-data"); //二进制传输数据
//设置响应头,就是当用户想把请求所得的内容存为一个文件的时候提供一个默认的文件名。
//Content-Disposition属性有两种类型:inline 和 attachment
//inline :将文件内容直接显示在页面
//attachment:弹出对话框让用户下载具体例子:
response.setHeader("Content-Disposition",
"attachment;fileName="+ URLEncoder.encode(fileName, "UTF-8"));
// 通过url获取文件
File file = new File("D:/upload/"+fileName);
//2、 读取文件--输入流
fileInputStream = new FileInputStream(file);
//3、 写出文件--输出流
outputStream = response.getOutputStream();
byte[] buffer = new byte[1024];
int len;
//4、执行写出操作
while ((len = fileInputStream.read(buffer)) != -1){
outputStream.write(buffer,0,len);
outputStream.flush();
}
return R.success();
} catch (IOException e) {
e.printStackTrace();
return R.fail();
}finally {
if( fileInputStream != null ){
try {
// 5、关闭输入流
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if( outputStream != null ){
try {
// 5、关闭输出流
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}7.3.2 ResponseEntity 方式
@GetMapping("/download2")
public ResponseEntity<byte[]> download2(){
try {
String fileName = "wang.jpg";
byte[] bytes = FileUtils.readFileToByteArray(new File("D:/upload/"+fileName));
HttpHeaders headers=new HttpHeaders();
// Content-Disposition就是当用户想把请求所得的内容存为一个文件的时候提供一个默认的文件名。
headers.set("Content-Disposition","attachment;filename="+ URLEncoder.encode(fileName, "UTF-8"));
headers.set("charsetEncoding","utf-8");
headers.set("content-type","multipart/form-data");
ResponseEntity<byte[]> entity=new ResponseEntity<>(bytes,headers, HttpStatus.OK);
return entity;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}8.Spring MVC 的核心组件
- DispatcherServlet:前端控制器,是整个流程控制的核心,控制其他组件的执行,进行统一调度,降低组件之间的耦合性,相当于总指挥。
- Handler:处理器,完成具体的业务逻辑,相当于 Servlet 或 Controller。
- HandlerMapping:DispatcherServlet 接收到请求之后,通过 HandlerMapping 将不同的请求映射到不同的 Handler。
- HandlerInterceptor:处理器拦截器,是一个接口,如果需要完成一些拦截处理,可以实现该接口。
- HandlerExecutionChain:处理器执行链,包括两部分内容:Handler 和 HandlerInterceptor(系统会有一个默认的 HandlerInterceptor,如果需要额外设置拦截,可以添加拦截器)。
- HandlerAdapter:处理器适配器,Handler 执行业务方法之前,需要进行一系列的操作,包括表单数据的验证、数据类型的转换、将表单数据封装到 JavaBean 等,这些操作都是由 HandlerApater 来完成,开发者只需将注意力集中业务逻辑的处理上,DispatcherServlet 通过 HandlerAdapter 执行不同的 Handler。
- ModelAndView:装载了模型数据和视图信息,作为 Handler 的处理结果,返回给 DispatcherServlet。
- ViewResolver:视图解析器,DispatcheServlet 通过它将逻辑视图解析为物理视图,最终将渲染结果响应给客户端
8.Spring MVC 的工作流程

- 发起请求:客户端通过 HTTP 协议向服务器发起请求。
- 前端控制器:这个请求会先到前端控制器 ,它是整个流程的入口点,负责接收请求并将其分发给相应的处理器。
- 处理器映射器:DispatcherServlet 调用 HandlerMapping 来确定哪个 Controller 应该处理这个请求。通常会根据请求的 URL 来确定。
- 处理器适配器:一旦找到目标 Controller,DispatcherServlet 会使用 HandlerAdapter 来调用 Controller 的处理方法。
- 执行处理器:Controller 处理请求,处理完后返回一个 ModelAndView 对象,其中包含模型数据和逻辑视图名。
- 视图解析器:DispatcherServlet 接收到 ModelAndView 后,会使用 ViewResolver 来解析视图名称,找到具体的视图页面。
- 渲染视图:视图使用模型数据渲染页面,生成最终的页面内容。
- 响应结果:DispatcherServlet 将视图结果返回给客户端。
10.过滤器和拦截器的区别
1、什么是过滤器?
过滤器 Filter 基于 Servlet 实现,过滤器的主要应用场景是对字符编码、跨域等问题进行过滤。Servlet 的工作原理是拦截配置好的客户端请求,然后对 Request 和 Response 进行处理。Filter 过滤器随着 web 应用的启动而启动,只初始化一次
三个方法:
- init:在容器启动时调用初始化方法,只会初始化一次
- doFilter:每次请求都会调用 doFilter 方法,通过 FilterChain 调用后续的方法
- destroy:当容器销毁时,执行 destory 方法,只会被调用一次
2、什么是拦截器?
拦截器是 SpringMVC 中实现的一种基于 Java 反射(动态代理)机制的方法增强工具,拦截器的实现是继承 HandlerInterceptor 接口,并实现接口的 preHandle、postHandle 和 afterCompletion 方法。
三个方法:
- preHandle:请求方法前置拦截,该方法会在 Controller 处理之前进行调用
- postHandle:preHandle 返回结果为 true 时,在 Controller 方法执行之后,视图渲染之前被调用
- afterCompletion:在 preHandle 返回 ture,并且整个请求结束之后,执行该方法
3、过滤器和拦截器的区别?
相同点:
- 拦截器与过滤器都是体现了AOP 的思想,对方法实现增强,都可以拦截请求方法
- 拦截器和过滤器都可以通过Order 注解设定执行顺序
不同点:
- 过滤器属于 Servlet 级别,拦截器属于 Spring 级别 Filter 是在 javax.servlet 包中定义的,要依赖于网络容器,因此只能在 web 项目中使用
- 过滤器和拦截器的执行顺序不同:首先当一个请求进入 Servlet 之前,过滤器的 doFilter 方法进行过滤,进入 Servlet 容器之后,执行 Controller 方法之前,拦截器的 preHandle 方法进行拦截,执行 Controller 方法之后,视图渲染之前,拦截器的 postHandle 方法进行拦截,请求结束之后,执行拦截器的 aferCompletion 方法
版权所有
版权归属:haipeng-lin