文章目录
一.概述
1.1 概念
SpringMVC:spring web mvc.是Spring内置的一个MVC框架,支持RESTful风格的URL请求。
1.2 原理
在没有使用SpringMVC之前我们都是使用Servlet在做Web开发。但是使用Servlet开发在接收请求参数,数据共享,页面跳转等操作相对比较复杂。servlet是java进行web开发的标准,既然springMVC是对servlet的封装,那么很显然SpringMVC底层就是Servlet,SpringMVC就是对Servlet进行深层次的封装。
二.案例入门
2.1 环境准备
2.1.1 创建maven项目
2.1.2 pom.xml添加依赖和插件
<dependencies>
<!--spring-webmvc依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.13.RELEASE</version>
</dependency>
<!--springmvc底层还是servlet,所以必须添加serlvet依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
<!--插件运行的时候没有范围插件启动会失败-->
</dependency>
</dependencies>
<build>
<plugins>
<!-- 编码和编译和JDK版本 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<!--tomcat插件-->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<path>/</path>
<port>8080</port>
</configuration>
</plugin>
</plugins>
</build>
2.1.3 创建Spring和SpringMVC的配置文件

2.1.4 web.xml配置
<!--spring配置-->
<context-param>
<param-name>contextConfigLocation</param-name>
<!--javaweb中通过applicationContext读取路径,springmvc通过这种方法进行简化-->
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--springmvc配置-->
<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>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
2.2 实际开发
2.2.1 创建控制器
@Controller
public class TeamController {
@Autowired
private TeamService teamService;
@RequestMapping("hello.do")
public ModelAndView add(){
System.out.println("TeamController----add---");
teamService.add();
ModelAndView mv=new ModelAndView();
//相当于 request.setAttrubuite("teanName","湖人");
mv.addObject("teamName","湖人");
//未来经过springmvc的视图解析器处理,转换成物理资源路径
// 相当于request.getRequestDispatcher("index.jsp").forward();
//经过InternalResourceViewResolver对象的处理之后加上前后缀就变为了/jsp/index.jsp
mv.setViewName("index");
return mv;
}
2.2.2 配置视图解析器
在springmvc.xml配置
<bean id="internalResourceViewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/jsp/"></property> <!--逻辑路径的前缀-->
<property name="suffix" value=".jsp"></property> <!--逻辑路径的后缀-->
</bean>
<!--配置后springmvc可通过注解形式进行驱动,即会处理@RequestMapping注解,将其注册到映射表中,还可以处理请求的适配器,确定调用哪个类的哪个方法-->
<mvc:annotation-driven/>
2.2.3 编写jsp页面,调试
2.3 流程解析
当 Spring 和 SpringMVC 同时出现,我们的项目中将存在两个容器,一个是 Spring 容器,另一个是SpringMVC 容器,Spring 容器通过 ContextLoaderListener 来加载,SpringMVC 容器则通过DispatcherServlet 来加载,这两个容器不一样:
- ContextLoaderListener 初始化的上下文加载的 Bean 是对于整个应用程序共享的,一般如 dao层、service层 的bean;
- DispatcherServlet 初始化的上下文加载的 bean 是只对 Spring Web MVC 有效的 bean,如Controller、HandlerMapping、HandlerAdapter 等等,该初始化上下文应该只加载 Web相关组件。
1.为何Spring不能扫描所有bean?
当用户发送的请求达到服务端后,会寻找前端控制器DispatcherServlet去处理,只在SpringMVC容器中找,所以Controller必须在SpringMVC容器中扫描。
2. SpringMVC中的bean为何不放在Spring中一起扫描?
理论上可以放在Spring中扫描,但实际开发中一般不这样做:
(1)为了方便配置文件的管理
(2)在SSM组合中,要写的配置内容很多,一般根据功能分开编写。
三.SpringMVC工作流程
三.@RequestMapping注解
3.1 出现位置
可出现在类上或方法上
3.2 指定请求提交方式
通过method属性对被注解方法所处理的请求的提交方式进行限制
- method=RequestMethod.GET
- method=RequestMethod.POST
3.3 补充url-pattern解析
在web.xml配置SpringMVC的前端控制器时有这个节点,该节点的值的写法有两种:
- .do
在没有特殊要求的情况下,SpringMVC 的前端控制器 DispatcherServlet 的常使用后辍匹配方 式,可以写为.do 或者 *.action, *.mvc 等。- /
当写成 / 时,DispatcherServlet会将静态资源也当成是一个Controller请求,从而去找对应的HandlerMapping,但是访问路径中不存在,因此会报错。
为了解决这个问题,引入了静态资源访问
3.3.1 静态资源访问
任意一种方式,在springmvc.xml中进行配置
法一:
<mvc:default-servlet-handler/>
声 明 了 <mvc:default-servlet-handler /> 后 ,springmvc框架会在容器中创建DefaultServletHttpRequestHandler处理器对象。该对象会对所有进入DispatcherServlet的URL进行检查如果发现是静态资源的请求,就将该请求转由Web应用服务器默认的Servlet处理。
法二:<mvc:resources location="xxx" mapping="xxx/**"/>
例:<mvc:resources location="/images/" mapping="/images/**"/
location: 表示静态资源所在目录。当然,目录不要使用/WEB-INF/及其子目录。 mapping: 表示对该资源的请求。注意,后面是两个星号**。
四.处理器参数传递
4.1 使用方法的参数逐个接收
用户请求的参数名称(前端传来的值)和方法的参数名称必须保持一致
好处:不需要类型转换(以前接收前端数据都是以字符串形式得到)

4.2 使用对象接收多个参数(常用)
- 用户请求的参数名称和对象的属性保持一致

4.3 请求参数和方法名称不一致
通过 @RequestParam进行矫正
required属性表示参数是否是必须的:
- true:必须赋值(默认),否则报出400错;
- false:可以不赋值, 结果就是null。
即前端参数和@RequestParam里的参数名不一致时,required为false,此时不会报错,而是当做获取到了null值。

4.4 Request对象获取参数(原生方式)

4.5 URL地址传参(常用)
借助 @PathVariable注解,形参名字任意
使用场景:
1.测试(下图)
2.ajax请求路径传值

4.6 日期类型参数
- 实体类日期属性加上DateTimeFormat(pattern=“xxxx-xx-xx”)

4.7 数组类型参数

4.8 集合类型参数
通过 @RequestParam注解实现
对象集合不支持直接获取,要封装在类中,作为属性操作(即相当于传一个对象,同4.2)

五.请求参数中文乱码
5.1 解决方案
- 在web.xml进行以下配置
<!--配置中文乱码的过滤-->
<filter>
<filter-name>characterEncodingFilter</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>
<!--强制request使用字符集encoding-->
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<!--强制response使用字符集encoding-->
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
5.2 原理
- 如果在某个类中设置了编码格式,最后还是以web.xml配置文件中的为准

六.处理器方法的返回值
6.1 页面跳转
- 两种方法都可通过视图解析器将逻辑视图转换为物理视图

6.2 数据返回
借助 @ResponseBody注解
使用场景:结合Ajax,返回json格式的数据
pom.xml添加两个依赖
<!--springmvc返回json格式数据-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
<!--json数据绑定-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
6.2.1 返回基础类型
@RequestMapping("test03")
@ResponseBody
public String test03() {
return "111";//返回字符串111
}
6.2.2 返回自定义类型
@RequestMapping("test04")
@ResponseBody
public Team test04() {
Team team = new Team();
team.setTeamId(11);
return team;
}
6.2.3 返回集合类型
list类型较为常见,这里主要讲解map类型,前端对其取值与list略有不同
@RequestMapping("test05")
@ResponseBody
public Map<String, Team> test05() {
Map<String, Team> map = new HashMap<>();
for (int i = 0; i < 5; i++) {
Team team = new Team();
team.setTeamId(i + 1);
map.put(team.getTeamId() + "", team);
}
return map;
}
通过each函数可简化操作
$.ajax({
type: "POST",
url:/result/test05,
data: "",
success:function (map) {
var str = "";
$.each(map, function (i, obj) {//obj名字任意
str+=obj.teamName;
});
}
}
);
七.页面导航的方式
7.1 分类
页面跳转主要有两种:转发 和 重定向
springmvc实现方式有两种:返回字符串和ModelAndView,返回字符串相当于原生的javaweb。
7.2 转发

7.3 重定向

7.4 转发或重定向到控制器

总结:只有转发才可以不写绝对路径,而重定向无论是哪种方式,都需要添加redirect前缀
八.异常处理
8.1 @Exception注解
该注解可以将一个方法指定为异常处理方法
属性value默认缺省,其值为一个Class<?>数组

8.2 优化异常类
- 对于一些需要特殊处理的异常,一般用单独的方法进行处理,而这些方法又全部放在一个异常处理类中。
- 借助 @ControllerAdvice注解对类修饰,而其内部的方法可借助 @ExceptionHandler注解
当使用 @RequestMapping注解修饰的类抛出异常时,会执行@ControllerAdvice修饰的类中的异常处理方法。- @ControllerAdvice注解修饰的类需在springmvc.xml进行包扫描

九.拦截器(Interceptor)
9.1 概念介绍
- 作用:拦截指定的用户请求,并执行相应的预处理和后处理。
- 拦截时间点: HandlerMapping根据用户提交的请求映射出了所要执行的处理器类,并且也找到了要执行该处理器类的处理器适配器,在处理器适配器HandlerAdaptor执行处理器之前
- 在处理器映射器映射出所要执行的处理器类时,已经将拦截器与处理器组合为了一个处理器执行链HandlerExecutionChain,并返回给了前端控制器。
9.2 自定义拦截器
实现HandlerInterceptor接口中的三个方法

在springmvc.xml进行拦截器配置
测试结果
测试结果分析
- 当preHandle返回值为true时就会执行处理器方法,同时将afterCompletion()方法放入到一个专门的方法栈中进行执行,而栈有着先进后出的特点。
- aftercCompletion方法是在前端控制器渲染(数据填充)了响应页面后才执行的,此时对ModelAndView再操作也无意义,一般用于消除资源,例如在Controller方法中加入数据。
十.文件上传和下载
10.1 背景介绍
SpringMVC对于文件上传是通过即插即用的MultipartResolver实现,常用的实现类为CommonsMultipartResolver
10.2 环境配置
- 添加依赖
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
- 配置MultipartResolver

10.3 文件上传
10.3.1 开发流程
- 前端页面

- 文件的保存路径

- controller

10.3.2 优化
- 限制文件大小

- 限制文件类型

public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
//1. 判断是否是文件上传的请求
boolean flag=true;
if (request instanceof MultipartHttpServletRequest) {
MultipartHttpServletRequest multipartHttpServletRequest =
(MultipartHttpServletRequest) request;
//2. 获取文件集合
Map<String, MultipartFile> fileMap = multipartHttpServletRequest.getFileMap();
//3. 遍历文件
Iterator<String> iterator = fileMap.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
MultipartFile file = multipartHttpServletRequest.getFile(key);
//4. 获取文件名
String originalFilename = file.getOriginalFilename();
//5. 获取文件后缀
String hz = originalFilename.substring(originalFilename.lastIndexOf("."));
//6. 判断后缀是否是jpg或png格式
if (!hz.toLowerCase().equals("jpg") && !hz.toLowerCase().equals("png")) {
request.getRequestDispatcher("/fileTypeError.jsp").
forward(request, response);
flag=false;
}
}
}
return flag;
}
10.4 文件下载


十一 Restful风格
11.1 概念介绍
11.1.1 REST
REST:Representational State Transfer,即表述性状态转换,用来描述一个架构样式的网络系统。它本身并没有什么使用性,其价值在于如何设计出符合REST风格的网络接口。
11.1.2 RESTful
满足REST架构的应用程序或设计就是RESTful
http协议对资源的操作有四类五种
GET:获取资源
POST:新建资源
PUT:更新资源
PATCH:更新部分资源
DELETE:删除资源
- 该风格主要体现在前端通过ajax获取数据以及后台通过访问路径确定该方法是什么类型:新增、删除、更新、查询。
11.2 API/URL设计
11.2.1 动宾结构
GET:/expresses 正确
/getExpress 错误,因为路径包含了动词
- 此外,对于复数资源才使用复数,例如查询全部数据
11.2.2 避免多级URL
GET/team/1001/player/1005 不推荐,此写法语义不明确
GET/team/1001?player=1005 推荐,即属性通常在url中通过问号拼接
11.3 HTTP状态码
状态码一般分为五类
1xx:相关信息
2xx:操作成功(最希望看到的)
3xx:重定向
4xx:客户端错误
5xx:服务器错误
- API不需要1xx状态码,此类别可直接忽略
- 后端开发人员关注2xx,4xx,5xx即可
11.3.1 2xx
GET: 200 OK 表示一切正常
POST: 201 Created 表示新的资源已经成功创建
PUT: 200 OK
PATCH: 200 OK
DELETE: 204 No Content 表示资源已经成功删除
11.3.2 3xx
- API 用不到 301 状态码(永久重定向)和 302 状态码(暂时重定向, 307 也是这个含义),因为它们可
以由应用级别返回,浏览器会直接跳转,API 级别可以不考虑这两种情况。- API 用到的 3xx 状态码,主要是 303 See Other ,表示参考另一个 URL。它与 302 和 307 的含义一
样,也是"暂时重定向",区别在于 302 和 307 用于 GET 请求,而 303 用于 POST 、 PUT 和 DELETE 请求。- 收到 303 以后,浏览器不会自动跳转,而会让用户自己决定下一步怎么办。我们只需要关注一下304状态码就可以了
304 : Not Modified 客户端使用缓存数据
11.3.3 4xx
400 Bad Request:服务器不理解客户端的请求,未做任何处理。
401 Unauthorized:用户未提供身份验证凭据,或者没有通过身份验证。
403 Forbidden:用户通过了身份验证,但是不具有访问资源所需的权限。
404 Not Found:所请求的资源不存在,或不可用。
405 Method Not Allowed:用户已经通过身份验证,但是所用的 HTTP 方法****不在他的权限之内。(get/post方式请求写混淆)
410 Gone:所请求的资源已从这个地址转移,不再可用。
415 Unsupported Media Type:客户端要求的返回格式不支持。比如,API 只能返回 JSON 格式,但是 客户端要求返回 XML 格式。
422 Unprocessable Entity :客户端上传的附件无法处理,导致请求失败。
429 Too Many Requests:客户端的请求次数超过限额。
11.3.4 5xx
- 一般来说,API 不会向用户透露服务器的详细信息,所以只要两个状态码就够了。
500 Internal Server Error:客户端请求有效,服务器处理时发生了意外。(请求路径写错)
503 Service Unavailable:服务器无法处理请求,一般用于网站维护状态。
11.4 案例分析
GET和POST,DELETE和PUT大同小异,此处各选其中一种进行分析。
- GET–查询

- 接下来的DELETE和PUT需要注意,需要修改Type的类型,具体的原因后面分析,现在先说解决方案。
- 视需求修改相应的type的类型
若data中有数据,DELETE和PUT的type都需要写成POST,且在data后加上"&_method=PUT/DELETE",
若data中无数据,则type可写成原本的类型,不过推荐写成POST类型,这样以后数据修改也方便,同时在data处写上 “_method=PUT/DELETE”- 修改web.xml配置文件,添加过滤器
- DELETE–删除
- data无数据

- data有数据

- 总结
对于四种业务类型,有些url传参,有些data传参,下面的总结只针对常见的业务。.
- GET
url传参:查询单个
url不传参:查询所有- POST
data传值:封装页面数据,添加内容- DELETE
url传参:删除单个
url不传参:删除所有(比较少见)- PUT
url传参:更新内容
data传值:封装页面数据,更新内容
11.4 Restful更新和删除
11.4.1 遇到的问题
在Ajax中,采用Restful风格PUT和DELETE请求传递参数无效,传递到后台的参数值为null
11.4.2 产生的原因
Tomcat封装请求参数的过程:
1.将请求体中的数据,封装成一个map
2.request.getParameter(key)会从这个map中取值
3.SpringMvc封装POJO对象的时候,会把POJO中每个属性的值进行request.getParamter();
- AJAX发送PUT或者DELETE请求时,请求体中的数据通过request.getParamter()拿不到。
- Tomcat一旦检测到是PUT或者DELETE就不会封装请求体中的数据为map,只有POST形式的请求才封装请求为 map。
11.4.3 解决的原理
通过查看spring框架中的HiddenHttpMethodFilter类的源码发现,只要type类型是POST,就可以通过配置该过滤器,添加_method属性,覆盖POST原有的请求。

11.5 封装响应结果
通过封装响应结果,可以不用通过@ResponseBody注解在当前页面返回数据,如果需要跳转页面,可以直接返回逻辑视图名称。
private Integer code;//状态码
private String msg;//返回信息
private List<T> list;//返回集合
private T obj;//返回对象
//构造方法一般不包含所有属性
public AjaxResultVo() {
code=200;
msg = "ok";
list=null;
obj=null;
}
public AjaxResultVo(Integer code, String msg) {
this.code=code;
this.msg = msg;
}
public AjaxResultVo(Integer code, String msg, List<T> list) {
this.code = code;
this.msg = msg;
this.list = list;
}
public AjaxResultVo(Integer code, String msg, T obj) {
this.code = code;
this.msg = msg;
this.obj = obj;
}

