📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍
文章目录
- 写在前面的话
- 搭建 SpringMVC
- Step1、创建 Maven 项目
- Step2、引入 Maven 依赖
- Step3、配置 web.xml
- Step4、创建 spring-mvc.xml
- Step5、编写控制器类
- Step6、部署 Tomcat
- 搭建 SpringMVC 复盘
- JSON 类型数据传递
- 关于静态资源处理
- 对比 SpringBoot
- 知识补充
- @RequestMapping
- 获取请求参数
- 报文信息转换
- 文件上传下载
- 拦截器的配置
- 总结陈词
写在前面的话
博主所在公司早期后端采用 SSM(Spring + SpringMVC + MyBatis),2020年进行了微服务拆分,顺带技术栈升级,后端调整为 SpringCloud 和 SpringBoot,深刻体会到了SpringBoot为我们带来的遍历。
经常会听到新人之间在议论,“还是新框架好,旧框架已过时了,不要浪费时间学习旧框架”,那么 SpringBoot 是否完全取代了 SSM 技术栈呢?
那么,有了 SpringBoot,是否还需要学习 SpringMVC?
毋庸置疑,SpringBoot 的出现确实简化了 SpringMVC 的配置,但 SpringMVC 作为 Spring 生态中重要的组成部分,其底层原理和核心概念依然值得深入学习。
- 更深入理解:SpringBoot 虽然简化了配置,但它本质上还是基于 SpringMVC 的。深入理解 SpringMVC 可以让你更好地掌握 SpringBoot 的底层原理,从而更灵活地解决问题。
- 定制化开发:SpringBoot 提供了大量的自动配置,但有时我们可能需要进行高度定制化的开发。这时,对 SpringMVC 的底层原理的掌握就显得尤为重要。
- 面试:很多公司的面试都会考察 SpringMVC 的基础知识,深入了解 SpringMVC 可以帮助你更好地应对面试。
- 旧项目维护:如果需要维护一些基于 SpringMVC 的老旧项目,了解 SpringMVC 的知识也是必不可少的。
总结一下:
SpringBoot 和 SpringMVC 并不是对立的关系,而是相辅相成的。初学者建议优先学习 SpringBoot,可以让你快速上手,提高开发效率。接着深入学习 SpringMVC 的底层原理,更深入得使用其定制化能力,从而写出更高质量的代码。
接下来的本系列文章,将从 SpringMVC 的搭建、常见用法、源码分析、扩展点分析、企业实战等方面展开。
好,先开始基础篇的介绍!
搭建 SpringMVC
Step1、创建 Maven 项目
开发环境:
IDE:IDEA 2022
构建工具:Maven 3.x
服务器:Tomcat 8
Spring版本:5.3.15
创建 Maven 项目:
使用 IDEA 创建一个Maven 子模块, 可以使用模板方式创建剩下不少功夫,具体如下图:
先展示Demo全貌:
圈出来的文件也是下面几个步骤需要交互的,后续就不一一截图了。
Step2、引入 Maven 依赖
<dependencies>
<!-- SpringMVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.15</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- ServletAPI -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- Spring5和Thymeleaf整合包 -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
</dependencies>
引入后查看 Maven 依赖:
Step3、配置 web.xml
【知识扫盲】
web.xml 是一个传统的 Java Web 应用的部署描述文件,它用于配置 Web 应用的一些基本信息,比如:监听器、过滤器、过滤器、Servlet、欢迎页等。
在 Spring MVC 项目中,web.xml 主要负责:
1、加载 Spring 容器,通过 ContextLoaderListener 监听器加载 Spring 配置文件(applicationContext.xml 等),初始化 Spring 容器。
2、配置前端控制器 DispatcherServlet,DispatcherServlet 是 Spring MVC 的核心,负责拦截请求,分发给相应的 Controller 处理。
Tomcat 容器启动的时候,会加载 web.xml,这是一个复杂的过程,这边先不展开。
web.xml 内部元素的加载顺序通常是:context-param -> listener -> filter -> servlet。
值得一提的是,Spring Boot 内嵌了 Tomcat,不需要额外的部署描述文件。
【关键代码】
<!-- 配置SpringMVC的前端控制器,对浏览器发送的请求统一进行处理 -->
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 通过初始化参数指定SpringMVC配置文件的位置和名称 -->
<init-param>
<!-- contextConfigLocation为固定值 -->
<param-name>contextConfigLocation</param-name>
<!-- 使用classpath:表示从类路径查找配置文件,例如maven工程中的src/main/resources -->
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<!--
作为框架的核心组件,在启动过程中有大量的初始化操作要做
而这些操作放在第一次请求时才执行会严重影响访问速度
因此需要通过此标签将启动控制DispatcherServlet的初始化时间提前到服务器启动时
-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<!--
设置springMVC的核心控制器所能处理的请求的请求路径
/所匹配的请求可以是/login或.html或.js或.css方式的请求路径
但是/不能匹配.jsp请求路径的请求
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
Step4、创建 spring-mvc.xml
web.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-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- 自动扫描包 -->
<context:component-scan base-package="com.lw.mvc.web"/>
</beans>
Step5、编写控制器类
如下所示,一个方法返回视图,一个方法返回数据。
Tips:index.jsp是Maven模板自带的,如果要显示中文,注意加上编码设置。
Tips:目前基本都是前后端分离模式,因此主要关注返回数据的场景。
@Controller
public class HelloController {
@RequestMapping("/")
public String index() {
return "index";
}
@ResponseBody
@RequestMapping(value = {"/data", "/test"})
public String data() {
return "12345";
}
}
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<html>
<body>
<h2>Hello 战神!</h2>
</body>
</html>
Step6、部署 Tomcat
这步比较简单,直接看图:
点击启动后,会自动访问:http://localhost:8085/study_mvc/
修改访问地址:http://localhost:8085/study_mvc/data
也可以正常输出数据,搞定收工!
搭建 SpringMVC 复盘
JSON 类型数据传递
接下来,测试一下JSON类型数据的传递。
先定义一个实体类,并修改控制层代码,@RequestBody 接收参数,@ResponseBody 响应结果,如下所示。
public class Student implements Serializable {
private static final long serialVersionUID = 3227472127782930834L;
private Integer id;
private String name;
private String email;
private Integer age;
...
}
@Controller
public class HelloController {
@ResponseBody
@RequestMapping(value = "stu")
public Student stu(@RequestBody Student student) {
student.setName("战神");
return student;
}
}
启动代码试试看,Postman测试如下:
解决方案也简单,引入一下依赖:
<!-- jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
重启一下服务,再试试:
Tips:很多文章介绍需要额外的步骤,其实引入依赖才是核心。
关于静态资源处理
背景前提:
如果你的 DispatcherServlet 拦截的是 *.do 这样的URL,就不存在访问不到静态资源的问题,但要多书写.do。
如果你的 DispatcherServlet 拦截的是 /,代表拦截了所有的请求,同时对 .js、.jpg 的访问也就被拦截了。
但 DispatcherServlet 不具备静态文件的处理能力,所以需要额外的配置。
映射规则参考一下知识延伸 - url-pattern 映射规则
目前有三种方案,那么如何选择,主要需求是可以访问静态文件,不要报404,当然效率也要注意。
方案一:激活Tomcat的defaultServlet来处理静态文件(推荐)
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.jpg</url-pattern>
<url-pattern>*.gif</url-pattern>
<url-pattern>*.png</url-pattern>
<url-pattern>*.js</url-pattern>
<url-pattern>*.css</url-pattern>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
说明:要配置多个,每种文件配置一个,并且要写在 DispatcherServlet 的前面,让 defaultServlet 先拦截,这样就不会进入Spring了,我想性能是最好的吧。
补充说明:
这里的 default,其实是在$tomcat/conf/web.xml文件中配置的:
该 web.xml 的执行优先级低于项目自带的 web.xml,但如果项目的没注册这个Servlet,则以 Tomcat 的为准。
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
如果某个 Servlet 的映射路径仅仅是一个/,那么这个Servlet就成为当前web应用的默认Servlet它可以处理其它所有Servlet都不处理的请求。开发时最好不要出现这种情况,否则web应用的静态资源无法被访问,从而被此Servlet 拦截处理。
<!-- ================配置SpringMVC核心调度器================ -->
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:mvc-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
这种情况会覆盖由 tomcat 提供的默认的 Servlet,该Serlvet是为静态资源提供访问服务的。
所有要额外配置其他高优先级处理器,也就是按方案一的设置,指定类型的文件,访问的时候,先使用默认Servlet处理,找不到才使用 MVC 的。
方案二: 在spring3.0.4以后版本提供了mvc:resources **
mvc:resources 的使用方法:
<mvc:resources location=“/images/” mapping="/images/“/>
<mvc:resources location=”/js/" mapping=“/js/“/>
<mvc:resources location=”/css/" mapping="/css/”/>
/images/**映射到 ResourceHttpRequestHandler进行处理,location指定静态资源的位置,可以是web application根目录下、jar包里面,这样可以把静态资源压缩到jar包中。cache-period 可以使得静态资源进行web cache
如果出现下面的错误,可能是没有配置<mvc:annotation-driven />的原因。
报错WARNING: No mapping found for HTTP request with URI [/mvc/user/findUser/lisi/770] in DispatcherServlet with name ‘springMVC’
使用mvc:resources/元素,把mapping的URI注册到SimpleUrlHandlerMapping的urlMap中,key为mapping的URI pattern值,而value为ResourceHttpRequestHandler,
这样就巧妙的把对静态资源的访问由HandlerMapping转到ResourceHttpRequestHandler处理并返回,所以就支持classpath目录,jar包内静态资源的访问。
另外需要注意的一点是,不要对SimpleUrlHandlerMapping设置defaultHandler.因为对static uri的defaultHandler就是ResourceHttpRequestHandler,
否则无法处理static resources request。
方案三,使用mvc:default-servlet-handler/
Xml代码 :mvc:default-servlet-handler/
会把"/**" url 注册到SimpleUrlHandlerMapping的urlMap中,把对静态资源的访问由HandlerMapping转到 org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler 处理并返回。
DefaultServletHttpRequestHandler使用就是各个Servlet容器自己的默认Servlet。
需要搭配: mvc:annotation-driven/
补充说明1:
多个HandlerMapping的执行顺序问题:
DefaultAnnotationHandlerMapping的order属性值是:0
<mvc:resources/ >自动注册的 SimpleUrlHandlerMapping的order属性值是: 2147483646
mvc:default-servlet-handler/自动注册的SimpleUrlHandlerMapping的order属性值是:2147483647
spring会先执行order值比较小的。当访问一个a.jpg图片文件时,先通过DefaultAnnotationHandlerMapping 来找处理器,一定是找不到的,我们没有叫a.jpg的Action。再按order值升序找,由于最后一个SimpleUrlHandlerMapping 是匹配"/**"的,所以一定会匹配上,再响应图片。
补充说明2:
访问一个图片,还要走层层匹配。真不知性能如何?改天做一下压力测试,与Apache比一比。
最后再说明一下,如何你的DispatcherServlet拦截 *.do这样的URL,就不存上述问题了。
对比 SpringBoot
玩过 SpringBoot 的人,看到上面步骤,一定感觉十分繁琐(对熟练的人另说)。
就像这篇《 搭建拥有数据交互的 SpringBoot 》介绍的,可能两三下就可以获取一个 SpringBoot 项目。
前面提到的,返回JSON数据也不需要额外的配置(依赖都导入了),静态资源也有默认约定。
果然还是便捷啊,不过还是前面说的,先使用 SpringBoot,再深入理解 SpringMVC。
知识补充
@RequestMapping
Tips:控制层 @Controller 的核心注解,设置接口的访问路径,类和方法上可以添加。
@RequestMapping 注解的功能
从注解名称上我们可以看到,@RequestMapping 注解的作用就是将请求和处理请求的控制器方法关联起来,建立映射关系。SpringMVC 接收到指定的请求,就会来找到在映射关系中对应的控制器方法来处理这个请求。
@RequestMapping 注解的位置
@RequestMapping 标识一个类:设置映射请求的请求路径的初始信息。
@RequestMapping 标识一个方法:设置映射请求请求路径的具体信息。
Tips:@Target({ElementType.TYPE, ElementType.METHOD})
@RequestMapping 注解的 value 属性
@RequestMapping 注解的 value 属性通过请求的请求地址匹配请求映射。
@RequestMapping 注解的 value 属性是一个字符串类型的数组,表示该请求映射能够匹配多个请求地址所对应的请求,通常数组类型的注解属性,值可以是如下两种形式:
@RequestMapping(value = {"/data", "/test"})
@RequestMapping("/testParam")
@RequestMapping 注解的 value 属性必须设置,至少通过请求地址匹配请求映射
@RequestMapping 注解的 method 属性
method 属性通过请求的请求方式(get或post)匹配请求映射
method 属性是一个 RequestMethod 类型的数组,表示该请求映射能够匹配多种请求方式的请求
若当前请求的请求地址满足请求映射的value属性,但是请求方式不满足method属性,则浏览器报错405:Request method ‘POST’ not supported
写法如下:method = {RequestMethod.GET, RequestMethod.POST}
补充:@RequestMapping 的派生注解
对于处理指定请求方式的控制器方法,SpringMVC中提供了@RequestMapping的派生注解
处理get请求的映射–>@GetMapping
处理post请求的映射–>@PostMapping
处理put请求的映射–>@PutMapping
处理delete请求的映射–>@DeleteMapping
补充:常用的请求方式
常用的请求方式有 get,post,put,delete,但是目前浏览器只支持get和post,若在form表单提交时,为method 设置了其他请求方式的字符串(put或delete),则按照默认的请求方式get处理。
Tips:form 不支持发送这个method设置,但 ajax 和 axios 支持。
@RequestMapping 注解的 params属性
Tips:这个属性设置的多个值必须同时满足才能触发,本属性基本不用,了解即可。
params 属性通过请求的请求参数匹配请求映射,是一个字符串类型的数组,可以通过四种表达式设置请求参数和请求映射的匹配关系。
“param”:要求请求映射所匹配的请求必须携带param请求参数
“!param”:要求请求映射所匹配的请求必须不能携带param请求参数
“param=value”:要求请求映射所匹配的请求必须携带param请求参数且param=value
“param!=value”:要求请求映射所匹配的请求必须携带param请求参数但是param!=value
来一段Demo:
@RequestMapping(
value = {"/testRequestMapping", "/test"}
,method = {RequestMethod.GET, RequestMethod.POST}
,params = {"username","password!=123456"}
)
若当前请求满足 value和method属性,但不满足params属性,此时页面回报错400:
Parameter conditions “username, password!=123456” not met for actual request parameters: username={admin}, password={123456}
@RequestMapping 注解的 headers 属性
基本同上,用的很少!
headers 属性通过请求的请求头信息匹配请求映射是一个字符串类型的数组,可以通过四种表达式设置请求头信息和请求映射的匹配关系。
“header”:要求请求映射所匹配的请求必须携带header请求头信息
“!header”:要求请求映射所匹配的请求必须不能携带header请求头信息
“header=value”:要求请求映射所匹配的请求必须携带header请求头信息且header=value
“header!=value”:要求请求映射所匹配的请求必须携带header请求头信息且header!=value
若当前请求满足@RequestMapping注解的value和method属性,但是不满足headers属性,此时页面显示404错误,即资源未找到
SpringMVC 支持ant风格的路径
即模糊匹配用法
?:表示任意的单个字符,有且仅能1个,不能是“/”这类型特殊字符
*:表示任意的0个或多个字符
:表示任意的一层或多层目录,只能使用//xxx的方式
SpringMVC 支持路径中的占位符
原始方式:/deleteUser?id=1
rest方式:/deleteUser/1
SpringMVC 路径中的占位符常用于 RESTful 风格中,当请求路径中将某些数据通过路径的方式传输到服务器中,就可以在相应的 @RequestMapping 注解的 value 属性中通过占位符 {xxx} 表示传输的数据,再通过@PathVariable 注解,将占位符所表示的数据赋值给控制器方法的形参。
来一段Demo:
///testRest/1/admin
//最终输出的内容为-->id:1,username:admin
@RequestMapping("/testRest/{id}/{username}")
public String testRest(@PathVariable("id") String id, @PathVariable("username") String username){
System.out.println("id:"+id+",username:"+username);
return "success";
}
获取请求参数
接收参数的方式,与请求传递的 ContentType 紧密有关,常见的有 application/x-www-form-urlencoded 和 application/json 两种类型,后续专栏介绍。
1、普通参数
可以使用 @RequestParam 注解接收,或者HttpServletRequest#getParameter接收,详细细节后续篇章介绍。
这里只能处理 x-www-form-urlencoded 类型的请求,get和post方式都可以,但 json 不行。
@ResponseBody
@RequestMapping("/testParam")
public String testParam(HttpServletRequest request, String xxx,
@RequestParam("xxx2") String xxx2) {
System.out.println("xxx:" + xxx);
System.out.println("xxx2:" + xxx2);
String xxx3 = request.getParameter("xxx2");
System.out.println("xxx3:" + xxx3);
return "hello";
}
2、POJO 类型参数
这种情况,不能使用 @RequestParam 接收,直接写实体即可,效果如下。
这里是 x-www-form-urlencoded 的示例,如果是 json 请求,直接参考上面章节,使用 @RequestBody。
@ResponseBody
@RequestMapping("/testParam2")
public Student testParam2(Student student) {
student.setName("战神");
return student;
}
Tips:SpringMVC会使用构造器实例化出一个pojo类对象,即Student,然后使用setXxx()方法进行赋值,如果没有构造器会报错,没有set方法那么这个成员变量就为空null值。
3、其他类型
还有数组、List、Map,以及相应的复杂结构,后续章节展开介绍。
报文信息转换
HttpMessageConverter,报文信息转换器,将请求报文转换为Java对象,或将Java对象转换为响应报文。
HttpMessageConverter提供了两个注解和两个类型:
@RequestBody,@ResponseBody,RequestEntity,ResponseEntity
@RequestBody
@RequestBody 可以获取请求体,需要在控制器方法设置一个形参,使用@RequestBody进行标识,当前请求的请求体就会为当前注解所标识的形参赋值。
<form th:action="@{/testRequestBody}" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="submit">
</form>
// 输出结果:requestBody:username=admin&password=123456
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody String requestBody){
System.out.println("requestBody:"+requestBody);
return "success";
}
RequestEntity
RequestEntity封装请求报文的一种类型,需要在控制器方法的形参中设置该类型的形参,当前请求的请求报文就会赋值给该形参,可以通过getHeaders()获取请求头信息,通过getBody()获取请求体信息。
@RequestMapping("/testRequestEntity")
public String testRequestEntity(RequestEntity<String> requestEntity){
System.out.println("requestHeader:"+requestEntity.getHeaders());
System.out.println("requestBody:"+requestEntity.getBody());
return "success";
}
输出结果:
requestHeader:[host:“localhost:8080”, connection:“keep-alive”, content-length:“27”, cache-control:“max-age=0”, sec-ch-ua:“” Not A;Brand";v=“99”, “Chromium”;v=“90”, “Google Chrome”;v=“90"”, sec-ch-ua-mobile:“?0”, upgrade-insecure-requests:“1”, origin:“http://localhost:8080”, user-agent:“Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36”]
requestBody:username=admin&password=123
@ResponseBody
@ResponseBody用于标识一个控制器方法,可以将该方法的返回值直接作为响应报文的响应体响应到浏览器
@ResponseBody 处理 json
a>导入jackson的依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.1</version>
</dependency>
b>在SpringMVC的核心配置文件中开启mvc的注解驱动,此时在HandlerAdaptor中会自动装配一个消息转换器:MappingJackson2HttpMessageConverter,可以将响应到浏览器的Java对象转换为Json格式的字符串
<mvc:annotation-driven />
c>在处理器方法上使用@ResponseBody注解进行标识
d>将Java对象直接作为控制器方法的返回值返回,就会自动转换为Json格式的字符串
@RestController注解
@RestController注解是springMVC提供的一个复合注解,标识在控制器的类上,就相当于为类添加了@Controller注解,并且为其中的每个方法添加了@ResponseBody注解。
ResponseEntity
ResponseEntity 用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文。
文件上传下载
使用 ResponseEntity 实现下载文件的功能
@RequestMapping("/testDown")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException {
//获取ServletContext对象
ServletContext servletContext = session.getServletContext();
//获取服务器中文件的真实路径
String realPath = servletContext.getRealPath("/static/img/1.jpg");
//创建输入流
InputStream is = new FileInputStream(realPath);
//创建字节数组
byte[] bytes = new byte[is.available()];
//将流读到字节数组中
is.read(bytes);
//创建HttpHeaders对象设置响应头信息
MultiValueMap<String, String> headers = new HttpHeaders();
//设置要下载方式以及下载文件的名字
headers.add("Content-Disposition", "attachment;filename=1.jpg");
//设置响应状态码
HttpStatus statusCode = HttpStatus.OK;
//创建ResponseEntity对象
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers, statusCode);
//关闭输入流
is.close();
return responseEntity;
}
文件上传
文件上传要求form表单的请求方式必须为post,并且添加属性enctype=“multipart/form-data”
SpringMVC中将上传的文件封装到MultipartFile对象中,通过此对象可以获取文件相关信息。
1、添加依赖:
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
2、在SpringMVC的配置文件中添加配置(注意ID命名一定是 multipartResolver):
<!--必须通过文件解析器的解析才能将文件转换为MultipartFile对象-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="utf-8"></property>
<property name="maxUploadSize" value="2097152"></property><!--限制文件上传2M内 -->
<property name="maxInMemorySize" value="40960"></property>
</bean>
3、编写控制层代码:
@RequestMapping("/testUp")
public String testUp(MultipartFile photo, HttpSession session) throws IOException {
//获取上传的文件的文件名
String fileName = photo.getOriginalFilename();
//处理文件重名问题
String hzName = fileName.substring(fileName.lastIndexOf("."));
fileName = UUID.randomUUID().toString() + hzName;
//获取服务器中photo目录的路径
ServletContext servletContext = session.getServletContext();
String photoPath = servletContext.getRealPath("photo");
File file = new File(photoPath);
if(!file.exists()){
file.mkdir();
}
String finalPath = photoPath + File.separator + fileName;
//实现上传功能
photo.transferTo(new File(finalPath));
return "success";
}
拦截器的配置
SpringMVC 中的拦截器用于拦截控制器方法的执行。
SpringMVC 中的拦截器需要实现 HandlerInterceptor,必须在SpringMVC的配置文件中进行配置:
<bean class="com.atguigu.interceptor.FirstInterceptor"></bean>
<ref bean="firstInterceptor"></ref>
<!-- 以上两种配置方式都是对DispatcherServlet所处理的所有的请求进行拦截 -->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/testRequestEntity"/>
<ref bean="firstInterceptor"></ref>
</mvc:interceptor>
<!--
以上配置方式可以通过ref或bean标签设置拦截器,通过mvc:mapping设置需要拦截的请求,通过mvc:exclude-mapping设置需要排除的请求,即不需要拦截的请求
-->
<!-- Demo项目使用自定义Spring拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.demo.common.interceptor.SpringMVCInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
拦截器的三个抽象方法
SpringMVC中的拦截器有三个抽象方法:
1、preHandle:控制器方法执行之前执行preHandle(),其boolean类型的返回值表示是否拦截或放行,返回true为放行,即调用控制器方法;返回false表示拦截,即不调用控制器方法
2、postHandle:控制器方法执行之后执行postHandle()
3、afterComplation:处理完视图和模型数据,渲染视图完毕之后执行afterComplation()
拦截器的执行顺序
浏览器 - 过滤器 - DispatcherServlet - preHandle - 控制层 - postHandle - 视图渲染 - afterComplation
多个拦截器的执行顺序
a>若每个拦截器的preHandle()都返回true
此时多个拦截器的执行顺序和拦截器在SpringMVC的配置文件的配置顺序有关:
preHandle()会按照配置的顺序执行,而postHandle()和afterComplation()会按照配置的反序执行
b>若某个拦截器的preHandle()返回了false
preHandle()返回false和它之前的拦截器的preHandle()都会执行,postHandle()都不执行,返回false的拦截器之前的拦截器的afterComplation()会执行
拦截器源码分析
参考:DispatcherServlet#doDispatch
部分代码如下,关于执行顺序也从源码里面可以看出来,执行流程其实围绕着
处理执行链 HandlerExecutionChain 进行。
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}
具体示例:
//Step1. 自定义的拦截器,实现的接口HandlerInterceptor
public class CustomInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//预处理,返回true则继续执行。如果需要登录校验,校验不通过返回false即可,通过则返回true。
System.out.println("执行preHandle()方法");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//后处理
System.out.println("执行postHandle()方法");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//在DispatcherServlet完全处理完请求后被调用
System.out.println("执行afterCompletion()方法");
}
}
/**
* Step2. 注册拦截器到容器中,/**代表所有路径
* @param registry 拦截器注册表对象
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CustomInterceptor()).addPathPatterns("/**");
}
总结陈词
此篇文章介绍了SpringMVC
项目的基础搭建和一些知识介绍,仅供参考。
后续还会继续从 SpringMVC 的常见用法、源码分析、扩展点分析、企业实战等方面展开。
💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。