参考视频:SpringMVC教程,SpringMVC从零到精通,老杜SpringMVC,动力节点SpringMVC

第8章 文件上传与下载

1. 文件上传

使用SpringMVC6版本,不需要添加以下依赖:

1
2
3
4
5
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.5</version>
</dependency>

1、前端页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>文件上传</title>
</head>
<body>

<!--文件上传表单-->
<form th:action="@{/file/up}" method="post" enctype="multipart/form-data">
文件:<input type="file" name="fileName"/><br>
<input type="submit" value="上传">
</form>

</body>
</html>

PS:form表单采用post请求,enctype是multipart/form-data,并且上传组件是:type="file"

2、web.xml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!--前端控制器-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<multipart-config>
<!--设置单个支持最大文件的大小-->
<max-file-size>102400</max-file-size>
<!--设置整个表单所有文件上传的最大值-->
<max-request-size>102400</max-request-size>
<!--设置最小上传文件大小-->
<file-size-threshold>0</file-size-threshold>
</multipart-config>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

PS:在DispatcherServlet配置时,添加 multipart-config 配置信息。(这是Spring6,如果是Spring5,则不是这样配置,而是在springmvc.xml文件中配置:CommonsMultipartResolver)SpringMVC6中把这个类已经删除了。废弃了。

3、Controller中的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Controller
public class FileController {

@PostMapping(value = "/file/up")
public String fileUp(@RequestParam("fileName") MultipartFile multipartFile, HttpServletRequest request) throws IOException {
String name = multipartFile.getName();
System.out.println(name);
// 获取文件名
String originalFilename = multipartFile.getOriginalFilename();
System.out.println(originalFilename);
// 将文件存储到服务器中
// 获取输入流
InputStream in = multipartFile.getInputStream();
// 获取上传之后的存放目录
File file = new File(request.getServletContext().getRealPath("/upload"));
// 如果服务器目录不存在则新建
if (!file.exists()) {
file.mkdirs();
}
// 开始写
//BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file.getAbsolutePath() + "/" + originalFilename));
// 可以采用UUID来生成文件名,防止服务器上传文件时产生覆盖
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file.getAbsolutePath() + "/" + UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."))));
byte[] bytes = new byte[1024 * 100];
int readCount = 0;
while ((readCount = in.read(bytes)) != -1) {
out.write(bytes, 0, readCount);
}
// 刷新缓冲流
out.flush();
// 关闭流
in.close();
out.close();

return "ok";
}

}

4、最终测试结果:

测试文件上传

文件上传成功

查看上传的文件

建议:上传文件时,文件起名采用UUID。以防文件覆盖。

2. 文件下载

1、index.html

1
2
<!--文件下载-->
<a th:href="@{/download}">文件下载</a>

2、文件下载核心程序,使用ResponseEntity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@GetMapping("/download")
public ResponseEntity<byte[]> downloadFile(HttpServletResponse response, HttpServletRequest request) throws IOException {
File file = new File(request.getServletContext().getRealPath("/upload") + "/muyoukule.jpg");
// 创建响应头对象
HttpHeaders headers = new HttpHeaders();
// 设置响应内容类型
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
// 设置下载文件的名称
headers.setContentDispositionFormData("attachment", file.getName());

// 下载文件
ResponseEntity<byte[]> entity = new ResponseEntity<byte[]>(Files.readAllBytes(file.toPath()), headers, HttpStatus.OK);
return entity;
}

效果:

测试文件下载

查看下载的文件

第9章 异常处理器

1. 什么是异常处理器

Spring MVC在处理器方法执行过程中出现了异常,可以采用异常处理器进行应对。
一句话概括异常处理器作用:处理器方法执行过程中出现了异常,跳转到对应的视图,在视图上展示友好信息。

SpringMVC为异常处理提供了一个接口:HandlerExceptionResolver

1
2
3
4
5
6
7
8
9
10
package org.springframework.web.servlet;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;

public interface HandlerExceptionResolver {
@Nullable
ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}

核心方法是:resolveException

该方法用来编写具体的异常处理方案。返回值ModelAndView,表示异常处理完之后跳转到哪个视图。

HandlerExceptionResolver 接口有两个常用的默认实现:

  • DefaultHandlerExceptionResolver
  • SimpleMappingExceptionResolver

2. 默认的异常处理器

DefaultHandlerExceptionResolver 是默认的异常处理器。

核心方法:

DefaultHandlerExceptionResolver的核心方法

当请求方式和处理方式不同时,DefaultHandlerExceptionResolver的默认处理态度是:

DefaultHandlerExceptionResolver面对请求和处理方式不同的默认处理态度

3. 自定义的异常处理器

自定义异常处理器需要使用:SimpleMappingExceptionResolver

自定义异常处理机制有两种语法:

  • 通过XML配置文件
  • 通过注解

3.1 配置文件方式

1
2
3
4
5
6
7
8
9
10
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--用来指定出现异常后,跳转的视图-->
<prop key="java.lang.Exception">tip</prop>
</props>
</property>
<!--将异常信息存储到request域,value属性用来指定存储时的key。-->
<property name="exceptionAttribute" value="e"/>
</bean>

在视图页面上展示异常信息:

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>出错了</title>
</head>
<body>
<h1>出错了,请联系管理员!</h1>
<div th:text="${e}"></div>
</body>
</html>

自定义的异常处理器

3.2 注解方式

1
2
3
4
5
6
7
8
9
@ControllerAdvice
public class ExceptionController {

@ExceptionHandler
public String tip(Exception e, Model model){
model.addAttribute("e", e);
return "tip";
}
}

第10章 拦截器

1. 拦截器概述

拦截器(Interceptor)类似于过滤器(Filter)

Spring MVC的拦截器作用是在请求到达控制器之前或之后进行拦截,可以对请求和响应进行一些特定的处理。

拦截器可以用于很多场景下:

  • 登录验证:对于需要登录才能访问的网址,使用拦截器可以判断用户是否已登录,如果未登录则跳转到登录页面。
  • 权限校验:根据用户权限对部分网址进行访问控制,拒绝未经授权的用户访问。
  • 请求日志:记录请求信息,例如请求地址、请求参数、请求时间等,用于排查问题和性能优化。
  • 更改响应:可以对响应的内容进行修改,例如添加头信息、调整响应内容格式等。

拦截器和过滤器的区别在于它们的作用层面不同。

  • 过滤器更注重在请求和响应的流程中进行处理,可以修改请求和响应的内容,例如设置编码和字符集、请求头、状态码等。
  • 拦截器则更加侧重于对控制器进行前置或后置处理,在请求到达控制器之前或之后进行特定的操作,例如打印日志、权限验证等。

Filter、Servlet、Interceptor、Controller的执行顺序:

Filter、Servlet、Interceptor、Controller的执行顺序

2. 拦截器的创建与基本配置

2.1 定义拦截器

实现 org.springframework.web.servlet.HandlerInterceptor 接口,共有三个方法可以进行选择性的实现:

  • preHandle:处理器方法调用之前执行(只有该方法有返回值,返回值是布尔类型,true放行,false拦截。)
  • postHandle:处理器方法调用之后执行
  • afterCompletion:渲染完成后执行

2.2 拦截器基本配置

在springmvc.xml文件中进行如下配置:

第一种方式:

1
2
3
<mvc:interceptors>
<bean class="com.muyoukule.interceptors.Interceptor1"/>
</mvc:interceptors>

第二种方式:

1
2
3
<mvc:interceptors>
<ref bean="interceptor1"/>
</mvc:interceptors>

第二种方式的前提:

  • 前提1:包扫描

拦截器基本配置前提1

  • 前提2:使用 @Component 注解进行标注

拦截器基本配置前提1

PS:对于这种基本配置来说,拦截器是拦截所有请求的。

2.3 拦截器部分源码分析

2.3.1 方法执行顺序的源码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class DispatcherServlet extends FrameworkServlet {
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 调用所有拦截器的 preHandle 方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 调用处理器方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 调用所有拦截器的 postHandle 方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
// 处理视图
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
// 渲染页面
render(mv, request, response);
// 调用所有拦截器的 afterCompletion 方法
mappedHandler.triggerAfterCompletion(request, response, null);
}
}

2.3.2 拦截与放行的源码分析

1
2
3
4
5
6
7
8
9
public class DispatcherServlet extends FrameworkServlet {
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 调用所有拦截器的 preHandle 方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
// 如果 mappedHandler.applyPreHandle(processedRequest, response) 返回false,以下的return语句就会执行
return;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class HandlerExecutionChain {
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < this.interceptorList.size(); i++) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
// 如果 interceptor.preHandle(request, response, this.handler) 返回 false,以下的 return false;就会执行。
return false;
}
this.interceptorIndex = i;
}
return true;
}
}

3. 拦截器的高级配置

采用以上基本配置方式,拦截器是拦截所有请求路径的。如果要针对某些路径进行拦截,某些路径不拦截,可以采用高级配置:

1
2
3
4
5
6
7
8
9
10
<mvc:interceptors>
<mvc:interceptor>
<!--拦截所有路径-->
<mvc:mapping path="/**"/>
<!--除 /test 路径之外-->
<mvc:exclude-mapping path="/test"/>
<!--拦截器-->
<ref bean="interceptor1"/>
</mvc:interceptor>
</mvc:interceptors>

以上的配置表示,除 /test 请求路径之外,剩下的路径全部拦截。

4. 拦截器的执行顺序

4.1 执行顺序

4.1.1 如果所有拦截器preHandle都返回true

按照springmvc.xml文件中配置的顺序,自上而下调用 preHandle:

1
2
3
4
<mvc:interceptors>
<ref bean="interceptor1"/>
<ref bean="interceptor2"/>
</mvc:interceptors>

执行顺序:

所有拦截器preHandle都返回true执行顺序

4.1.2 如果其中一个拦截器preHandle返回false

1
2
3
4
<mvc:interceptors>
<ref bean="interceptor1"/>
<ref bean="interceptor2"/>
</mvc:interceptors>

如果interceptor2preHandle返回false,执行顺序:

其中一个拦截器preHandle返回false执行顺序

规则:只要有一个拦截器preHandle返回false,任何postHandle都不执行。但返回false的拦截器的前面的拦截器按照逆序执行afterCompletion

4.2 源码分析

DispatcherServletHandlerExecutionChain 的部分源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class DispatcherServlet extends FrameworkServlet {
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 按照顺序执行所有拦截器的preHandle方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 执行处理器方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 按照逆序执行所有拦截器的 postHanle 方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
// 处理视图
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
// 渲染视图
render(mv, request, response);
// 按照逆序执行所有拦截器的 afterCompletion 方法
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class HandlerExecutionChain {
// 顺序执行 preHandle
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < this.interceptorList.size(); i++) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
// 如果其中一个拦截器preHandle返回false
// 将该拦截器前面的拦截器按照逆序执行所有的afterCompletion
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
return true;
}
// 逆序执行 postHanle
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
interceptor.postHandle(request, response, this.handler, mv);
}
}
// 逆序执行 afterCompletion
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}