1,SpringMVC简介
看到SpringMVC这个名字我们会发现其中包含Spring,那么SpringMVC和Spring之间的会有关系么?答案是肯定有,SpringMVC隶属于Spring,是Spring技术中的一部分。
-
回想web阶段,我们学习过Servlet,而SpringMVC与Servlet技术功能等同,均属于web层或者说表现层开发技术。
-
SpringMVC是用来替换Servlet的,所以Servlet能实现的,SpringMVC就能实现
1.1 SpringMVC概述
当前WEB程序的工作流程:
三层架构
-
web程序通过浏览器访问前端页面,发送异步请求到后端服务器
-
后台服务器采用三层架构进行功能开发
-
表现层负责接收请求和数据然后将数据转交给业务层
-
业务层负责调用数据层完成数据库表的增删改查,并将结果返给表现层
-
表现层将数据转换成json格式返回给前端
-
-
前端页面将数据进行解析最终展示给用户。
表现层与数据层的技术选型:
-
数据层采用Mybatis框架
-
变现层采用SpringMVC框架,SpringMVC==主要==负责的内容有:
-
controller如何接收请求和数据
-
如何将请求和数据转发给业务层
-
如何将响应数据转换成json发回到前端
-
2,SpringMVC程序流程
1.浏览器发送请求到Tomcat服务器
2.Tomcat服务器接收到请求后,会将请求交给SpringMVC中的==DispatcherServlet[前端控制器]==来处理请求
3.DispatcherServlet不真正处理请求,只是按照对应的规则将请求分发到对应的Bean对象
4.Bean对象是有我们自己编写来处理不同的请求,每个Bean中可以处理一个或多个不同的请求url
5.DispatcherServlet和Bean对象都需要交给Spring容器来进行管理
2.1 案例制作
步骤1:创建Maven项目,并导入对应的jar包
步骤2:创建控制器类
//2.制作控制器类,等同于Servlet
//2.1必须是一个spring管理的bean
//2.2定义具体处理请求的方法
//2.3设置当前方法的访问路径
//2.4设置响应结果为json数据
@Controller
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(){
System.out.println("user save ...");
return "{'module':'springmvc'}";
}
}
步骤3:创建配置类
//3.定义配置类加载Controller对应的bean
@Configuration
@ComponentScan("com.itheima.controller")
public class SpringMvcConfig {
}
步骤4:创建Tomcat的Servlet容器配置类
//4.定义servlet容器的配置类
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
//加载springMVC配置
protected WebApplicationContext createServletApplicationContext() {
//初始化WebApplicationContext对象
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
//加载指定配置类
ctx.register(SpringMvcConfig.class);
return ctx;
}
//设置Tomcat接收的请求哪些归SpringMVC处理
protected String[] getServletMappings() {
return new String[]{"/"};
}
//设置spring相关配置
protected WebApplicationContext createRootApplicationContext() {
return null;
}
}
-
AbstractDispatcherServletInitializer类是SpringMVC提供的快速初始化Web3.0容器的抽象类
-
AbstractDispatcherServletInitializer提供三个接口方法供用户实现
-
createRootApplicationContext()方法,如果创建Servlet容器时需要加载非SpringMVC对应的bean,使用当前方法进行,使用方式同createServletApplicationContext()
-
createServletApplicationContext()方法,创建Servlet容器时,加载SpringMVC对应的bean并放入WebApplicationContext对象范围中,而WebApplicationContext的作用范围为ServletContext范围,即整个web容器范围
-
getServletMappings()方法,设定SpringMVC对应的请求映射路径,设置为/表示拦截所有请求,任意请求都将转入到SpringMVC进行处理
-
createServletApplicationContext用来加载SpringMVC环境
-
createRootApplicationContext用来加载Spring环境
-
知识点1:@Controller
名称 | @Controller |
---|---|
类型 | 类注解 |
位置 | SpringMVC控制器类定义上方 |
作用 | 设定SpringMVC的核心控制器bean |
知识点2:@RequestMapping
名称 | @RequestMapping |
---|---|
类型 | 类注解或方法注解 |
位置 | SpringMVC控制器类或方法定义上方 |
作用 | 设置当前控制器方法请求访问路径 |
相关属性 | value(默认),请求访问路径 |
知识点3:@ResponseBody
名称 | @ResponseBody |
---|---|
类型 | 类注解或方法注解 |
位置 | SpringMVC控制器类或方法定义上方 |
作用 | 设置当前控制器方法响应内容为当前返回值,无需解析 |
2.2 入门案例工作流程分析
为了更好的使用SpringMVC,我们将SpringMVC的使用过程总共分两个阶段来分析,分别是启动服务器初始化过程
和单次请求过程
2.2.1 启动服务器初始化过程
-
服务器启动,执行ServletContainersInitConfig类,初始化web容器
-
执行createServletApplicationContext方法,创建了WebApplicationContext对象
-
该方法加载SpringMVC的配置类SpringMvcConfig来初始化SpringMVC的容器
-
-
加载SpringMvcConfig配置类
-
执行@ComponentScan加载对应的bean
-
扫描指定包下所有类上的注解,如Controller类上的@Controller注解
-
-
加载UserController,每个@RequestMapping的名称对应一个具体的方法
-
执行getServletMappings方法,定义所有的请求都通过SpringMVC
2.2.2 单次请求过程
-
发送请求localhost/save
-
web容器发现所有请求都经过SpringMVC,将请求交给SpringMVC处理
-
因为符合上面第六步设置的请求路径,所以该请求会交给SpringMVC来处理
-
-
解析请求路径/save
-
由/save匹配执行对应的方法save()
-
上面的第五步已经将请求路径和方法建立了对应关系,通过/save就能找到对应的save方法
-
-
执行save()
-
检测到有@ResponseBody直接将save()方法的返回值作为响应求体返回给请求方
知识点1:@ComponentScan
名称 | @ComponentScan |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 设置spring配置类扫描路径,用于加载使用注解格式定义的bean |
相关属性 | excludeFilters:排除扫描路径中加载的bean,需要指定类别(type)和具体项(classes) includeFilters:加载指定的bean,需要指定类别(type)和具体项(classes) |
3,请求与响应
-
请求映射路径
-
请求参数
-
日期类型参数传递
-
响应json数据
3.1 设置请求映射路径
@Controller
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(){
@Controller
public class BookController {
@RequestMapping("/save")
@ResponseBody
public String save(){
3.1.1 问题分析
团队多人开发,每人设置不同的请求路径,冲突问题该如何解决?
解决思路:为不同模块设置模块名作为请求路径前置
3.1.2 设置映射路径
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(){
@Controller
@RequestMapping("/book")
public class BookController {
@RequestMapping("/save")
@ResponseBody
public String save(){
注意:
-
当类上和方法上都添加了
@RequestMapping
注解,前端发送请求的时候,要和两个注解的value值相加匹配才能访问到。 -
@RequestMapping注解value属性前面加不加
/
都可以
3.2 请求参数
关于请求参数的传递与接收是和请求方式有关系的,目前比较常见的两种请求方式为:
-
GET
-
POST
GET请求中文乱码
需要修改pom.xml来解决GET请求中文乱码问题
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<port>80</port><!--tomcat端口号-->
<path>/</path> <!--虚拟目录-->
<uriEncoding>UTF-8</uriEncoding><!--访问路径编解码字符集-->
</configuration>
</plugin>
</plugins>
</build>
POST请求中文乱码
解决方案:配置过滤器
3.3 五种类型参数传递
-
普通参数
-
POJO类型参数
-
嵌套POJO类型参数
-
数组类型参数
-
集合类型参数
3.3.1 普通参数
-
普通参数:url地址传参,地址参数名与形参变量名相同,定义形参即可接收参数。
-
如果形参与地址参数名不一致该如何解决?
-
解决方案:使用@RequestParam注解
http://localhost/commonParamDifferentName?name=张三&age=18 (@RequestPaam("name") String userName , int age)
3.3.2 POJO数据类型
简单数据类型一般处理的是参数个数比较少的请求,如果参数比较多,那么后台接收参数的时候就比较复杂,这个时候我们可以考虑使用POJO数据类型。
-
POJO参数:请求参数名与形参对象属性名相同,定义POJO类型形参即可接收参数
-
POJO参数接收,前端GET和POST发送请求数据的方式不变。
-
==请求参数key的名称要和POJO中属性的名称一致,否则无法封装。==
-
POJO类: public class User { private String name; private int age; //setter...getter...略 } 传参:(User user) //POJO参数:请求参数与形参对象中的属性对应即可完成参数传递 @RequestMapping("/pojoParam") @ResponseBody public String pojoParam(User user){
3.3.3 嵌套POJO类型参数
public class Address {
private String province;
private String city;
//setter...getter...略
}
public class User {
private String name;
private int age;
private Address address;
//setter...getter...略
}
-
嵌套POJO参数:请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套POJO属性参数
-
//POJO参数:请求参数与形参对象中的属性对应即可完成参数传递 @RequestMapping("/pojoParam") @ResponseBody public String pojoParam(User user){ System.out.println("pojo参数传递 user ==> "+user); return "{'module':'pojo param'}"; }
3.3.4 数组类型参数
-
数组参数:请求参数名与形参对象属性名相同且请求参数为多个,定义数组类型即可接收参数
3.3.5 集合类型参数
//集合参数:同名请求参数可以使用@RequestParam注解映射到对应名称的集合对象中作为数据
@RequestMapping("/listParam")
@ResponseBody
public String listParam(@RequestParam List<String> likes){
System.out.println("集合参数传递 likes ==> "+ likes);
return "{'module':'list param'}";
}
-
集合保存普通参数:请求参数名与形参集合对象名相同且请求参数为多个,@RequestParam绑定参数关系
-
对于简单数据类型使用数组会比集合更简单些。
知识点1:@RequestParam
名称 | @RequestParam |
---|---|
类型 | 形参注解 |
位置 | SpringMVC控制器方法形参定义前面 |
作用 | 绑定请求参数与处理器方法形参间的关系 |
相关参数 | required:是否为必传参数 defaultValue:参数默认值 |
3.4 JSON数据传输参数
对于JSON数据类型,我们常见的有三种:
-
json普通数组(["value1","value2","value3",...])
-
json对象({key1:value1,key2:value2,...})
-
json对象数组([{key1:value1,...},{key2:value2,...}])
JSON普通数组
步骤1:pom.xml添加依赖
SpringMVC默认使用的是jackson来处理json的转换,所以需要在pom.xml添加jackson依赖
步骤2:PostMan发送JSON数据
步骤3:开启SpringMVC注解支持
在SpringMVC的配置类中开启SpringMVC的注解支持,这里面就包含了将JSON转换成对象的功能。
@Configuration
@ComponentScan("com.itheima.controller")
//开启json数据类型自动转换
@EnableWebMvc
public class SpringMvcConfig {
}
步骤4:参数前添加@RequestBody
//使用@RequestBody注解将外部传递的json数组数据映射到形参的集合对象中作为数据
@RequestMapping("/listParamForJson")
@ResponseBody
JSON对象数据,JSON对象数组和上面上面方式一样,只需修改,请求类型和控制器(controller)中的方法形参类型,在参数前加@RequestBody 即可
小结
SpringMVC接收JSON数据的实现步骤为:
(1)导入jackson包
(2)使用PostMan发送JSON数据
(3)开启SpringMVC注解驱动,在配置类上添加@EnableWebMvc注解
(4)Controller方法的参数前添加@RequestBody注解
知识点1:@EnableWebMvc
名称 | @EnableWebMvc |
---|---|
类型 | ==配置类注解== |
位置 | SpringMVC配置类定义上方 |
作用 | 开启SpringMVC多项辅助功能 |
知识点2:@RequestBody
名称 | @RequestBody |
---|---|
类型 | ==形参注解== |
位置 | SpringMVC控制器方法形参定义前面 |
作用 | 将请求中请求体所包含的数据传递给请求参数,此注解一个处理器方法只能使用一次 |
3.5 日期类型参数传递
(@DateTimeFormat(pattern="yyyy-MM-dd") Date date)在形参中加上@DateTimeFormat注解,并设置日期格式
知识点1:@DateTimeFormat
名称 | @DateTimeFormat |
---|---|
类型 | ==形参注解== |
位置 | SpringMVC控制器方法形参前面 |
作用 | 设定日期时间型数据格式 |
相关属性 | pattern:指定日期时间格式字符串 |
内部实现原理
讲解内部原理之前,我们需要先思考个问题:
-
前端传递字符串,后端使用日期Date接收
-
前端传递JSON数据,后端使用对象接收
-
后台需要的数据类型有很多中
-
前端传递字符串,后端使用Integer接收
-
在数据的传递过程中存在很多类型的转换
问:谁来做这个类型转换?
答:SpringMVC
问:SpringMVC是如何实现类型转换的?
答:Converter接口
在框架中,有一个类型转换接口
-
Converter接口
注意:Converter所属的包为org.springframework.core.convert.converter
框架中有提供很多对应Converter接口的实现类,用来实现不同数据类型之间的转换。前面咱们其实一直在使用这个类型转换,如:
-
请求参数年龄数据(String→Integer)
-
json数据转对象(json → POJO)
-
日期格式转换(String → Date)
==注意:SpringMVC的配置类把@EnableWebMvc当做标配配置上去,不要省略==
3.6 响应
知识点1:@ResponseBody
名称 | @ResponseBody |
---|---|
类型 | ==方法\类注解== |
位置 | SpringMVC控制器方法定义上方和控制类上 |
作用 | 设置当前控制器返回值作为响应体, 写在类上,该类的所有方法都有该注解功能 |
相关属性 | pattern:指定日期时间格式字符串 |
说明:
-
该注解可以写在类上或者方法上
-
写在类上就是该类下的所有方法都有@ReponseBody功能
-
当方法上有@ReponseBody注解后
-
方法的返回值为字符串,会将其作为文本内容直接响应给前端
-
方法的返回值为对象,会将对象转换成JSON响应给前端
-
此处又使用到了类型转换,内部还是通过Converter接口的实现类完成的,所以Converter除了前面所说的功能外,它还可以实现:
-
对象转Json数据(POJO -> json)
-
集合转Json数据(Collection -> json)
4,Rest风格
4.1 REST简介
-
==REST==(Representational State Transfer),表现形式状态转换,它是一种软件架构==风格==
当我们想表示一个网络资源的时候,可以使用两种方式:
-
传统风格资源描述形式
-
http://localhost/user/getById?id=1
查询id为1的用户信息 -
http://localhost/user/saveUser
保存用户信息
-
-
REST风格描述形式
-
http://localhost/user/1
-
http://localhost/user
-
-
REST的优点有:
-
隐藏资源的访问行为,无法通过地址得知对资源是何种操作
-
书写简化
请求的方式比较多,但是比较常用的就4种,分别是GET
,POST
,PUT
,DELETE
。
按照不同的请求方式代表不同的操作类型。
-
发送GET请求是用来做查询
-
发送POST请求是用来做新增
-
发送PUT请求是用来做修改
-
发送DELETE请求是用来做删除
但是==注意==:
-
上述行为是约定方式,约定不是规范,可以打破,所以称REST风格,而不是REST规范
-
REST提供了对应的架构方式,按照这种架构设计项目可以降低开发的复杂性,提高系统的可伸缩性
-
REST中规定GET/POST/PUT/DELETE针对的是查询/新增/修改/删除,但是我们如果非要用GET请求做删除,这点在程序上运行是可以实现的
-
但是如果绝大多数人都遵循这种风格,你写的代码让别人读起来就有点莫名其妙了。
-
-
描述模块的名称通常使用复数,也就是加s的格式描述,表示此类资源,而非单个资源,例如:users、books、accounts......
清楚了什么是REST风格后,我们后期会经常提到一个概念叫RESTful
,那什么又是RESTful呢?
-
根据REST风格对资源进行访问称为==RESTful==。
后期我们在进行开发的过程中,大多是都是遵从REST风格来访问我们的后台服务,所以可以说咱们以后都是基于RESTful来进行开发的。
4.2 RESTful快速开发
普通Rest风格:
问题1:每个方法的@RequestMapping注解中都定义了访问路径/books,重复性太高。
将@RequestMapping提到类上面,用来定义所有方法共同的访问路径。
问题2:每个方法的@RequestMapping注解中都要使用method属性定义请求方式,重复性太高。
使用@GetMapping @PostMapping @PutMapping @DeleteMapping代替
问题3:每个方法响应json都需要加上@ResponseBody注解,重复性太高。
1.将ResponseBody提到类上面,让所有的方法都有@ResponseBody的功能 2.使用@RestController注解替换@Controller与@ResponseBody注解,简化书写
知识点1:@RestController
名称 | @RestController |
---|---|
类型 | ==类注解== |
位置 | 基于SpringMVC的RESTful开发控制器类定义上方 |
作用 | 设置当前控制器类为RESTful风格, 等同于@Controller与@ResponseBody两个注解组合功能 |
知识点2:@GetMapping @PostMapping @PutMapping @DeleteMapping
名称 | @GetMapping @PostMapping @PutMapping @DeleteMapping |
---|---|
类型 | ==方法注解== |
位置 | 基于SpringMVC的RESTful开发控制器方法定义上方 |
作用 | 设置当前控制器方法请求访问路径与请求动作,每种对应一个请求动作, 例如@GetMapping对应GET请求 |
相关属性 | value(默认):请求访问路径 |
简化后如下:
@RestController
@RequestMapping("/books")
public class BookController {
@PostMapping
public String save(@RequestBody Book book){
System.out.println("book save ==> "+ book);
return "{'module':'book save success'}";
}
@GetMapping
public List<Book> getAll(){