javascript
最全面的SpringMVC教程(二)——SpringMVC核心技术篇
前言
本文為 【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。
🍀好處
- 一個(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)型才返回;
params: 指定request中必須包含某些參數(shù)值處理器才會(huì)繼續(xù)執(zhí)行;
headers: 指定request中必須包含某些指定的header值處理器才會(huì)繼續(xù)執(zhí)行。
@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í)如下:
| 全路徑匹配,例如:配置路由/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è)面。
🍀(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)證。
| @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):
| 被注解的元素必須是電子郵箱地址 | |
| @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è)視圖解析器都可以生效:
添加兩個(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。
🍀(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)
十五、全局配置類(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)題。
- 上一篇: 关于已上发布app,升级admob后,激
- 下一篇: JS重点整理之JS原型链彻底搞清楚