04、Spring MVC
Spring MVC是Spring的Web模块,用来开发Web应用的,它最终作为B/S、C/S模式下的Server端
Web应用的核心就是处理HTTP请求并响应。
一、关于两种开发模式说明
我们使用Spring MVC有两个开发模式
- 前后分离(数据与页面分离)
- @ResponseBody
- @RestController
- 其涉及的生要机制是:序列化(对象 -> 串)、反序列化(串 -> 对象)
- 前后不分离(页面由服务端进行渲染)
- 转发
- 重定向
后面我们对这些内容都会做详细的说明
二、入门案例
新建模块:springmvc-01-helloword
前提:我们使用脚手架搭建的是SpringBoot项目!
pom文件中添加如下依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
新增Controller类:HelloController
@Controller // 告诉spring这是一个控制器(用来处理请求)
public class HelloController {
@ResponseBody // 把返回值放到响应体当中
@RequestMapping("/hello")
public String handle() {
return "Hello,SpringMVC! 你好!~~~"; // 默认返回值是跳转到一个页面
}
}
此时我们运行脚手架,浏览器访问地址:http://localhost:8080/hello,可以看到浏览器页面上输出文本信息 Hello,SpringMVC!你好!~~~~
三、对请求的处理
@RequestMapping
通配符
这个注解称为路径映射注解,可以使用得某一个方法与一个路径进行绑定,以后收到一个请求时它的路径规则与之匹配时就会对应的使用这个绑定的方法进行处理。
路径规则:在路径位置是可以使用通配符的
*:匹配任意多个字符
**:匹配多层路径
?:匹配任意单个字符,有且必须有一个
关于通配符的一些注意点:
- ? 必须要有且仅有一个字符,没有不行多了也不行!不可以匹配/
- 如果我们请求的路与一个没有带通配符的映射匹配并且也与一个带了通配符的映射匹配则优先匹配那个不带通配符的,这个是精确匹配到的,所以精确匹配优先级高于模糊匹配
- * 可以匹配0~N个字符,但是不可以匹配/,因为/是请求路径分割符
- 当*与?的匹配都可以匹配上的时候优先匹配?的
- 精确匹配的优先级:完全匹配 > ?> * > **
- ** 通配符可以匹配多层路径,它只能放在最后,而*却是可以放到字符中间的
- ** 它就是为了匹配多层路径的只能写在最后/**
- 对于都是精确的请求路径映射绑定了多个方法的时候启动项目会报错,精确路径必须要全局唯一
请求限定
我们来说请求限定前先看看@RequestMapping的定义
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
@Reflective({ControllerMappingReflectiveProcessor.class})
public @interface RequestMapping {
String name() default "";
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
RequestMethod[] method() default {};
String[] params() default {};
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {};
}
这个注解中属性name,value,path都是指的同一个东西(映射的路径)
method属性
它用来指定请求的方法
请求的方法有:GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS,TRACE
如果请求的路径匹配,但是请求的方法不匹配会报错405 Method Not Allowed
如下示例:我们要求test01这个方法绑定的请求路径/test01只能是DELETE或者是GET请求
@RequestMapping(value = "/test01", method = {RequestMethod.DELETE,RequestMethod.GET})
public String test01() {
return "test01";
}
params属性
它用来指定请求参数
- 写法一:params="username" 表示必须要带有username参数,具体请求时携带的值是什么不关心
- 写法二:params="age=18" 表示必须要带上一个参数age,并且请求时携带的值是18
- 写法三:params="gender!=1" 表示给定的参数gender值不可以为1,注意,这个时候如果请求没有gender参数也表示不为1,也是可行的
- 写法四:params={"username=admin","age","gender!=1"} 一次可以指定多个参数规则
- 写法五:params="!username" 表示不可以携带username这个参数
如果请求时,请求参数不匹配会返回400错误 Bad Request
如下例所示:
@RequestMapping(value = "/test02", params = {"username=admin", "age", "gender!=1"})
public String test02() {
return "test02";
}
headers属性
它的写法与params的写法是一样的,只不过它匹配是针对的请求头中的内容
如果请求头不匹配则会返回404错误 Not Found
如下例所示:
@RequestMapping(value = "/test03", headers = "haha")
public String test03() {
return "test03";
}
consumes属性
这个属性用来限定请求内容类型
consumes="application/json" 表示请求的内容类型为application/json
如果请求内容类型不匹配会返回415错误 Unsupported Media Type
如下例所示:
@RequestMapping(value = "test04",consumes = "application/json")
public String test04() {
return "test04";
}
produces属性
这个属性用来限定响应类型
produces="text/html;charset=utf-8" 指定向浏览器响应的类型
如下例所示:
@RequestMapping(value = "/test05",produces = "text/html;charset=utf-8")
public String test05() {
return "<h1>你好,张三</h1>";
}
关于HTTP
对于web模型来说就是请求与响应,在请求与响应之间数据交换都是使用的HTTP
请求
请求的结构:
- 请求首行:包含 请求方式,请求路径,请求协议
- 请求头:key: value(有多个key: value,每一对占一行)
- 请求体:对于get请求来说,请求参数会携带在请求路径上,post请求的请求参数会放在请求体中
HTTP的默认端口号是:80
HTTPS的默认端口号是:443
get请求与post请求对比:
- get请求参数附带在浏览地址上以明文展示,不安全;post请求参数放在请求体当中相对安全一点
- 请求路径长度是有限制的,使用get请求参数过长需要使用post来请求
- 对于类似于文件上传这种请求,数据无法携带在地址栏上,需要使用post请求
- get请求一般用来查询服务器中的资源,而不对资源进行变更,post请求则一般是提交资源到服务器可能会导致资源的变更
请求头中有很多重要的信息,使用Spring MVC可以快速地获取到它们
请求体中携带大量的数据,特别是post请求,会把请求的参数放到请求体当中
JSON数据格式
JSON:JavaScript Objecet Notation(JavaScript对象表示法)
它用于把结构化的数据表示为JavaScript对象的标准格式,常常用于在网站上表示和传输数据
JSON可以作为一个对象或者字符串存在
- 作为对象用于解读JSON中的数据,作为字符串用于在网络上传输
- JavaScript提供了一个全局可访问的JSON对象来对这两种数据进行转换
JSON是一种纯数据格式,它只包含属性,没有方法的
注:把字符串转为原生对象叫做反序列化;把原生成象转为可以通过网络传输的字符串叫做序列化
请求数据的接收
普通变量收集请求
如下所示
// 请求参数:username=zhangsan&password=123456&cellphone=12345678909&agreement=on
@RequestMapping("/handle01")
public String handle01(String username,String password,String cellphone,boolean agreement) {
System.out.println("handle01()方法执行了!");
System.out.println("username="+username+",password="+password+",cellphone="+cellphone+",agreement="+agreement);
return "ok";
}
处理方法中的参数保持与请求提交过来的参数对应
- 如果我们的请求参数中没有某个参数,那么处理方法中把这个参数封装为默认值
- 如果请求参数中名称与方法的参数名能匹配上则直接封装上
注意:使用这种方式来接收请求参数的值要求方法中的参数名要与请求的参数名保持一致!!
使用@RequestParam注解
这个注解要写在方法参数上,如下所示
@RequestMapping("/handle02")
public String handle02(@RequestParam("username") String name,
@RequestParam("password") String pwd,
@RequestParam("cellphone") String phone,
@RequestParam("agreement") boolean ok) {
System.out.println("handle02()方法执行了!");
System.out.println("username="+name+",password="+pwd+",cellphone="+phone+",agreement="+ok);
return "ok";
}
这个时候客户端提交的请求参数至少要有@RequestParam中指定的这四个参数,当然比它多是没有问题的,如果少了就会报400错误 Bad Requet
如果其中某个参数不是必须的,也就是说客户端可能不传也可能传这个参数,这个时候可以在@RequestParam上指定一个属性required=false,表示这个请求参数可有可无,如下所示
@RequestParam(value = "agreement",required = false) boolean ok
这个时候请求参数:agreement 不是必须的,客户端可以不传
如果我们在没有传某个请求参数时,又不希望处理方法中的参数被赋值为默认值,想让我们的要求同赋值,则可以在@RequestParam中再加上一个属必 defaultValue="指定默认值",如下所示:
@RequestParam(value = "password",required = false,defaultValue = "123456") String pwd
这个时候请求参数password没有传的时候,pwd参数不会被赋默认的null,而是会赋值“123456”
注意:一旦我们指定了defaultValue这个属性后,其中required=false是可以省略不写的!!
使用POJO封装请求参数
先定义一个实体类
@Data
public class Person {
private String username;
private String password;
private String cellphone;
private boolean agreement;
}
@RequestMapping("/handle03")
public String handle03(Person person) {
System.out.println("handle03()方法执行了!");
System.out.println(person);
return "ok";
}
如果我们目标方法上的参数是一个POJO时,SpringMVC会自动把请求参数与POJO属性进行匹配
匹配的原理:利用反射对处理方法上的pojo对象属性进行赋值
注意:要保证可以封装上的话需要请求参数的名称与pojo对象的属性名可以匹配上!!
如果请求参数中没有带某个对应属性的参数,则pojo中对应属性的值会封装为默认值。这个默认值如果我们自己要控制的话可以在pojo属性声明上直接给一个初始值就好了!
@RequestHeader获取请求头
如下所示:
@RequestMapping("/handle04")
public String handle04(@RequestHeader(value = "host",required = false,defaultValue = "localhost") String host,
@RequestHeader("user-agent") String userAgent) {
System.out.println("handle04()方法执行了!");
System.out.println("userAgent="+userAgent);
System.out.println("host="+host);
return "ok";
}
获取请求头的方式与@RequestParam来获取请求参数的方式是类似的只是这个针对的是请求头的信息。
@CookieValue获取Cookie
如下所示:
@RequestMapping("/handle05")
public String handle05(@CookieValue("haha") String haha) {
System.out.println("handle05()方法执行了!");
System.out.println("haha="+haha);
return "ok";
}
注意:Cookie在前后端分离的项目中是使用不了了的!!
POJO级联封装复杂对象
如下所示:
我们有一个复杂的POJO类Person
@Data
public class Person {
private String username;
private String password;
private String cellphone;
private boolean agreement;
private Address address;
private String sex;
private String[] hobby;
private String grade;
}
@Data
class Address {
private String province;
private String city;
private String area;
}
在处理器方法参数中使用这个类的对象来进行接收,会封装为这个POJO的对象
@RequestMapping("/handle06")
public String handle06(Person person) {
System.out.println("handle06()方法执行了!");
System.out.println(person);
return "ok";
}
@RequestBody封装json对象
前面我们获取请求数据的时候请求的数据都是按key=value这种格式来的
@RequestBody可以取出请求体的json数据自动转为对象(这里做了一个反序列化)
注意:
1、json中的数据在请求体中,所以不可以使用get请求
2、在json中不像在表单中,对于boolean值要使用"true"/"false",而不是“on”/"off"
文件文件
SpringMVC中专门使用MultipartFile
专门用来封装文件上传的文件项
要对应的取请求中的哪个文件一般配合@RequestParam来指定
@RequestMapping("/handle08")
public String handle08(Person person,
@RequestParam(value = "headerImg",required = false) MultipartFile headerImgFile,
@RequestParam(value = "lifeImg",required = false) MultipartFile[] lifeImageFiles) throws IOException {
System.out.println("handle08()方法执行了!");
// 创建目标目录
File targetDir = new File("E:\\Base\\ssm\\ssm-parent\\img");
if (!targetDir.exists()) {
targetDir.mkdirs(); // 如果目录不存在,则创建目录
}
if (headerImgFile != null) {
// 获取原始文件名
String originalFilename = headerImgFile.getOriginalFilename();
// 获取文件大小
long size = headerImgFile.getSize();
// 获取文件流
InputStream inputStream = headerImgFile.getInputStream();
System.out.println("originalFilename=" + originalFilename + ",size=" + size);
// 文件保存
headerImgFile.transferTo(new File(targetDir, originalFilename));
}
if (lifeImageFiles.length > 0) {
for (MultipartFile lifeImageFile : lifeImageFiles) {
String originalFilename = lifeImageFile.getOriginalFilename();
long size = lifeImageFile.getSize();
InputStream inputStream = lifeImageFile.getInputStream();
System.out.println("originalFilename=" + originalFilename + ",size=" + size);
lifeImageFile.transferTo(new File(targetDir, originalFilename));
}
}
System.out.println("=========文件保存成功========");
System.out.println(person);
return "ok";
}
SpringMVC上传文件是有默认的大小限制的,如是我们要改这个限制则需要在配置文件中修改
# 单个文件大小限制,默认是1MB,以下配置单件大小限制100MB
spring.servlet.multipart.max-file-size=100MB
# 所以文件大小限制,默认是10MB,以下配置为所有文件大小限制1GB
spring.servlet.multipart.max-request-size=1GB
HttpEntity封装请求原始数据
HttpEntity作为请求处理方法中的参数类型,它有一个泛型,其中泛型是什么就是请求体中是什么 。
HttpEntity可以一次性把整个原始请求拿过来
对于请求体中的json也可以自动反序列化为指定的对象,比如我们请求体中是一个josn想让它自动转为Person对象则可以使用HttpEntity<Person>
如下所示:
@RequestMapping("/handle09")
public String handle09(HttpEntity<Person> entity) {
System.out.println("handle09()方法执行了!");
// 拿到所有的请求头
Ht