目錄
一.前提了解
1.tomcat和servlet的關(guān)系?
2.springmvc想要實(shí)現(xiàn)web開(kāi)發(fā)必須滿足的條件是什么?
二.什么是SpringMVC
三.基于SpringMVC創(chuàng)建web項(xiàng)目
①創(chuàng)建項(xiàng)目并選擇依賴
?②設(shè)置熱部署(部分代碼改動(dòng)不需要手動(dòng)重新run即可生效)
四.理解前后端分離的開(kāi)發(fā)過(guò)程
五.SpringMVC實(shí)現(xiàn)web開(kāi)發(fā)
1.詳解用戶端返回的響應(yīng)
@Controller
@ResponseBody
重定向和轉(zhuǎn)發(fā)
轉(zhuǎn)發(fā)
重定向
轉(zhuǎn)發(fā)和重定向的區(qū)別:(M)
自定義返回類(lèi)型
@RestController
@RequestMapping
?2.詳解服務(wù)端接收用戶端的請(qǐng)求
2.1關(guān)于請(qǐng)求路徑和請(qǐng)求頭中的參數(shù)
@Pathvirable
使用postman對(duì)測(cè)試結(jié)果進(jìn)行分析:
@RequestHeader
@CookieValue
@SessionAttribute
2.2關(guān)于請(qǐng)求參數(shù)
無(wú)注解的請(qǐng)求參數(shù)
@RequestParam
@RequestPart
@RequestBody
通過(guò)配置類(lèi)設(shè)置編程式的配置方案
設(shè)置后端路徑的統(tǒng)一前綴
設(shè)置攔截器
統(tǒng)一異常處理
統(tǒng)一的結(jié)果處理:
關(guān)于responseBodyAdvice的作用
一.前提了解
在我們真正的去講述springmvc之前,我們先明確以下兩個(gè)問(wèn)題:
1.tomcat和servlet的關(guān)系?
我們都知道,servlet是實(shí)現(xiàn)動(dòng)態(tài)頁(yè)面的技術(shù),servlet是進(jìn)行web開(kāi)發(fā)的規(guī)范,而tomcat是servlet容器,它在符合servlet規(guī)范的基礎(chǔ)上對(duì)外提供了web服務(wù)器和端口號(hào),實(shí)現(xiàn)對(duì)servlet的統(tǒng)一管理;同時(shí)可以接收用用戶的請(qǐng)求,將請(qǐng)求發(fā)送給servlet,并在servlet給出響應(yīng)之后將響應(yīng)發(fā)送給客戶端
2.springmvc想要實(shí)現(xiàn)web開(kāi)發(fā)必須滿足的條件是什么?
通過(guò)上面的論述,我們能清晰理解的是,tomcat滿足了servlet規(guī)范,并在此基礎(chǔ)上進(jìn)行了web開(kāi)發(fā),springmvc如果想要實(shí)現(xiàn)web開(kāi)發(fā),也必須滿足servlet規(guī)范。
二.什么是SpringMVC
SpringMVC是進(jìn)行web開(kāi)發(fā)的框架,它同樣依托于springboot框架(springboot內(nèi)包含了springmvc),受益于springboot框架中約定大于配置的原則,springboot內(nèi)部默認(rèn)配置了springmvc的文件路徑,并對(duì)springmvc進(jìn)行了很多默認(rèn)的配置(不需要用戶自己手動(dòng)配置),springmvc內(nèi)置了滿足servlet規(guī)范的web服務(wù)器(相當(dāng)于定制化的服務(wù)器)(對(duì)servlet進(jìn)行了進(jìn)一步的封裝),而如果我們想要使用springmvc進(jìn)行web開(kāi)發(fā),只需要滿足springmvc的使用規(guī)范即可。
使用springmvc進(jìn)行web開(kāi)發(fā)的目的也很明了:使web開(kāi)發(fā)更方便,提高web開(kāi)發(fā)的效率
三.基于SpringMVC創(chuàng)建web項(xiàng)目
①創(chuàng)建項(xiàng)目并選擇依賴
?②設(shè)置熱部署(部分代碼改動(dòng)不需要手動(dòng)重新run即可生效)
?
③禁用JMX:因?yàn)槿绻粚?duì)其進(jìn)行排除會(huì)導(dǎo)致在項(xiàng)目啟動(dòng)時(shí)報(bào)錯(cuò),雖然這個(gè)報(bào)錯(cuò)不影響我們項(xiàng)目的實(shí)現(xiàn),但是規(guī)范化起見(jiàn),我們還是加上
?④禁用tomcat,取而代之undertow(非必須選項(xiàng),換是因?yàn)閡ndertow的效率略高于tomcat)
⑤修改編碼集
四.理解前后端分離的開(kāi)發(fā)過(guò)程
springMVC 的MVC 是由以下三部分組成:①M(fèi)odel?(模型)②View(視圖)?③ Controller(控制器)?
我們結(jié)合web請(qǐng)求和響應(yīng)的過(guò)程來(lái)理解開(kāi)發(fā)過(guò)程和springmc的三個(gè)組成部分:
用戶發(fā)送http請(qǐng)求,通過(guò)controller層結(jié)合model層分析請(qǐng)求信息并給出響應(yīng)響應(yīng),將響應(yīng)的數(shù)據(jù)返回給view層,最后通過(guò)view層給出http響應(yīng) 。
結(jié)合具體的放方法對(duì)此進(jìn)行分析:
事實(shí)上專(zhuān)業(yè)的邏輯應(yīng)該是這樣的:
?
處理流程
DispatcherServlet 的處理流程可以分為以下幾個(gè)步驟:接收客戶端請(qǐng)求
? ? ? ? 當(dāng)客戶端發(fā)送請(qǐng)求時(shí),DispatcherServlet 會(huì)接收并處理該請(qǐng)求。接收請(qǐng)求的方式取決于 DispatcherServlet 的配置,通常情況下,它會(huì)將請(qǐng)求映射到一個(gè) URL,然后監(jiān)聽(tīng)該 URL 的請(qǐng)求。創(chuàng)建請(qǐng)求對(duì)象
? ? ? ? DispatcherServlet 會(huì)根據(jù)客戶端請(qǐng)求創(chuàng)建一個(gè)請(qǐng)求對(duì)象,該對(duì)象中包含了客戶端請(qǐng)求的所有信息,例如請(qǐng)求方法、請(qǐng)求頭、請(qǐng)求參數(shù)等。處理請(qǐng)求映射
DispatcherServlet 會(huì)將請(qǐng)求映射到相應(yīng)的控制器進(jìn)行處理。請(qǐng)求映射是通過(guò) HandlerMapping 進(jìn)行的,HandlerMapping 負(fù)責(zé)將請(qǐng)求映射到一個(gè)或多個(gè)控制器,以便選擇最合適的控制器進(jìn)行處理。調(diào)用控制器
? ? ? ? DispatcherServlet 會(huì)調(diào)用相應(yīng)的控制器進(jìn)行處理,控制器會(huì)根據(jù)請(qǐng)求參數(shù)和業(yè)務(wù)邏輯進(jìn)行相應(yīng)的處理,并返回一個(gè) ModelAndView 對(duì)象。渲染視圖
? ? ? ? DispatcherServlet 會(huì)將 ModelAndView 對(duì)象傳遞給視圖解析器(ViewResolver),視圖解析器會(huì)根據(jù) ModelAndView 中的視圖名稱來(lái)解析相應(yīng)的視圖對(duì)象。然后,DispatcherServlet 將模型數(shù)據(jù)傳遞給視圖對(duì)象,以便渲染視圖。最終,視圖對(duì)象會(huì)生成相應(yīng)的響應(yīng)結(jié)果并返回給客戶端。
?
五.SpringMVC實(shí)現(xiàn)web開(kāi)發(fā)
1.詳解用戶端返回的響應(yīng)
@Controller
@controller表示被這個(gè)注解修飾的類(lèi)是一個(gè)bean(一般只能用來(lái)修飾類(lèi)),并且這個(gè)bean負(fù)責(zé)處理web請(qǐng)求和響應(yīng)
@ResponseBody
@ResponseBody表示響應(yīng)正文,即規(guī)定返回值的類(lèi)型(既可以修飾類(lèi),又可以修飾方法,修飾類(lèi)代表所有的方法都設(shè)置返回值類(lèi)型,修飾方法代表只有這個(gè)方法規(guī)定好返回值的類(lèi)型),@ResponseBody規(guī)定被其修飾的方法的返回值以特定的數(shù)據(jù)格式進(jìn)行返回,默認(rèn)的返回形式是json
重定向和轉(zhuǎn)發(fā)
如果沒(méi)有使用@ResponseBody來(lái)規(guī)定好返回值類(lèi)型的格式,的默認(rèn)的返回值類(lèi)型是String,并且代表一個(gè)路徑:這個(gè)路徑一般分為兩種應(yīng)用場(chǎng)景:轉(zhuǎn)發(fā)和重定向
轉(zhuǎn)發(fā)
語(yǔ)法格式:forward:/+路徑
抓包查看特征:
①只有一次請(qǐng)求和響應(yīng) 響應(yīng)的資源是一個(gè)html頁(yè)面
?
重定向
語(yǔ)法格式:"redirect:/+路徑”
抓包查看特征:
第一次請(qǐng)求:?
?第一次響應(yīng):
第二次請(qǐng)求:
第二次響應(yīng):
轉(zhuǎn)發(fā)和重定向的區(qū)別:(M)
1.重定向訪問(wèn)服務(wù)器兩次,轉(zhuǎn)發(fā)只訪問(wèn)服務(wù)器一次。
2.轉(zhuǎn)發(fā)頁(yè)面的URL不會(huì)改變,而重定向地址會(huì)改變
3.轉(zhuǎn)發(fā)只能轉(zhuǎn)發(fā)到自己的web應(yīng)用內(nèi),重定向可以重定義到任意資源路徑。
4.轉(zhuǎn)發(fā)相當(dāng)于服務(wù)器跳轉(zhuǎn),相當(dāng)于方法調(diào)用,在執(zhí)行當(dāng)前文件的過(guò)程中轉(zhuǎn)向執(zhí)行目標(biāo)文件,兩個(gè)文件(當(dāng)前文件和目標(biāo)文件)屬于同一次請(qǐng)求,前后頁(yè) 共用一個(gè)request,可以通過(guò)此來(lái)傳遞一些數(shù)據(jù)或者session信息,request.setAttribute()和 request.getAttribute()。而重定向會(huì)產(chǎn)生一個(gè)新的request,不能共享request域信息與請(qǐng)求參數(shù)
5.由于轉(zhuǎn)發(fā)相當(dāng)于服務(wù)器內(nèi)部方法調(diào)用,所以轉(zhuǎn)發(fā)后面的代碼仍然會(huì)執(zhí)行(轉(zhuǎn)發(fā)之后記得return);重定向代碼執(zhí)行之后是方法執(zhí)行完成之后進(jìn)行重定向操作,也就是訪問(wèn)第二個(gè)請(qǐng)求,如果是方法的最后一行進(jìn)行重定向那就會(huì)馬上進(jìn)行重定向(重定向也需要return)。
?
自定義返回類(lèi)型
除了使用@ResponseBody設(shè)置返回值類(lèi)型和進(jìn)行轉(zhuǎn)發(fā)和重定向之外,還能實(shí)現(xiàn)自定義的返回值類(lèi)型,操作如下:
我們舉一個(gè)例子來(lái)進(jìn)行說(shuō)明:我們傳輸給客戶端一個(gè).doc對(duì)象(網(wǎng)絡(luò)資源下載)
1.創(chuàng)建文件路徑對(duì)象
2.讀取路徑中的字節(jié)數(shù)組
3.設(shè)置自定義的返回對(duì)象
@Controller
public class SelfController {
@GetMapping("/object1")
public ResponseEntity test() throws IOException {
//創(chuàng)建返回值
//傳輸字節(jié)碼文件
Path p=new File("D:\\大物實(shí)驗(yàn)報(bào)告\\42109211014_20221018142451.doc").toPath();
byte[]bytes= Files.readAllBytes(p);
return ResponseEntity.ok().header("content-type", "application/msword").body(bytes);
}
}
我們使用路徑進(jìn)行訪問(wèn)
@RestController
我們暫且可以將其作用簡(jiǎn)單理解為:@Controller和@Responsebody的組合注解
使用方法:加載類(lèi)前面
@RequestMapping
@RequestMapping代表請(qǐng)求路徑,該注解既可以加到類(lèi)上,也能加到方法上如果類(lèi)上和方法上同時(shí)加了這個(gè)注解,則請(qǐng)求路徑表示為:類(lèi)路徑+方法路徑
使用方法:
部分屬性:
●value:定義request請(qǐng)求的映射地址
●method:定義地request址請(qǐng)求的方式,包括【GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.】默認(rèn)接受get請(qǐng)求,如果請(qǐng)求方式和定義的方式不一樣則請(qǐng)求無(wú)法成功。
●params:定義request請(qǐng)求中必須包含的參數(shù)值。
?2.詳解服務(wù)端接收用戶端的請(qǐng)求
2.1關(guān)于請(qǐng)求路徑和請(qǐng)求頭中的參數(shù)
@Pathvirable
使用方式:用于修飾形參
@Pathvirable 作用:標(biāo)識(shí)請(qǐng)求路徑中的動(dòng)態(tài)參數(shù)(從使用@RequestMapping中請(qǐng)求路徑中讀取被@Pathvirable 修飾的方法形參變量名一致的變量,將其讀取至方法中相同變量名的形參變量中。)
代碼演示:
@RestController
public class PathTest {
@RequestMapping("/test/{id}")
public Object test(@PathVariable Long id){
//創(chuàng)建容器
HashMap<String, Long> stringLongHashMap = new HashMap<>();
stringLongHashMap.put("id", id);
return stringLongHashMap;
}
使用postman對(duì)測(cè)試結(jié)果進(jìn)行分析:
?@pathvirable修飾的變量類(lèi)型可以對(duì)請(qǐng)求路徑傳來(lái)的變量進(jìn)行校驗(yàn),如果請(qǐng)求路徑中的參數(shù)和參數(shù)的類(lèi)型不符,會(huì)報(bào)400錯(cuò)誤
@RequestHeader
作用:通過(guò)綁定請(qǐng)求頭中的字段名,獲取請(qǐng)求頭的字段信息
使用示例:
@RequestMapping("/header") public Object getHeader(@RequestHeader("user-agent") String headers){ HashMap<String, String> stringLongHashMap = new HashMap<>(); stringLongHashMap.put("user-agent", headers); return stringLongHashMap; }
@CookieValue
作用:通過(guò)綁定cookie中的鍵名,獲取cookie中的信息
使用方式:
@RequestMapping("/cookie") public Object getCookie(@CookieValue("JSESSIONID") String cookie){ HashMap<String, String> stringLongHashMap = new HashMap<>(); stringLongHashMap.put("my-cookie", cookie); return stringLongHashMap;
@SessionAttribute
在講解這個(gè)之前,我們先簡(jiǎn)要說(shuō)一下cookie和session之間的關(guān)系:session保存在服務(wù)端,服務(wù)端用它來(lái)保存用戶信息,我們可以將session理解為鍵值對(duì),鍵為隨機(jī)生成的sessionId,值為當(dāng)前用戶的session對(duì)象;cookie保存到客戶端,也是用來(lái)保存用戶信息,再校驗(yàn)用戶信息時(shí)根據(jù)sessionId在服務(wù)端匹配對(duì)應(yīng)的sessoin對(duì)象,進(jìn)行身份校驗(yàn)
@SessionAttribute用來(lái)處獲取請(qǐng)求參數(shù)中的session信息(根據(jù)sessionId在服務(wù)端獲取session對(duì)象,之后進(jìn)行校驗(yàn)用戶信息)
使用實(shí)例:
?@RequestMapping("/login") public Object info(HttpServletRequest request){ //通過(guò)session存儲(chǔ)session信息 HttpSession session = request.getSession(true); //存儲(chǔ)session信息 session.setAttribute("user", "zhangsan"); //創(chuàng)建容器并返回 HashMap<String, String> map = new HashMap<>(); map.put("user", "zhangsan"); return map; } @RequestMapping("/check") public Object checkLogin(@SessionAttribute("user") String name){ HashMap<String, String> map = new HashMap<>(); map.put("user", name); return map; }
一般在進(jìn)行登錄校驗(yàn)時(shí)的大體流程如下:
首次登錄時(shí)檢查session對(duì)象是否存在(不存在則新創(chuàng)建一個(gè)session對(duì)象),之后根據(jù)前端傳來(lái)的用戶信息對(duì)其進(jìn)行存儲(chǔ),返回響應(yīng),之后如果再進(jìn)行其他操作(需要在用戶登錄的基礎(chǔ)上)則需要根據(jù)sessionId從服務(wù)器中獲取session對(duì)象進(jìn)行校驗(yàn)
結(jié)果分析:
2.2關(guān)于請(qǐng)求參數(shù)
無(wú)注解的請(qǐng)求參數(shù)
?前端的請(qǐng)求參數(shù)通常分為四種數(shù)據(jù)格式進(jìn)行傳輸:query-string ,表單類(lèi)型的數(shù)據(jù)、json格式的數(shù)據(jù)和form-data類(lèi)型的數(shù)據(jù),而我們后端接收請(qǐng)求的java對(duì)象,我們將其分為簡(jiǎn)單數(shù)據(jù)類(lèi)型(基本數(shù)據(jù)類(lèi)型+包裝類(lèi)+String)和復(fù)雜數(shù)據(jù)類(lèi)型(集合框架和自定義的類(lèi)型),以下我們將從這兩種數(shù)據(jù)類(lèi)型進(jìn)行分析;
我們先給出結(jié)論:無(wú)論是簡(jiǎn)單數(shù)據(jù)類(lèi)型還是復(fù)雜數(shù)據(jù)類(lèi)型,在沒(méi)有注解的情況下后端能進(jìn)行數(shù)據(jù)解析的只有query-string ,表單類(lèi)型的數(shù)據(jù)、json格的數(shù)據(jù),json類(lèi)型的數(shù)據(jù)需要使用注解(@RequestBody),我們根據(jù)這個(gè)畫(huà)一個(gè)表格對(duì)此進(jìn)行說(shuō)明:
我們分別對(duì)這幾種前端數(shù)據(jù)類(lèi)型進(jìn)行分析:
?
@RequestParam
@RequestParam(能修飾除json之外的其他類(lèi)型,還可以修飾map 、list等集合)
參數(shù):vlaue/name:用來(lái)標(biāo)識(shí)請(qǐng)求參數(shù)的名稱(請(qǐng)求路徑中請(qǐng)求參數(shù)的名稱),這個(gè)參數(shù)也可以不傳,只要保證方法中的參數(shù)和請(qǐng)求路徑參數(shù)完全一致即可
required:這個(gè)參數(shù)在請(qǐng)求參數(shù)中是否必須提供:FALSE代表不是必須提供,TRUE代表必須提供
參數(shù)類(lèi)型類(lèi)型不匹配或者要求必須提供的參數(shù)沒(méi)有提供會(huì)報(bào)400錯(cuò)誤
作用:將請(qǐng)求參數(shù)轉(zhuǎn)化為控制器方法的形參
使用實(shí)例:
?@RequestMapping("/requestB") public Object res(@RequestParam String name ,@RequestParam() Integer id){ HashMap<String, String> map = new HashMap<>(); map.put("name", name); map.put("id", id+""); return map; }
@RequestPart
@RequestPart一般用于用于將
multipart/form-data
類(lèi)型數(shù)據(jù)映射到控制器處理方法的參數(shù)中注解解析
??① value:
????綁定的參數(shù)名稱,參數(shù)值為String類(lèi)型。
??② name:
????綁定的參數(shù)名稱,參數(shù)值為String類(lèi)型。name和value可以同時(shí)使用,但兩者的值需一致,否則會(huì)出現(xiàn)錯(cuò)誤。(400)
??③ required:????請(qǐng)求頭中是否必須包含指定的值,默認(rèn)值為true。
????required為true時(shí),如果請(qǐng)求頭中缺少指定的值,則會(huì)拋出異常。
????required為false時(shí),如果請(qǐng)求頭中缺少指定的值,則會(huì)返回null。
使用用例:
@RequestMapping("/upload") //上傳文件 public Object upload(@RequestPart MultipartFile head, User user) throws IOException { head.transferTo(new File("D:/上傳的"+head.getOriginalFilename())); HashMap<String, Object> map = new HashMap<>(); map.put("file", head); return map; }
文件上傳可能出現(xiàn)的錯(cuò)誤:系統(tǒng)找不到指定文件
java.io.FileNotFoundException: C:\Users\86131\AppData\Local\Temp\tomcat.8080.6906634590984434583\work\Tomcat\localhost\ROOT\upload_135c4883_3414_49af_b54e_39dbe063b0de_00000002.tmp (系統(tǒng)找不到指定的文件。)我們?nèi)ハ到y(tǒng)指定的目錄中查看文件是否上傳成功:
發(fā)現(xiàn)雖然報(bào)了錯(cuò)誤,但是文件上傳成功了:
在分析錯(cuò)誤原因前我們先對(duì)文件上傳的邏輯進(jìn)行分析:客戶端向服務(wù)端傳輸文件,文件首先報(bào)錯(cuò)到系統(tǒng)網(wǎng)卡然后部分文件信息保存到服務(wù)端內(nèi)存,而有關(guān)文件內(nèi)容將會(huì)在本地的一個(gè)臨時(shí)目錄中進(jìn)行保存,如果我們調(diào)用transferTo(),會(huì)將默認(rèn)保存的系統(tǒng)文件移動(dòng)到我們指定的文件目錄中,這種情況下原來(lái)臨時(shí)目錄的文件就沒(méi)有了,所以當(dāng)我們后面再調(diào)用有關(guān)文件信息的方法,首先在服務(wù)端的內(nèi)存中進(jìn)行查找,如果服務(wù)端內(nèi)存沒(méi)有這個(gè)信息,則需要到臨時(shí)目錄中進(jìn)行查找,如果臨時(shí)目錄中沒(méi)有這個(gè)文件,則會(huì)報(bào)以上的錯(cuò)誤。
如何解決這個(gè)問(wèn)題呢?
解決思路也比較清晰:在調(diào)用文件相關(guān)信息的時(shí)候,在transferTo()方法執(zhí)行之前進(jìn)行調(diào)用即可。
@RequestBody
我們前面有說(shuō)到的是無(wú)論是無(wú)注解的請(qǐng)求參數(shù)還是使用@RequestParam注解,都不支持對(duì)json數(shù)據(jù)的解析,而@RequestBody能夠?qū)崿F(xiàn)對(duì)json數(shù)據(jù)的解析,不支持對(duì)其他數(shù)據(jù)類(lèi)型的解析。
使用實(shí)例:
@RequestMapping("/json") public Object jsonSend( @RequestBody User user){ HashMap<String, String> map = new HashMap<>(); map.put("name", user.getName()); map.put("id", user.getId()+""); return map; }
通過(guò)配置類(lèi)設(shè)置編程式的配置方案
我們知道可以通過(guò)設(shè)置配置文件的格式對(duì)springboot項(xiàng)目進(jìn)行設(shè)置,但是通過(guò)配置文件設(shè)置比較麻煩或者設(shè)置通過(guò)配置文件無(wú)法實(shí)現(xiàn)的我們可以通過(guò)編程(設(shè)置配置類(lèi))來(lái)設(shè)置配置。通過(guò)@Configuration來(lái)實(shí)現(xiàn)這個(gè)功能:被@Configuration修飾的類(lèi)會(huì)在springboot項(xiàng)目啟動(dòng)時(shí)進(jìn)行加載配置,我們通過(guò)使用以下兩個(gè)實(shí)例進(jìn)行加載說(shuō)明:
設(shè)置后端路徑的統(tǒng)一前綴
功能:為所有的后端控制器添加統(tǒng)一的前綴(名為:api)
應(yīng)用場(chǎng)景:在訪問(wèn)后端的請(qǐng)求路徑時(shí)需要對(duì)其的部分路徑的處理邏輯加一些統(tǒng)一的校驗(yàn),為了區(qū)分前端路徑和后端路徑,我們統(tǒng)一為所有的控制器都加一個(gè)前綴.
我們給出代碼實(shí)例:
因?yàn)槲覀冞M(jìn)行的是有關(guān)web開(kāi)發(fā)的配置,所以我們進(jìn)行的配置要繼承WebMvcConfigurer的接口
@Configuration
public class AppConfig implements WebMvcConfigurer {
//調(diào)用路徑匹配的api,為所有的后端邏輯添加前綴
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("api", c->{
//通過(guò)循環(huán)遍歷所有的后端controller判斷是否要加前綴(暫時(shí)設(shè)置所有的路徑都為前綴)
//如果需要在此處使某些控制器不加前綴,在此處應(yīng)該加一些別的邏輯經(jīng)即可
return true;
});
}
我們?cè)趯?shí)際的路徑中并沒(méi)有api請(qǐng)求路徑,?但是我們進(jìn)行訪問(wèn)時(shí)必須通過(guò)“api”路徑進(jìn)行訪問(wèn),否則直接報(bào)404
?
設(shè)置攔截器
我們首先了解一下web開(kāi)發(fā)的三大組件:servlet(連接器)、listener(監(jiān)聽(tīng)器)和filter(過(guò)濾器),我們需要明確的是:spring官方并沒(méi)有提供攔截器,攔截器是springmvc提供的,但是其實(shí)現(xiàn)原理是和過(guò)濾器類(lèi)似。
攔截器的處理邏輯如下:
?在客戶端返送請(qǐng)求之前會(huì)通過(guò)攔截器preHander相關(guān)方法進(jìn)行處理,preHander返回一個(gè)Boolean值,TRUE則繼續(xù)向下執(zhí)行,F(xiàn)ALSE則直接返回;在controller層返回響應(yīng)后同樣也會(huì)通過(guò)攔截器,這時(shí)回調(diào)用postHander()方法,最后將響應(yīng)返回給客戶端,我們自定義的攔截器通常會(huì)重寫(xiě)以下三個(gè)方法:
1、preHandler(HttpServletRequest request, HttpServletResponse response, Object handler) 方法在請(qǐng)求處理之前被調(diào)用。該方法在 Interceptor 類(lèi)中最先執(zhí)行,用來(lái)進(jìn)行一些前置初始化操作或是對(duì)當(dāng)前請(qǐng)求做預(yù)處理,也可以進(jìn)行一些判斷來(lái)決定請(qǐng)求是否要繼續(xù)進(jìn)行下去。該方法的返回至是 Boolean 類(lèi)型,當(dāng)它返回 false 時(shí),表示請(qǐng)求結(jié)束,后續(xù)的 Interceptor 和 Controller 都不會(huì)再執(zhí)行;當(dāng)它返回為 true 時(shí)會(huì)繼續(xù)調(diào)用下一個(gè) Interceptor 的 preHandle 方法,如果已經(jīng)是最后一個(gè) Interceptor 的時(shí)候就會(huì)調(diào)用當(dāng)前請(qǐng)求的 Controller 方法。
2、postHandler(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) 方法在當(dāng)前請(qǐng)求處理完成之后,也就是 Controller 方法調(diào)用之后執(zhí)行,但是它會(huì)在 DispatcherServlet 進(jìn)行視圖返回渲染之前被調(diào)用,所以我們可以在這個(gè)方法中對(duì) Controller 處理之后的 ModelAndView 對(duì)象進(jìn)行操作。
3、afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex) 方法需要在當(dāng)前對(duì)應(yīng)的 Interceptor 類(lèi)的 preHandle 方法返回值為 true 時(shí)才會(huì)執(zhí)行。顧名思義,該方法將在整個(gè)請(qǐng)求結(jié)束之后,也就是在 DispatcherServlet 渲染了對(duì)應(yīng)的視圖之后執(zhí)行。此方法主要用來(lái)進(jìn)行資源清理。
我們?cè)O(shè)置攔截器的過(guò)程可以從以下三個(gè)方面進(jìn)行設(shè)計(jì)分析:①自定義攔截器②設(shè)置攔截路徑③設(shè)置攔截器的處理邏輯
我們給出一個(gè)攔截器中以下的三種代碼:
①路徑處理:配置攔截路徑支持模糊匹配:而進(jìn)行配置的方式,一般是通過(guò)添加攔截路徑和排除一些不攔截的路徑來(lái)處理
/**:添加任意路徑
/api/**:添加api下的任意路徑
/api/*:添加api下的一層目錄
設(shè)置排除路徑:
/api/login:排除api下的login的目錄
/api/register:排除api下的register目錄
②自定義攔截器:
自定義攔截器并引用HandlerInterceptor接口,然后重寫(xiě)接口中的方法
③實(shí)現(xiàn)方法邏輯
在重寫(xiě)的方法中實(shí)現(xiàn)方法邏輯
完整代碼:
@Configuration public class AppConfig implements WebMvcConfigurer { //調(diào)用路徑匹配的api,為所有的后端邏輯添加前綴 @Override public void configurePathMatch(PathMatchConfigurer configurer) { configurer.addPathPrefix("api", c->{ //通過(guò)循環(huán)遍歷所有的后端controller判斷是否要加前綴(暫時(shí)設(shè)置所有的路徑都為前綴) return true; }); } @Override public void addInterceptors(InterceptorRegistry registry) { //使用注冊(cè)器進(jìn)行注冊(cè)過(guò)濾器 registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/api/**").excludePathPatterns("/api/login") .excludePathPatterns("/api/register"); } }
public class LoginInterceptor implements HandlerInterceptor { //設(shè)置請(qǐng)求邏輯:在請(qǐng)求前進(jìn)行處理 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //獲取session HttpSession session = request.getSession(false); if(session!=null){ String user = (String)session.getAttribute("user"); if(user!=null){ //判斷是否是用戶 if(user.equals("admin")){ return true; } } } response.setStatus(401); return false; } }
Interceptor 作用
日志記錄:記錄請(qǐng)求信息的日志,以便進(jìn)行信息監(jiān)控、信息統(tǒng)計(jì)、計(jì)算 PV(Page View)等;
權(quán)限檢查:如登錄檢測(cè),進(jìn)入處理器檢測(cè)用戶是否登錄;
性能監(jiān)控:通過(guò)攔截器在進(jìn)入處理器之前記錄開(kāi)始時(shí)間,在處理完后記錄結(jié)束時(shí)間,從而得到該請(qǐng)求的處理時(shí)間。(反向代理,如 Apache 也可以自動(dòng)記錄)
通用行為:讀取 Cookie 得到用戶信息并將用戶對(duì)象放入請(qǐng)求,從而方便后續(xù)流程使用,還有如提取 Locale、Theme 信息等,只要是多個(gè)處理器都需要的即可使用攔截器實(shí)現(xiàn)。
統(tǒng)一異常處理
如果我們通過(guò)瀏覽器進(jìn)行請(qǐng)求時(shí)出現(xiàn)錯(cuò)誤,極有可能把后端的報(bào)錯(cuò)信息全部顯示到瀏覽器上,一方面這樣存在極大的安全隱患(其他程序員根據(jù)報(bào)錯(cuò)可能會(huì)對(duì)我們后端的實(shí)現(xiàn)有一定的了解,從而進(jìn)行破壞性操作),另一方面對(duì)用戶的體驗(yàn)也并不是很友好。
因此我們需要進(jìn)行統(tǒng)一的異常處理,將后端報(bào)錯(cuò)進(jìn)行統(tǒng)一起來(lái),我們進(jìn)行如下操作:使用@ControllerAdvice注解進(jìn)行控制器增強(qiáng),使用@ExceptionHandle(異常類(lèi))進(jìn)行統(tǒng)一的異常處理,@ExceptionHandle(異常類(lèi))的處理邏輯如下:根據(jù)括號(hào)中的異常類(lèi)信息,當(dāng)發(fā)生括號(hào)內(nèi)的異常類(lèi)中的報(bào)錯(cuò)信息之后,使用其下的方法進(jìn)行報(bào)錯(cuò)的處理(起到的作用是異常處理的catch)
@ControllerAdvice
public class ExceptionHandle {
//默認(rèn)以json形式返回?cái)?shù)據(jù)
@ResponseBody
//統(tǒng)一捕獲異常類(lèi)
@ExceptionHandler(Exception.class)
public Object handle(Exception e){
HashMap<String, Object> map = new HashMap<>();
map.put("code",500);
map.put("message",e.getMessage());
return map;
}
需要注意的是:@ExceptionHandle(異常類(lèi))只能捕獲其括號(hào)內(nèi)的異常,一旦出了其括號(hào)內(nèi)異常類(lèi)的范圍,還是會(huì)直接將錯(cuò)誤信息直接返回給客戶端:修改之后的錯(cuò)誤信息如下:
@ControllerAdvice
public class ExceptionHandle {
//默認(rèn)以json形式返回?cái)?shù)據(jù)
// @ResponseBody
// //統(tǒng)一捕獲異常類(lèi)
// @ExceptionHandler(Exception.class)
// public Object handle(Exception e){
// HashMap<String, Object> map = new HashMap<>();
// map.put("code",500);
// map.put("message",e.getMessage());
// return map;
// }
@ResponseBody
//統(tǒng)一捕獲異常類(lèi)
@ExceptionHandler(IOException.class)
public Object handleIo(Exception e){
HashMap<String, Object> map = new HashMap<>();
map.put("code",500);
map.put("message",e.getMessage());
return map;
}
統(tǒng)一的結(jié)果處理:
①自定義結(jié)果類(lèi)
@Data
public class Result {
private Boolean ok;
private Object data;
private String error;
}
②使用@ControllerAdvice統(tǒng)一處理結(jié)果:設(shè)置ok data(data在返回的body中直接處理) 和error
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
//進(jìn)行賦值處理
Result result = new Result();
result.setOk(true);
result.setData(body);
result.setError(null);
return result;
}
}
我們使用這個(gè)結(jié)果類(lèi)訪問(wèn)以下代碼:
import java.util.HashMap;
/**
* @author tongchen
* @create 2023-04-28 10:34
*/
@RestController
public class UserController {
@RequestMapping("/user/{id}/{name}")
public Object user(@PathVariable String name,@PathVariable Integer id ){
HashMap<String, Object> map = new HashMap<>();
map.put("user", name);
return map;
}
}
結(jié)果如下:
但是我們發(fā)現(xiàn)一個(gè)問(wèn)題,在這個(gè)代碼中,我們將返回信息除了data外直接寫(xiě)死了 ,我們思考一個(gè)問(wèn)題:在真正的業(yè)務(wù)場(chǎng)景中,我們應(yīng)該如何統(tǒng)一處理返回結(jié)果呢?(異常結(jié)果處理和統(tǒng)一結(jié)果處理)
A:修改返回類(lèi),取消統(tǒng)一設(shè)置返回方法,在具體的業(yè)務(wù)場(chǎng)景中具體返回結(jié)果,異常類(lèi)單獨(dú)返回結(jié)果
具體code及結(jié)果如下:
@Data
public class Result<T> {
private Integer ok;//1代表成功 0代表失敗
private T data;
private String msg;//正確或異常的信息
public static <T>Result<T> success(T data){
Result<T> result = new Result<>();
result.setData(data);
result.setOk(1);
return result;
}
public static <T>Result<T>error(String msg){
Result<T> result = new Result<>();
result.setOk(0);
result.setMsg(msg);
return result;
}
}
?正常的結(jié)果類(lèi):
@RestController
public class UserController {
@RequestMapping("/user/{id}/{name}")
public Object user(@PathVariable String name,@PathVariable Integer id ){
HashMap<String, Object> map = new HashMap<>();
map.put("id", id);
map.put("name",name);
return Result.success(map);
}
}
異常的結(jié)果類(lèi):
public class MyException extends Exception{
public MyException(String message) {
super(message);
}
}
@RestController
public class ExceptionController {
@RequestMapping("/test")
public void test() throws MyException {
throw new MyException("HAHAHH");
}
}
異常的結(jié)果類(lèi)處理:
?
@ControllerAdvice
public class ExceptionHandle {
//默認(rèn)以json形式返回?cái)?shù)據(jù)
// @ResponseBody
// //統(tǒng)一捕獲異常類(lèi)
// @ExceptionHandler(Exception.class)
// public Object handle(Exception e){
// HashMap<String, Object> map = new HashMap<>();
// map.put("code",500);
// map.put("message",e.getMessage());
// return map;
// }
@ResponseBody
//統(tǒng)一捕獲異常類(lèi)
@ExceptionHandler(MyException.class)
public Object handleIo(Exception e){
HashMap<String, Object> map = new HashMap<>();
return Result.error("服務(wù)器錯(cuò)誤");
}
}
正常結(jié)果:
?異常結(jié)果:
關(guān)于responseBodyAdvice的作用
?
ResponseBodyAdvice 接口是在 Controller 執(zhí)行 return 之后,在 response 返回給客戶端之前,執(zhí)行的對(duì) response 的一些處理,可以實(shí)現(xiàn)對(duì) response 數(shù)據(jù)的一些統(tǒng)一封裝或者加密等操作。
該接口一共有兩個(gè)方法:
(1)supports? ——?判斷是否要執(zhí)行beforeBodyWrite方法,true為執(zhí)行,false不執(zhí)行? ——? 通過(guò)supports方法,我們可以選擇哪些類(lèi)或哪些方法要對(duì)response進(jìn)行處理,其余的則不處理。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-456066.html
(2)beforeBodyWrite? ——? 對(duì) response 處理的具體執(zhí)行方法。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-456066.html
到了這里,關(guān)于1.4W字!讓我?guī)阕x懂springmvc的世界!的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!