日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 前端技术 > javascript >内容正文

javascript

最全面的SpringMVC教程(二)——SpringMVC核心技术篇

發(fā)布時(shí)間:2023/12/16 javascript 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 最全面的SpringMVC教程(二)——SpringMVC核心技术篇 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言

本文為 【SpringMVC教程】核心技術(shù)篇 相關(guān)詳細(xì)介紹,具體將對(duì)視圖和模型拆分,重定向與轉(zhuǎn)發(fā),RequestMapping與其衍生注解,URL 模式匹配,牛逼的傳參,設(shè)定字符集,返回json數(shù)據(jù)(序列化),獲取請(qǐng)求中的json數(shù)據(jù),數(shù)據(jù)轉(zhuǎn)化,數(shù)據(jù)校驗(yàn),視圖解析器詳解,全局異常捕獲,處理資源,攔截器,全局配置類(lèi)等SpringMVC相關(guān)核心技術(shù)進(jìn)行詳盡介紹~

📌博主主頁(yè):小新要變強(qiáng) 的主頁(yè)
👉Java全棧學(xué)習(xí)路線可參考:【Java全棧學(xué)習(xí)路線】最全的Java學(xué)習(xí)路線及知識(shí)清單,Java自學(xué)方向指引,內(nèi)含最全Java全棧學(xué)習(xí)技術(shù)清單~
👉算法刷題路線可參考:算法刷題路線總結(jié)與相關(guān)資料分享,內(nèi)含最詳盡的算法刷題路線指南及相關(guān)資料分享~
👉Java微服務(wù)開(kāi)源項(xiàng)目可參考:企業(yè)級(jí)Java微服務(wù)開(kāi)源項(xiàng)目(開(kāi)源框架,用于學(xué)習(xí)、畢設(shè)、公司項(xiàng)目、私活等,減少開(kāi)發(fā)工作,讓您只關(guān)注業(yè)務(wù)!)

??本文上接:最全面的SpringMVC教程(一)——SpringMVC簡(jiǎn)介


目錄

文章標(biāo)題

  • 前言
  • 目錄
  • 一、視圖和模型拆分
  • 二、重定向與轉(zhuǎn)發(fā)
  • 三、RequestMapping與其衍生注解
  • 四、URL 模式匹配
  • 五、牛逼的傳參
  • 六、設(shè)定字符集
  • 七、返回json數(shù)據(jù)(序列化)
  • 八、獲取請(qǐng)求中的json數(shù)據(jù)
  • 九、數(shù)據(jù)轉(zhuǎn)化
  • 十、數(shù)據(jù)校驗(yàn)
  • 十一、視圖解析器詳解
  • 十二、全局異常捕獲
  • 十三、處理資源
  • 十四、攔截器
  • 十五、全局配置類(lèi)
  • 后記

一、視圖和模型拆分

視圖和模型相伴相生,但是springmvc給我們提供了更好的,更優(yōu)雅的解決方案:

  • Model會(huì)在調(diào)用handler時(shí)通過(guò)參數(shù)的形式傳入
  • View可以簡(jiǎn)化為字符串形式返回

這樣的解決方案也是企業(yè)開(kāi)發(fā)中最常用的:

@RequestMapping("/test1") public String testAnnotation(Model model){model.addAttribute("hello","hello annotationMvc as string");return "annotation"; }

二、重定向與轉(zhuǎn)發(fā)

在返回的字符串中,默認(rèn)使用視圖解析器進(jìn)行視圖跳轉(zhuǎn)。

springmvc給我們提供了更好的解決【重定向和轉(zhuǎn)發(fā)】的方案:

🍀返回視圖字符串加前綴redirect就可以進(jìn)行重定向

redirect:/redirectController/redirectTest redirect:https://www.baidu.com

🍀返回視圖字符串加前綴forward就可以進(jìn)行請(qǐng)求轉(zhuǎn)發(fā),而不走視圖解析器

// 會(huì)將請(qǐng)求轉(zhuǎn)發(fā)至/a/b forward:/a/b

三、RequestMapping與其衍生注解

  • @RequestMapping這個(gè)注解很關(guān)鍵,他不僅僅是一個(gè)方法級(jí)的注解,還是一個(gè)類(lèi)級(jí)注解。
  • 如果放在類(lèi)上,相當(dāng)于給每個(gè)方法默認(rèn)都加上一個(gè)前綴url。
@Controller @RequestMapping("/user/") public class AnnotationController {@RequestMapping("register")public String register(Model model){......return "register";}@RequestMapping("login")public String login(){......return "register";} }

🍀好處

  • 一個(gè)類(lèi)一般處理一類(lèi)業(yè)務(wù),可以統(tǒng)一加上前綴,好區(qū)分
  • 簡(jiǎn)化書(shū)寫(xiě)復(fù)雜度

🍀RequestMapping注解有六個(gè)屬性

value: 指定請(qǐng)求的實(shí)際地址,指定的地址可以是URI Template 模式(后面將會(huì)說(shuō)明);
method: 指定請(qǐng)求的method類(lèi)型, GET、POST、PUT、DELETE等;
consumes: 指定處理中的請(qǐng)求的內(nèi)容類(lèi)型(Content-Type),例如application/json;
produces: 指定返回響應(yīng)的內(nèi)容類(lèi)型,僅當(dāng)request請(qǐng)求頭中的(Accept)類(lèi)型中包含該指定類(lèi)型才返回;

@GetMapping(value = "{id}",produces = {"application/json;charset=utf-8"})

params: 指定request中必須包含某些參數(shù)值處理器才會(huì)繼續(xù)執(zhí)行;
headers: 指定request中必須包含某些指定的header值處理器才會(huì)繼續(xù)執(zhí)行。

@RequestMapping(value = "add",method = RequestMethod.POST,consumes = "application/json",produces = "text/plain",headers = "name",params = {"age","times"}) @ResponseBody public String add(Model model){model.addAttribute("user","add user");return "user"; }

@RequestMapping還有幾個(gè)衍生注解,用來(lái)處理特定方法的請(qǐng)求:

@GetMapping("getOne") public String getOne(){return "user"; }@PostMapping("insert") public String insert(){return "user"; }@PutMapping("update") public String update(){return "user"; }@DeleteMapping("delete") public String delete(){return "user"; }

源碼中能看帶GetMapping注解中有@RequestMapping作為元注解修飾:

@RequestMapping(method = {RequestMethod.GET}) public @interface GetMapping {}

四、URL 模式匹配

@RequestMapping可以支持【URL模式匹配】,為此,spring提供了兩種選擇(兩個(gè)類(lèi)):

  • PathPattern:PathPattern是 Web 應(yīng)用程序的推薦解決方案,也是 Spring WebFlux 中的唯一選擇,比較新。
  • AntPathMatcher:使用【字符串模式與字符串路徑】匹配。這是Spring提供的原始解決方案,用于選擇類(lèi)路徑、文件系統(tǒng)和其他位置上的資源。

小知識(shí): 二者目前都存在于Spring技術(shù)棧內(nèi),做著相同的事。雖說(shuō)現(xiàn)在還鮮有同學(xué)了解到PathPattern,我認(rèn)為淘汰掉AntPathMatcher只是時(shí)間問(wèn)題(特指web環(huán)境哈),畢竟后浪總歸有上岸的一天。但不可否認(rèn),二者將在較長(zhǎng)時(shí)間內(nèi)共處,那么它倆到底有何區(qū)別呢?

  • (1)出現(xiàn)時(shí)間,AntPathMatcher是一個(gè)早在2003年(Spring的第一個(gè)版本)就已存在的路徑匹配器,而PathPattern是Spring
    5新增的,旨在用于替換掉較為“古老”的AntPathMatcher。
  • (2)功能差異,PathPattern去掉了Ant字樣,但保持了很好的向下兼容性:除了不支持將**寫(xiě)在path中間之外,其它的匹配規(guī)則從行為上均保持和AntPathMatcher一致,并且還新增了強(qiáng)大的{*pathVariable}的支持,他能匹配最后的多個(gè)路勁,并獲取路徑的值。
  • (3)性能差異,Spring官方說(shuō)PathPattern的性能優(yōu)于AntPathMatcher。

🍀一些模式匹配的示例

  • “/resources/ima?e.png” - 匹配路徑段中的一個(gè)字符
  • “/resources/*.png” - 匹配路徑段中的零個(gè)或多個(gè)字符
  • “/resources/**” - 匹配多個(gè)路徑段
  • “/projects/{project}/versions” - 匹配路徑段并將其【捕獲為變量】
  • “/projects/{project:[a-z]+}/versions” - 使用正則表達(dá)式匹配并【捕獲變量】

捕獲的 URI 變量可以使用@PathVariable注解,示例例如:

@GetMapping("/owners/{ownerId}/pets/{petId}") public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {// ... }

還可以在類(lèi)和方法級(jí)別聲明 URI 變量,如以下示例所示:

@Controller @RequestMapping("/owners/{ownerId}") public class OwnerController {@GetMapping("/pets/{petId}")public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {// ...} }

🍀一個(gè)url可以匹配到多個(gè)路由的情況

有時(shí)候會(huì)遇到一個(gè)url可以匹配到多個(gè)路由的情況,這個(gè)時(shí)候就是由Spring的AntPatternComparator完成優(yōu)先級(jí)處理,大致規(guī)律如下:

比如:有兩個(gè)匹配規(guī)則一個(gè)是 /a/**,一個(gè)是 /a/b/**,還有一個(gè)是/a/b/*,如果訪問(wèn)的url是/a/b/c,其實(shí)這三個(gè)路由都能匹配到,在匹配優(yōu)先級(jí)中,有限級(jí)如下:

匹配方式優(yōu)先級(jí)
全路徑匹配,例如:配置路由/a/b/c第一優(yōu)先級(jí)
帶有{}路徑的匹配,例如:/a//c第二優(yōu)先級(jí)
正則匹配,例如:/a/{regex:\d{3}}/c第三優(yōu)先級(jí)
帶有*路徑的匹配,例如:/a/b/*第四優(yōu)先級(jí)
帶有**路徑的匹配,例如:/a/b/**第五優(yōu)先級(jí)
僅僅是雙通配符:/**最低優(yōu)先級(jí)

注意:

  • 當(dāng)有多個(gè)*和多個(gè)‘{}'時(shí),命中單個(gè)路徑多的,優(yōu)先越高;
  • 多’*’的優(yōu)先級(jí)高于‘**’,會(huì)優(yōu)先匹配帶有*。

🍀我們還可以從一個(gè)類(lèi)中看出,當(dāng)一個(gè)url匹配了多個(gè)處理器時(shí),優(yōu)先級(jí)是如何考慮的,這個(gè)類(lèi)是AntPathMatcher的一個(gè)內(nèi)部類(lèi)

protected static class AntPatternComparator implements Comparator<String> {@Overridepublic int compare(String pattern1, String pattern2) {PatternInfo info1 = new PatternInfo(pattern1);PatternInfo info2 = new PatternInfo(pattern2);.....boolean pattern1EqualsPath = pattern1.equals(this.path);boolean pattern2EqualsPath = pattern2.equals(this.path);// 完全相等,是無(wú)法比較的if (pattern1EqualsPath && pattern2EqualsPath) {return 0;}// pattern1和urlequals,返回負(fù)數(shù) 1勝出else if (pattern1EqualsPath) {return -1;}// pattern2和urlequals,返回正數(shù),2勝出else if (pattern2EqualsPath) {return 1;}// 都是前綴匹配,長(zhǎng)的優(yōu)先 /a/b/** /a/**if (info1.isPrefixPattern() && info2.isPrefixPattern()) {return info2.getLength() - info1.getLength();}// 非前綴匹配的優(yōu)先級(jí)高else if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) {return 1;}else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) {return -1;}// 匹配數(shù)越少,優(yōu)先級(jí)越高if (info1.getTotalCount() != info2.getTotalCount()) {return info1.getTotalCount() - info2.getTotalCount();}// 路徑越短越好if (info1.getLength() != info2.getLength()) {return info2.getLength() - info1.getLength();}// 單通配符個(gè)數(shù),數(shù)量越少優(yōu)先級(jí)越高if (info1.getSingleWildcards() < info2.getSingleWildcards()) {return -1;}else if (info2.getSingleWildcards() < info1.getSingleWildcards()) {return 1;}// url參數(shù)越少越優(yōu)先if (info1.getUriVars() < info2.getUriVars()) {return -1;}else if (info2.getUriVars() < info1.getUriVars()) {return 1;}return 0;} }

源碼中我們看到的信息如下:

  • (1)完全匹配者,優(yōu)先級(jí)最高
  • (2)都是前綴匹配(/a/**), 匹配路由越長(zhǎng),優(yōu)先級(jí)越高
  • (3)前綴匹配優(yōu)先級(jí),比非前綴的低
  • (4)需要匹配的數(shù)量越少,優(yōu)先級(jí)越高,this.uriVars + this.singleWildcards + (2 * this.doubleWildcards);
  • (5)路勁越短優(yōu)先級(jí)越高
  • (6)*越少優(yōu)先級(jí)越高
  • (7){}越少優(yōu)先級(jí)越高

五、牛逼的傳參

在學(xué)習(xí)servlet時(shí),我們是這樣獲取請(qǐng)求參數(shù)的:

@PostMapping("insert") public String insert(HttpServletRequest req){String username = req.getParameter("username");String password = req.getParameter("password");// 其他操作return "success"; }

有了springmvc之后,我們不再需要使用getParamter一個(gè)一個(gè)獲取參數(shù):

@Controller @RequestMapping("/user/") public class LoginController {@RequestMapping("login")public String login(String username,String password){System.out.println(username);System.out.println(password);return "login";} }

如果一個(gè)表單幾十個(gè)參數(shù)怎么獲取啊?更牛的傳參方式如下:

需要提前定義一個(gè)User對(duì)象:

public class User {private String username;private String password;private int age;public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public int getAge() {return age;}public void setAge(int age) {this.age = age;} }

直接在參數(shù)中申明user對(duì)象:

@Controller @RequestMapping("/user/") public class LoginController {@RequestMapping("register")public String register(User user){System.out.println(user);return "register";}@RequestMapping("login")public String login(String username,String password){System.out.println(username);System.out.println(password);return "login";} }

🍀(1)@RequestParam

可以使用@RequestParam注解將【請(qǐng)求參數(shù)】(即查詢(xún)參數(shù)或表單數(shù)據(jù))綁定到控制器中的方法參數(shù)。

@Controller @RequestMapping("/pets") public class EditPetForm {@GetMappingpublic String setupForm(@RequestParam("petId") int petId, Model model) { Pet pet = this.clinic.loadPet(petId);model.addAttribute("pet", pet);return "petForm";} }

默認(rèn)情況下,使用此注解的方法參數(shù)是必需的,但我們可以通過(guò)將@RequestParam注解的【required標(biāo)志設(shè)置】為 false來(lái)指定方法參數(shù)是可選的。如果目標(biāo)方法參數(shù)類(lèi)型不是String,則應(yīng)用會(huì)自動(dòng)進(jìn)行類(lèi)型轉(zhuǎn)換,這個(gè)后邊會(huì)講。

請(qǐng)注意,使用@RequestParam是可選的。默認(rèn)情況下,任何屬于簡(jiǎn)單值類(lèi)型且未被任何其他參數(shù)解析器解析的參數(shù)都被視為使用【@RequestParam】。

🍀(2)@RequestHeader

可以使用@RequestHeader注解將請(qǐng)求的首部信息綁定到控制器中的方法參數(shù)中:

假如我們的請(qǐng)求header如下:

Host localhost:8080 Accept text/html,application/xhtml+xml,application/xml;q=0.9 Accept-Language fr,en-gb;q=0.7,en;q=0.3 Accept-Encoding gzip,deflate Accept-Charset ISO -8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive 300

以下示例獲取Accept-Encoding和Keep-Alive標(biāo)頭的值:

@GetMapping("/demo") public void handle(@RequestHeader("Accept-Encoding") String encoding, @RequestHeader("Keep-Alive") long keepAlive) { //... }

小知識(shí):當(dāng)@RequestHeader注解上的使用Map<String, String>, MultiValueMap<String, String>或HttpHeaders參數(shù),則map會(huì)被填充有所有header的值。當(dāng)然,我們依然可以使用requied的屬性來(lái)執(zhí)行該參數(shù)不是必須的。

🍀(3)@CookieValue

可以使用@CookieValue注解將請(qǐng)求中的 cookie 的值綁定到控制器中的方法參數(shù)。

假設(shè)我們的請(qǐng)求中帶有如下cookie:

JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

以下示例顯示了如何獲取 cookie 值:

@GetMapping("/demo") public void handle(@CookieValue("JSESSIONID") String cookie) { //... }

🍀(4)@ModelAttribute

可以使用@ModelAttribute注解在方法參數(shù)上來(lái)訪問(wèn)【模型中的屬性】,或者在不存在的情況下對(duì)其進(jìn)行實(shí)例化。模型的屬性會(huì)覆蓋來(lái)自 HTTP Servlet 請(qǐng)求參數(shù)的值,其名稱(chēng)與字段名稱(chēng)匹配,這稱(chēng)為數(shù)據(jù)綁定,它使您不必【處理解析】和【轉(zhuǎn)換單個(gè)查詢(xún)參數(shù)】和表單字段。以下示例顯示了如何執(zhí)行此操作:

@RequestMapping("/register") public String register(@ModelAttribute("user") UserForm user) {... }

還有一個(gè)例子:

@ModelAttribute 和 @RequestMapping 注解同時(shí)應(yīng)用在方法上時(shí),有以下作用:

  • 方法的【返回值】會(huì)存入到 Model 對(duì)象中,key為 ModelAttribute 的 value 屬性值。
  • 方法的返回值不再是方法的訪問(wèn)路徑,訪問(wèn)路徑會(huì)變?yōu)?@RequestMapping 的 value值,例如:@RequestMapping(value = "/index") 跳轉(zhuǎn)的頁(yè)面是 index.jsp 頁(yè)面。
@Controller public class ModelAttributeController {// @ModelAttribute和@RequestMapping同時(shí)放在方法上@RequestMapping(value = "/index")@ModelAttribute("name")public String model(@RequestParam(required = false) String name) {return name;} }

🍀(5)@SessionAttribute

如果您需要訪問(wèn)全局管理的預(yù)先存在的會(huì)話屬性,并且可能存在或可能不存在,您可以@SessionAttribute在方法參數(shù)上使用注解,如下所示示例顯示:

@RequestMapping("/") public String handle(@SessionAttribute User user) { // ... }

🍀(6)@RequestAttribute

和@SessionAttribute一樣,可以使用@RequestAttribute注解來(lái)訪問(wèn)先前創(chuàng)建的存在與請(qǐng)求中的屬性(例如,由 ServletFilter 或HandlerInterceptor)創(chuàng)建或在請(qǐng)求轉(zhuǎn)發(fā)中添加的數(shù)據(jù):

@GetMapping("/") public String handle(@RequestAttribute Client client) { // ... }

🍀(7)@SessionAttributes

@SessionAttributes注解應(yīng)用到Controller上面,可以將Model中的屬性同步到session當(dāng)中:

@Controller @RequestMapping("/Demo.do") @SessionAttributes(value={"attr1","attr2"}) public class Demo {@RequestMapping(params="method=index")public ModelAndView index() {ModelAndView mav = new ModelAndView("index.jsp");mav.addObject("attr1", "attr1Value");mav.addObject("attr2", "attr2Value");return mav;}@RequestMapping(params="method=index2")public ModelAndView index2(@ModelAttribute("attr1")String attr1, @ModelAttribute("attr2")String attr2) {ModelAndView mav = new ModelAndView("success.jsp");return mav;} }

附加一個(gè)注解使用的案例:

@RequestMapping("insertUser")public String insertUser(@RequestParam(value = "age",required = false) Integer age,@RequestHeader(value = "Content-Type",required = false) String contentType,@RequestHeader(required = false) String name,@CookieValue(value = "company",required = false) String company,@SessionAttribute(value = "username",required = false) String onlineUser,@RequestAttribute(required = false) Integer count,@ModelAttribute("date") Date date,@SessionAttribute(value = "date",required = false) Date sessionDate) {System.out.println("sessionDate = " + sessionDate);System.out.println("date = " + date);System.out.println("count = " + count);System.out.println("onlineUser = " + onlineUser);System.out.println("age = " + age);System.out.println("contentType = " + contentType);System.out.println("name = " + name);System.out.println("company = " + company);return "user";}

🍀(8)數(shù)組的傳遞

在類(lèi)似批量刪除的場(chǎng)景中,我們可能需要傳遞一個(gè)id數(shù)組,此時(shí)我們僅僅需要將方法的參數(shù)指定為數(shù)組即可:

@GetMapping("/array") public String testArray(@RequestParam("array") String[] array) throws Exception {System.out.println(Arrays.toString(array));return "array"; }

我們可以發(fā)送如下請(qǐng)求,可以是多個(gè)名稱(chēng)相同的key,也可以是一個(gè)key,但是值以逗號(hào)分割的參數(shù):

http://localhost:8080/app/hellomvc?array=1,2,3,4

或者

http://localhost:8080/app/hellomvc?array=1&array=3

結(jié)果都是沒(méi)有問(wèn)題的:

🍀(9)復(fù)雜參數(shù)的傳遞

當(dāng)然我們?cè)谶M(jìn)行參數(shù)接收的時(shí)候,其中可能包含很復(fù)雜的參數(shù),一個(gè)請(qǐng)求中可能包含很多項(xiàng)內(nèi)容,比如以下表單:

當(dāng)然我們要注意表單中的name(參數(shù)中key)的寫(xiě)法:

<form action="user/queryParam" method="post">排序字段:<br><input type="text" name="sortField"><hr>數(shù)組:<br><input type="text" name="ids[0]"> <br><input type="text" name="ids[1]"><hr>user對(duì)象:<br><input type="text" name="user.username" placeholder="姓名"><br><input type="text" name="user.password" placeholder="密碼"><hr>list集合<br>第一個(gè)元素:<br><input type="text" name="userList[0].username" placeholder="姓名"><br><input type="text" name="userList[0].password" placeholder="密碼"><br>第二個(gè)元素: <br><input type="text" name="userList[1].username" placeholder="姓名"><br><input type="text" name="userList[1].password" placeholder="密碼"><hr>map集合<br>第一個(gè)元素:<br><input type="text" name="userMap['user1'].username" placeholder="姓名"><br><input type="text" name="userMap['user1'].password" placeholder="密碼"><br>第二個(gè)元素:<br><input type="text" name="userMap['user2'].username" placeholder="姓名"><br><input type="text" name="userMap['user2'].password" placeholder="密碼"><br><input type="submit" value="提交"> </form>

然后我們需要搞一個(gè)實(shí)體類(lèi)用來(lái)接收這個(gè)表單的參數(shù):

@Data public class QueryVo {private String sortField;private User user;private Long[] ids;private List<User> userList;private Map<String, User> userMap; }

編寫(xiě)接口進(jìn)行測(cè)試,我們發(fā)現(xiàn)表單的數(shù)據(jù)已經(jīng)盡數(shù)傳遞了進(jìn)來(lái):

@PostMapping("queryParam") public String queryParam(QueryVo queryVo) {System.out.println(queryVo);return "user"; }

🍀拓展知識(shí)

  • VO(View Object): 視圖對(duì)象,用于展示層,它的作用是把某個(gè)指定頁(yè)面(或組件)的所有數(shù)據(jù)封裝起來(lái)。
  • DTO(Data Transfer Object): 數(shù)據(jù)傳輸對(duì)象,這個(gè)概念來(lái)源于J2EE的設(shè)計(jì)模式,原來(lái)的目的是為了EJB的分布式應(yīng)用提供粗粒度的數(shù)據(jù)實(shí)體,以減少分布式調(diào)用的次數(shù),從而提高分布式調(diào)用的性能和降低網(wǎng)絡(luò)負(fù)載,但在這里,我泛指用于展示層與服務(wù)層之間的數(shù)據(jù)傳輸對(duì)象。
  • DO(Domain Object): 領(lǐng)域?qū)ο?#xff0c;就是從現(xiàn)實(shí)世界中抽象出來(lái)的有形或無(wú)形的業(yè)務(wù)實(shí)體。
  • PO(Persistent Object): 持久化對(duì)象,它跟持久層(通常是關(guān)系型數(shù)據(jù)庫(kù))的數(shù)據(jù)結(jié)構(gòu)形成一一對(duì)應(yīng)的映射關(guān)系,如果持久層是關(guān)系型數(shù)據(jù)庫(kù),那么,數(shù)據(jù)表中的每個(gè)字段(或若干個(gè))就對(duì)應(yīng)PO的一個(gè)(或若干個(gè))屬性。

下面以一個(gè)時(shí)序圖建立簡(jiǎn)單模型來(lái)描述上述對(duì)象在三層架構(gòu)應(yīng)用中的位置:

大致流程如下:

  • 用戶(hù)發(fā)出請(qǐng)求(可能是填寫(xiě)表單),表單的數(shù)據(jù)在展示層被匹配為VO;
  • 展示層把VO轉(zhuǎn)換為服務(wù)層對(duì)應(yīng)方法所要求的DTO,傳送給服務(wù)層;
  • 服務(wù)層首先根據(jù)DTO的數(shù)據(jù)構(gòu)造(或重建)一個(gè)DO,調(diào)用DO的業(yè)務(wù)方法完成具體業(yè)務(wù);
  • 服務(wù)層把DO轉(zhuǎn)換為持久層對(duì)應(yīng)的PO(可以使用ORM工具,也可以不用),調(diào)用持久層的持久化方法,把PO傳遞給它,完成持久化操作;
  • 數(shù)據(jù)傳輸順序:VO => DTO => DO => PO

相對(duì)來(lái)說(shuō)越是靠近顯示層的概念越不穩(wěn)定,復(fù)用度越低。分層的目的,就是復(fù)用和相對(duì)穩(wěn)定性。

小知識(shí): 一般的簡(jiǎn)單工程中,并不會(huì)進(jìn)行這樣的設(shè)計(jì),我們可能有一個(gè)User類(lèi)就可以了,并不需要什么VO、DO啥的。但是,隨著項(xiàng)目工程的復(fù)雜化,簡(jiǎn)單的對(duì)象已經(jīng)沒(méi)有辦法在各個(gè)層的使用,項(xiàng)目越是復(fù)雜,就需要越是復(fù)雜的設(shè)計(jì)方案,這樣才能滿(mǎn)足高擴(kuò)展性和維護(hù)性。

六、設(shè)定字符集

springmvc內(nèi)置了一個(gè)統(tǒng)一的字符集處理過(guò)濾器,我們只要在web.xml中配置即可:

<filter><filter-name>CharacterEncodingFilter</filter-name><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class><init-param><param-name>encoding</param-name><param-value>utf-8</param-value></init-param> </filter> <filter-mapping><filter-name>CharacterEncodingFilter</filter-name><url-pattern>/*</url-pattern> </filter-mapping>

七、返回json數(shù)據(jù)(序列化)

我們經(jīng)常需要使用ajax請(qǐng)求后臺(tái)獲取數(shù)據(jù),而不需要訪問(wèn)任何的頁(yè)面,這種場(chǎng)景在前后分離的項(xiàng)目當(dāng)中尤其重要。

這種做法其實(shí)很簡(jiǎn)單,大致步驟如下:

  • 將我們的對(duì)象轉(zhuǎn)化為json字符串。
  • 將返回的內(nèi)容直接寫(xiě)入響應(yīng)體,不走視圖解析器。
  • 然后將Content-Type設(shè)置為application/json即可。

為了實(shí)現(xiàn)這個(gè)目的,我們可以引入fastjson:

<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.68</version> </dependency> // produces指定了響應(yīng)的Content-Type @RequestMapping(value = "getUsers",produces = {"application/json;charset=utf-8"}) @ResponseBody // 將返回的結(jié)果直接寫(xiě)入響應(yīng)體,不走視圖解析器 public String getUsers(){List<User> users = new ArrayList<User>(){{add(new User("Tom","2222"));add(new User("jerry","333"));}};return JSONArray.toJSONString(users); }

測(cè)試: 成功!

注意:@ResponseBody能將返回的結(jié)果直接放在響應(yīng)體中,不走視圖解析器。

瀏覽器中添加插件json viewer可以有如上顯示。

當(dāng)然springmvc也考慮到了,每次這樣寫(xiě)也其實(shí)挺麻煩,我們還可以向容器注入一個(gè)專(zhuān)門(mén)處理消息轉(zhuǎn)換的bean。

這個(gè)轉(zhuǎn)化器的作用就是:當(dāng)不走視圖解析器時(shí),如果發(fā)現(xiàn)【返回值是一個(gè)對(duì)象】,就會(huì)自動(dòng)將返回值轉(zhuǎn)化為json字符序列:

<mvc:annotation-driven ><mvc:message-converters><bean id="fastjson" class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"><property name="supportedMediaTypes"><list><!-- 這里順序不能反,一定先寫(xiě)text/html,不然ie下會(huì)出現(xiàn)下載提示 --><value>text/html;charset=UTF-8</value><value>application/json;charset=UTF-8</value></list></property></bean></mvc:message-converters> </mvc:annotation-driven>

以后我們的controller就可以寫(xiě)成下邊的樣子了:

@RequestMapping(value = "getUsersList") @ResponseBody public List<User> getUsersList(){return new ArrayList<User>(){{add(new User("邸智偉","2222"));add(new User("劉展鵬","333"));}}; }

當(dāng)然我們還可以使用一個(gè)更加流行的組件jackson來(lái)處理,他的工作和fastjson一致

首先需要引入以下依賴(lài):

<!--jackson--> <dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId> </dependency> <dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId> </dependency> <dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId> </dependency>

我們還可以對(duì)序列化的過(guò)程進(jìn)行額外的一些配置:

public class CustomObjectMapper extends ObjectMapper {public CustomObjectMapper() {super();//去掉默認(rèn)的時(shí)間戳格式configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);//設(shè)置為東八區(qū)setTimeZone(TimeZone.getTimeZone("GMT+8"));//設(shè)置日期轉(zhuǎn)換yyyy-MM-dd HH:mm:sssetDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));// 設(shè)置輸入:禁止把POJO中值為null的字段映射到j(luò)son字符串中configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);// 空值不序列化setSerializationInclusion(JsonInclude.Include.NON_NULL);// 反序列化時(shí),屬性不存在的兼容處理getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);// 序列化枚舉是以toString()來(lái)輸出,默認(rèn)false,即默認(rèn)以name()來(lái)輸出configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true);} }

編寫(xiě)配置文件:

<mvc:annotation-driven><mvc:message-converters><bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"><!-- 自定義Jackson的objectMapper --><property name="objectMapper" ref="customObjectMapper" /><property name="supportedMediaTypes"><list><value>text/plain;charset=UTF-8</value><value>application/json;charset=UTF-8</value></list></property></bean></mvc:message-converters></mvc:annotation-driven> <!--注入我們寫(xiě)的對(duì)jackson的配置的bean--> <bean name="customObjectMapper" class="com.ydlclass.CustomObjectMapper"/>

測(cè)試: 成功!

八、獲取請(qǐng)求中的json數(shù)據(jù)

在前端發(fā)送的數(shù)據(jù)中可能會(huì)如如下情況,Contetn-Type是application/json,請(qǐng)求體中是json格式數(shù)據(jù):

@RequestBody注解可以【直接獲取請(qǐng)求體的數(shù)據(jù)】。

如果我們配置了消息轉(zhuǎn)化器,消息轉(zhuǎn)化器會(huì)將請(qǐng)求體中的json數(shù)據(jù)反序列化成目標(biāo)對(duì)象,如下所示:

@PostMapping("insertUser") public String insertUser(@RequestBody User user) {System.out.println(user);return "user"; }

當(dāng)然,我們可以把消息轉(zhuǎn)化器注解掉,直接使用一個(gè)String來(lái)接收請(qǐng)求體的內(nèi)容。

九、數(shù)據(jù)轉(zhuǎn)化

假如有如下場(chǎng)景,前端傳遞過(guò)來(lái)一個(gè)日期字符串,但是后端需要使用Date類(lèi)型進(jìn)行接收,這時(shí)就需要一個(gè)類(lèi)型轉(zhuǎn)化器進(jìn)行轉(zhuǎn)化。

自定義的類(lèi)型轉(zhuǎn)化器只支持從requestParam獲取的參數(shù)進(jìn)行轉(zhuǎn)化,我們可以定義如下,其實(shí)學(xué)習(xí)spring時(shí)我們已經(jīng)接觸過(guò)這個(gè)Converter接口:

public class StringToDateConverter implements Converter<String, Date> {@Overridepublic Date convert(String source) {SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy_MM_dd hh,mm,ss");try {return simpleDateFormat.parse(source);} catch (ParseException e) {e.printStackTrace();}return null;} }

然后,我們需要在配置文件中進(jìn)行配置:

<!-- 開(kāi)啟mvc的注解 --> <mvc:annotation-driven conversion-service="conversionService" /><bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"><property name="converters"><set><bean id="stringToDateConverter" class="cn.itnanls.convertors.StringToDateConverter"/></set></property> </bean>

對(duì)于時(shí)間類(lèi)型的處理,springmvc給我們提供了一個(gè)比較完善的解決方案,使用注解@DateTimeFormat,同時(shí)配合jackson提供的@JsonFormat注解幾乎可以滿(mǎn)足我們的所有需求。

  • @DateTimeFormat:當(dāng)從requestParam中獲取string參數(shù)并需要轉(zhuǎn)化為Date類(lèi)型時(shí),會(huì)根據(jù)此注解的參數(shù)pattern的格式進(jìn)行轉(zhuǎn)化。
  • @JsonFormat:當(dāng)從請(qǐng)求體中獲取json字符序列,需要反序列化為對(duì)象時(shí),時(shí)間類(lèi)型會(huì)按照這個(gè)注解的屬性?xún)?nèi)容進(jìn)行處理。

這兩個(gè)注解需要加在實(shí)體類(lèi)的對(duì)應(yīng)字段上即可:

// 對(duì)象和json互相轉(zhuǎn)化的過(guò)程當(dāng)中按照此轉(zhuǎn)化方式轉(zhuǎn)哈 @JsonFormat(pattern = "yyyy年MM月dd日",timezone = "GMT-8") // 從requestParam中獲取參數(shù)并且轉(zhuǎn)化 @DateTimeFormat(pattern = "yyyy年MM月dd日") private Date birthday;

處理的過(guò)程大致如下:

十、數(shù)據(jù)校驗(yàn)

  • JSR 303 是 Java 為 Bean 數(shù)據(jù)合法性校驗(yàn)提供的標(biāo)準(zhǔn)框架,它包含在 JavaEE 6.0 中。
  • JSR 303 通過(guò)在 Bean 屬性上標(biāo)注類(lèi)似于 @NotNull、@Max 等標(biāo)準(zhǔn)的注解指定校驗(yàn)規(guī)則,并通過(guò)標(biāo)準(zhǔn)的驗(yàn)證接口對(duì) Bean
    進(jìn)行驗(yàn)證。
Constraint詳細(xì)信息
@Null被注解的元素必須為 null
@NotNull被注解的元素必須不為 null
@AssertTrue被注解的元素必須為 true
@AssertFalse被注解的元素必須為 false
@Min(value)被注解的元素必須是一個(gè)數(shù)字,其值必須大于等于指定的最小值
@Max(value)被注解的元素必須是一個(gè)數(shù)字,其值必須小于等于指定的最大值
@DecimalMin(value)被注解的元素必須是一個(gè)數(shù)字,其值必須大于等于指定的最小值
@DecimalMax(value)被注解的元素必須是一個(gè)數(shù)字,其值必須小于等于指定的最大值
@Size(max, min)被注解的元素的大小必須在指定的范圍內(nèi)
@Digits (integer, fraction)被注解的元素必須是一個(gè)數(shù)字,其值必須在可接受的范圍內(nèi)
@Past被注解的元素必須是一個(gè)過(guò)去的日期
@Future被注解的元素必須是一個(gè)將來(lái)的日期
@Pattern(value)被注解的元素必須符合指定的正則表達(dá)式

🍀Hibernate Validator 擴(kuò)展注解

Hibernate Validator 是 JSR 303 的一個(gè)參考實(shí)現(xiàn),除支持所有標(biāo)準(zhǔn)的校驗(yàn)注解外,它還支持以下的擴(kuò)展注解(Hibernate Validator 附加的 constraint):

Constraint詳細(xì)信息
@Email被注解的元素必須是電子郵箱地址
@Length被注解的字符串的大小必須在指定的范圍內(nèi)
@NotEmpty被注解的字符串的必須非空
@Range被注解的元素必須在合適的范圍內(nèi)

🍀Spring MVC 數(shù)據(jù)校驗(yàn)

Spring MVC 可以對(duì)表單參數(shù)進(jìn)行校驗(yàn),并將結(jié)果保存到對(duì)應(yīng)的【BindingResult】或 【Errors 】對(duì)象中。

要實(shí)現(xiàn)數(shù)據(jù)校驗(yàn),需要引入已下依賴(lài):

<dependency><groupId>javax.validation</groupId><artifactId>validation-api</artifactId><version>2.0.1.Final</version> </dependency> <dependency><groupId>org.hibernate</groupId><artifactId>hibernate-validator</artifactId><version>6.0.9.Final</version> </dependency>

并在實(shí)體類(lèi)加上特定注解:

@Data @AllArgsConstructor @NoArgsConstructor public class UserVO {@NotNull(message = "用戶(hù)名不能為空")private String username;@NotNull(message = "用戶(hù)名不能為空")private String password;@Min(value = 0, message = "年齡不能小于{value}")@Max(value = 120,message = "年齡不能大于{value}")private int age;@JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT-8")@DateTimeFormat(pattern = "yyyy-MM-dd")@Past(message = "生日不能大于今天")private Date birthday;@Pattern(regexp = "^1([358][0-9]|4[579]|66|7[0135678]|9[89])[0-9]{8}$", message = "手機(jī)號(hào)碼不正確")private String phone;@Emailprivate String email; }

在配置文件中配置如下內(nèi)容,增加hibernate校驗(yàn):

<bean id="localValidator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"><property name="providerClass" value="org.hibernate.validator.HibernateValidator"/> </bean> <!--注冊(cè)注解驅(qū)動(dòng)--> <mvc:annotation-driven validator="localValidator"/>

controller使用@Validated標(biāo)識(shí)驗(yàn)證的對(duì)象,緊跟著的BindingResult獲取錯(cuò)誤信息

@PostMapping("insert") public String insert(@Validated UserVO user, BindingResult br) {List<ObjectError> allErrors = br.getAllErrors();Iterator<ObjectError> iterator = allErrors.iterator();// 打印以下錯(cuò)誤結(jié)果while (iterator.hasNext()){ObjectError error = iterator.next();log.error("user數(shù)據(jù)校驗(yàn)錯(cuò)誤:{}",error.getDefaultMessage());}if(allErrors.size() > 0){return "error";}System.out.println(user);return "user"; }

注意: 永遠(yuǎn)不要相信用戶(hù)的輸入,我們開(kāi)發(fā)的系統(tǒng)凡是涉及到用戶(hù)輸入的地方,都要進(jìn)行校驗(yàn),這里的校驗(yàn)分為前臺(tái)校驗(yàn)和后臺(tái)校驗(yàn),前臺(tái)校驗(yàn)通常由javascript來(lái)完成,后臺(tái)校驗(yàn)主要由java來(lái)負(fù)責(zé),這里我們可以通過(guò)spring mvc+hibernate validator完成。

十一、視圖解析器詳解

我們默認(rèn)的視圖解析器是如下的配置,它主要是處理jsp頁(yè)面的映射渲染:

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver"><!-- 前綴 --><property name="prefix" value="/WEB-INF/page/" /><!-- 后綴 --><property name="suffix" value=".jsp" /> </bean>

如果我們想添加新的視圖解析器,則需要給舊的新增一個(gè)order屬性,或者直接刪除原有的視圖解析器:

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver"><!-- 前綴 --><property name="prefix" value="/WEB-INF/page/" /><!-- 后綴 --><property name="suffix" value=".jsp" /><property name="order" value="10"/> </bean>
  • 這里的order表示視圖解析的【優(yōu)先級(jí)】,數(shù)字越小優(yōu)先級(jí)越大(即:0為優(yōu)先級(jí)最高,所以?xún)?yōu)先進(jìn)行處理視圖),InternalResourceViewResolver在項(xiàng)目中的優(yōu)先級(jí)一般要設(shè)置為最低,也就是order要最大。不然它會(huì)影響其他視圖解析器。
  • 當(dāng)處理器返回邏輯視圖時(shí)(也就是return “string”),要經(jīng)過(guò)視圖解析器鏈,如果前面的解析器能處理,就不會(huì)繼續(xù)往下傳播。如果不能處理就要沿著解析器鏈繼續(xù)尋找,直到找到合適的視圖解析器。

如下圖所示:


然后,我們可以配置一個(gè)新的Tymeleaf視圖解析器,order設(shè)置的低一些,這樣兩個(gè)視圖解析器都可以生效:

<!--thymeleaf的視圖解析器--> <bean id="templateResolver"class="org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver"><property name="prefix" value="/WEB-INF/templates/" /><property name="suffix" value=".html" /><property name="templateMode" value="HTML" /><property name="cacheable" value="true" /> </bean> <!--thymeleaf的模板引擎配置--> <bean id="templateEngine"class="org.thymeleaf.spring4.SpringTemplateEngine"><property name="templateResolver" ref="templateResolver" /><property name="enableSpringELCompiler" value="true" /> </bean> <bean id="viewResolver" class="org.thymeleaf.spring4.view.ThymeleafViewResolver"><property name="order" value="1"/><property name="characterEncoding" value="UTF-8"/><property name="templateEngine" ref="templateEngine"/> </bean>

添加兩個(gè)相關(guān)依賴(lài):

<dependency><groupId>org.thymeleaf</groupId><artifactId>thymeleaf</artifactId><version>3.0.14.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf-spring4 --> <dependency><groupId>org.thymeleaf</groupId><artifactId>thymeleaf-spring4</artifactId><version>3.0.14.RELEASE</version> </dependency>

模板中需要添加對(duì)應(yīng)的命名空間:

<html xmlns:th="http://www.thymeleaf.org" >

thymeleaf官網(wǎng)地址:https://www.thymeleaf.org/

十二、全局異常捕獲

🍀(1)HandlerExceptionResolver

在Java中,對(duì)于異常的處理一般有兩種方式:

  • 一種是當(dāng)前方法捕獲處理(try-catch),這種處理方式會(huì)造成業(yè)務(wù)代碼和異常處理代碼的耦合。
  • 另一種是自己不處理,而是拋給調(diào)用者處理(throws),調(diào)用者再拋給它的調(diào)用者,也就是一直向上拋,指導(dǎo)傳遞給瀏覽器。

被異常填充的頁(yè)面是長(zhǎng)這個(gè)樣子的:


在這種方法的基礎(chǔ)上,衍生出了SpringMVC的異常處理機(jī)制。系統(tǒng)的dao、service、controller都通過(guò)throws Exception向上拋出,最后由springmvc前端控制器交由異常處理器進(jìn)行異常處理,如下圖:

小知識(shí): service層盡量不要處理異常,如果自己捕獲并處理了,異常就不生效了。特別是不要生吞異常。


Spring MVC的Controller出現(xiàn)異常的默認(rèn)處理是響應(yīng)一個(gè)500狀態(tài)碼,再把錯(cuò)誤信息顯示在頁(yè)面上,如果用戶(hù)看到這樣的頁(yè)面,一定會(huì)覺(jué)得你這個(gè)網(wǎng)站太LOW了。

要解決Controller的異常問(wèn)題,當(dāng)然也不能在每個(gè)處理請(qǐng)求的方法中加上異常處理,那樣太繁瑣了。

通過(guò)源碼我們得知,需要寫(xiě)一個(gè)HandlerExceptionResolver,并實(shí)現(xiàn)其方法:

public class GlobalExceptionResolver implements HandlerExceptionResolver {@Overridepublic ModelAndView resolveException(HttpServletRequest request,HttpServletResponse response, Object handler, Exception ex) {ModelAndView modelAndView = new ModelAndView();modelAndView.addObject("error", ex.getMessage());modelAndView.setViewName("error");return modelAndView;} } <bean id="globalExecptionResovler" class="com.lagou.exception.GlobalExecptionResovler"></bean> @Component public class GlobalExecptionResovler implements HandlerExceptionResolver {}

小知識(shí): 在web中我們也能對(duì)異常進(jìn)行統(tǒng)一處理:

<!--處理500異常--> <error-page><error-code>500</error-code><location>/500.jsp</location> </error-page> <!--處理404異常--> <error-page><error-code>404</error-code><location>/404.jsp</location> </error-page>

🍀(2)@ControllerAdvice

該注解同樣能實(shí)現(xiàn)異常的全局統(tǒng)一處理,而且實(shí)現(xiàn)起來(lái)更加簡(jiǎn)單優(yōu)雅,當(dāng)然使用這個(gè)注解有一下三個(gè)功能:

  • 處理全局異常
  • 預(yù)設(shè)全局?jǐn)?shù)據(jù)
  • 請(qǐng)求參數(shù)預(yù)處理

我們主要學(xué)習(xí)其中的全局異常處理,@ControllerAdvice 配合 @ExceptionHandler 實(shí)現(xiàn)全局異常處理:

@Slf4j @ControllerAdvice public class GlobalExceptionResolverController {@ExceptionHandler(ArithmeticException.class)public String processArithmeticException(ArithmeticException ex){log.error("發(fā)生了數(shù)學(xué)類(lèi)的異常:",ex);return "error";}@ExceptionHandler(BusinessException.class)public String processBusinessException(BusinessException ex){log.error("發(fā)生了業(yè)務(wù)相關(guān)的異常:",ex);return "error";}@ExceptionHandler(Exception.class)public String processException(Exception ex){log.error("發(fā)生了其他的異常:",ex);return "error";} }

十三、處理資源

當(dāng)我們使用了springmvc后,所有的請(qǐng)求都會(huì)交給springmvc進(jìn)行管理,當(dāng)然也包括靜態(tài)資源,比如/static/js/index.js,這樣的請(qǐng)求如果走了中央處理器,必然會(huì)拋出異常,因?yàn)闆](méi)有與之對(duì)應(yīng)的controller,這樣我們可以使用一下配置進(jìn)行處理:

<mvc:resources mapping="/js/**" location="/static/js/"/> <mvc:resources mapping="/css/**" location="/static/css/"/> <mvc:resources mapping="/img/**" location="/static/img/"/>

十四、攔截器

  • (1)SpringMVC提供的攔截器類(lèi)似于JavaWeb中的過(guò)濾器,只不過(guò)SpringMVC攔截器只攔截被前端控制器攔截的請(qǐng)求,而過(guò)濾器攔截從前端發(fā)送的【任意】請(qǐng)求。
  • (2)熟練掌握SpringMVC攔截器對(duì)于我們開(kāi)發(fā)非常有幫助,在沒(méi)使用權(quán)限框架(shiro,spring security)之前,一般使用攔截器進(jìn)行認(rèn)證和授權(quán)操作。
  • (3)SpringMVC攔截器有許多應(yīng)用場(chǎng)景,比如:登錄認(rèn)證攔截器,字符過(guò)濾攔截器,日志操作攔截器等等。


🍀(1)自定義攔截器

SpringMVC攔截器的實(shí)現(xiàn)一般有兩種方式:

  • (1)自定義的Interceptor類(lèi)要實(shí)現(xiàn)了Spring的HandlerInterceptor接口。
  • (2)繼承實(shí)現(xiàn)了HandlerInterceptor接口的類(lèi),比如Spring已經(jīng)提供的實(shí)現(xiàn)了HandlerInterceptor接口的抽象類(lèi)HandlerInterceptorAdapter。
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {} }

🍀(2)攔截器攔截流程

🍀(3)攔截器規(guī)則

我們可以配置多個(gè)攔截器,每個(gè)攔截器中都有三個(gè)方法。下面將總結(jié)多個(gè)攔截器中的方法執(zhí)行規(guī)律。

  • preHandle: Controller方法處理請(qǐng)求前執(zhí)行,根據(jù)攔截器定義的順序,正向執(zhí)行。
  • postHandle: Controller方法處理請(qǐng)求后執(zhí)行,根據(jù)攔截器定義的順序,逆向執(zhí)行。需要所有的preHandle方法都返回true時(shí)才會(huì)調(diào)用。
  • afterCompletion: View視圖渲染后處理方法:根據(jù)攔截器定義的順序,逆向執(zhí)行。preHandle返回true也會(huì)調(diào)用。

🍀(4)登錄攔截器

接下來(lái)編寫(xiě)一個(gè)登錄攔截器,這個(gè)攔截器可以實(shí)現(xiàn)認(rèn)證操作。就是當(dāng)我們還沒(méi)有登錄的時(shí)候,如果發(fā)送請(qǐng)求訪問(wèn)我們系統(tǒng)資源時(shí),攔截器不放行,請(qǐng)求失敗。只有登錄成功后,攔截器放行,請(qǐng)求成功。登錄攔截器只要在preHandle()方法中編寫(xiě)認(rèn)證邏輯即可,因?yàn)槭窃谡?qǐng)求執(zhí)行前攔截。代碼實(shí)現(xiàn)如下:

/*** 登錄攔截器*/ public class LoginInterceptor implements HandlerInterceptor {/**在執(zhí)行Controller方法前攔截,判斷用戶(hù)是否已經(jīng)登錄,登錄了就放行,還沒(méi)登錄就重定向到登錄頁(yè)面*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {HttpSession session = request.getSession();User user = session.getAttribute("user");if (user == null){//還沒(méi)登錄,重定向到登錄頁(yè)面response.sendRedirect("/toLogin");}else {//已經(jīng)登錄,放行return true;}}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {} }

編寫(xiě)完SpringMVC攔截器,我們還需要在springmvc.xml配置文件中,配置我們編寫(xiě)的攔截器,配置代碼如下:

  • 配置需要攔截的路徑
  • 配置不需要攔截的路徑
  • 配置我們自定義的攔截器類(lèi)
<mvc:interceptors><mvc:interceptor><!--mvc:mapping:攔截的路徑/**:是指所有文件夾及其子孫文件夾/*:是指所有文件夾,但不包含子孫文件夾/:Web項(xiàng)目的根目錄--><mvc:mapping path="/**"/><!--mvc:exclude-mapping:不攔截的路徑,不攔截登錄路徑/toLogin:跳轉(zhuǎn)到登錄頁(yè)面/login:登錄操作--><mvc:exclude-mapping path="/toLogin"/><mvc:exclude-mapping path="/login"/><!--class屬性就是我們自定義的攔截器--><bean id="loginInterceptor" class="com.ydlclass.interceptor.LoginInterceptor"/></mvc:interceptor> </mvc:interceptors>

十五、全局配置類(lèi)

springmvc有一個(gè)可用作用于做全局配置的接口,這個(gè)接口是WebMvcConfigurer,在這個(gè)接口中有很多默認(rèn)方法,每一個(gè)默認(rèn)方法都可以進(jìn)行一項(xiàng)全局配置,這些配置可以和我們配置文件的配置一一對(duì)應(yīng):這些配置在全局的xml中也可以進(jìn)行配置。

🍀列舉幾個(gè)xml的配置

<!--處理靜態(tài)資源--> <mvc:resources mapping="/js/**" location="/static/js/"/> <mvc:resources mapping="/css/**" location="/static/css/"/> <mvc:resources mapping="/./image/**" location="/static/./image/"/><!--配置頁(yè)面跳轉(zhuǎn)--> <mvc:view-controller path="/toGoods" view-name="goods"/> <mvc:view-controller path="/toUpload" view-name="upload"/> <mvc:view-controller path="/websocket" view-name="websocket"/><mvc:cors><mvc:mapping path="/goods/**" allowed-methods="*"/> </mvc:cors>

🍀列舉幾個(gè)常用的WebMvcConfigurer的配置

@Configuration @EnableWebMvc public class MvcConfiguration implements WebMvcConfigurer {// 攔截器進(jìn)行配置@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").excludePathPatterns(List.of("/toLogin","/login")).order(1);}// 資源的配置@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/js/**").addResourceLocations("/static/js/");registry.addResourceHandler("/css/**").addResourceLocations("/static/css/");}// 跨域的全局配置@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/api/**").allowedOrigins("*").allowedMethods("GET","POST","PUT","DELETE").maxAge(3600);}// 頁(yè)面跳轉(zhuǎn)的配置@Overridepublic void addViewControllers(ViewControllerRegistry registry) {registry.addViewController("/index").setViewName("index");}}

后記

👉Java全棧學(xué)習(xí)路線可參考:【Java全棧學(xué)習(xí)路線】最全的Java學(xué)習(xí)路線及知識(shí)清單,Java自學(xué)方向指引,內(nèi)含最全Java全棧學(xué)習(xí)技術(shù)清單~
👉算法刷題路線可參考:算法刷題路線總結(jié)與相關(guān)資料分享,內(nèi)含最詳盡的算法刷題路線指南及相關(guān)資料分享~

總結(jié)

以上是生活随笔為你收集整理的最全面的SpringMVC教程(二)——SpringMVC核心技术篇的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。