本節(jié)課介紹了 ThinkPHP 框架的應(yīng)用范圍,介紹了 ThinkPHP 框架兩個(gè)版本的新特性,并對(duì) ThinkPHP、Laravel、Yii 框架做了簡(jiǎn)單的對(duì)比。對(duì)于剛?cè)腴T的 PHP 程序員來(lái)說(shuō),ThinkPHP 框架是最適合學(xué)習(xí)的一款 MVC 框架,但在學(xué)習(xí)本套教程之前需要掌握 PHP 基礎(chǔ)知識(shí)、Linux 基礎(chǔ)知識(shí)、MySQL 基礎(chǔ)知識(shí)。Tips:想要學(xué)習(xí)更多 ThinkPHP 相關(guān)知識(shí),可以點(diǎn)擊Excel 導(dǎo)入學(xué)生信息Excel 導(dǎo)出學(xué)生信息后臺(tái)處理數(shù)據(jù)
本節(jié)課和大家一起講解了 Spring MVC 的地址請(qǐng)求映射,認(rèn)識(shí)了 @RequestMapping 注解。此注解最主要的功能就是使用一個(gè)邏輯名把內(nèi)部控制器或控制器中的方法暴露給使用者。@RequestMapping 提供了很靈活的映射方案,也提供了相應(yīng)的內(nèi)部方法對(duì)請(qǐng)求 URL 進(jìn)一步過(guò)濾、篩選。記住這個(gè)注解的使用,你便能變著花樣把控制器映射出去。
Spring MVC 內(nèi)部提供了很多優(yōu)雅的異常處理機(jī)制。其中之一就是把不同的異常映射成 HTTP 狀態(tài)碼。如下面實(shí)例:首先定義一個(gè)異常類:@ResponseStatus(value = HttpStatus.FORBIDDEN, reason = "數(shù)字范圍不符合要求!(不能是 20)")public class NumberException extends RuntimeException { }如果在方法中拋出的異常已經(jīng)被映射成了狀態(tài)嗎。則在瀏覽器中顯示出來(lái)的異常信息會(huì)明確很多。@RequestMapping("/exception")public String test(@RequestParam("num") int num) { if (num == 20) { throw new NumberException(); } return "success";}在瀏覽器中輸入 http://localhost:8888/sm-demo/exception?num=13 ??梢钥吹剑憾ㄖ苹男畔⒁呀?jīng)有所改善,但是,還是不夠友好。
打開項(xiàng)目的 pom.xml 文件,在其中添加如下內(nèi)容:<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.1</version></dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version></dependency><!-- 數(shù)據(jù)庫(kù)驅(qū)動(dòng)包 --><dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.21</version> <scope>compile</scope></dependency><dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-dbcp2</artifactId> <version>2.1.1</version></dependency><dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.1.13.RELEASE</version> <scope>compile</scope></dependency>Tips:Spring MVC 連接數(shù)據(jù)庫(kù)時(shí),需要添加 spring-jdbc 依賴包。
打開項(xiàng)目中的 pom.xml 文件,添加項(xiàng)目對(duì) Thymeleaf 包的依賴;<dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf-spring5</artifactId> <version>3.0.11.RELEASE</version></dependency>打開項(xiàng)目中的 WebConfig 配置類。在類中添加一個(gè) ApplicationContext 類型的屬性。并讓 Spring 自動(dòng)注入進(jìn)來(lái);@Autowiredprivate ApplicationContext applicationContext;Tips: Thymeleaf 相關(guān)組件需要依賴上下文容器對(duì)象。在 WebConfig 配置類中配置 Thymeleaf 模板組件信息,并指定模板文件存放位置;@Beanpublic SpringResourceTemplateResolver templateResolver() { SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver(); templateResolver.setApplicationContext(this.applicationContext); templateResolver.setPrefix("/WEB-INF/templates/"); templateResolver.setSuffix(".html"); templateResolver.setTemplateMode(TemplateMode.HTML); templateResolver.setCacheable(true); //templateResolver.setOrder(1); return templateResolver;}Tips: Spring MVC 項(xiàng)目中可以使用多視圖技術(shù)??梢允褂媚0鍖?duì)象的 setOrder ( ) 指定查找順序。本章節(jié)主要講解 Thymeleaf 視圖技術(shù)。所以,請(qǐng)大家注釋掉配置過(guò)的其它視圖技術(shù)的相關(guān)信息。在 WebConfig 配置類中指定模板引擎對(duì)象;先配置 SpringTemplateEngine 組件: 從字面上很好理解,模板引擎組件。@Beanpublic SpringTemplateEngine templateEngine() { SpringTemplateEngine templateEngine = new SpringTemplateEngine(); templateEngine.setTemplateResolver(templateResolver()); templateEngine.setEnableSpringELCompiler(true); return templateEngine;}Tips: 這里有組件與組件的依賴關(guān)系。? 配置 ThymeleafViewResolver 組件: 視圖解析組件,依賴于模板引擎,模板引擎依賴模板資源。@Beanpublic ViewResolver viewResolver(SpringTemplateEngine templateEngine) { ThymeleafViewResolver viewResolver = new ThymeleafViewResolver(); viewResolver.setTemplateEngine(templateEngine); viewResolver.setCharacterEncoding("UTF-8"); return viewResolver;}經(jīng)過(guò)上述配置后,Spring MVC 就具有了對(duì) Thymeleaf 的支持。
本節(jié)將會(huì)引入一個(gè)全新的概念——適配器,這個(gè)名字很形象,和電源適配器的功能類似,從程序設(shè)計(jì)的角度出發(fā),它可以將不同類型、不同結(jié)構(gòu)的數(shù)據(jù)適配到一起。在 Android 中,適配器是 UI 組件和數(shù)據(jù)之間的橋梁,它幫助我們將數(shù)據(jù)填充到 UI 組件當(dāng)中,實(shí)現(xiàn)了一個(gè)典型的 MVC 模式。我們可以分別編寫?yīng)毩⒌?UI 樣式和數(shù)據(jù)模型,至于數(shù)據(jù)如何與 UI 組件綁定都由 Adapter 幫我們完成,這樣的好處就是做到 UI 和數(shù)據(jù)的解耦。Android 系統(tǒng)為我們提供了多種 Adapter,今天就來(lái)介紹幾種常見同場(chǎng)景下 Adapter 的基本用法。
GridView 在 Android App 中運(yùn)用非常廣泛,比如我們手機(jī)的系統(tǒng)相冊(cè)將我們的照片及照片名稱按照網(wǎng)格的樣式排列起來(lái),并且可以上下滾動(dòng),這種效果非常適合用 GridView 實(shí)現(xiàn)。為了實(shí)現(xiàn) MVC 模式,更方便的管理數(shù)據(jù)與 UI,GridView 通過(guò) Adapter 完成數(shù)據(jù)的填充,Adapter 的使用幾乎和 ListView 一樣,另外系統(tǒng)提供了幾種簡(jiǎn)單的 Adapter,具體可以參考 23 節(jié)和 24 節(jié)的內(nèi)容。
無(wú)論是基于原生 Servlet 、還是基于 Spring MVC 開發(fā),中文亂碼總是一件讓人頭痛的事情。當(dāng)用戶在網(wǎng)頁(yè)中輸入中文信息提交給服務(wù)器后,服務(wù)器端解析時(shí)得到可能是一個(gè)亂碼。或者當(dāng)服務(wù)器發(fā)送中文數(shù)據(jù)給客戶端時(shí),瀏覽器中顯示出來(lái)的也可能是亂碼。為什么會(huì)產(chǎn)生中文亂碼?服務(wù)器端在解析客戶端數(shù)據(jù)時(shí),默認(rèn)情況下使用的并不是中文編碼。同理,在服務(wù)器端把數(shù)據(jù)放入響應(yīng)包中時(shí),使用的也不是中文編碼。知道事情原因后,就好辦了,只需要在服務(wù)器端解析數(shù)據(jù)或向數(shù)據(jù)包中寫入數(shù)據(jù)時(shí)選擇正確的中文編碼就可以了。在登錄頁(yè)面輸入用戶名和密碼時(shí),如下圖所示輸入中文:在控制器中解析數(shù)據(jù)時(shí),得到的是亂碼,既然是亂碼,后面的邏輯想都不用想,肯定是通不過(guò)的。瀏覽器封裝數(shù)據(jù)到請(qǐng)求包時(shí),使用的是支持中文的如 UTF-8 編碼。但是,在服務(wù)器解碼數(shù)據(jù)時(shí),使用的并不是 UTF-8 編碼。編碼與解碼的前后不一致,導(dǎo)致中文亂碼的出現(xiàn)。知道原因后,問(wèn)題就不再是問(wèn)題。無(wú)論 Spring MVC 顯得多么高級(jí),但解析數(shù)據(jù)還是由 HttpServletRequest 對(duì)象完成的,最直接的解決方案就是讓 Spring 注入原生的 HttpServletRequest 對(duì)象,再重設(shè)解析數(shù)據(jù)的編碼即可。當(dāng)然,為了保證中文數(shù)據(jù)能正確的寫入響應(yīng)包,也需要注入 HttpServletResponse 。@RequestMapping(value="/login") public String login01(HttpServletRequest request,HttpServletResponse response) throws UnsupportedEncodingException { request.setCharacterEncoding("utf-8"); response.setContentType("text/html;charset=utf-8"); String userName=request.getParameter("userName"); String userPassword=request.getParameter("userPassword"); if("慕課".equals(userName) && "123".equals(userPassword)) { return "index"; } return "fail"; }}再次在瀏覽器中發(fā)起中文登錄請(qǐng)求,控制器能正確解析中文,驗(yàn)證通過(guò),跳轉(zhuǎn)到 “index” 視圖。問(wèn)題是得到了解決,但有種共產(chǎn)主義社會(huì)又回到原始社會(huì)的感覺(jué),Spring MVC 的優(yōu)勢(shì)蕩然無(wú)存。這種方案只能說(shuō)是一種方案,而不能說(shuō)是一種企業(yè)級(jí)的快速解決方案。
下面,使用純 JAVA 方式為 Spring MVC 的初次運(yùn)行提供必備的配置信息 。先認(rèn)識(shí)一個(gè)組件,做好思想準(zhǔn)備,這個(gè)組件名字很長(zhǎng),別嚇到你了:AbstractAnnotationConfigDispatcherServletInitializer。名字雖然有點(diǎn)長(zhǎng),但是很體貼,怕你不知道它的作用,名字本身就告訴了你:基于注解的初始化 DispatcherServlet 的抽象組件,簡(jiǎn)單說(shuō)就是一個(gè) WEB 初始化組件,用來(lái)替代 web.xml 的功能。該動(dòng)手了!構(gòu)建一個(gè)繼承了AbstractAnnotationConfigDispatcherServletInitializer 的類:package com.mk.config;import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer{ @Override protected Class<?>[] getRootConfigClasses() { return null; } @Override protected Class<?>[] getServletConfigClasses() { return null; } @Override protected String[] getServletMappings() { return null; }}嘿!居然有 3 個(gè)方法強(qiáng)制性要求實(shí)現(xiàn),那么,這 3 個(gè)方法分別是干嘛用的?WebInitializer 這個(gè)類會(huì)自動(dòng)創(chuàng)建 Spring MVC 的 DispatcherServlet 核心組件,并且創(chuàng)建 2 個(gè) WebApplication 上下文對(duì)象。Spring 不能白學(xué),是吧,工廠再厲害,你也得告訴它你要?jiǎng)?chuàng)建什么吧。大家還記得這些信息你原來(lái)是放在哪里的嗎?想起來(lái)了吧,你是放在 XML 文件中的,本文則是放在 JAVA 類中。所以這 3 個(gè)方法中有 2 個(gè)方法是用來(lái)指定這 2 個(gè)上下文對(duì)象分別對(duì)應(yīng)的配置類是誰(shuí)。知道原因了,立馬準(zhǔn)備好 2 個(gè)配置類。//web 上下文對(duì)象的配置類@Configuration@ComponentScan(basePackages = { "com.mk.web" }, excludeFilters = { @Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class) })public class RootConfig {}// Root 上下文對(duì)象的配置類@Configuration@EnableWebMvc@ComponentScan(basePackages = { "com.mk.web.action" })public class WebConfig {}這里有 3 個(gè)注解需要說(shuō)明 一下:@Configuration 注解:說(shuō)明這個(gè)類是配置類;Tips: 配置類用來(lái)替代 XML 配置文件。@EnableWebMvc 注解: 很好理解,啟動(dòng) Spring MVC 相關(guān)功能;@ComponentScan 注解: 指定上下文對(duì)象各自負(fù)責(zé)的組件所在包。Tips: Root 配置類中需要排除 Web 配置類負(fù)責(zé)的區(qū)域。在重新完善 WebInitializer 類之前,說(shuō)明一下,另一個(gè)方法是用來(lái)指定前端控制器可以響應(yīng)的請(qǐng)求地址。OK!讓我們?cè)賮?lái)看看這 3 個(gè)方法本來(lái)應(yīng)該的樣子:@Overrideprotected Class<?>[] getRootConfigClasses() { return new Class[] { RootConfig.class};}@Overrideprotected Class<?>[] getServletConfigClasses() {return new Class[] { WebConfig.class};}//接受所有請(qǐng)求@Overrideprotected String[] getServletMappings() { return new String[] {"/"};}
數(shù)據(jù)驗(yàn)證是對(duì)數(shù)據(jù)進(jìn)行邏輯處理之前需要進(jìn)行的一個(gè)很重要環(huán)節(jié),如果不合要求的數(shù)據(jù)進(jìn)入邏輯處理環(huán)節(jié)后,會(huì)導(dǎo)致程序的崩潰。WEB 應(yīng)用程序中,驗(yàn)證用戶提交的表單數(shù)據(jù)的合法性既可以在客戶端實(shí)現(xiàn),也可以在服務(wù)器端實(shí)現(xiàn)??蛻舳蓑?yàn)證: 表單提交之前,通過(guò)客戶端的 JS 腳本對(duì)表單中預(yù)提交的數(shù)據(jù)進(jìn)行驗(yàn)證;服務(wù)器端驗(yàn)證: 數(shù)據(jù)提交到服務(wù)器后,由服務(wù)器端的的代碼進(jìn)行驗(yàn)證。對(duì)于非敏感性的數(shù)據(jù)可以在客戶端驗(yàn)證,如一些常規(guī)性的輸入格式要求驗(yàn)證、類似于密碼長(zhǎng)度要多少、信息不能為空等。服務(wù)器端相比較于客戶端驗(yàn)證,會(huì)多出網(wǎng)絡(luò)流量消耗。但是,對(duì)于一些敏感性的數(shù)據(jù),比如身份證信息、銀行卡信息…… 還是需要交給服務(wù)器的。Spring MVC 提供的有驗(yàn)證框架,能夠在綁定請(qǐng)求包中的數(shù)據(jù)時(shí),按開發(fā)者提出的格式驗(yàn)證規(guī)則對(duì)數(shù)據(jù)進(jìn)行驗(yàn)證。Spring MVC 驗(yàn)證框架的使用非常便利,只需要幾個(gè)注解就能輕松解決問(wèn)題。使用之前,有幾個(gè)概念先要交代清楚。
如下面的實(shí)例:@RequestMapping("/response02")public String response02(ModelMap model) throws IOException { //發(fā)送給客戶端的響應(yīng)數(shù)據(jù) String hello="Hello"; model.addAttribute("data", hello); return "hello";}上面代碼是一個(gè)典型的 Spring MVC 控制器代碼:變量 hello 中保存的就是要發(fā)送給瀏覽器的數(shù)據(jù)。ModelMap 類型的數(shù)據(jù)模型可以說(shuō)是一個(gè)中間載體,用來(lái)臨時(shí)保存 hello 中的數(shù)據(jù);return 后面的 “hello” 是視圖的邏輯名,由視圖解析器解析并找到真正的頁(yè)面。下面是一個(gè)標(biāo)準(zhǔn)的視圖模板,頁(yè)面中已經(jīng)提供了樣式,只等數(shù)據(jù)的到來(lái)。 <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <div style="color:red"> ${data} </div> </body> </html>有了這些信息后,視圖解析器把數(shù)據(jù)和視圖合二為一后進(jìn)行渲染,得到純 HTML 后寫入響應(yīng)包,發(fā)送給瀏覽器。這便于轉(zhuǎn)發(fā)或者叫派發(fā)。默認(rèn)情況下,控制器的響應(yīng)方式使用的是轉(zhuǎn)發(fā)。轉(zhuǎn)發(fā)是在一次請(qǐng)求中一氣呵成完成的。如上代碼,在瀏覽器輸入請(qǐng)求:http://localhost:8888/sm-demo/response02。服務(wù)端響應(yīng)包中的數(shù)據(jù)這就是響應(yīng)給客戶端的最終數(shù)據(jù)。Tips: Spring MVC 的整個(gè)請(qǐng)求和響應(yīng)過(guò)程是由多個(gè)組件協(xié)作完成的,這里不深究細(xì)節(jié)。
上一章節(jié)提到了 Spring MVC 的幾大核心組件,并對(duì)前端控制器、用戶控制器、映射器 3 大組件做了較全方面的講解,相信大家一定對(duì)它們有了更理性的認(rèn)識(shí)。本節(jié)課繼續(xù)講解適配器、視圖解析器組件。通過(guò)本節(jié)課,你將了解到:適配器的功能及配置;視圖解析器的基本功能及配置;組件之間是如何協(xié)作完成用戶的請(qǐng)求的。這個(gè)是本章節(jié)的重點(diǎn),也是對(duì)各組件的歸納和總結(jié)。
Spring Security 的核心特性包括:認(rèn)證和授權(quán)、常規(guī)攻擊防范、與 Servlet 接口集成、與 Spring MVC 集成等。認(rèn)證和授權(quán)的目的是,讓系統(tǒng)知道使用者是誰(shuí)(認(rèn)證)?是什么樣的身份?允許他做什么?禁止他做什么?通常的做法是要求用戶輸入自己的用戶名和密碼,來(lái)實(shí)現(xiàn)登錄和鑒權(quán)的過(guò)程。常規(guī)攻擊防范在 Spring Security 安全框架中是默認(rèn)開啟的,常見的威脅抵御方式有:防止偽造跨站請(qǐng)求(CSRF)安全響應(yīng)頭(HTTP Response headers)HTTP 通訊安全作為 Spring 大家族的一員,Spring Security 在與 Spring 引用,尤其是與 Spring boot 應(yīng)用的結(jié)合時(shí),顯得極為便利。Spring Security 三大功能
application.xml 是 Spring 上下文容器所關(guān)聯(lián)的配置文件,可稱其為全局上下文對(duì)象的關(guān)聯(lián)配置文件。<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config></context:annotation-config> <context:component-scan base-package="com.mk.web"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" /> </context:component-scan></beans>元素解釋說(shuō)明:context:annotation-config: 啟動(dòng)注解支持,建議需要全局上下文對(duì)象創(chuàng)建、維護(hù)的對(duì)象使用注解的方式;context:component-scan: 掃描位置,需要排除開 Spring MVC 容器對(duì)象掃描的位置。Tips: application.xml 文件中一般用來(lái)配置業(yè)務(wù)層邏輯組件、數(shù)據(jù)層邏輯組件、通用組件、第三方組件相關(guān)的信息。
OOP 代碼中經(jīng)常會(huì)出現(xiàn)類似于 A 對(duì)象引用 B 對(duì)象,B 對(duì)象引用 C 對(duì)象的現(xiàn)象。 類似于現(xiàn)實(shí)生活中的小王有一輛汽車,汽車有一把鑰匙……如果每一個(gè)用戶都有一輛汽車,用 OOP 描述,意味著 User 類中有一個(gè)對(duì) Car 的引用類型屬性。public class User { private String userName; private String userPassword; private Car car; //……}假設(shè) Car 類結(jié)構(gòu)如下:public class Car {private String carType;private String carColor;//……}在注冊(cè)時(shí),除了要輸入用戶信息之外,還需要指定用戶所擁有的汽車類型、顏色。那么,控制器是否能自動(dòng)綁定用戶以及汽車數(shù)據(jù)?Tips: 為什么注冊(cè)時(shí)要輸入汽車信息,不要糾結(jié),只是一個(gè)用來(lái)說(shuō)明問(wèn)題的例子。答案是肯定的。只需要在表單頁(yè)面中添加如下代碼,控制器端不做任何修改。如此,除了能接收用戶數(shù)據(jù)外,還能接收汽車的信息。<form action="user/register" method="post"> 用戶名:<input type="text" value="" name="userName" /> <br /> 密碼:<input type="password" value="" name="userPassword" /> <br /> 汽車類型:<input type="text" value="" name="car.carType" /> <br /> 汽車顏色:<input type="text" value="" name="car.carColor" /> <br /> <input type="submit" value="注冊(cè)" name="btnRegister" /> <input type="reset" value="重置" name="btnReset" /></form>也就是說(shuō),Spring MVC 支持對(duì)象級(jí)聯(lián)自動(dòng)數(shù)據(jù)綁定。Spring MVC 支持多層級(jí)的對(duì)象級(jí)聯(lián)。
在控制器的方法中聲明 InputStream 作為入?yún)ⅲ琒pring MVC 就能注入你想要的 InputStream 對(duì)象。@RequestMapping(value = "/testApi04",method = RequestMethod.POST)public void hello(InputStream inputStream) throws IOException { byte[] buff=new byte[128]; int read= inputStream.read(buff); System.out.println(new String(buff,0,read));}上面的實(shí)例,能讀取到請(qǐng)求包中的數(shù)據(jù),但過(guò)于低級(jí),可讀性并不是很好。Tips: 控制器方法的映射機(jī)制有只接受 POST 方法的限制,如果是 GET 方法的請(qǐng)求包,直接使用 InputStream 對(duì)象無(wú)法獲取到請(qǐng)求包中的數(shù)據(jù)。GET 方法的請(qǐng)求數(shù)據(jù)是附加在 URL 上的,InputStream 只能讀取實(shí)體部分的數(shù)據(jù)。
本節(jié)課程和大家一起講解了如何在控制器中把 OOP 數(shù)據(jù)轉(zhuǎn)換成 JSON 格式后傳遞給瀏覽器。開發(fā)者可以自己編碼把對(duì)象數(shù)據(jù)轉(zhuǎn)換成特定格式的字符串后響應(yīng)給瀏覽器,但是,這個(gè)過(guò)程很勞心勞力。使用 Spring MVC 框架提供的 JSON 序列化轉(zhuǎn)換組件,開發(fā)者不用關(guān)心底層具體轉(zhuǎn)換過(guò)程便能達(dá)到目的??蚣軒烷_發(fā)者解決了很多共性的問(wèn)題,讓開發(fā)者可以有更多時(shí)間關(guān)注自己的核心邏輯。記?。ResponseBody 注解很重要。
2.2.1 驗(yàn)證前的準(zhǔn)備打開項(xiàng)目中的 pom.xml 文件,添加 validation-api 依賴包,大家需要注意一下,包名是以 javax 開頭的。因?yàn)?Spring MVC 并沒(méi)有實(shí)現(xiàn) JSR 接口規(guī)范,這里選擇 hibernate-validator ;Tips: hibernate-validator 是 Hibernate 提供的 JSR 具體實(shí)現(xiàn)模塊。<dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>2.0.1.Final</version></dependency><dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>6.1.0.Final</version></dependency>打開 WebConfig 配置類,通知 Spring MVC 創(chuàng)建 LocalValidatorFactoryBean 對(duì)象。可以使用這個(gè)工廠對(duì)象創(chuàng)建具體的實(shí)現(xiàn)了 JSR 規(guī)范的驗(yàn)證器。@Beanpublic LocalValidatorFactoryBean validator() { LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean(); localValidatorFactoryBean.setProviderClass(HibernateValidator.class); return localValidatorFactoryBean;}2.2.2 JSR 驗(yàn)證流程先設(shè)定一個(gè)需求:添加老師信息。構(gòu)建一個(gè) Teacher 類,并在 Teacher 類的相關(guān)屬性上添加對(duì)應(yīng)注解;public class Teacher {@NotNull(message = "姓名不能為空")private String name; @Min(value = 22,message = "年齡不能小于 22 歲")private Integer age;Tips: JSR 注解有一個(gè) message 屬性,用來(lái)保存錯(cuò)誤提示信息。編寫 teacher.html 頁(yè)面;<form action="teacher/save" method="post"> 老師姓名:<input type="text" value="" name="name"/> <br/> 老師年齡:<input type="text" value="" name="age"/> <br/> <input type="submit" value="添加" name="btnSave"/> <input type="reset" value="重置" name="btnReset"/></form>編寫響應(yīng)控制器;@Controller@RequestMapping("/teacher")public class TeacherAction { @RequestMapping(value = "/save",method = RequestMethod.POST) public String register(@Valid Teacher teacher,BindingResult result) { if (result.hasErrors()) { return "fail"; } return "success"; }}Tips: @Valid 注解表示在綁定數(shù)據(jù)之后對(duì)數(shù)據(jù)進(jìn)行驗(yàn)證。BindingResult 組件用來(lái)保存驗(yàn)證過(guò)程中的錯(cuò)誤信息。除了可以使用 BindingResult ,此處還可以使用 Errors 替代。在瀏覽器中訪問(wèn)到 teacher.html 頁(yè)面,輸入不符合規(guī)則的數(shù)據(jù)后提交(年齡小于 22 歲);最后會(huì)在瀏覽器中看到。如此,整個(gè)驗(yàn)證過(guò)程完畢。
模型是一個(gè)用的很廣的概念。本課程中數(shù)據(jù)模型應(yīng)該有 2 層含義:WEB 程序中封裝數(shù)據(jù)的組件;此模型對(duì)象會(huì)有自己特定作用域。上一小節(jié)中,向大家介紹了 Map 模型對(duì)象,其本質(zhì)是對(duì) HttpServletRequest 對(duì)象中的存儲(chǔ)功能的引用。Map 還是比較原始和天然的。Spring MVC 為了展現(xiàn)自己的特色服務(wù),對(duì) Map 進(jìn)行了多層面的封裝,提供了更豐富的數(shù)據(jù)模型對(duì)象。Tips: 重要的事情強(qiáng)調(diào)一下,你將認(rèn)識(shí)或使用到的數(shù)據(jù)模型會(huì)很多,但其本質(zhì)是:如果是請(qǐng)求作用域,你只是在間接使用 HttpServletRequest 對(duì)象;如果是會(huì)話作用域,你只是在間接使用 HttpSession 對(duì)象。
通過(guò)對(duì)幾大核心組件的介紹,相信大家對(duì)它們各自的功能有所了解。但是,你知道它們之間是如何協(xié)作一起完成用戶的一次請(qǐng)求的嗎?當(dāng)用戶在瀏覽器發(fā)出請(qǐng)求的那一刻起,這些組件就緊密地團(tuán)結(jié)在一起,為用戶的請(qǐng)求保駕護(hù)航。如上圖所述,簡(jiǎn)要描述一下它們是如何協(xié)調(diào)一致完成工作的:瀏覽器的請(qǐng)求到達(dá)前端控制器(DispatcherServlet);前端控制器解析出請(qǐng)求路徑后詢問(wèn)映射器,咱們是否提供的有用戶需要的用戶控制器,映射器把查詢結(jié)果返回給前端控制器;適配器的作用就是統(tǒng)一不同類型的用戶控制器(也體現(xiàn)了 Spring MVC 中用戶控制器的多樣性和靈活性);用戶控制器開始工作(具體的響應(yīng)邏輯);用戶控制器返回視圖邏輯名和視圖中所需要的數(shù)據(jù)(ModelAndView);前端控制器詢問(wèn)視圖解析器,你能夠根據(jù)邏輯名找到物理視圖嗎?視圖解析器開始工作并找到物理視圖;前端控制器渲染物理視圖和數(shù)據(jù),生成瀏覽器能夠識(shí)別的數(shù)據(jù)格式;響應(yīng)瀏覽器,并在瀏覽器中顯示最終請(qǐng)求結(jié)果。Spring MVC 中,用戶的每一次請(qǐng)求都是眾多組件通力合作完成的,它們是相親相愛的一家人。
本章節(jié)和大家一起講解 Spring MVC 框架如何實(shí)現(xiàn)轉(zhuǎn)發(fā)和重定向,從實(shí)現(xiàn)角度來(lái)講,沒(méi)有太多難度。默認(rèn)情況下就是轉(zhuǎn)發(fā),加上 redirect 關(guān)鍵字后就能實(shí)現(xiàn)重定向。需要注意的是,redirect 后面需要指定視圖文件的完整路徑。轉(zhuǎn)發(fā)和重定向都是一種響應(yīng)方式,其內(nèi)在區(qū)別就在于一個(gè)是尋找視圖的方式是不同的。一個(gè)是控制器幫著找,一個(gè)是瀏覽器通過(guò)再次發(fā)起請(qǐng)求尋找,所以,重定向和轉(zhuǎn)發(fā)對(duì)數(shù)據(jù)模型的作用域要求是不同的。
以字節(jié)數(shù)組的方式接收上傳的文件數(shù)據(jù),過(guò)于原始、低級(jí),很難獲取到文件的元數(shù)據(jù)。Spring MVC 提供有 MultipartFile 接口。查看 MultipartFile 接口源代碼,可以知道 MultipartFile 接口提供了很多方法,能解析出上傳文件的更多元數(shù)據(jù),包括文件名、文件大小等,方便開發(fā)者更靈活地處理數(shù)據(jù)。public interface MultipartFile extends InputStreamSource { String getName(); @Nullable String getOriginalFilename(); @Nullable String getContentType(); boolean isEmpty(); long getSize(); byte[] getBytes() throws IOException; @Override InputStream getInputStream() throws IOException; default Resource getResource() { return new MultipartFileResource(this); } void transferTo(File dest) throws IOException, IllegalStateException; default void transferTo(Path dest) throws IOException, IllegalStateException { FileCopyUtils.copy(getInputStream(), Files.newOutputStream(dest)); }}MultipartFile 最常用的是 transferTo 方法,用來(lái)把上傳文件存儲(chǔ)到指定位置。重構(gòu)上面的控制器代碼。 @RequestMapping("/upload") public String upload(@RequestPart("upFile") MultipartFile file) throws IOException { String path = System.getProperty("webapp.root"); String filePath = path + "\\upload\\"+file.getOriginalFilename(); System.out.println(filePath); file.transferTo(new File(filePath)); return "success"; }如上面一樣測(cè)試文件上傳,結(jié)果沒(méi)有什么不一樣。
在控制器的方法中注入 OutputStream 對(duì)象,只需要在方法中添加參數(shù)聲明。如下實(shí)例:可使用 OutputStream 對(duì)象讀取指定文件中的內(nèi)容后直接響應(yīng)給瀏覽器。@RequestMapping(value = "/testApi05")public void hello(OutputStream outputStream) throws IOException { Resource res = new ClassPathResource("/test.txt"); FileCopyUtils.copy(res.getInputStream(), outputStream);}test.txt 文件的內(nèi)容是”this is a test’。文件直接放在項(xiàng)目的 src/main/java 目錄下。在瀏覽器中輸入請(qǐng)求路徑 http://localhost:8888/sm-demo/testApi05 。你將在瀏覽器中看到:有句話叫做 “條條道路通羅馬”,用在 Spring MVC 中真的是合適,依靠 Spring 強(qiáng)大的注入功能,只要原生開發(fā)中能有的對(duì)象基本上都能注入進(jìn)去。
Spring MVC 還可以使用注解的方式集中處理異常。@ExceptionHandler 注解:此注解所標(biāo)注的方法能夠處理同一個(gè)控制器中所有方法所拋出的異常。如下面實(shí)例:@ExceptionHandler(Exception.class)public String exception() { return "exception";}@RequestMapping("/exception04")public String exception04(String userName) throws Exception { if (StringUtils.isEmpty(userName)) { throw new Exception("用戶名不能為空"); } return "index";}當(dāng)控制器中的方法拋出異常后,會(huì)由 exception() 方法統(tǒng)一捕獲,然后跳到指定頁(yè)面。@ControllerAdvice 注解:此注解放在類的前面,且類中可以包含一個(gè)或多個(gè)如下類型的方法。Tips: 由 @ControllerAdvice 注解標(biāo)注的類本質(zhì)上就是一個(gè)基于 AOP 思想的攔截器。@ExceptionHandler 注解標(biāo)注的方法;@InitBinder 注解標(biāo)注的方法;@ModelAttribute 注解標(biāo)注的方法。@ControllerAdvice 實(shí)現(xiàn)案例如下:@ControllerAdvicepublic class ExceptionAdviceAction { @ExceptionHandler(Exception.class) public String exception() { return "exception"; }}無(wú)論哪一個(gè)控制器中拋出異常,都會(huì)由 ExceptionAdviceAction 類中的 exception() 方法統(tǒng)一響應(yīng)處理。