1.SpringMVC概述
SpringMVC功能与优点
SpringMVC
是一种基于Java
实现MVC
模型的轻量级Web
框架
SpringMVC
技术与Servlet
技术功能一样(对Servlet
进行了封装),都属于Web
层开发技术
SpringMVC
的主要的作用就是用来接收前端发过来的请求和数据然后经过处理并将处理的结果响应给前端:
controller
如何接收请求和数据- 如何将请求和数据转发给业务层
- 如何将响应数据转换成
json
发回到前端
SpringMVC
的优点:
- 使用简单、开发便捷(相比于
Servlet
):上图为使用Servlet
开发,下图为使用SpringMVC
开发
- 灵活性强
SpringMVC和Servlet的三层架构
- 同步调用阶段:
- 浏览器发送一个请求给后端服务器(使用
Servlet
来接收请求和数据)- 将后端服务器
Servlet
拆分成三层,分别是web
、service
和dao
(所有的处理都交给Servlet
来处理导致耦合度高,对后期的维护和扩展不利)
web
层主要由servlet
来处理,负责页面请求和数据的收集以及响应结果给前端service
层主要负责业务逻辑的处理dao
层主要负责数据的增删改查操作- 针对
web
层进行了优化(servlet
处理请求和数据时,一个servlet
只能处理一个请求),采用了MVC
设计模式,将其设计为controller
、view
和Model
controller
负责请求和数据的接收,接收后将其转发给service
进行业务处理service
根据需要会调用dao
对数据进行增删改查dao
把数据处理完后将结果交给service
,service
再交给controller
controller
根据需求组装成Model
和View
,二者组合起来生成页面转发给前端浏览器(controller
可以处理多个请求,并对请求进行分发,执行不同的业务操作)- 异步调用阶段:同步调用的性能跟不上需求
- 异步调用时后端不需要返回
view
视图,将其去除- 前端如果通过异步调用的方式进行交互,后台就需要将返回的数据转换成
json
格式进行返回
2.SpringMVC入门
主要步骤
- 导入
SpringMVC
坐标与Servlet
坐标(scope
设置为provided
,具体原因见代码中的注释)- 创建
SpringMVC
控制器类(等同于Servlet
功能,具体代码见UserController.java
)- 初始化
SpringMVC
环境,设定SpringMVC
加载对应的bean
(具体代码见SpringMvcConfig.java
)
@ResponseBody
:设置当前控制器方法响应内容(json
格式)为当前返回值,无需解析成某个资源在项目中的路径@RequestMapping
:设置当前控制器方法请求访问路径- 初始化
Servlet
容器,加载SpringMVC
环境,并设置SpringMVC
技术处理的请求(具体代码见ServletContainersInitConfig.java
,简化版本可见Springmvc_02_bean_load中的)
createServletApplicationContext
:创建Servlet
容器时,加载SpringMVC
对应的bean
并放入WebApplicationContext
对象范围(即整个web
容器范围)中getServletMappings
方法:设定SpringMVC
对应的请求映射路径(即SpringMVC
拦截哪些请求)createRootApplicationContext
方法:如果创建Servlet
容器时需要加载非SpringMVC
对应的bean
,使用当前方法进行- 通过插件启动
tomcat
(先按下图进行配置)
代码参考Springmvc_01_quickstart
工作流程解析
- 启动服务器初始化过程:
- 服务器启动,执行
ServletContainersInitConfig
类,初始化web
容器(类似于以前的web.xml
)- 执行
createServletApplicationContext
方法,创建WebApplicationContext
对象- 加载
SpringMvcConfig
配置类- 执行
@ComponentScan
加载对应的bean
- 加载
UserController
,每个@RequestMapping
的名称对应一个具体的方法(所有映射会放置在一起管理)- 执行
getServletMappings
方法,设定SpringMVC
拦截请求的路径规则
- 单次请求过程:
- 发送请求http://localhost/save
web
容器发现该请求满足SpringMVC
拦截规则,将请求交给SpringMVC
处理- 解析请求路径
/save
,由/save
匹配执行对应的save
方法并执行- 检测到有
@ResponseBody
直接将save
方法的返回值作为响应体返回给请求方
bean加载控制
在
com.psj
包下有controller
、service
和dao
等包,在SpringMVC
的配置类SpringMvcConfig
中扫描范围为controller
,而在Spring
的配置类SpringConfig
中扫描的范围(com.psj
)中包含了controller
,如何避免Spring
错误加载SpringMVC
的bean
?
Spring
加载的bean
设定扫描范围为精准范围,例如service
包、dao
包等@ComponentScan({"com.psj.service","com.psj.dao"})
Spring
加载的bean
设定扫描范围为com.psj
,但排除掉controller
包// excludeFilters属性:设置扫描加载bean时,排除的过滤规则 // type属性:设置排除规则,当前使用按照bean定义时的注解类型进行排除 // classes属性:设置排除的具体注解类,当前设置排除@Controller定义的bean @ComponentScan(value = "com.psj", excludeFilters = @ComponentScan.Filter( type = FilterType.ANNOTATION, classes = Controller.class ) )
- 不区分
Spring
与SpringMVC
的环境,加载到同一个环境中代码参考Springmvc_02_bean_load
3.请求与响应
请求映射路径
团队多人开发时,每人设置在设置路径时出现冲突该如何解决?比如下面这种情况,启动
tomcat
时会报错// UserController中存在一个save方法,路径设置为/save public class UserController { @RequestMapping("/save") @ResponseBody public String save(){ .... } } // BookController中存在一个save方法,路径设置也为/save public class BookController { @RequestMapping("/save") @ResponseBody public String save(){ .... } }
需要在类上加
@RequestMapping
注解进行优化,优化后的代码如下:@Controller @RequestMapping("/user") public class UserController { @RequestMapping("/save") @ResponseBody public String save(){ ... } } @Controller @RequestMapping("/book") public class UserController { @RequestMapping("/save") @ResponseBody public String save(){ ... } }
代码参考Springmvc_03_request_mapping
请求方式和参数
请求方式有哪几种?主要是
GET
和POST
接收到请求后,如何接收页面传递的参数?开发中发送
JSON
格式数据为主,@RequestBody
应用较广。如果发送非JSON
格式数据,选用@RequestParam
接收请求参数代码参考Springmvc_04_request_param
-
请求方式:
GET
发送不同种类的参数:- 如果参数中传输中文导致接收到的参数出现乱码,可以在
pom.xml
中添加<uriEncoding>
)
- 如果参数中传输中文导致接收到的参数出现乱码,可以在
POST
发送不同种类的参数:和GET
请求一致(只不过参数写在请求体中)- 使用
PostMan
发送POST
请求时,选择form-data
和x-www-form-urlencoded
的区别在于后者可以传输文件 - 如果控制台打印出现中文乱码问题,可以在
ServletContainersInitConfig
中配置过滤器
- 使用
-
请求参数:具体参考
UserController.java
-
普通参数:
- 参数名与形参变量名相同时,定义形参即可接收参数
// http://localhost/commonParam?name=psj&age=18 @RequestMapping("/commonParam") @ResponseBody public String commonParam(String name ,int age){ ... }
- 参数名与形参变量名不相同时,在形参前使用
@RequestParam
注解
// http://localhost/commonParamDifferentName?name=psj&age=18 @RequestMapping("/commonParamDifferentName") @ResponseBody public String commonParamDifferentName(@RequestParam("name") String userName, int age){ ... }
-
POJO
类型参数:请求参数名与形参对象属性名相同,定义POJO
类型形参即可接收参数(请求参数key
的名称要和POJO
中属性的名称一致,否则无法封装)
// http://localhost/pojoParam?name=psj&age=18(User对象中包含这两个属性) @RequestMapping("/pojoParam") @ResponseBody public String pojoParam(User user){ System.out.println("pojo参数传递 user ==> "+user); return "{'module':'pojo param'}"; }
- 嵌套
POJO
类型参数:与POJO
类型参数一致,只不过在发生请求时有所区别
// http://localhost/pojoContainPojoParam?name=psj&age=18&address.city=yunnan(User对象中包含这两个属性以及Address对象) @RequestMapping("/pojoContainPojoParam") @ResponseBody public String pojoContainPojoParam(User user){ ... }
- 数组类型参数:请求参数名与形参对象属性名相同且请求参数为多个,定义数组类型即可接收参数
// http://localhost/arrayParam?likes=game&likes=ball(传入两个属性名一致的参数) @RequestMapping("/arrayParam") @ResponseBody public String arrayParam(String[] likes){ ... }
- 集合类型参数:请求参数名与形参集合对象名相同且请求参数为多个,使用
@RequestParam
绑定参数关系
// http://localhost/listParam?likes=game&likes=ball(传入两个属性名一致的参数) @RequestMapping("/listParam") @ResponseBody // SpringMVC将List看做是一个POJO对象来处理(相当于把likes视为List中的属性),但是List是一个接口无法创建对象,所以报错,需要使用@RequestParam public String listParam(@RequestParam List<String> likes){ System.out.println("集合参数传递 likes ==> "+ likes); return "{'module':'list param'}"; }
-
JSON
类型参数(GET
和POST
皆可):SpringMVC
默认使用的是jackson
来处理JSON
的转换,所以需要在pom.xml
添加jackson
依赖。并且在PostMan
中需要设置为Body
然后再写入JSON
数据。最后要在SpringMvcConfig
中开启SpringMVC
的注解支持(包含了将JSON
转换成对象的功能)JSON
普通数组:如{"game, "ball"}
@RequestMapping("/listParamForJson") @ResponseBody // 使用@RequestBody注解将外部传递的JSON数组数据映射到形参的集合对象中作为数据 public String listParamForJson(@RequestBody List<String> likes){ ... }
JSON
对象数据:如{"name":"psj", "age":18}
@RequestMapping("/pojoParamForJson") @ResponseBody public String pojoParamForJson(@RequestBody User user){ ... }
JSON
对象数组:如[{"name":"psj", "age":18},{"name":"psw", "age":18}]
@RequestMapping("/listPojoParamForJson") @ResponseBody public String listPojoParamForJson(@RequestBody List<User> list){ ... }
-
日期类型参数:如果传入的参数为日期,并且每个参数的日期格式不固定
@RequestMapping("/dataParam") @ResponseBody // 使用@DateTimeFormat注解设置日期类型数据格式,默认格式yyyy/MM/dd public String dataParam(Date date, @DateTimeFormat(pattern="yyyy-MM-dd") Date date1, @DateTimeFormat(pattern="yyyy/MM/dd HH:mm:ss") Date date2){ ... }
-
响应
主要就包含两部分内容:
- 响应页面:不能添加
@ResponseBody
注解- 响应数据:
- 文本数据:需要依赖
@ResponseBody
注解(方法的返回值为字符串,会将其作为文本内容直接响应给前端)JSON
数据:需要依赖@ResponseBody
注解和@EnableWebMvc
注解,同时要在pom.xml
中添加jackson
依赖包用于转换为JSON
(方法的返回值为对象,会将对象转换成JSON
响应给前端)代码参考Springmvc_05_response
4.REST风格
REST
:Representational State Transfer
,它是一种软件架构风格
RESTful
:即根据REST
风格对资源进行访问
简介
要表示一个网络资源的时候,可以使用两种方式:
- 传统风格资源描述形式:不仅麻烦,也不安全(通过
URL
就可知道是什么操作)
- http://localhost/user/getById?id=1
- http://localhost/user/saveUser
- REST风格描述形式:简化请求地址,并且很难猜出该
URL
的具体功能
- http://localhost/user/1
- http://localhost/user
一个相同的
URL
可是新增也可是修改或者查询,如何区分该请求是什么操作?按照不同的请求方式代表不同的操作类型(这只是风格,并不是规范),比如发送http://localhost/users 请求时,根据发送时的请求方式即可区分
- 发送
GET
请求是用来做查询- 发送
POST
请求是用来做新增- 发送
PUT
请求是用来做修改- 发送
DELETE
请求是用来做删除
入门案例
代码参考Springmvc_06_rest中的
UserController.java
注意事项:
- 如果有多个参数需要传递,比如前端发送请求http://localhost/users/1/psj
@Controller public class UserController { @RequestMapping(value = "/users/{id}/{name}",method = RequestMethod.DELETE) @ResponseBody public String delete(@PathVariable Integer id,@PathVariable String name) { System.out.println("user delete..." + id +","+name); return "{'module':'user delete'}"; } }
形参中的注解
@RequestBody
、@RequestParam
、@PathVariable
的区别和应用:
区别:
@RequestParam
用于接收URL
传参或表单传参@RequestBody
用于接收JSON
数据@PathVariable
用于接收路径参数(即使用{参数名称}
描述的路径参数)应用:
- 发送请求参数超过1个时,以
JSON
格式为主,@RequestBody
应用较广- 发送非
JSON
格式数据,选用@RequestParam
接收请求参数- 采用
RESTful
进行开发,且参数数量只有1、2个时,可用@PathVariable
接收请求路径变量
RESTful快速开发
在代码中有许多重复的地方,以下图为例:
- 每个方法的
@RequestMapping
注解中都定义了访问路径/books
- 每个方法的
@RequestMapping
注解中都要使用method
属性定义请求方式- 每个方法响应
JSON
都需要加上@ResponseBody
注解解决方式如下:
- 将
@RequestMapping
提到类上面,用来定义所有方法共同的访问路径- 使用
@GetMapping
、@PostMapping
、@PutMapping
和@DeleteMapping
代替@RequestMapping
注解及其method
属性- 将
@ResponseBody
提到类上面,此时类上有@Controller
和@ResponseBody
,可以使用@RestController
替换这两个注解
代码参考Springmvc_06_rest中的BookController.java
5.案例
代码参考Springmvc_07_rest_case
注意事项:
- 页面访问处理:
- 当页面发送请求http://localhost/pages/book.html,正常来说应该显示
html
页面,但是会显示404,这是因为SpringMVC
拦截了静态资源(ServletContainersInitConfig
中的getServletMappings
方法),根据/pages/books.html
去controller
找对应的方法SpringMVC
需要将静态资源进行放行(具体代码在SpringMvcSupport
中),并且对SpringMvcConfig
进行修改让其能扫描到该配置类
参考
https://www.bilibili.com/video/BV1Fi4y1S7ix?p=43-58