?个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343
?️热门专栏:
? Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm=1001.2014.3001.5482
? Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482
?Java EE(96平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482
?MySql数据库(93平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482
?算法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12676091.html?spm=1001.2014.3001.5482
? Spring(97平均质量分)https://blog.csdn.net/2301_80050796/category_12724152.html?spm=1001.2014.3001.5482
感谢点赞与关注~~~
目录
1. 什么是Spring Web MVC1.1 什么是MVC1.2 什么是Spring MVC 2. Spring MVC深入学习2.1 建立连接2.2.1 @RequsetMapping注解介绍2.2.2 @RequsetMapping的使用2.2.3 @RequsetMapping支持哪些方法类型的请求 2.3 请求2.3.1 传递单个参数2.3.2 传递多个参数2.3.3 传递对象2.3.4 后端参数重命名2.3.5 传递数组2.3.6 传递集合2.3.7 传递json数据2.3.8 获取URL中的参数@PathVariable2.3.9 提交表单数据@RequestPart2.3.10 获取Cookie/Session2.3.11 获取Header 2.4 响应2.4.1 返回静态页面2.4.2 返回数据@ResponseBody2.4.3 返回html代码片段2.4.4 返回json2.4.5 设置状态码2.4.6 设置header
1. 什么是Spring Web MVC
Spring Web MVC是基于Servlet API构建的原始Web框架,从⼀开始就包在Spring框架中。它的正式名称“Spring Web MVC”来自其源模块的名称(Spring-webmvc),但它通常被称为"Spring MVC".
总结来说,Spring Web MVC是一个Web框架.
想要理解什么是Spring MVC我们首先先要理解什么是MVC
1.1 什么是MVC
MVC是Model View Controller的缩写,是软件工程中共的一种软件架构的设计模式.把软件系统分为模型,控制器,视图三个部分.
比如我们去饭店吃饭:
顾客进店之后,服务员来接待客户点餐,客户点完餐之后,把客户菜单交给前厅,前厅根据客户菜单给后厨下达命令.后厨负责做饭,做完之后,再根据菜单告诉服务员,这是X号餐桌客人的饭.
在这个过程中:
1.2 什么是Spring MVC
MVC是一种架构模式,也是一种思想.而Spring MVC是对MVC思想的具体实现.除此之外,Spring MVC还是一个Web框架.
总结:Spring MVC是一个实现了MVC软件设计模式的Web框架.
其实Spring MVC在前面我们就使用过了.在我们创建Spring Boot项目的时候,选择Spring Web的时候,其实就是Spring MVC框架.也就是在创建的Spring Boot项目中添加了Spring Web MVC 的相关依赖,使得该项目具有了网络通信的功能.
Spring Boot只是实现Spring MVC的一种方式而已.Spring Boot中可以添加很多依赖,我们在Spring Boot项目中添加了Spring MVC的框架,那么这个Spring Boot项目就可以实现Web的功能.
不过Spring MVC在实现MVC模式的时候,也结合了自身的一些特点,下面这个图更加适合描述Spring MVC.
通过浏览器来向后端发送请求的时候,没有经过view,而是直接把请求传递给了controller,之后controller选择合适的模型,传递给model,model处理数据之后,把响应返回给controller,之后controller再把响应返回给view,之后view把响应返回给浏览器.
就比如我们去公司面试:我们(浏览器)想要面试的时候,我们可以直接找到公司某部门的负责人(controller),说我要面试(请求),之后部门负责人会找到面试你的那个人(model),面试之后,加入你通过了面试,面试你的那个人会把面试结果传递给部门负责人,之后部门负责人把消息通知给HR(view),之后HR会给你发offer.
2. Spring MVC深入学习
学习Spring MVC,重点也就是学习用户通过浏览器与服务端交互的过程.
主要分为一下三个点:
比如去银行存款:
建立连接:去柜台传递参数:拿着身份证,银行卡去存款返回结果:银行返回一张存折.掌握了上面的三个功能就相当于掌握了Spring MVC.
2.1 建立连接
在Spring MVC中使用@RequestMapping
来实现URL的路由映射,也就是通过这个注解来使得Spring项目与浏览器建立连接.代码如下:
package com.example.demo;import org.springframework.web.bind.annotation.*;@RestControllerpublic class DemoController { @RequestMapping("/hello")//可以理解为资源路径 public String hello() { return "Hello World"; }}
接下来访问http://127.0.0.1:8080/hello
就可以看到返回的程序了.
2.2.1 @RequsetMapping注解介绍
@RequestMapping
是Spring Web MVC应用程序最常被用到的注解之一.它用来注册接口的路由映射.表示的是,服务器在接收到请求的时候,路径为/hello
的请求就会调用hello这个方法的代码.
何为路由映射?
当用户访问⼀个URL时,将用户的请求对应到程序中某个类的某个方法的过程就叫路由映射.
@RequestMapping
已经达到了我们的目的,我们为什么还要加@RestController
呢?@RestController
在资源访问的过程中起着相当重要的作用,在Spring项目接收到一个请求之后,Spring会对所有的类进行扫描,如果家里注解@RestController
,Spring才会去看这个类里面有没有加@RequestMapping
这个注解,才可以通过浏览器中输入的URL对应到这个类中注册的路由映射.如果我们把
@RestController
去掉,就访问不到了. package com.example.demo;import org.springframework.web.bind.annotation.*;@RequestMapping("/demo")public class DemoController { @RequestMapping("/hello")//可以理解为资源路径 public String hello() { return "Hello World"; }}
2.2.2 @RequsetMapping的使用
不仅仅方法前面可以加上@RequestMapping
注解,类的前面也可以加该注解,即@RequestMapping
不仅仅可以修饰方法,还可以修饰类.当修饰类和方法的时候,访问的地址是类路径+方法路径
package com.example.demo;import org.springframework.web.bind.annotation.*;@RestController@RequestMapping("/demo")public class DemoController { @RequestMapping("/hello")//可以理解为资源路径 public String hello() { return "Hello World"; }}
那么在访问hello这个方法的时候,路径就会变为/demo/hello
.
注: 注解中的/hello
和"demo"
虽然不加/也可以正确响应,但是为了编程的规范,还是建议加上.
2.2.3 @RequsetMapping支持哪些方法类型的请求
首先给出结论,@RequsetMapping
支持所有方法类型的请求.下面我们来通过Postman构造请求来实验一下.
GET方法支持
POST方法支持
剩下的方法都是同样的道理,显示的结果都是Hello World,这里不再一一展示.
@RequsetMapping
只接受指定的几种方法的请求呢?我们就需要再注解中加上另外的一个参数,method键值对
@RequestMapping(value = "/hello1",method = RequestMethod.GET)public String hello1() { return "Hello World 1";}
我们看到/hello1
对应的路由映射只支持GET方法.
现在我们去看看method键值对截取的部分源码
public @interface RequestMapping { String name() default ""; @AliasFor("path") String[] value() default {}; @AliasFor("value") String[] path() default {}; RequestMethod[] method() default {};//method返回的是一个RequestMethod类型的数组 String[] params() default {}; String[] headers() default {}; String[] consumes() default {}; String[] produces() default {};}
在源码中,我们可以看到,method返回的是一个RequestMethod类型的数组.那么这个RequestMethod
中都有什么,我们再去查看截取的部分源码.
public enum RequestMethod { GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;}
这里我们可以看到RequestMethod
是一个枚举类型,这里面存放的都是请求中的方法.
由于method
那里接收的是一个关于RequestMethod
枚举类型的数组,所以我们在注解后的键值对的值上传入的也是枚举类型的数组,比如我们想支持GET和POST两种方法:
@RequestMapping(value = "/hello1",method = {RequestMethod.GET, RequestMethod.POST})public String hello1() { return "Hello World 1";}
当然当元素只有一个的时候,大括号是可以省略的.就比如上面那个只有GET方法的例子.
如果指定的方法只有一种,我们也可以采用其他注解来解决比如只支持GET方法,我们就可以使用
@GetMapping
注解来解决. @GetMapping("/hello3")public String hello3() { return "Hello World 3";}
在比如只支持POST方法,可以使用@PostMapping
来解决
@PostMapping("/hello4")public String hello4() { return "Hello World 4";}
2.3 请求
访问不同的路径,就是发送不同的请求.在发送请求时,可能会带⼀些参数,所以学习Spring的请求,主要是学习如何传递参数到后端以及后端如何接收.
传递参数,主要是以浏览器和Postman来模拟.
2.3.1 传递单个参数
在前面,我们的方法都是没有参数存在的,如果给我们的方法加上参数之后会怎么样呢?
@RequestMapping("/name1")public String name1(String name) { System.out.println("接收到了" + name); return "接收到了" + name;}
我们在请求的URL中加上查询字符串,即参数:http://127.0.0.1:8080/demo/name1?name=zhangsan
(?后面的是参数)
在URL中加上的参数传入到后端之后,Spring MVC会根据方法的参数名,找到对应的参数,赋值给方法.之后拿到传入的参数在方法中进行一系列操作之后返回给前端.比如我们将这个请求通过Postman发送给Spring项目.
我们发现成功返回了响应,并且返回了正确的响应.
如果参数不一致,则获取不到参数.
@RequestMapping("/age1")public String age1(int age) { System.out.println("接收到了" + age); return "接收到了" + age;}@RequestMapping("/age2")public String age2(Integer age) { return "接收到了" + age;}
但是如果我们不传递任何参数的时候,这时候基本类型和包装类型就会有所区别,基本类型会直接抛出500错误,而包装类型会输出默认的空值null.
所以,我们在企业开发中,对于参数可能为空的数据,我们建议使用包装类型.
2.3.2 传递多个参数
和接收单个参数⼀样,直接使用方法的参数接收即可.使用多个形参.
@RequestMapping("/person1")public String person1(String name,Integer age) { return "接收到了name" + name + "接收到了age" + age;}
注:
2.3.3 传递对象
如果需要传递的参数比较多的时候,我们不妨把这些参数封装成一个对象.
@RequestMapping("/person3")public String person3(Person person) { return person.getName()+person.getAge()+person.getSex();}package com.example.demo;public class Person { public String name; public int age; public String sex; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; }}
之后我们使用Postman进行请求发送.http://127.0.0.1:8080/demo/person3?name=zhangsan&age=20&sex=男
注意:
我们可以看到age的初始值被默认赋值为了0.如果我们针对参数是对象的使用form表单进行参数传递,在GET和POST两种方法中,只有POST方法会返回正确的结果,而GET方法会返回默认的空值.
2.3.4 后端参数重命名
在一些特殊的情况下,前端传递的参数key和我们后端接收的key可能不⼀致,比如我们传递了一个Name给后端,但是后端需要接收的参数是name,这时候就需要用到@RequestParam
(翻译:请求参数)来对后端的参数进行重命名.
@RequestMapping("/person2")public String person2(@RequestParam("Name") String name,Integer age) { return "接收到name" + name + "接收到age" + age;}
上面这段代码,其中Name就是前端要传递的参数,而name是后端使用的参数,此时Spring可以正确的把请求传递的参数Name绑定到后端参数name参数上.
我们使用http://127.0.0.1:8080/demo/person2?Name=zhangsan&age=20
来进行请求传递
如果我们把Name改成name,就无法进行正确的参数传递了.
[注意事项]
在使用@RequestParam
对参数进行重命名的时候,参数就变成了必传参数.
那么造成上面这种情况的原因是什么呢,又该如何让他变成一个非必传参数呢?现在我们来查看@RequestParam
的源码:
public @interface RequestParam { @AliasFor("name") String value() default ""; @AliasFor("value") String name() default ""; boolean required() default true; String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";}
我们可以看到,required那一栏默认的值是true,表示的含义就是,该注解修饰的参数是必传参数.既然如此,我们可以通过设置@RequestParam
的required参数=false来实现让这个参数成为非必传参数.
@RequestMapping("/person2")public String person2(@RequestParam(value = "Name",required = false) String name,Integer age) { return "接收到name" + name + "接收到age" + age;}
此时Name为传递的时候,会有默认的初始值null来返回.
2.3.5 传递数组
Spring MVC可以自动绑定数组参数的赋值.
@RequestMapping("/param1")public String param1(String[] arrayParam) { return Arrays.toString(arrayParam);}
使用Postman进行传参:
请求参数名与形参数组名称相同且请求参数为多个,后端的方法形式参数中即可自动接收传输过来的参数.把所要传递的参数合并在一起传递,中间用逗号隔开.
可以看到以上两种方法均返回了正确的响应.我们如果使用from表单进行发送的话,这时候请求GET和POST就只有POST返回的是正确的结果,而GET返回的是null.
2.3.6 传递集合
集合参数: 和数组传递参数的方法类似,可以是相同的参数名多个参数,也可以把参数写到一起,但是在后端那里,需要加上@RequestParam
来绑定参数关系.
默认的情况下,请求中的参数名相同的多个值,封装的时候是一个数组,而如果要封装集合的话,就需要在参数前面加上@RequestParam
来绑定参数关系,表示传过来的是一个数组,需要转换成集合.
@RequestMapping("/param2")public String param2(@RequestParam("ArrayParam") List<String> arrayParam) { return Arrays.toString(arrayParam.toArray());}
如果不加注解后面的参数的话,在前端传递参数的时候默认就和后端的方法参数是一样的.
2.3.7 传递json数据
什么是jsonJSON就是⼀种数据格式,有自己的格式和语法,使用文本表示一个对象或数组的信息,因此JSON本质是字符串.主要负责在不同的语言中数据传递和交换.json的语法
json是一个字符串,其格式非常类似于python中的字典和JavaScript对象字面量的格式. 数据存储在键值对(key/value)中,key和value之间用:分割.键值对之间由,分割.对象用{ }表示数组用[ ]表示值可以为对象,数组,字符串等等. json的两种结构 对象: 大括号
{}
保存的对象是⼀个无序的键值对集合.⼀个对象以左括号{
开始,右括号}
结束。每个"键"后跟⼀个冒号:
,键值对使用逗号,
分隔数组: 中括号[]
保存的数组是值(value)的有序集合.⼀个数组以左中括号[
开始,右中括号]
结束,值之间使用逗号, 分隔,数组中可以存放多个对象,对象和对象之间用,
分割. 下面我们展示一段json字符串:
{ "squadName": "Super hero squad", "homeTown": "Metro City", "formed": 2016, "secretBase": "Super tower", "active": true, //数据保存在键值对中 //键和值之间使用:分割,键值对之间使用,分割 "members": [{ "name": "Molecule Man", "age": 29, "secretIdentity": "Dan Jukes", "powers": ["Radiation resistance", "Turning tiny", "Radiation blast"]//数组中可以包含多个元素 }, {//这个元素也可以是对象 "name": "Madame Uppercut", "age": 39, "secretIdentity": "Jane Wilson", "powers": ["Million tonne punch", "Damage resistance", "Superhuman reflexes"] }, { "name": "Eternal Flame", "age": 1000000, "secretIdentity": "Unknown", "powers": ["Immortality", "Heat Immunity", "Inferno", "Teleportation", "Interdimensional travel"] }]}
也可以压缩表示为:
{"squadName":"Super hero squad","homeTown":"Metro City","formed":2016,"secretBase":"Super tower","active":true,"members":[{"name":"Molecule Man","age":29,"secretIdentity":"Dan Jukes","powers":["Radiation resistance","Turning tiny","Radiation blast"]},{"name":"Madame Uppercut","age":39,"secretIdentity":"Jane Wilson","powers":["Million tonne punch","Damage resistance","Superhuman reflexes"]},{"name":"Eternal Flame","age":1000000,"secretIdentity":"Unknown","powers":["Immortality","Heat Immunity","Inferno","Teleportation","Interdimensional travel"]}]}
可以使用json在线工具来校验json的书写,如https://www.json.cn/.
json字符串和Java对象的互转Spring MVC框架集成了json的转换工具,我们可以直接拿来使用.我们可以使用
ObjectMapper
中的一系列方法来对json和Java对象两者之间进行转换. package com.example.demo;import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.ObjectMapper;public class json { private static ObjectMapper objectMapper = new ObjectMapper(); public static void main(String[] args) throws JsonProcessingException { Person person = new Person(); person.setName("zhangsan"); person.setAge(18); person.setSex("男"); String json = objectMapper.writeValueAsString(person); System.out.println(json); Person person2 = objectMapper.readValue(json, Person.class);//传入一个json字符串和一个类的类对象 System.out.println(person2.toString()); }}package com.example.demo;public class Person { public String name; public int age; public String sex; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", sex='" + sex + '\'' + '}'; }}
如何传递json对象接收json对象,需要使用
@RequestBody
注解,这个注解翻译过来就是请求正文的意思,意思是这个注解起到的作用是从请求中的正文部分获取数据.请求的参数必须写在正文中.而我们的json字符串就写在请求的正文中.后端是这样实现的:
@RequestMapping("/param3")public String param3(@RequestBody Person person) { return person.toString();}
接下来我们使用Postman构造请求,把json字符串写入请求的正文中:
{ "name":"zhangsan", "age":18, "sex":"male"}
响应结果如下:
如果去掉注解@RequestBody
,那么后端就无法正确接收json数据,前端返回的响应也不正确.
由于后端没有接收到关于person对象传递的任何信息,所以都默认都赋值为了初始值.
2.3.8 获取URL中的参数@PathVariable
PathVariable翻译过来之后,是"路径可变"的意思,意思是我们要通过URL获取路径作为传递过来的参数,而这个路径是不固定的.
这个注解主要的作用就是在请求URL路径上的数据绑定.之前我们通过http请求传递参数都是在?
后面的查询字符串中.而现在我们要通过URL中的资源路径来传递参数.
后端代码实现如下:
在@RequestMapping
的参数中在加上一些路径的标识,每个参数外面使用{ }
括起来.
@RequestMapping("/param4/{name}/{age}")public String param4(@PathVariable String name,@PathVariable Integer age){ return name+age;}
我们通过Postman来构造请求http://127.0.0.1:8080/demo/param4/zhangsan/18
我们看到前端返回了正确的响应.
[注意事项]
如果方法参数名称和需要绑定的URL中的变量名称不⼀致时,需要@PathVariable
的属性value赋值.
@RequestMapping("/param4/{Name}/{Age}")public String param4(@PathVariable("Name") String name,@PathVariable("Age") Integer age){ return name+age;}
前端还是会返回正确的响应.
2.3.9 提交表单数据@RequestPart
@RequestPart
注解用于接收前端传递的表单数据,也就是前端在提交数据的时候使用的是表单的形式进行提交的,所以我们在使用Postman进行发送请求的时候,我们就要使用form-data
进行提交数据.
在提交表单数据的时候,我们可以指定表单中的内容为文件,下面我们来展示如何提交文件.
后端代码实现:
@RequestMapping("/param5")public String param5(@RequestPart("file") MultipartFile file) throws IOException { file.transferTo(new File("D:/personal/" + file.getOriginalFilename())); //上传文件,前面写的是存储文件的路径,后面加上原文件的名字 return "已经获取到" + file.getOriginalFilename();}
使用Postman向指定位置发送文件.
指定位置接收到了文件.
但在表单中还存在一个value为json字符串的键值对的时候,后端在解析MultipartFile
类型的数据时会报错:Content type 'application/octet-stream' not supported
,原因就是,后端想要的数据是一个二进制的数据流(octet-stream),而json是一个字符串的格式,我们需要在项目中添加如下代码即可:
@Componentpublic class MultipartJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter { protected MultipartJackson2HttpMessageConverter(ObjectMapper objectMapper) { // MediaType.APPLICATION_OCTET_STREAM 表⽰这个转换器⽤于处理⼆进制流数据,通常⽤于⽂件上传。 super(objectMapper, MediaType.APPLICATION_OCTET_STREAM); } @Override public boolean canWrite(Class<?> clazz, MediaType mediaType) { return false; } @Override public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) { return false; } @Override protected boolean canWrite(MediaType mediaType) { return false; }}
2.3.10 获取Cookie/Session
回顾Cookie和Session
Cookie中的内容和URL中的query string内容类似,都是键值对内容,它们都是程序员自定义的.每个键值对之间用";“隔开,键和值之间用”="隔开.
浏览器会针对不同的域名,每个网站都有自己的Cookie文件保存在硬盘中.Cookie从哪里来?
Cookie中的数据,来自于服务器(服务器返回给浏览器的数据),访问网站的时候,网站的服务器会返回http响应,在http响应中,会包含Set-Cookie这样的header,它就会把一些键值对保存到浏览器的Cookie中.Cookie保存到浏览器之后,后续浏览器访问该网站的时候,就会在请求的header中,把之前保存的键值对都带入进去,在返回给服务器.那么问题又来了,为什么还要返回给服务器?
这是因为Cookie可以使客户端存储一些必要的"配置信息",从而让服务器对于用户提供的服务更加"个性化".
举例说明:剪头发
A去了一家理发店之后,理发师就问他:“你想怎么剪?” 但是这时候A就会反问理发师:“你能怎么剪”?于是理发师就说:“可以剪平头,毛寸…”,于是A就说:"给我剪个平头."在A下次去了之后,就可以直接告诉理发师,剪个平头.同理,B也经历了上述过程,他最终选择了毛寸.
和上面剪头发是相同的道理,客户端也不止有一个,每个客户端都会有自己的偏好,此时就需要让每个客户端保存这样的数据,之后就可以通过Cookie随时把这样的信息返回给服务器.例如:浏览器的夜间模式和白日模式,一次设置好了之后,下次再打开服务器的时候,浏览器的颜色模式不会改变.
Cookie自动登录Cookie中虽然有很多的键值对都是程序员自定义的,但是往往会有一个特殊的键值对,用来标识用户的身份信息.
首先在获取登录页面与返回登录登录页面的html的过程中不包含任何的Cookie.在用户输入用户名和密码之后,这时候用户名和密码就会交给服务器,验证它们的正确性,在确认正确之后,就会创建会话(session),(会话可以理解为一个类,其中类中具体包含什么,要看业务逻辑,但是其中一定有sessionId,也就是令牌)并把sessionId返回给浏览器,这个sessionId存在于响应报文header中的Set-Cookie中,我们也可以把他叫做"令牌",令牌中存储的是一个字符串,类似于"身份标识",不会存储太多的信息,在浏览器收到sessionId之后,就会把Id存储在硬盘中,即创建了Cookie.在之后客户端要访问该域名下的其他页面的时候,就可以把sessionId交给服务器,服务器获取到sessionId之后,就可以根据这个值,知道用户的详细信息.也就是直接通过之前创建的sessionId的Cookie就可以访问到,无需再次登录.
举例说明:去医院看病
到了医院先挂号.挂号时候需要提供⾝份证,同时得到了⼀张"就诊卡",这个就诊卡存储着关于患者身份信息的sessionId,就相当于患者的"令牌".后续去各个科室进行检查,诊断,开药等操作,都不必再出示⾝份证了,只要凭就诊卡即可识别出当前患者的⾝份.在就诊室的刷卡机上刷一下就诊卡,医生就会知道你的所有信息.看完病了之后,不想要就诊卡了,就可以注销这个卡.此时患者的⾝份和就诊卡的关联就销毁了.(类似于⽹站的注销操作)⼜来看病,可以办⼀张新的就诊卡,此时就得到了⼀个新的"令牌" 使用Spring获取Cookie 传统获取Cookie@RequestMapping("/param6")public String param6(HttpServletRequest request, HttpServletResponse response) throws IOException { Cookie[] cookies = request.getCookies(); for (Cookie cookie : cookies) { System.out.print(cookie.getName()+":"+cookie.getValue()); } return "已经获取到Cookie";}
之后我们通过浏览器伪造Cookie,来向后端传递请求.http://127.0.0.1:8080/demo/param6
我们看到了后端已经成功拿到了Cookie中的数据.
Spring MVC是Spring基于Servlet实现的.其中上面一段代码中的
HttpServletRequest
和HttpServletResponse
是Servlet提供的两个类.这两个类在每一个接口中均默认存在,需要的时候写出来就可以.HttpServletRequest
对象代表客户端的请求.请求中的所有信息均可以通过这个类拿到.HttpServletResponse
对象代表服务器的响应.响应中的所有信息均可以通过这个类拿到.获取Cookie中的某一个键值对方法一: 使用
equals()
方法@RequestMapping("/param7")public String param7(HttpServletRequest request) { Cookie[] cookies = request.getCookies(); for (Cookie cookie : cookies) { if ("bite".equals(cookie.getName())) { return cookie.getValue(); } } return "为获取到指定的Cookie";}
通过浏览器构造Cookie,我们看到了前端返回了正确的响应.后端打印了相应的日志:
方法二:通过
@CookieValue
注解获取. @RequestMapping("/param8") public String param8(@CookieValue("bite") String bite) { System.out.println("获取到了" + bite); return bite; }
前端与后端响应均正确:Session的存储和获取
Session是服务器端的机制,它需要先存储,才能获取到. Session的存储
@RequestMapping("/param9")public String param9(HttpServletRequest request){ //获取Session对象,如果不存在Session对象,getSession之后不加或参数为true会自动创建 HttpSession session = request.getSession(); if (session != null) {//确保Session成功创建 session.setAttribute("bite", "888"); } return "Session存储成功";}
方法解释:getSession(boolean create)
: 如果参数为true的时候,就会在Session不存在的时候,自动创建Session,如果参数为false,就不会创建.getSession()
:和上一种方法参数为true的时候效果相同.setAttribute(String s,String o)
:设置Session中的参数.Session的读取读取Session依然使用
HttpServletRequest
@RequestMapping("/param10")public String param10(HttpServletRequest request){ HttpSession session = request.getSession(false); if (session != null){ String s = (String) session.getAttribute("bite"); System.out.println("获取到了Session"); return s; } return "未获取到Session";}
运行[注意事项] 在重启服务器之后,上一次存在内存中的Session数据会被清空,需要重新设置Session之后才可以获取到.
我们可以看到,存储了Session之后,浏览器把SessionID存储在了Cookie中.
之后我们可以使用浏览器存储的令牌获取Session.
简洁获取Session
上面获取Session的方法比较传统,我们下面展示两种简洁的方法.
方法一:使用
@SessionAttribute
注解@RequestMapping("/param11")public String param11(@SessionAttribute(value = "bite",required = false) String bite){ return bite;}
运行结果如下:@SessionAttribute
的后面两个注解表示的意思和前面@RequestParam
注解后面的两个参数的作用非常像.第一个作用是参数绑定的作用,第二个参数如果为true或者不写,表示这个参数是必传参数,如果为false,就是非必传参数,如果Session传递未成功,就返回null.方法二:直接使用
HttpSession
作为参数@RequestMapping("/param12")public String param12(HttpSession session){ String string = (String) session.getAttribute("bite"); System.out.println("成功获取到了Session"); return string;}
HttpSession
作为参数的时候,效果和getSession()
方法一样,在没有Session的时候,会自动创建Session.运行结果如下:
2.3.11 获取Header
传统获取方法传统的方法依然是从
HttpServletRequst
中获取. @RequestMapping("/param13")public String param13(HttpServletRequest request){ return request.getHeader("User-Agent");}
我们使用getHeader
方法来获取Header.在后面的参数是Header中的"key".
运行测试:
下面是我们通过抓包软件抓取的网络通行信息.我们发现Header中的User-Agent一栏与浏览器上的一致.
通过
@RequestHeader
注解来获取,在注解后面加上需要获取Header中的"key". @RequestMapping("/param14")public String param14(@RequestHeader("sec-ch-ua-platform") String string){ return string;}
运行结果:
与抓包工具中的结果一致:
2.4 响应
在我们前面代码的例子中,每次浏览器都会返回响应的响应,而前面的响应都是数据响应,响应还可以是页面,状态码,Header等.
2.4.1 返回静态页面
首先我们需要穿件一个前端页面index.html.创建的文件放在static目录下.
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title></head><body><h1>我是index文件</h1></body></html>
[注意] 在写完前端文件之后,不要通过idea右上角的浏览器小图标的方式打开,我们需要通过后端返回页面的方式打开.
下面我们展示一种后端代码的写法:
@RequestMapping("/demo2")@RestControllerpublic class DemoController2 { @RequestMapping("/param1") public Object param1(){ return "/index.html"; }}
我们看到,浏览器并没有返回对应的html页面,而是直接返回了一串字符串数据.那么怎么解决呢.我们需要把@RestController
注解变成@Controller
注解.
@RequestMapping("/demo2")@Controllerpublic class DemoController2 { @RequestMapping("/param1") public Object param1(){ return "/index.html"; }}
我们看到这次浏览器返回了对应的html页面.
@RestController
和@Controller
有什么区别呢?@RestController
一般用来返回数据,这些数据的响应报头中的Content-Type都是text/html格式的,@Controller
一般用来返回视图.这个视图一般是用前端的代码写好的文件.就是前面我们在MVC设计模式中提到过的视图(view).@RestController
== @Controller
+ @ResponseBody
.其中@Controller
表示的是我们前面我们MVC模式中的控制器.而@ResponseBody
表示的是响应正文,定义返回的数据为非视图模式.@RestController
的源码如下,我们也可以看到这个注解上面也标有@Controller
+ @ResponseBody
两个注解: @Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Controller@ResponseBodypublic @interface RestController { @AliasFor( annotation = Controller.class ) String value() default "";}
2.4.2 返回数据@ResponseBody
上面我们提到了@ResponseBody
注解表示的是返回数据,也就是以text/html的格式返回
@ResponseBody
即是类注解又是方法注解.给类加上该注解之后,就表示类中所有的方法返回的都是数据,给方法加上之后,表示的是只有这个方法返回的是数据.
同样,如果类上有@RestController
注解的时候,就证明给所有的方法都加了@ResponseBody
注解,所有的方法都以数据的方式返回.
如果一个类中即有页面要返回,也有数据要返回,就在需要返回数据的方法上面加上@ResponseBody
.类的控制器使用@Controller
.
@RequestMapping("/demo2")@Controllerpublic class DemoController2 { @RequestMapping("/param1") public Object param1(){ return "/index.html"; } @RequestMapping("param2") @ResponseBody public Object param2(){ return "String"; }}
浏览器返回的视图与数据如下:
2.4.3 返回html代码片段
@RequestMapping("/param3") @ResponseBody public String param3(){ return "<h1>我是html~</h1>"; }
2.4.4 返回json
后端返回json的方法是使用对象返回.可以使用HashMap返回,也可以另外创建一个对象,按属性返回.
方法一:创建HashMap
@RequestMapping("/param4")@ResponseBodypublic HashMap<String,Integer> param4(){ HashMap<String, Integer> map = new HashMap<>(); map.put("aa",1); map.put("bb",2); map.put("cc",3); return map;}
浏览器返回响应,是json格式的数据:
方法二:通过类中的属性
@RequestMapping("/param5")@ResponseBodypublic Person param5(){ Person person = new Person(); person.setName("zhangsan"); person.setAge(19); person.setSex("male"); return person;}
浏览器返回响应,依然是json格式的数据,返回的是对象中属性的值:
2.4.5 设置状态码
SpringMVC也为程序员提供了自定义状态码的功能,可以让程序员手动指定状态码.
使用HttpServletResponse
+setStatus
方法访问到响应中的状态码.
@RequestMapping("/param6")@ResponseBodypublic String param6(HttpServletResponse response){ response.setStatus(400); return "设置状态码为400";}
浏览器返回响应,需要注意的是,我们自定义的错误状态码返回的不一定是浏览器报错的一大坨信息,状态码并不影响页面的显示.
我们通过抓包工具发现,响应的状态码被设置为了400.图标也变为了红色.
2.4.6 设置header
Http响应报头也会向客户端传递一些附加信息,如:Content-Type,Local等.这些信息就是通过@RequestMapping
来实现的.先来看一看@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 {};}
value(),path()
:指定映射的URL.method()
:指定请求的方法.如GET,POST等.consumes()
:指定处理的请求的提交内容类型(Content-Type),例如:application/json, text/html等.produces()
:指定返回的内容类型,也就是浏览器上响应之后显示的内容类型.和上面的consumes()
一样. 设置Content-Type我们通过设置produces的值,设置响应报头的Content-Type.
//返回的响应是json格式@RequestMapping(value = "/param7",produces = "application/json")@ResponseBodypublic String param7(){ return "{\"success\":true}";}
我们看到浏览器返回的响应数据是json格式的数据.
如果不在@RequestMapping
后面加上produces参数的话,返回响应的时候,Spring还是会以默认的text.html格式返回.
设置其他Header,需要使用Spring中提供的
HttpServletResponse
提供的方法来设置. @RequestMapping("/param8")@ResponseBodypublic String param8(HttpServletResponse response){ response.setHeader("MyHeader","value"); return "设置Header成功";}
我们通过Fiddler抓包可以看到,报头中自定义的header已经被加入进去了.
如果header的名称已经存在,在设置它的Value的时候会覆盖掉原来的值.