Spring?Web?MVC基礎(chǔ)理論概念
1. 什么是Spring Web MVC
Spring Web MVC是基于Servlet API構(gòu)建的原始Web框架,從?開始就包在Spring框架中。它的正式名稱“Spring Web MVC”來自其源模塊的名稱(Spring-webmvc),但它通常被稱為"Spring MVC".
總結(jié)來說,Spring Web MVC是一個Web框架.
想要理解什么是Spring MVC我們首先先要理解什么是MVC
1.1 什么是MVC
MVC是Model View Controller的縮寫,是軟件工程中共的一種軟件架構(gòu)的設(shè)計模式.把軟件系統(tǒng)分為模型,控制器,視圖三個部分.
- view(視圖): 指的是在應(yīng)用中專門用來與瀏覽器交互,展示數(shù)據(jù)的資源.
- model(模型): 只應(yīng)用程序的主題部分,用來處理程序中數(shù)據(jù)邏輯的部分.
- controller(控制器): 可以理解為一個分發(fā)器,用來決定對于視圖發(fā)來的請求,需要哪一個模型來處理,以及處理之后需要跳回哪個視圖.即用來連接視圖和模型.
比如我們?nèi)ワ埖瓿燥?
服務(wù)員就是view(視圖):負責(zé)接待顧客,給顧客點餐,給顧客端飯.前廳就是controller(控制器):用來給后廚下達做菜的命令.后廚就是model(模型):根據(jù)前廳發(fā)來的要求來做菜.
顧客進店之后,服務(wù)員來接待客戶點餐,客戶點完餐之后,把客戶菜單交給前廳,前廳根據(jù)客戶菜單給后廚下達命令.后廚負責(zé)做飯,做完之后,再根據(jù)菜單告訴服務(wù)員,這是X號餐桌客人的飯.
在這個過程中:
1.2 什么是Spring MVC
MVC是一種架構(gòu)模式,也是一種思想.而Spring MVC是對MVC思想的具體實現(xiàn).除此之外,Spring MVC還是一個Web框架.
總結(jié):Spring MVC是一個實現(xiàn)了MVC軟件設(shè)計模式的Web框架.
其實Spring MVC在前面我們就使用過了.在我們創(chuàng)建Spring Boot項目的時候,選擇Spring Web的時候,其實就是Spring MVC框架.也就是在創(chuàng)建的Spring Boot項目中添加了Spring Web MVC 的相關(guān)依賴,使得該項目具有了網(wǎng)絡(luò)通信的功能.
那么這時候問題又來了,Spring Boot和Spring MVC究竟有什么關(guān)系?
Spring Boot只是實現(xiàn)Spring MVC的一種方式而已.Spring Boot中可以添加很多依賴,我們在Spring Boot項目中添加了Spring MVC的框架,那么這個Spring Boot項目就可以實現(xiàn)Web的功能.
不過Spring MVC在實現(xiàn)MVC模式的時候,也結(jié)合了自身的一些特點,下面這個圖更加適合描述Spring MVC.
通過瀏覽器來向后端發(fā)送請求的時候,沒有經(jīng)過view,而是直接把請求傳遞給了controller,之后controller選擇合適的模型,傳遞給model,model處理數(shù)據(jù)之后,把響應(yīng)返回給controller,之后controller再把響應(yīng)返回給view,之后view把響應(yīng)返回給瀏覽器.
就比如我們?nèi)ス久嬖?我們(瀏覽器)想要面試的時候,我們可以直接找到公司某部門的負責(zé)人(controller),說我要面試(請求),之后部門負責(zé)人會找到面試你的那個人(model),面試之后,加入你通過了面試,面試你的那個人會把面試結(jié)果傳遞給部門負責(zé)人,之后部門負責(zé)人把消息通知給HR(view),之后HR會給你發(fā)offer.
2. Spring MVC深入學(xué)習(xí)
學(xué)習(xí)Spring MVC,重點也就是學(xué)習(xí)用戶通過瀏覽器與服務(wù)端交互的過程.
主要分為一下三個點:
- 建立連接:將用戶(瀏覽器)和Java程序連接起來,也就是Java程序打開了大門,允許外界訪問.此時訪問的地址能夠調(diào)用我們的Spring程序.
- 傳遞參數(shù):用戶請求的時候會帶一些參數(shù).這些參數(shù)會傳遞到后端,在后端程序中要想辦法獲取到參數(shù).
- 返回結(jié)果:執(zhí)行了業(yè)務(wù)邏輯之后,需要把執(zhí)行的結(jié)果返回給用戶,也就是響應(yīng).
比如去銀行存款:
- 建立連接:去柜臺
- 傳遞參數(shù):拿著身份證,銀行卡去存款
- 返回結(jié)果:銀行返回一張存折.
掌握了上面的三個功能就相當于掌握了Spring MVC.
2.1 建立連接
在Spring MVC中使用@RequestMapping
來實現(xiàn)URL的路由映射,也就是通過這個注解來使得Spring項目與瀏覽器建立連接.代碼如下:
package com.example.demo; import org.springframework.web.bind.annotation.*; @RestController public class DemoController { @RequestMapping("/hello")//可以理解為資源路徑 public String hello() { return "Hello World"; } }
接下來訪問http://127.0.0.1:8080/hello
就可以看到返回的程序了.
2.1.1 @RequsetMapping注解介紹
@RequestMapping
是Spring Web MVC應(yīng)用程序最常被用到的注解之一.它用來注冊接口的路由映射.表示的是,服務(wù)器在接收到請求的時候,路徑為/hello
的請求就會調(diào)用hello這個方法的代碼.
何為路由映射?
當用戶訪問?個URL時,將用戶的請求對應(yīng)到程序中某個類的某個方法的過程就叫路由映射.
- 問題:既然
@RequestMapping
已經(jīng)達到了我們的目的,我們?yōu)槭裁催€要加@RestController
呢?
@RestController
在資源訪問的過程中起著相當重要的作用,在Spring項目接收到一個請求之后,Spring會對所有的類進行掃描,如果家里注解@RestController
,Spring才會去看這個類里面有沒有加@RequestMapping
這個注解,才可以通過瀏覽器中輸入的URL對應(yīng)到這個類中注冊的路由映射.
如果我們把@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.1.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這個方法的時候,路徑就會變?yōu)?code>/demo/hello.
注: 注解中的/hello
和"demo"
雖然不加/也可以正確響應(yīng),但是為了編程的規(guī)范,還是建議加上.
2.1.3 @RequsetMapping支持哪些方法類型的請求
首先給出結(jié)論,@RequsetMapping
支持所有方法類型的請求.下面我們來通過Postman構(gòu)造請求來實驗一下.
GET方法支持
POST方法支持
剩下的方法都是同樣的道理,顯示的結(jié)果都是Hello World,這里不再一一展示.
- 那么如何使
@RequsetMapping
只接受指定的幾種方法的請求呢?
我們就需要再注解中加上另外的一個參數(shù),method鍵值對
@RequestMapping(value = "/hello1",method = RequestMethod.GET) public String hello1() { return "Hello World 1"; }
我們看到/hello1
對應(yīng)的路由映射只支持GET方法.
現(xiàn)在我們?nèi)タ纯磎ethod鍵值對截取的部分源碼
public @interface RequestMapping { String name() default ""; @AliasFor("path") String[] value() default {}; @AliasFor("value") String[] path() default {}; RequestMethod[] method() default {};//method返回的是一個RequestMethod類型的數(shù)組 String[] params() default {}; String[] headers() default {}; String[] consumes() default {}; String[] produces() default {}; }
在源碼中,我們可以看到,method返回的是一個RequestMethod類型的數(shù)組.那么這個RequestMethod
中都有什么,我們再去查看截取的部分源碼.
public enum RequestMethod { GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE; }
這里我們可以看到RequestMethod
是一個枚舉類型,這里面存放的都是請求中的方法.
由于method
那里接收的是一個關(guān)于RequestMethod
枚舉類型的數(shù)組,所以我們在注解后的鍵值對的值上傳入的也是枚舉類型的數(shù)組,比如我們想支持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.2 請求
訪問不同的路徑,就是發(fā)送不同的請求.在發(fā)送請求時,可能會帶?些參數(shù),所以學(xué)習(xí)Spring的請求,主要是學(xué)習(xí)如何傳遞參數(shù)到后端以及后端如何接收.
傳遞參數(shù),主要是以瀏覽器和Postman來模擬.
2.2.1 傳遞單個參數(shù)
在前面,我們的方法都是沒有參數(shù)存在的,如果給我們的方法加上參數(shù)之后會怎么樣呢?
@RequestMapping("/name1") public String name1(String name) { System.out.println("接收到了" + name); return "接收到了" + name; }
我們在請求的URL中加上查詢字符串,即參數(shù):http://127.0.0.1:8080/demo/name1?name=zhangsan
(?后面的是參數(shù))
在URL中加上的參數(shù)傳入到后端之后,Spring MVC會根據(jù)方法的參數(shù)名,找到對應(yīng)的參數(shù),賦值給方法.之后拿到傳入的參數(shù)在方法中進行一系列操作之后返回給前端.比如我們將這個請求通過Postman發(fā)送給Spring項目.
我們發(fā)現(xiàn)成功返回了響應(yīng),并且返回了正確的響應(yīng).
如果參數(shù)不一致,則獲取不到參數(shù).
注意事項:參數(shù)類型是包裝類型和基本類型的區(qū)別
當參數(shù)類型是包裝類型和基本類型的時候,傳入的參數(shù)Spring進行隱式轉(zhuǎn)換之后發(fā)現(xiàn)參數(shù)類型不一致均會報400錯誤.
@RequestMapping("/age1") public String age1(int age) { System.out.println("接收到了" + age); return "接收到了" + age; } @RequestMapping("/age2") public String age2(Integer age) { return "接收到了" + age; }
但是如果我們不傳遞任何參數(shù)的時候,這時候基本類型和包裝類型就會有所區(qū)別,基本類型會直接拋出500錯誤,而包裝類型會輸出默認的空值null.
所以,我們在企業(yè)開發(fā)中,對于參數(shù)可能為空的數(shù)據(jù),我們建議使用包裝類型.
2.2.2 傳遞多個參數(shù)
和接收單個參數(shù)?樣,直接使用方法的參數(shù)接收即可.使用多個形參.
@RequestMapping("/person1") public String person1(String name,Integer age) { return "接收到了name" + name + "接收到了age" + age; }
注:
- 也可以通過構(gòu)造form表單來發(fā)送請求,這時候在URL中就沒有了參數(shù)的存在,參數(shù)跑到了請求中的正文內(nèi)容.
- 如果在項目運行的過程中,我們要對方法的參數(shù)進行修改,我們不建議在原來的方法上直接進行修改,而是另起一個方法重新寫.
2.2.3 傳遞對象
如果需要傳遞的參數(shù)比較多的時候,我們不妨把這些參數(shù)封裝成一個對象.
@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進行請求發(fā)送.http://127.0.0.1:8080/demo/person3?name=zhangsan&age=20&sex=男
注意:
如果某個基本類型的參數(shù)未傳遞,如果這個基本類型參數(shù)是一個類中的成員變量,如果這個參數(shù)未傳遞,那么這個參數(shù)會有默認的初始值,這一點是和上面直接把基本參數(shù)類型的參數(shù)寫在方法的參數(shù)列表中是不一樣的.比如我沒有傳入age參數(shù).
我們可以看到age的初始值被默認賦值為了0.如果我們針對參數(shù)是對象的使用form表單進行參數(shù)傳遞,在GET和POST兩種方法中,只有POST方法會返回正確的結(jié)果,而GET方法會返回默認的空值.
2.2.4 后端參數(shù)重命名
在一些特殊的情況下,前端傳遞的參數(shù)key和我們后端接收的key可能不?致,比如我們傳遞了一個Name給后端,但是后端需要接收的參數(shù)是name,這時候就需要用到@RequestParam
(翻譯:請求參數(shù))來對后端的參數(shù)進行重命名.
@RequestMapping("/person2") public String person2(@RequestParam("Name") String name,Integer age) { return "接收到name" + name + "接收到age" + age; }
上面這段代碼,其中Name就是前端要傳遞的參數(shù),而name是后端使用的參數(shù),此時Spring可以正確的把請求傳遞的參數(shù)Name綁定到后端參數(shù)name參數(shù)上.
我們使用http://127.0.0.1:8080/demo/person2?Name=zhangsan&age=20
來進行請求傳遞
如果我們把Name改成name,就無法進行正確的參數(shù)傳遞了.
[注意事項]
在使用@RequestParam
對參數(shù)進行重命名的時候,參數(shù)就變成了必傳參數(shù).
那么造成上面這種情況的原因是什么呢,又該如何讓他變成一個非必傳參數(shù)呢?現(xiàn)在我們來查看@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,表示的含義就是,該注解修飾的參數(shù)是必傳參數(shù).既然如此,我們可以通過設(shè)置@RequestParam
的required參數(shù)=false來實現(xiàn)讓這個參數(shù)成為非必傳參數(shù).
@RequestMapping("/person2") public String person2(@RequestParam(value = "Name",required = false) String name,Integer age) { return "接收到name" + name + "接收到age" + age; }
此時Name為傳遞的時候,會有默認的初始值null來返回.
2.2.5 傳遞數(shù)組
Spring MVC可以自動綁定數(shù)組參數(shù)的賦值.
@RequestMapping("/param1") public String param1(String[] arrayParam) { return Arrays.toString(arrayParam); }
使用Postman進行傳參:
請求參數(shù)名與形參數(shù)組名稱相同且請求參數(shù)為多個,后端的方法形式參數(shù)中即可自動接收傳輸過來的參數(shù).
把所要傳遞的參數(shù)合并在一起傳遞,中間用逗號隔開.
可以看到以上兩種方法均返回了正確的響應(yīng).我們?nèi)绻褂胒rom表單進行發(fā)送的話,這時候請求GET和POST就只有POST返回的是正確的結(jié)果,而GET返回的是null.
2.2.6 傳遞集合
集合參數(shù): 和數(shù)組傳遞參數(shù)的方法類似,可以是相同的參數(shù)名多個參數(shù),也可以把參數(shù)寫到一起,但是在后端那里,需要加上@RequestParam
來綁定參數(shù)關(guān)系.
默認的情況下,請求中的參數(shù)名相同的多個值,封裝的時候是一個數(shù)組,而如果要封裝集合的話,就需要在參數(shù)前面加上@RequestParam
來綁定參數(shù)關(guān)系,表示傳過來的是一個數(shù)組,需要轉(zhuǎn)換成集合.
@RequestMapping("/param2") public String param2(@RequestParam("ArrayParam") List<String> arrayParam) { return Arrays.toString(arrayParam.toArray()); }
如果不加注解后面的參數(shù)的話,在前端傳遞參數(shù)的時候默認就和后端的方法參數(shù)是一樣的.
2.2.7 傳遞json數(shù)據(jù) 什么是json
- 什么是json
JSON就是?種數(shù)據(jù)格式,有自己的格式和語法,使用文本表示一個對象或數(shù)組的信息,因此JSON本質(zhì)是字符串.主要負責(zé)在不同的語言中數(shù)據(jù)傳遞和交換.
- json的語法
json是一個字符串,其格式非常類似于python中的字典和JavaScript對象字面量的格式.
- 數(shù)據(jù)存儲在鍵值對(key/value)中,key和value之間用:分割.
- 鍵值對之間由,分割.
- 對象用{ }表示
- 數(shù)組用[ ]表示
- 值可以為對象,數(shù)組,字符串等等.
- json的兩種結(jié)構(gòu)
- 對象: 大括號{}保存的對象是?個無序的鍵值對集合.?個對象以左括號{ 開始,右括號}結(jié)束。每個"鍵"后跟?個冒號: ,鍵值對使用逗號,分隔
- 數(shù)組: 中括號[]保存的數(shù)組是值(value)的有序集合.?個數(shù)組以左中括號[開始,右中括號]結(jié)束,值之間使用逗號, 分隔,數(shù)組中可以存放多個對象,對象和對象之間用,分割.
下面我們展示一段json字符串:
{ "squadName": "Super hero squad", "homeTown": "Metro City", "formed": 2016, "secretBase": "Super tower", "active": true, //數(shù)據(jù)保存在鍵值對中 //鍵和值之間使用:分割,鍵值對之間使用,分割 "members": [{ "name": "Molecule Man", "age": 29, "secretIdentity": "Dan Jukes", "powers": ["Radiation resistance", "Turning tiny", "Radiation blast"]//數(shù)組中可以包含多個元素 }, {//這個元素也可以是對象 "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對象的互轉(zhuǎn)
Spring MVC框架集成了json的轉(zhuǎn)換工具,我們可以直接拿來使用.我們可以使用ObjectMapper
中的一系列方法來對json和Java對象兩者之間進行轉(zhuǎn)換.
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
注解,這個注解翻譯過來就是請求正文的意思,意思是這個注解起到的作用是從請求中的正文部分獲取數(shù)據(jù).請求的參數(shù)必須寫在正文中.而我們的json字符串就寫在請求的正文中.
后端是這樣實現(xiàn)的:
@RequestMapping("/param3") public String param3(@RequestBody Person person) { return person.toString(); }
接下來我們使用Postman構(gòu)造請求,把json字符串寫入請求的正文中:
{ "name":"zhangsan", "age":18, "sex":"male" }
響應(yīng)結(jié)果如下:
如果去掉注解@RequestBody
,那么后端就無法正確接收json數(shù)據(jù),前端返回的響應(yīng)也不正確.
由于后端沒有接收到關(guān)于person對象傳遞的任何信息,所以都默認都賦值為了初始值.
2.2.8 獲取URL中的參數(shù)@PathVariable
PathVariable翻譯過來之后,是"路徑可變"的意思,意思是我們要通過URL獲取路徑作為傳遞過來的參數(shù),而這個路徑是不固定的.
這個注解主要的作用就是在請求URL路徑上的數(shù)據(jù)綁定.之前我們通過http請求傳遞參數(shù)都是在?
后面的查詢字符串中.而現(xiàn)在我們要通過URL中的資源路徑來傳遞參數(shù).
后端代碼實現(xiàn)如下:
在@RequestMapping
的參數(shù)中在加上一些路徑的標識,每個參數(shù)外面使用{ }
括起來.
@RequestMapping("/param4/{name}/{age}") public String param4(@PathVariable String name,@PathVariable Integer age){ return name+age; }
我們通過Postman來構(gòu)造請求http://127.0.0.1:8080/demo/param4/zhangsan/18
我們看到前端返回了正確的響應(yīng).
[注意事項]
如果方法參數(shù)名稱和需要綁定的URL中的變量名稱不?致時,需要@PathVariable
的屬性value賦值.
@RequestMapping("/param4/{Name}/{Age}") public String param4(@PathVariable("Name") String name,@PathVariable("Age") Integer age){ return name+age; }
前端還是會返回正確的響應(yīng).
2.2.9 上傳文件@RequestPart
后端代碼實現(xiàn):
@RequestMapping("/param5") public String param5(@RequestPart("file") MultipartFile file) throws IOException { file.transferTo(new File("D:/personal/" + file.getOriginalFilename())); //上傳文件,前面寫的是存儲文件的路徑,后面加上原文件的名字 return "已經(jīng)獲取到" + file.getOriginalFilename(); }
使用Postman向指定位置發(fā)送文件.
指定位置接收到了文件.
2.2.10 獲取Cookie/Session
- 回顧Cookie和Session
Cookie中的內(nèi)容和URL中的query string內(nèi)容類似,都是鍵值對內(nèi)容,它們都是程序員自定義的.每個鍵值對之間用";“隔開,鍵和值之間用”="隔開.
- Cookie是瀏覽器本地存儲的一種機制,Cookie本質(zhì)上可以在客戶端的硬盤上持久化保存的.是瀏覽器給網(wǎng)頁的一種能夠持久化存儲數(shù)據(jù)的機制.
- 有的網(wǎng)站,需要在客戶端這邊存儲一些必要的信息,希望可以持久化存儲,于是瀏覽器就給網(wǎng)頁提供了Cookie,是瀏覽器對于硬盤的操作做了一些特殊的封裝,相當于提供了一個或者一組特殊的文件,并且內(nèi)容只能是鍵值對.
- 那么Cookie是具體如何保存的呢?
- 瀏覽器會針對不同的域名,每個網(wǎng)站都有自己的Cookie文件保存在硬盤中.
- Cookie從哪里來?
- Cookie中的數(shù)據(jù),來自于服務(wù)器(服務(wù)器返回給瀏覽器的數(shù)據(jù)),訪問網(wǎng)站的時候,網(wǎng)站的服務(wù)器會返回http響應(yīng),在http響應(yīng)中,會包含Set-Cookie這樣的header,它就會把一些鍵值對保存到瀏覽器的Cookie中.Cookie保存到瀏覽器之后,后續(xù)瀏覽器訪問該網(wǎng)站的時候,就會在請求的header中,把之前保存的鍵值對都帶入進去,在返回給服務(wù)器.
- 那么問題又來了,為什么還要返回給服務(wù)器?
- 這是因為Cookie可以使客戶端存儲一些必要的"配置信息",從而讓服務(wù)器對于用戶提供的服務(wù)更加"個性化".
舉例說明:剪頭發(fā)
A去了一家理發(fā)店之后,理發(fā)師就問他:“你想怎么剪?” 但是這時候A就會反問理發(fā)師:“你能怎么剪”?于是理發(fā)師就說:“可以剪平頭,毛寸…”,于是A就說:"給我剪個平頭."在A下次去了之后,就可以直接告訴理發(fā)師,剪個平頭.同理,B也經(jīng)歷了上述過程,他最終選擇了毛寸.
和上面剪頭發(fā)是相同的道理,客戶端也不止有一個,每個客戶端都會有自己的偏好,此時就需要讓每個客戶端保存這樣的數(shù)據(jù),之后就可以通過Cookie隨時把這樣的信息返回給服務(wù)器.例如:瀏覽器的夜間模式和白日模式,一次設(shè)置好了之后,下次再打開服務(wù)器的時候,瀏覽器的顏色模式不會改變.
- Cookie自動登錄
Cookie中雖然有很多的鍵值對都是程序員自定義的,但是往往會有一個特殊的鍵值對,用來標識用戶的身份信息.
- 首先在獲取登錄頁面與返回登錄登錄頁面的html的過程中不包含任何的Cookie.
- 在用戶輸入用戶名和密碼之后,這時候用戶名和密碼就會交給服務(wù)器,驗證它們的正確性,在確認正確之后,就會創(chuàng)建會話(session),(會話可以理解為一個類,其中類中具體包含什么,要看業(yè)務(wù)邏輯,但是其中一定有sessionId,也就是令牌)并把sessionId返回給瀏覽器,這個sessionId存在于響應(yīng)報文header中的Set-Cookie中,我們也可以把他叫做"令牌",令牌中存儲的是一個字符串,類似于"身份標識",不會存儲太多的信息,在瀏覽器收到sessionId之后,就會把Id存儲在硬盤中,即創(chuàng)建了Cookie.
- 在之后客戶端要訪問該域名下的其他頁面的時候,就可以把sessionId交給服務(wù)器,服務(wù)器獲取到sessionId之后,就可以根據(jù)這個值,知道用戶的詳細信息.也就是直接通過之前創(chuàng)建的sessionId的Cookie就可以訪問到,無需再次登錄.
舉例說明:去醫(yī)院看病
- 到了醫(yī)院先掛號.掛號時候需要提供?份證,同時得到了?張"就診卡",這個就診卡存儲著關(guān)于患者身份信息的sessionId,就相當于患者的"令牌".
- 后續(xù)去各個科室進行檢查,診斷,開藥等操作,都不必再出示?份證了,只要憑就診卡即可識別出當前患者的?份.在就診室的刷卡機上刷一下就診卡,醫(yī)生就會知道你的所有信息.
- 看完病了之后,不想要就診卡了,就可以注銷這個卡.此時患者的?份和就診卡的關(guān)聯(lián)就銷毀了.(類似于?站的注銷操作)
- ?來看病,可以辦?張新的就診卡,此時就得到了?個新的"令牌"
- 使用Spring獲取Cookie
- 傳統(tǒng)獲取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 "已經(jīng)獲取到Cookie"; }
之后我們通過瀏覽器偽造Cookie,來向后端傳遞請求.http://127.0.0.1:8080/demo/param6
我們看到了后端已經(jīng)成功拿到了Cookie中的數(shù)據(jù).
Spring MVC是Spring基于Servlet實現(xiàn)的.其中上面一段代碼中的HttpServletRequest
和HttpServletResponse
是Servlet提供的兩個類.這兩個類在每一個接口中均默認存在,需要的時候?qū)懗鰜砭涂梢?HttpServletRequest
對象代表客戶端的請求.請求中的所有信息均可以通過這個類拿到.HttpServletResponse
對象代表服務(wù)器的響應(yīng).響應(yīng)中的所有信息均可以通過這個類拿到.
- 獲取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"; }
通過瀏覽器構(gòu)造Cookie,我們看到了前端返回了正確的響應(yīng).
后端打印了相應(yīng)的日志:
方法二:通過@CookieValue
注解獲取.
@RequestMapping("/param8") public String param8(@CookieValue("bite") String bite) { System.out.println("獲取到了" + bite); return bite; }
前端與后端響應(yīng)均正確:
Session的存儲和獲取
Session是服務(wù)器端的機制,它需要先存儲,才能獲取到.
Session的存儲
@RequestMapping("/param9") public String param9(HttpServletRequest request){ //獲取Session對象,如果不存在Session對象,getSession之后不加或參數(shù)為true會自動創(chuàng)建 HttpSession session = request.getSession(); if (session != null) {//確保Session成功創(chuàng)建 session.setAttribute("bite", "888"); } return "Session存儲成功"; }
方法解釋:getSession(boolean create)
: 如果參數(shù)為true的時候,就會在Session不存在的時候,自動創(chuàng)建Session,如果參數(shù)為false,就不會創(chuàng)建.getSession()
:和上一種方法參數(shù)為true的時候效果相同.setAttribute(String s,String o)
:設(shè)置Session中的參數(shù).
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"; }
運行
[注意事項] 在重啟服務(wù)器之后,上一次存在內(nèi)存中的Session數(shù)據(jù)會被清空,需要重新設(shè)置Session之后才可以獲取到.
我們可以看到,存儲了Session之后,瀏覽器把SessionID存儲在了Cookie中.
之后我們可以使用瀏覽器存儲的令牌獲取Session.
簡潔獲取Session
上面獲取Session的方法比較傳統(tǒng),我們下面展示兩種簡潔的方法.
方法一:使用@SessionAttribute
注解
@RequestMapping("/param11") public String param11(@SessionAttribute(value = "bite",required = false) String bite){ return bite; }
運行結(jié)果如下:
@SessionAttribute
的后面兩個注解表示的意思和前面@RequestParam
注解后面的兩個參數(shù)的作用非常像.第一個作用是參數(shù)綁定的作用,第二個參數(shù)如果為true或者不寫,表示這個參數(shù)是必傳參數(shù),如果為false,就是非必傳參數(shù),如果Session傳遞未成功,就返回null.
方法二:直接使用HttpSession
作為參數(shù)
@RequestMapping("/param12") public String param12(HttpSession session){ String string = (String) session.getAttribute("bite"); System.out.println("成功獲取到了Session"); return string; }
HttpSession
作為參數(shù)的時候,效果和getSession()
方法一樣,在沒有Session的時候,會自動創(chuàng)建Session.
運行結(jié)果如下:
2.2.11 獲取Header 傳統(tǒng)獲取方法
傳統(tǒng)的方法
依然是從HttpServletRequst
中獲取.
@RequestMapping("/param13") public String param13(HttpServletRequest request){ return request.getHeader("User-Agent"); }
我們使用getHeader
方法來獲取Header.在后面的參數(shù)是Header中的"key".
運行測試:
下面是我們通過抓包軟件抓取的網(wǎng)絡(luò)通行信息.我們發(fā)現(xiàn)Header中的User-Agent一欄與瀏覽器上的一致.
簡潔獲取方法
通過@RequestHeader
注解來獲取,在注解后面加上需要獲取Header中的"key".
@RequestMapping("/param14") public String param14(@RequestHeader("sec-ch-ua-platform") String string){ return string; }
運行結(jié)果:
與抓包工具中的結(jié)果一致:
2.3 響應(yīng)
在我們前面代碼的例子中,每次瀏覽器都會返回響應(yīng)的響應(yīng),而前面的響應(yīng)都是數(shù)據(jù)響應(yīng),響應(yīng)還可以是頁面,狀態(tài)碼,Header等.
2.3.1 返回靜態(tài)頁面
首先我們需要穿件一個前端頁面index.html.創(chuàng)建的文件放在static目錄下.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>我是index文件</h1> </body> </html>
[注意] 在寫完前端文件之后,不要通過idea右上角的瀏覽器小圖標的方式打開,我們需要通過后端返回頁面的方式打開.
下面我們展示一種后端代碼的寫法:
@RequestMapping("/demo2") @RestController public class DemoController2 { @RequestMapping("/param1") public Object param1(){ return "/index.html"; } }
我們看到,瀏覽器并沒有返回對應(yīng)的html頁面,而是直接返回了一串字符串數(shù)據(jù).那么怎么解決呢.我們需要把@RestController
注解變成@Controller
注解.
@RequestMapping("/demo2") @Controller public class DemoController2 { @RequestMapping("/param1") public Object param1(){ return "/index.html"; } }
我們看到這次瀏覽器返回了對應(yīng)的html頁面.
那么@RestController
和@Controller
有什么區(qū)別呢?@RestController
一般用來返回數(shù)據(jù),這些數(shù)據(jù)的響應(yīng)報頭中的Content-Type都是text/html格式的,@Controller
一般用來返回視圖.這個視圖一般是用前端的代碼寫好的文件.就是前面我們在MVC設(shè)計模式中提到過的視圖(view).@RestController
== @Controller
+ @ResponseBody
.其中@Controller
表示的是我們前面我們MVC模式中的控制器.而@ResponseBody
表示的是響應(yīng)正文,定義返回的數(shù)據(jù)為非視圖模式.@RestController
的源碼如下,我們也可以看到這個注解上面也標有@Controller
+ @ResponseBody
兩個注解:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Controller @ResponseBody public @interface RestController { @AliasFor( annotation = Controller.class ) String value() default ""; }
2.3.2 返回數(shù)據(jù)@ResponseBody
上面我們提到了@ResponseBody
注解表示的是返回數(shù)據(jù),也就是以text/html的格式返回@ResponseBody
即是類注解又是方法注解.給類加上該注解之后,就表示類中所有的方法返回的都是數(shù)據(jù),給方法加上之后,表示的是只有這個方法返回的是數(shù)據(jù).
同樣,如果類上有
@RestController
注解的時候,就證明給所有的方法都加了@ResponseBody
注解,所有的方法都以數(shù)據(jù)的方式返回.
如果一個類中即有頁面要返回,也有數(shù)據(jù)要返回,就在需要返回數(shù)據(jù)的方法上面加上@ResponseBody
.類的控制器使用@Controller
.
@RequestMapping("/demo2") @Controller public class DemoController2 { @RequestMapping("/param1") public Object param1(){ return "/index.html"; } @RequestMapping("param2") @ResponseBody public Object param2(){ return "String"; } }
瀏覽器返回的視圖與數(shù)據(jù)如下:
2.3.3 返回html代碼片段
@RequestMapping("/param3") @ResponseBody public String param3(){ return "<h1>我是html~</h1>"; }
2.3.4 返回json
后端返回json的方法是使用對象返回.可以使用HashMap返回,也可以另外創(chuàng)建一個對象,按屬性返回.
方法一:創(chuàng)建HashMap
@RequestMapping("/param4") @ResponseBody public HashMap<String,Integer> param4(){ HashMap<String, Integer> map = new HashMap<>(); map.put("aa",1); map.put("bb",2); map.put("cc",3); return map; }
瀏覽器返回響應(yīng),是json格式的數(shù)據(jù):
方法二:通過類中的屬性
@RequestMapping("/param5") @ResponseBody public Person param5(){ Person person = new Person(); person.setName("zhangsan"); person.setAge(19); person.setSex("male"); return person; }
瀏覽器返回響應(yīng),依然是json格式的數(shù)據(jù),返回的是對象中屬性的值:
2.3.5 設(shè)置狀態(tài)碼
SpringMVC也為程序員提供了自定義狀態(tài)碼的功能,可以讓程序員手動指定狀態(tài)碼.
使用HttpServletResponse
+setStatus
方法訪問到響應(yīng)中的狀態(tài)碼.
@RequestMapping("/param6") @ResponseBody public String param6(HttpServletResponse response){ response.setStatus(400); return "設(shè)置狀態(tài)碼為400"; }
瀏覽器返回響應(yīng),需要注意的是,我們自定義的錯誤狀態(tài)碼返回的不一定是瀏覽器報錯的一大坨信息,狀態(tài)碼并不影響頁面的顯示.
我們通過抓包工具發(fā)現(xiàn),響應(yīng)的狀態(tài)碼被設(shè)置為了400.圖標也變?yōu)榱思t色.
2.3.6 設(shè)置header
Http響應(yīng)報頭也會向客戶端傳遞一些附加信息,如:Content-Type,Local等.這些信息就是通過@RequestMapping
來實現(xià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 {}; }
value(),path()
:指定映射的URL.method()
:指定請求的方法.如GET,POST等.consumes()
:指定處理的請求的提交內(nèi)容類型(Content-Type),例如:application/json, text/html等.produces()
:指定返回的內(nèi)容類型,也就是瀏覽器上響應(yīng)之后顯示的內(nèi)容類型.和上面的consumes()
一樣.
設(shè)置Content-Type
我們通過設(shè)置produces的值,設(shè)置響應(yīng)報頭的Content-Type.
//返回的響應(yīng)是json格式 @RequestMapping(value = "/param7",produces = "application/json") @ResponseBody public String param7(){ return "{\"success\":true}"; }
我們看到瀏覽器返回的響應(yīng)數(shù)據(jù)是json格式的數(shù)據(jù).
如果不在
@RequestMapping
后面加上produces參數(shù)的話,返回響應(yīng)的時候,Spring還是會以默認的text.html格式返回.
- 設(shè)置其他的Header
設(shè)置其他Header,需要使用Spring中提供的HttpServletResponse
提供的方法來設(shè)置.
@RequestMapping("/param8") @ResponseBody public String param8(HttpServletResponse response){ response.setHeader("MyHeader","value"); return "設(shè)置Header成功"; }
我們通過Fiddler抓包可以看到,報頭中自定義的header已經(jīng)被加入進去了.
如果header的名稱已經(jīng)存在,在設(shè)置它的Value的時候會覆蓋掉原來的值.
到此這篇關(guān)于Spring Web MVC基礎(chǔ)理論的文章就介紹到這了,更多相關(guān)Spring Web MVC內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java 關(guān)鍵字 volatile 的理解與正確使用
本文主要介紹 volatile 的使用準則,以及使用過程中需注意的地方,感興趣的朋友一起看看吧2017-06-06Java實現(xiàn)的模糊匹配某文件夾下的文件并刪除功能示例
這篇文章主要介紹了Java實現(xiàn)的模糊匹配某文件夾下的文件并刪除功能,涉及java針對目錄與文件的遍歷、匹配、判斷、刪除等相關(guān)操作技巧,需要的朋友可以參考下2018-02-02關(guān)于接口ApplicationContext中的getBean()方法使用
這篇文章主要介紹了關(guān)于接口ApplicationContext中的getBean()方法使用,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09Spring Boot 整合單機websocket的步驟 附github源碼
websocket 是一個通信協(xié)議,通過單個 TCP 連接提供全雙工通信,這篇文章主要介紹了Spring Boot 整合單機websocket的步驟(附github源碼),需要的朋友可以參考下2021-10-10