SpringMVC基础¶
约 3351 个字 276 行代码 5 张图片 预计阅读时间 15 分钟
MVC架构模式介绍¶
MVC(Model View Controller)是软件工程中的一种软件架构模式,它把软件系统分为模型(Model)、视图(View)和控制器(Controller)三个基本部分。用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑
- M:
Model
模型层:存放和数据库对象的实体类以及一些用于存储非数据库表完整相关的VO(valueObject)对象或者存放一些对数据进行逻辑运算操作的的一些业务处理代码 - V:
View
视图层:存放一些视图文件相关的代码,例如HTML、CSS、JS - C:
Controller
控制层:接收客户端请求,获得请求数据,最后将准备好的数据响应给客户端
MVC模式工作流程如下图所示:
MVC模式下项目中的常见包:
-
Model
模型层:- 实体类包(
pojo
/entity
/bean
)专门存放和数据库对应的实体类和一些VO对象 - 数据库访问包(
dao
/mapper
)专门存放对数据库不同表格CURD方法封装的一些类 - 服务包(
service
)专门存放对数据进行业务逻辑运算的一些类
- 实体类包(
-
Controller
控制层:控制层包(controller
) -
View
视图层:Web目录下的视图资源,例如HTML、CSS、JS、图片
SpringMVC介绍¶
SpringMVC,全称为Spring Web MVC,是基于Servlet API构建的原始Web框架,从一开始就包含在Spring框架中。它的正式名称Spring Web MVC来自其源模块的名称(Spring-webmvc
),在大部分情况下都会称为SpringMVC
关于Servlet
Servlet(server applet)是运行在服务端(例如Tomcat)的Java小程序,是SUN公司提供一套定义动态资源规范。从代码层面上来讲Servlet就是一个接口,其主要用来接收、处理客户端请求、响应给浏览器的动态资源。在整个Web应用中,Servlet主要负责接收处理请求、协同调度功能以及响应数据。因此可以把Servlet称为Web应用中的控制器
不是所有的Java类都能用于处理客户端请求,能处理客户端请求并做出响应的一套技术标准就是Servlet,因为其要处理客户端请求,所以Servlet必须在WEB项目中开发且在Tomcat这样的服务容器中运行
在Spring实现MVC时结合了自身项目的特点对MVC模式进行了一定的改变,如下图所示:
可以从图中看到最大的改变就是浏览器直接请求Controller
而不是通过视图发起请求
SpringMVC与SpringBoot¶
SpringMVC是Spring框架的一个Web模块,专注于MVC模式下的Web开发,而SpringBoot是Spring的扩展框架,通过自动配置和starters简化了Spring应用的搭建和部署,而SpringBoot是实现SpringMVC的其中一种方式,通过添加各种依赖和SpringMVC框架来实现Web功能
接下来的介绍会基于SpringBoot项目,在SpringBoot项目中同时介绍SpringMVC的相关知识。而本文档介绍SpringMVC基础内容时从下面三个方面进行:
- 建立连接
- 请求处理
- 响应处理
客户端与服务端建立连接¶
@RequestMapping
介绍¶
在SpringMVC中,要想做到浏览器可以请求到后端的动态资源可以使用@RequestMapping
注解实现URL路由映射。对应的,在具体的类上添加@RestController
例如下面的代码:
Java | |
---|---|
1 2 3 4 5 6 7 8 |
|
在@RequestMapping
注解中,第一个值为路径参数,例如上面例子中的"/sayHello"
,这个路径可以带/
,也可以不带/
,但是一般情况下推荐带上/
表示路径。接着,在浏览器通过地址http://localhost:8080/sayHello
中发起请求即可看到返回的字符串Hello World!
@RequestMapping
修饰位置¶
@RequestMapping
既可以修饰类,也可以修饰方法:
@RequestMapping
修饰类时,表示请求路径的初始信息@RequestMapping
修饰方法时,表示请求路径的具体信息
当修饰类和方法时,访问的地址就是类路径+方法路径,例如下面的代码:
Java | |
---|---|
1 2 3 4 5 6 7 8 9 |
|
此时的访问路径即为http://localhost:8080/hello/sayHello
,一般情况下建议在类上添加@RequestMapping
,这样可以避免方法路径的重复导致的服务器错误
@RequestMapping
多层路径¶
@RequestMapping
除了设置一层路径以外,还可以设置多层路径,例如:
Java | |
---|---|
1 2 3 4 5 6 7 8 9 |
|
此时的访问路径即为http://localhost:8080/user/hello/v1/sayHello
@RequestMapping
与@GetMapping
和@PostMapping
¶
默认情况下,@RequestMapping
可以处理所有类型的HTTP请求,包括但不限于GET
和POST
,如果想要指定只处理GET
请求和POST
请求,可以设置@RequestMapping
的第二个属性值
例如,对于GET
请求:
Java | |
---|---|
1 2 3 4 5 6 7 8 9 |
|
此时只有发送了GET
请求才可以执行hello()
方法
为了简化上面的写法,如果只想让方法处理GET
请求可以使用@GetMapping
,例如上面的代码可以修改为:
Java | |
---|---|
1 2 3 4 5 6 7 8 9 |
|
同样,对于POST
请求可以通过@RequestMapping
的第二个属性值进行指定,例如下面的代码:
Java | |
---|---|
1 2 3 4 5 6 7 8 9 |
|
同样,如果只想让方法处理POST
请求可以使用@PostMapping
,例如上面的代码可以修改为:
Java | |
---|---|
1 2 3 4 5 6 7 8 9 10 |
|
对于其他的请求方式也是同样的逻辑,此处不再演示
Note
需要注意的是,@GetMapping
和@PostMapping
不支持类上添加,只支持方法上添加,对于其他针对某一种请求方式的注解也是如此
请求处理¶
处理一个参数¶
当请求中只有一个参数,例如?name=zhangsan
时,接收方式很简单,只需要在处理方法中定义一个形参名字与请求参数key
的名字一样即可,例如下面的代码用于接收name
参数:
Java | |
---|---|
1 2 3 4 5 6 7 8 9 10 |
|
此时发送请求为http://localhost:8080/hello/sayHello?name=zhangsan
即可收到下面的信息:
Text Only | |
---|---|
1 |
|
对于POST
请求来说,参数放在请求参数或者请求体都是可以正常接收的,接收代码与GET
请求类似:
Java | |
---|---|
1 2 3 4 5 6 7 8 9 10 |
|
但是需要注意的是,如果方法参数是基本数据类型(不包括布尔类型,如果是布尔类型不传递参数时,默认值为false
)时,参数不可以不传递,否则就会报错,因为当不传递参数时,如果是引用数据类型,那么值为null
可以正常赋值给引用数据类型,但是如果参数是基本数据类型无法将null
转换为基本数据类型从而报错。所以在处理基本数据类型的参数时建议使用对应的包装类而非基本数据类型,例如下面的代码:
Java | |
---|---|
1 2 3 4 5 6 7 8 9 10 |
|
Java | |
---|---|
1 2 3 4 5 6 7 8 9 10 |
|
为什么前端URL参数和后端方法参数一致可以获取到参数
实际上这与Servlet有关,在Servlet开发中,客户端向服务器端发出请求,服务器端的软件Tomcat接收到用户请求后会将请求报文的信息转换为HttpServletRequest
对象,该对象中包含着请求中的所有信息,例如请求头、请求行。需要注意,这一过程中的HttpServletRequest
对象并不是由程序员手动创建的,而是由Tomcat自动创建,并且此时除了存在HttpServletRequest
对象以外,还有一个HttpServletResponse
对象,该对象用于存储响应报文信息。在Spring Web MVC中包括了Servlet,所以可以直接在方法参数中使用HttpServletRequest
对象,另外因为SpringBoot集成了Tomcat,所以该对象也会被正常设置。以获取参数String name
为例,实现下面的代码:
Java | |
---|---|
1 2 3 4 5 6 7 |
|
如果需要为形参名指定别名,可以在参数部分使用@RequestParam
注解,此时前端传递时应该匹配的时@RequestParam
中的名称而不是变量名:
Java | |
---|---|
1 2 3 4 5 6 7 8 9 |
|
在上面的代码中,前端传递参数时key
应该为username
而不再是name
,例如:http://localhost:8080/hello/sayHello?username=zhangsan
但是,一旦指定了别名,如果不传递参数就会出现无法找到指定资源的情况,即不可以不传递参数。以上面的代码演示,使用Postman发送不指定参数的请求结果如下:
出现这个问题可以从@RequestParam
源码分析:
Java | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 |
|
在@RequestParam
注解源码中有一个字段required
,这个字段默认值为true
,表示一定要传递参数,所以可以通过设置该字段为false
使其在不传递参数时也可以正常请求资源,例如下面的代码:
Java | |
---|---|
1 2 3 4 5 6 7 8 9 |
|
此时发起请求http://localhost:8080/hello/sayHello
即可得到下面的结果:
Text Only | |
---|---|
1 |
|
处理多个参数¶
处理多个参数与处理一个参数比较类似,只需要在方法参数部分添加多个参数即可,例如下面的代码:
Java | |
---|---|
1 2 3 4 5 6 7 8 9 |
|
需要注意的是,URL中的参数顺序可以不与方法参数的顺序一致,但是参数名必须一致,即请求地址可以为http://localhost:8080/hello/sayHello?id=1&name=zhangsan
处理对象¶
上面介绍了处理多个参数,但是多个参数彼此并没有任何关系,有时前端传递的一个参数可能是属于某一个类的对象的字段值,此时就推荐使用对象而不是使用多个参数的方式,例如下面有一个User
类(注意提供getter
和setter
):
Java | |
---|---|
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 39 40 |
|
处理对象的方式与处理多个参数的方式类似,只需要在方法参数部分添加对象类型的参数即可,例如下面的代码:
Java | |
---|---|
1 2 3 4 5 6 7 8 9 |
|
前端传递方式与多个参数一致,例如:http://localhost:8080/hello/sayHello?name=zhangsan&age=18&gender=man
。此时可以看到下面的结果:
Text Only | |
---|---|
1 |
|
需要注意的是,前面在处理一个参数的时候提到过,如果是基本数据类型(除了布尔类型),则参数必须要传递,但是在处理对象时,对象的字段可以是基础数据类型,因为此时基础数据类型存在默认值,如果没有传递则直接使用默认值。此处不再演示,但是还是推荐使用包装类而不是基本数据类型
处理数组¶
处理数组也是类似,只需要将参数部分修改为数组类型即可:
Java | |
---|---|
1 2 3 4 5 6 7 8 9 10 |
|
此时前端传递参数可以有两种方式:
key
和value
分开指定:http://localhost:8080/hello/sayHello?params=param1¶ms=param2¶ms=param3
key
与value
合并指定:http://localhost:8080/hello/sayHello?params=param1,param2,param3
这两种方式都可以正常得到结果:
Text Only | |
---|---|
1 |
|
但是现在有一个问题,如果是合并指定,那么数组中存储的是三个单独的元素还是一个整体的元素。为了验证这一点,可以在方法中打印数组的大小:
Java | |
---|---|
1 2 3 4 5 6 7 8 9 10 |
|
以合并指定的方式发送请求可以得到如下结果:
Text Only | |
---|---|
1 |
|
通过结果可以看到数组中存储的是三个单独元素而并非一个整体的元素
处理集合¶
集合与数组比较类似,但是需要注意的是,默认情况下,请求中参数名相同的多个值是封装到数组而并非集合,直接传递给参数的集合会出现错误。为了避免这个问题,可以使用@RequestParam
进行参数绑定,确保数组可以转换为集合,以List
为例:
Java | |
---|---|
1 2 3 4 5 6 7 8 9 |
|
请求方式与数组一致,此处不再演示
处理JSON数据¶
对于JSON字符串来说,需要在参数部分使用@RequestBody
进行绑定,而JSON字符串中保存的是一个JSON对象,所以考虑使用一个类对象进行接收(此处依旧使用User
类),例如下面的代码:
Java | |
---|---|
1 2 3 4 5 6 7 8 9 |
|
传递JSON数据时建议在请求体中传递,例如:
JSON | |
---|---|
1 2 3 4 5 |
|
此时可以得到结果:
Text Only | |
---|---|
1 |
|
获取到URL中的资源路径¶
在前面@RequestParam
中使用的都是静态的路径,如果路径中存在动态变化的部分,就需要在@RequestParam
中使用参数的形式(或者具体请求方式的注解,例如@GetMapping
),对应的方法要获取到这个动态变化的参数就需要使用@PathVariable
注解,例如下面的代码:
Java | |
---|---|
1 2 3 4 5 6 7 8 9 |
|
需要注意的是,此时必须保证路径参数和方法参数一致,即{id}
与Integer id
一致,此时请求路径为:http://localhost:8080/hello/sayHello/1
,得到的结果为:
Text Only | |
---|---|
1 |
|
也可以通过@PathVariable
指定别名,此时{user_id}
需要与@PathVariable("user_id")
一致,例如:
Java | |
---|---|
1 2 3 4 5 6 7 8 9 |
|
此时请求路径为:http://localhost:8080/hello/sayHello/1
,得到的结果为:
Text Only | |
---|---|
1 |
|
但是需要注意的是,虽然@PathVariable
注解也存在required
字段,但是不论是修改为true
还是false
都必须指定路径参数
上传文件¶
上传文件时使用的参数类型是MultipartFile
,例如下面的代码:
Java | |
---|---|
1 2 3 4 5 6 7 8 9 |
|
如果想为文件指定别名,可以使用@RequestPart
注解:
Java | |
---|---|
1 2 3 4 5 6 7 8 9 |
|