整合營(yíng)銷(xiāo)服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢(xún)熱線(xiàn):

          SpringMVC文件上傳下載(單文件、多文件)

          SpringMVC文件上傳下載(單文件、多文件)

          大家好,我是bigsai,今天我們學(xué)習(xí)SpringMVC的文件上傳下載。

          文件上傳和下載是互聯(lián)網(wǎng)web應(yīng)用非常重要的組成部分,它是信息交互傳輸?shù)闹匾乐弧D憧赡芙?jīng)常在網(wǎng)頁(yè)上傳下載文件,你可能也曾沉浸于互聯(lián)網(wǎng)技術(shù)的神秘,而本篇就為你解開(kāi)它神秘的面紗。

          您的關(guān)注、點(diǎn)贊、轉(zhuǎn)發(fā),是我創(chuàng)作努力源源不斷的動(dòng)力!

          本文為筆者原創(chuàng),已收錄在公眾號(hào):bigsai 頭條:碼農(nóng)bigsai 和博學(xué)谷中 同時(shí)同步,歡迎關(guān)注!

          案例分析

          你肯定會(huì)問(wèn):通過(guò)本篇可能能夠?qū)W到什么?

          那我很負(fù)責(zé)任的告訴你,通過(guò)本篇文章,你能夠掌握SpringMVC文件上傳(單文件、多文件)文件下載知識(shí)和內(nèi)容的使用,并能夠根據(jù)這些實(shí)現(xiàn)一些基本的案例。

          核心思路拆解

          你可能會(huì)問(wèn):,這么一個(gè)完整的項(xiàng)目是如何分工運(yùn)行?

          不急不急,我來(lái)告訴你,其實(shí)這么一個(gè)文件上傳下載的項(xiàng)目,它是一個(gè)b-s結(jié)構(gòu)的web項(xiàng)目,涉及到前端和服務(wù)端,從宏觀來(lái)看它是這樣的一個(gè)結(jié)構(gòu):

          但是從文件上傳、下載兩個(gè)功能來(lái)看它們之間又是有所區(qū)別的,文件上傳的主要核心是用戶(hù)上傳的文件服務(wù)端接收存儲(chǔ)

          而文件下載更重要的部分是用戶(hù)請(qǐng)求之后服務(wù)端給用戶(hù)返回二進(jìn)制文件

          所以文件上傳和文件下載的項(xiàng)目大體結(jié)構(gòu)相似,只是各個(gè)部分在具體實(shí)現(xiàn)上有差別,我們需要更多關(guān)注下文件上傳和下載服務(wù)端的實(shí)現(xiàn)和區(qū)別。

          案例所涉及知識(shí)點(diǎn)

          在本案例中,用到了以下知識(shí)點(diǎn):

          html頁(yè)面form表單:

          在前端無(wú)論是html還是jsp等模板引擎編寫(xiě)上傳的頁(yè)面時(shí)候。<form> 標(biāo)簽就意為一個(gè)(文件)上傳的表單。

          • 表單能夠包含若干 input 標(biāo)簽,而input標(biāo)簽又有不同類(lèi)型比如文本字段、復(fù)選框、單選框、文件等等。
          • 我們通常使用表單編寫(xiě)若干標(biāo)簽代表我們想要向服務(wù)端發(fā)送的數(shù)據(jù),然后通過(guò)標(biāo)簽的按鈕將數(shù)據(jù)請(qǐng)求提交至服務(wù)端。
          • 表單的method表示請(qǐng)求的類(lèi)型(一般為post),action表示需要請(qǐng)求的url地址,enctype表示傳輸數(shù)據(jù)類(lèi)型。

          SpringMVC:

          案例的文件上傳和下載基于SpringMVC,而我們?cè)赟pringboot項(xiàng)目中整合SpringMVC。

          • 本案例使用SpringMVC作為項(xiàng)目mvc架構(gòu)的框架,將模型(Model),視圖(View),控制器(Controller)分離降低項(xiàng)目的耦合性。
          • 本案例使用SpringMVC的MultipartFile接口和ResponseEntity接口實(shí)現(xiàn)文件上傳和下載。

          創(chuàng)建SpringMVC項(xiàng)目

          SpringMVC為一個(gè)mvc架構(gòu)的web框架,創(chuàng)建SpringMVC項(xiàng)目的方式有很多,你可以選擇直接通過(guò)IDEA創(chuàng)建SpringMVC項(xiàng)目,也可以通過(guò)Maven方式創(chuàng)建web項(xiàng)目然后添加SpringMVC的依賴(lài),但這兩種方式有太多的配置還需要配置tomcat,在效果一致的情況下咱們盡量簡(jiǎn)化一些開(kāi)發(fā)配置類(lèi)的工作,所以不采用以上兩種方式創(chuàng)建項(xiàng)目。

          而Springboot簡(jiǎn)化了Spring項(xiàng)目的開(kāi)發(fā),開(kāi)箱即用,且內(nèi)嵌tomcat,所以咱們選擇創(chuàng)建基于Springboot且整合SpringMVC的項(xiàng)目方便快捷,更能直奔主題進(jìn)行操作。

          項(xiàng)目創(chuàng)建

          首先,打開(kāi)IDEA,創(chuàng)建項(xiàng)目,選擇Spring Initializr類(lèi)型初始化點(diǎn)擊next。


          然后你會(huì)得到一個(gè)選擇項(xiàng)目名和一些配置的頁(yè)面,我們?cè)贕roup中填寫(xiě)com,而Artifact咱們填寫(xiě)fileupload。點(diǎn)擊next。


          接著在選擇對(duì)應(yīng)模塊依賴(lài)的時(shí)候,選擇Spring web 模塊,此模塊就是包含SpringMVC的web模塊


          接著選擇需要?jiǎng)?chuàng)建項(xiàng)目的地址目錄,點(diǎn)擊next

          這樣你就可以得到一個(gè)完整的包含web模塊(SpringMVC)的Springboot項(xiàng)目,就可以在里面編寫(xiě)咱們項(xiàng)目的代碼。

          目錄介紹

          上面創(chuàng)建完的基于Springboot的SpringMVC項(xiàng)目,默認(rèn)有若干文件和文件夾,不同文件和文件夾有著不同的職責(zé):

          • java:用來(lái)編寫(xiě)java服務(wù)端相關(guān)代碼,例如Controller,Dao,Service等。
          • application.properties: 編寫(xiě)一些項(xiàng)目和框架的配置內(nèi)容以及和第三方框架整合配置等
          • static: 靜態(tài)資源目錄,用來(lái)存放html、JavaScript、圖片等資源。
          • teamplates:用來(lái)編寫(xiě)Thymeleaf等模板引擎,這里不使用
          • pom.xml:編寫(xiě)maven項(xiàng)目jar包資源依賴(lài)。如果項(xiàng)目需要引入其他依賴(lài)或者修改打包方式可以進(jìn)行修改。

          對(duì)于web項(xiàng)目的文件上傳,需要進(jìn)行一定配置以滿(mǎn)足我們的使用需求,我們?cè)赼pplication.propertis進(jìn)行以下配置:

          # 允許項(xiàng)目中文件上傳
          spring.servlet.multipart.enabled=true
          # 上傳文件的臨時(shí)目錄 (一般情況下不用特意修改)
          #spring.servlet.multipart.location=# 上傳文件最大為 1M (默認(rèn)值 1M 根據(jù)自身業(yè)務(wù)自行控制即可)
          spring.servlet.multipart.max-file-size=104857600
          # 上傳請(qǐng)求最大為 10M(默認(rèn)值10M 根據(jù)自身業(yè)務(wù)自行控制即可)
          spring.servlet.multipart.max-request-size=104857600
          # 文件大小閾值,當(dāng)大于這個(gè)閾值時(shí)將寫(xiě)入到磁盤(pán),否則存在內(nèi)存中,(默認(rèn)值0 一般情況下不用特意修改)
          spring.servlet.multipart.file-size-threshold=0
          # 判斷是否要延遲解析文件(相當(dāng)于懶加載,一般情況下不用特意修改)
          spring.servlet.multipart.resolve-lazily=false

          當(dāng)然,你對(duì)文件有大小等其他要求可以對(duì)配置進(jìn)行自行更改。到這里帶有SpringMVC環(huán)境的項(xiàng)目已經(jīng)創(chuàng)建完成啦,剩下的只需要編寫(xiě)前端、服務(wù)端代碼運(yùn)行測(cè)試即可。

          單文件上傳

          下面請(qǐng)跟我實(shí)戰(zhàn) SpringMVC單文件上傳。一個(gè)完整的文件上傳項(xiàng)目有兩部分組成:前端界面和服務(wù)端程序。

          前端設(shè)計(jì)

          對(duì)于前端頁(yè)面,我們使用你一定熟悉的html而不選用其他模板引擎。而form表單是html文件上傳的核心組件,你在使用前需要了解它的一些屬性。

          表單的enctype屬性上面說(shuō)了一個(gè)表單文件傳輸?shù)拇篌w流程,你也知道表單有個(gè)至關(guān)重要的屬性:enctype。而entype值通常有以下三種:

          • application/x-www-form-urlencoded:默認(rèn)編碼方式,在發(fā)送前編碼所有字符(默認(rèn))使用url編碼方式,和get請(qǐng)求有些相似。但這種方式如果發(fā)送大量二進(jìn)制數(shù)據(jù)效率會(huì)比較低。
          • multipart/form-data:不對(duì)字符編碼。在使用包含文件上傳控件的表單時(shí),必須使用該值。通常用來(lái)向服務(wù)端發(fā)送二進(jìn)制數(shù)據(jù),而我們的文件也主要以二進(jìn)制的方式進(jìn)行傳輸。
          • text/plain:空格轉(zhuǎn)換為 "+" 加號(hào),但不對(duì)特殊字符編碼。

          所以本單文件上傳案例中,需要注意以下事項(xiàng):

          • 表單的enctype要為multipart/form-data類(lèi)型,表示二進(jìn)制傳輸。
          • 在一個(gè)form表單內(nèi)定義一個(gè)input為file屬性的標(biāo)簽,代表文件上傳。
          • form表單的method需要為post。
          • enctype要為multipart/form-data類(lèi)型,表示二進(jìn)制傳輸。

          前端頁(yè)面的規(guī)則了解之后你在static下創(chuàng)建一個(gè)index1.html文件,里面具體的代碼內(nèi)容為:

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <title>單文件上傳</title>
          </head>
          <body>
          <h2>單文件上傳</h2>
          <form  action="onfile" method="post" enctype='multipart/form-data'>
              <input type="file" name="file" ><br>
              <input type="submit" value="提交">
          </form>
          </body>
          </html>

          其中action="onfile"代表的為請(qǐng)求地址為onfile,這里都在項(xiàng)目?jī)?nèi)所以用相對(duì)地址即可,如果上傳為其他接口也可填寫(xiě)對(duì)應(yīng)的絕對(duì)地址。這樣前端頁(yè)面就編寫(xiě)完成,我們還需要編寫(xiě)文件上傳對(duì)應(yīng)服務(wù)端模塊。

          服務(wù)端設(shè)計(jì)

          服務(wù)端主要負(fù)責(zé)文件接受,在前端看起來(lái)實(shí)現(xiàn)文件上傳的頁(yè)面很簡(jiǎn)單,但實(shí)際上在服務(wù)端的文件接收并沒(méi)有那么容易,因?yàn)閭鬟^(guò)來(lái)的不光光是這一個(gè)(或多個(gè))二進(jìn)制文件,還附帶一些頭信息、文件名等等數(shù)據(jù)。打包過(guò)來(lái)的數(shù)據(jù)如果是文本數(shù)據(jù)解析可能還好,但是二進(jìn)制文件數(shù)據(jù)一旦出現(xiàn)一點(diǎn)錯(cuò)誤可能得到的整個(gè)文件都是損壞的。并且在咱們java web技術(shù)棧中文件上傳也是有一定發(fā)展的歷史的:

          servlet文件上傳(3.0以前)在servlet3.0以前,文件上傳在服務(wù)端接收需要使用request.getInputStream()獲取表單的二進(jìn)制數(shù)據(jù),但是在解析時(shí)候非常麻煩和復(fù)雜,對(duì)于文件上傳這么一個(gè)很基本的模塊在接收的時(shí)候可能要耗費(fèi)很大的成本和精力去解決它,并且很多初級(jí)攻城獅很可能由于對(duì)io模塊陌生無(wú)法實(shí)現(xiàn)上傳文件在服務(wù)端的接收。

          所以這個(gè)時(shí)候一些具有責(zé)任感的公司、組織就把它們的解析方法貢獻(xiàn)出來(lái)供大家使用,大家不需了解傳輸文件底層內(nèi)容,這些開(kāi)源的處理方式中,最流行的當(dāng)屬apache旗下開(kāi)源的commons-fileupload和 commons-io,把兩個(gè)jar包加入到項(xiàng)目中你直接了解下這個(gè)api如何使用即可。有了這兩個(gè)jar包,簡(jiǎn)單學(xué)習(xí)它的api,你就可以在普通的web項(xiàng)目中很容易的實(shí)現(xiàn)上傳文件的功能!


          servlet3.0以后
          隨著servlet版本更新,設(shè)計(jì)者可能看到j(luò)avaweb開(kāi)發(fā)中原生api對(duì)文件上傳支持不太友好的問(wèn)題,所以在api對(duì)文件上傳的支持得到優(yōu)化,簡(jiǎn)化了Java Web的開(kāi)發(fā)。在servlet3.0中主要增加Part這個(gè)類(lèi)用來(lái)讀取文件數(shù)據(jù)和信息,在Part中直接將傳輸文件的名稱(chēng)、頭信息、二進(jìn)制文件分割開(kāi),通過(guò)簡(jiǎn)單的api就可以實(shí)現(xiàn)文件上傳的功能。不需要再添加外部jar包


          SpringMVC文件上傳文件上傳和下載是web開(kāi)發(fā)常用模塊,而SpringMVC作為一款優(yōu)秀的web框架,對(duì)很多模塊和內(nèi)容進(jìn)行更高度的封裝和集成,而這么常用的文件上傳肯定是少不了的,所以SpringMVC的文件上傳基于apache旗下開(kāi)源的commons-fileupload和 commons-io包。將其進(jìn)行二次集成和封裝至SpringMVC,將方法和內(nèi)容封裝至MultipartFile接口讓我們使用起來(lái)更加方便,能夠容易實(shí)現(xiàn)單文件、多文件上傳。

          對(duì)于上述各種文件上傳服務(wù)端實(shí)現(xiàn)方式,大致可以通過(guò)下圖展示:


          通過(guò)上圖你就可明白SpringMVC文件上傳實(shí)現(xiàn)的原理,那么下面你就可以進(jìn)行大顯身手啦!SpringMVC處理上傳文件很簡(jiǎn)單,我們需要在java目錄下創(chuàng)建一個(gè)uploadController.java創(chuàng)建這么一個(gè)控制器,在上面加上@Controller注解。在Controller中編寫(xiě)以下代碼:


           @PostMapping("onfile")
           @ResponseBody
           public String onfile(MultipartFile file) throws IOException {
               File file1 =new File("F:/fileupload/"+file.getOriginalFilename());//創(chuàng)建file對(duì)象
               if(!file1.exists())
                      file1.createNewFile();//在磁盤(pán)創(chuàng)建該文件
               file.transferTo(file1);//將接受的文件存儲(chǔ)
               return "sucucess";
           }

          其中:

          • @PostMapping("onfile") 的意思為該請(qǐng)求方式為post,且請(qǐng)求的url在項(xiàng)目中的相對(duì)地址為onfile
          • @ResponseBody指不返回web頁(yè)面,而是返回字符串或json字符串,在這里我們直接用一個(gè)成功單詞代表跳轉(zhuǎn)后的界面。
          • public String onfile(MultipartFile file) 函數(shù)名不重復(fù)就行,而MultipartFile file就是SpringMVC封裝的一個(gè)處理文件的接口,其中參數(shù)名(這里是file)要和前端界面文件名相同(input type="file",name="file"中的name),通過(guò)這個(gè)接口你可以更容易的對(duì)文件進(jìn)行各種操作,而本案例就是將上傳的文件保存到本地F盤(pán)。

          對(duì)于函數(shù)中的幾行核心代碼各司其職,除了注釋的解釋外,大致的流程可以參考如下圖:

          運(yùn)行測(cè)試

          這樣啟動(dòng)項(xiàng)目,在瀏覽器輸入http://localhost:8080/index1.html,選擇文件上傳,點(diǎn)擊上傳之后就可以在本地看到上傳的文件啦。


          至此,單文件上傳就完成啦,單文件上傳前端需要注意的就是form表單的method類(lèi)型以及 enctype參數(shù),而服務(wù)端也只需要用MultipartFile 接口就可以很容易的對(duì)文件進(jìn)行接受。


          第四關(guān) 多文件上傳

          上面講的是單文件上傳,很多時(shí)候你可能遇到的需求不光光是單文件上傳。就比如你一定熟悉這個(gè)頁(yè)面:

          如上你可以看到,這么一次文件上傳不止一個(gè)圖片,并且數(shù)量也不確定,但都屬于同一名稱(chēng)和集合的內(nèi)容。這就是多文件上傳。對(duì)于這種情況無(wú)論在前端還是服務(wù)端也是很容易處理的。

          前端設(shè)計(jì)

          我們這里實(shí)現(xiàn)一個(gè)多張圖片的上傳,首先在static目錄下創(chuàng)建一個(gè)index2.html的頁(yè)面。里面的具體內(nèi)容為:

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <title>多文件上傳</title>
          </head>
          <body>
          <h2>同一類(lèi)別多個(gè)文件上傳</h2>
          <form name="onfile"  action="onfiles2" method="post" enctype="multipart/form-data">
              圖片:
              <input type="file" name="img"><br>
              <input type="file" name="img"><br>
              <input type="file" name="img"><br>
              <input type="file" name="img"><br>
              <input type="submit" value="提交">
          </form>
          </body>
          </html>

          這樣前端頁(yè)面就編寫(xiě)完成,其中action要改為onfiles2,也就是待會(huì)要在服務(wù)端編寫(xiě)的接口。還有注意的這些input 所有type為file代指類(lèi)型為文件,而name均為img意思是上傳一組名稱(chēng)為img圖片的集合。

          服務(wù)端設(shè)計(jì)

          而在我們服務(wù)端,其實(shí)用MultipartFile[]數(shù)組就可以對(duì)這樣的多文件進(jìn)行接收,我們?cè)赾ontroller中編寫(xiě)以下代碼:

          @PostMapping("onfiles2")
          @ResponseBody
          public String onfiles2(MultipartFile img[]) throws IOException {
             for(int i=0;i<img.length;i++)
             {
                 if(!img[i].isEmpty())//文件不空
                 {
                     File imgfile =new File("F:/fileupload/"+img[i].getOriginalFilename());
                     imgfile.createNewFile();
                     img[i].transferTo(imgfile);
                     logger.info(img[i].getOriginalFilename());
                 }
             }
              return "sucucess";
          }

          這個(gè)處理方式和前面的很相似,只不過(guò)是需要遍歷MultipartFile[]對(duì)每個(gè)文件進(jìn)行接收處理,當(dāng)然文件為空的時(shí)候不進(jìn)行處理。

          運(yùn)行測(cè)試

          這樣打開(kāi)瀏覽器輸入:http://localhost:8080/index2.html,上傳文件測(cè)試效果:

          這樣一組類(lèi)似相冊(cè)上傳的功能就完成啦,當(dāng)然實(shí)際開(kāi)發(fā)中的文件上傳的要求肯定比這個(gè)要求嚴(yán)格很多,可能對(duì)文件的格式、大小都有一定的要求,這就要求你在前端和服務(wù)端都要對(duì)文件的后綴名、大小等信息進(jìn)行校驗(yàn),以達(dá)到自己場(chǎng)景化的需求。

          文件下載

          文件下載估計(jì)你在日常生活中會(huì)經(jīng)常遇到,而你下載的其實(shí)就是服務(wù)端(服務(wù)器)的資源,對(duì)于文件類(lèi)型有多種多樣的,瀏覽器也能夠識(shí)別很多種資源,事實(shí)上你現(xiàn)在訪(fǎng)問(wèn)的這個(gè)網(wǎng)頁(yè)也是服務(wù)端的html文件、圖片文件等資源,只不過(guò)這些資源瀏覽器能夠顯示而不會(huì)保存到本地。

          直接訪(fǎng)問(wèn)資源VS下載資源

          如果直接訪(fǎng)問(wèn)的資源是瀏覽器所不能識(shí)別解析的,例如doc、zip等類(lèi)型文件,那訪(fǎng)問(wèn)的時(shí)候會(huì)默認(rèn)下載到本地。而當(dāng)你在SpringMVC中使用下載功能時(shí),無(wú)論是什么資源都以下載的形式返回給客戶(hù)端。這種區(qū)別可以參考下圖:

          在文件下載方面的實(shí)現(xiàn),servlet本身也是實(shí)現(xiàn)文件下載的,不過(guò)使用起來(lái)有點(diǎn)繁瑣。其原理就是往HttpServletResponse response的輸出流寫(xiě)字節(jié)內(nèi)容。而我們SpringMVC對(duì)文件下載也做了封裝,將下載功能封裝至ResponseEntity類(lèi)中,我們?cè)谑褂玫臅r(shí)候也很方便。下面就來(lái)實(shí)戰(zhàn)文件下載的功能。

          首先,我們?cè)贔盤(pán)建立download文件夾,在里面添加對(duì)應(yīng)文件,這個(gè)文件夾我們作為服務(wù)端的資源。

          前端設(shè)計(jì)

          我們?cè)趧?chuàng)建一個(gè)文件下載的前端頁(yè)面,在static目錄下創(chuàng)建index3.html,頁(yè)面的具體內(nèi)容為:

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <title>SpringMVC文件下載</title>
          </head>
          <body>
          <h2>SpringMVC文件下載</h2>
          個(gè)人照片<a href="/download/個(gè)人照片.png">個(gè)人照片.png</a><br>
          個(gè)人簡(jiǎn)歷<a href="/download/個(gè)人簡(jiǎn)歷.pdf">個(gè)人簡(jiǎn)歷.pdf</a>
          </body>
          </html>

          其中href是下載的超鏈接,download是下載的接口名,而鏈接最后面部分則是下載資源名稱(chēng)。

          服務(wù)端設(shè)計(jì)

          文件下載的原理就是服務(wù)端向客戶(hù)端返回二進(jìn)制流和信息,而SpringMVC通過(guò)ResponseEntity完成。我們?cè)赾ontroller中編寫(xiě)以下接口實(shí)現(xiàn)下載的功能:

          @GetMapping("download/{filename}")
          public ResponseEntity<byte[]>download(@PathVariable String filename) throws IOException {
              //下載文件的路徑(這里絕對(duì)路徑)
              String filepath= "F:/download/"+filename;
              File file =new File(filepath);
              //創(chuàng)建字節(jié)輸入流,這里不實(shí)用Buffer類(lèi)
              InputStream in = new FileInputStream(file);
              //available:獲取輸入流所讀取的文件的最大字節(jié)數(shù)
              byte[] body = new byte[in.available()];
              //把字節(jié)讀取到數(shù)組中
              in.read(body);
              //設(shè)置請(qǐng)求頭
              MultiValueMap<String, String> headers = new HttpHeaders();
              headers.add("Content-Disposition", "attchement;filename=" + file.getName());
              //設(shè)置響應(yīng)狀態(tài)
              HttpStatus statusCode = HttpStatus.OK;
              in.close();
              ResponseEntity<byte[]> entity = new ResponseEntity<byte[]>(body, headers, statusCode);
              return entity;//返回
          }

          這樣就是實(shí)現(xiàn)了文件下載功能,如果用傳統(tǒng)servlet的方式下載文件可能需要在HttpServletResponse response中設(shè)置各種信息,而使用SpringMVC的ResponseEntity只需要將文件二進(jìn)制主體、頭信息以及狀態(tài)碼設(shè)置好即可進(jìn)行文件下載,在易用性和簡(jiǎn)潔上更勝一籌。

          運(yùn)行測(cè)試

          打開(kāi)瀏覽器輸入:http://localhost:8080/index3.html;點(diǎn)擊需要下載的文件,就實(shí)現(xiàn)了文件下載的功能,運(yùn)行情況圖如下:

          此時(shí)你就遇到了一個(gè)文件下載非常常見(jiàn)的問(wèn)題:中文文件名錯(cuò)誤顯示。這個(gè)解決方案也很容易解決,只需將Content-Disposition內(nèi)容后面的文件名進(jìn)行url編碼即可,具體代碼為(替換上面對(duì)于部分):

          headers.add("Content-Disposition", "attchement;filename=" + URLEncoder.encode(file.getName(), "UTF-8"));

          這樣重啟程序,刷新頁(yè)面再次點(diǎn)擊下載的鏈接,你就會(huì)發(fā)現(xiàn)文件被成功的下載了:

          總結(jié)與拓展

          至此,SpringMVC的單文件上傳、多文件上傳以及文件下載你已經(jīng)全部掌握了,是不是滿(mǎn)滿(mǎn)的成就感想去實(shí)現(xiàn)一個(gè)自己的小網(wǎng)站并把相關(guān)內(nèi)容放進(jìn)去?不過(guò)SpringMVC文件上傳下載雖然簡(jiǎn)單,但你依然需要掌握其原理,學(xué)好java中的io文件傳輸,這樣在各種場(chǎng)景的文件傳輸任務(wù)中方能勝任。

          總結(jié)

          前面所講文件上傳,前端就是form表單用<input type="file">表示客戶(hù)端要上傳文件,而服務(wù)端主要使用MultipartFile或者M(jìn)ultipartFile[]分別接收單個(gè)文件和多個(gè)文件。而在存儲(chǔ)到本地也僅僅需要在本地磁盤(pán)創(chuàng)建對(duì)應(yīng)文件然后MultipartFile調(diào)用transferTo()方法即可將上傳的文件儲(chǔ)存。

          而文件下載的前端需要一個(gè)請(qǐng)求的url鏈接,服務(wù)端需要編寫(xiě)這個(gè)鏈接對(duì)應(yīng)的接口。通過(guò)一些名稱(chēng)找到文件在本地真實(shí)的位置通過(guò)ResponseEntity即可將二進(jìn)制文件返回給客戶(hù)達(dá)到文件下載的功能。而ResponseEntity使用也很簡(jiǎn)單在創(chuàng)建時(shí)候只需要傳入二進(jìn)制主體、頭和狀態(tài)碼即可成功返回,而這些SpringMVC已進(jìn)行了很好封裝你可以直接使用。

          而無(wú)論是文件上傳、多文件上傳還是文件下載,一個(gè)完整的案例大致都需要這樣一個(gè)過(guò)程:

          • 構(gòu)思需求和頁(yè)面大體樣式
          • 編寫(xiě)前端html頁(yè)面
          • 編寫(xiě)服務(wù)端響應(yīng)的請(qǐng)求
          • 啟動(dòng)程序運(yùn)行測(cè)試

          在其中過(guò)程如果有問(wèn)題可以根據(jù)編譯器的錯(cuò)誤提示、運(yùn)行時(shí)的錯(cuò)誤日志找到根源進(jìn)行修正,這樣完整的案例就可以成功完成啦!

          案例拓展

          你是否覺(jué)得自己掌握的可以了?那好,咱們拓展提升一下,我給你來(lái)一個(gè)需求:單文件和多文件混合上傳

          假設(shè)小明需要實(shí)現(xiàn)一個(gè)文件上傳功能,小明需要上傳一份簡(jiǎn)歷和若干份照片(小于3)。這個(gè)項(xiàng)目該如何設(shè)計(jì)呢?它的計(jì)劃頁(yè)面可能是這樣的:

          我覺(jué)得聰明的你一定不會(huì)被難住,對(duì)于前端界面的html有什么想法呢?

          對(duì)于種類(lèi)來(lái)說(shuō)有簡(jiǎn)歷和照片兩種文件,對(duì)于它們各自來(lái)說(shuō),簡(jiǎn)歷只有一份,而照片可能有多份。

          那么咱們的html頁(yè)面可以這樣設(shè)計(jì):

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <title>個(gè)人信息上傳</title>
          </head>
          <body>
          <h2>個(gè)人信息上傳</h2>
          <form name="onfile"  action="infoupload" method="post" enctype="multipart/form-data">
              姓名:<input type="text" name="name" ><br>
              年齡:<input type="text" name="age"> <br>
              圖片:<input type="file" name="img">
                   <input type="file" name="img">
              簡(jiǎn)歷:<input type="file" name="resume"><br>
              <input type="submit" value="提交">
          </form>
          </body>
          </html>

          這里面和前面的單文件上傳不同的是有多個(gè)input標(biāo)簽,此外action也要改成infoupload意思是你需要寫(xiě)這么一個(gè)接口來(lái)處理這個(gè)文件上傳的內(nèi)容。在controller中編寫(xiě)以下代碼:

           private  static Logger logger= LoggerFactory.getLogger(uploadController.class);
              @PostMapping("infoupload")
              @ResponseBody
              public String onfile(String name,String age, MultipartFile img[],MultipartFile resume) throws IOException {
                  logger.info(name);//日志中打印傳輸?shù)膎ame
                  logger.info(age);
                  //接收img[]
                  for(int i=0;i<img.length;i++)
                  {
                      if(!img[i].isEmpty())//文件不空
                      {
                          File imgfile =new File("F:/fileupload/"+img[i].getOriginalFilename());
                          imgfile.createNewFile();
                          img[i].transferTo(imgfile);
                      }
                  }
                   //接收resume
                  File resumefile =new File("F:/fileupload/"+resume.getOriginalFilename());
                  //在磁盤(pán)中創(chuàng)建文件,此時(shí)文件存在但沒(méi)有內(nèi)容
                  resumefile.createNewFile();
                  //將接受的文件復(fù)制到創(chuàng)建的文件中
                  resume.transferTo(resumefile);
                  return "sucucess";
              }

          這個(gè)理解起來(lái)其實(shí)也很容易,這個(gè)和上面主要的區(qū)別就是函數(shù)中的多參數(shù),其實(shí)每一個(gè)參數(shù)都是要和前端頁(yè)面的form表單input標(biāo)簽的內(nèi)容對(duì)應(yīng)(名稱(chēng)一致)。form表單中的file類(lèi)型在SpringMVC的controller中就是對(duì)應(yīng)MultipartFile類(lèi)型,form表單中的text類(lèi)型對(duì)應(yīng)controller中的String類(lèi)型。如果上傳單個(gè)文件,在服務(wù)端就用MultipartFile類(lèi)型參數(shù)接收,如果多文件就用MultipartFile[]進(jìn)行接收。上傳類(lèi)型和個(gè)數(shù)根據(jù)你自己的需求設(shè)計(jì)定義。

          我們啟動(dòng)程序打開(kāi)瀏覽器輸入http://localhost:8080/index4.html選擇文件進(jìn)行上傳,然后在本地你可以看到文件成功被保存。

          至此,本篇的內(nèi)容就結(jié)束了,本文主要簡(jiǎn)單講解了SpringMVC中文件上傳、多文件上傳、文件下載的實(shí)現(xiàn),現(xiàn)在你已熟練掌握。青山不改,綠水長(zhǎng)流,我們下期再見(jiàn)!下課!

          公眾號(hào):bigsai

          頭條號(hào):碼農(nóng)bigsai 一個(gè)等你關(guān)注而朝思夜暮的博主!如果有幫助,記得轉(zhuǎn)發(fā)分享!

          什么是rest?什么是restful?

          我相信很多人區(qū)分不開(kāi)來(lái),Rest的英文全稱(chēng)為Representational State Transfer,即表述性狀態(tài)轉(zhuǎn)移,就是將資源的狀態(tài)以最適合客戶(hù)端或服務(wù)端的形式從服務(wù)器端轉(zhuǎn)移到客戶(hù)端(或者反過(guò)來(lái))。Rest是一種軟件架構(gòu)風(fēng)格而不是標(biāo)準(zhǔn),提供了設(shè)計(jì)原則和約束,基于這個(gè)風(fēng)格設(shè)計(jì)的軟件可以更簡(jiǎn)潔,更有層次,更易于實(shí)現(xiàn)緩存等機(jī)制。而Restful是Rest的一種實(shí)現(xiàn),是基于Rest風(fēng)格構(gòu)建的API。

          Restful API優(yōu)點(diǎn)

          1.Rest面向資源,一目了然,具有自解釋性。

          2.Restful接口直接基于HTTP協(xié)議,輕量,不在需要任何別的諸如消息協(xié)議。

          3.數(shù)據(jù)格式默認(rèn)以Json為主體,數(shù)據(jù)簡(jiǎn)單易讀,前端不用單獨(dú)解析,可以直接使用。

          4.Rest是無(wú)狀態(tài)的,無(wú)狀態(tài)約束使服務(wù)器的變化對(duì)客戶(hù)端是不可見(jiàn)的,因?yàn)樵趦纱芜B續(xù)的請(qǐng)求中,客戶(hù)端并不依賴(lài)于同一臺(tái)服務(wù)器。就是說(shuō)Service端是不用保存Client端的狀態(tài)信息,比如登陸信息等。Client發(fā)送的請(qǐng)求必須包含有能夠讓服務(wù)器理解請(qǐng)求的全部信息,包括自己的狀態(tài)信息。這點(diǎn)在分布式系統(tǒng)上顯的比較重要。

          5.不用理會(huì)客戶(hù)端的類(lèi)型,不論你是Web端請(qǐng)求,還是移動(dòng)端請(qǐng)求,對(duì)后臺(tái)接口而言,我只需要返回相應(yīng)約定好格式的數(shù)據(jù)即可。

          Restful中的概念重要

          1、資源(Resources)

          REST的名稱(chēng)"表現(xiàn)層狀態(tài)轉(zhuǎn)化"中,省略了主語(yǔ)。"表現(xiàn)層"其實(shí)指的是"資源"(Resources)的"表現(xiàn)層"。資源是一種看待服務(wù)器的方式,即,將服務(wù)器看作是由很多離散的資源組成。每個(gè)資源是服務(wù)器上一個(gè)可命名的抽象概念,它可以是一段文本、一張圖片、一首歌曲、一種服務(wù)。資源是以名詞為核心來(lái)組織的,首先關(guān)注的是名詞。一個(gè)資源可以由一個(gè)或多個(gè)URI(統(tǒng)一資源定位符)來(lái)標(biāo)識(shí),URI既是資源的名稱(chēng),也是資源在Web上的地址。

          所謂"上網(wǎng)",就是與互聯(lián)網(wǎng)上一系列的"資源"互動(dòng),調(diào)用它的URI。嚴(yán)格地說(shuō),有些網(wǎng)址最后的".html"后綴名是不必要的,因?yàn)檫@個(gè)后綴名表示格式,屬于"表現(xiàn)層"范疇,而URI應(yīng)該只代表"資源"的位置。

          2、表現(xiàn)層(Representation)

          我們把"資源"具體呈現(xiàn)出來(lái)的形式,叫做它的Representation,是一段對(duì)于資源在某個(gè)特定時(shí)刻的狀態(tài)的描述。可以有多種格式,例如HTML/XML/JSON/純文本/二進(jìn)制格式/圖片/視頻/音頻等等。它的具體表現(xiàn)形式,應(yīng)該在HTTP請(qǐng)求的頭信息中用Accept和Content-Type字段指定,這兩個(gè)字段是對(duì)Representation的描述。

          3、狀態(tài)轉(zhuǎn)移(State Transfer)

          訪(fǎng)問(wèn)一個(gè)網(wǎng)站,就代表了客戶(hù)端和服務(wù)器的一個(gè)互動(dòng)過(guò)程——在客戶(hù)端和服務(wù)器端之間轉(zhuǎn)移(transfer)代表資源狀態(tài)的表述。而互聯(lián)網(wǎng)通信協(xié)議HTTP協(xié)議,是一個(gè)無(wú)狀態(tài)協(xié)議,所以所有的狀態(tài)都需保存在服務(wù)器端。

          SpringMVC實(shí)現(xiàn)Restful API

          了解了Restful之后,對(duì)于一個(gè)Java程序員,特別是主攻后端的Java程序員,如何編寫(xiě)一個(gè)標(biāo)準(zhǔn)的Restful API才是關(guān)鍵。

          在Rest中定位資源是通過(guò)URL進(jìn)行識(shí)別的;而Rest中的行為是通過(guò)HTTP方法來(lái)定義的,所以我們還需要了解一下HTTP方法的定義:

          POST:創(chuàng)建資源

          READ:獲取資源

          PUT/PATCH:更新資源

          DELETE:刪除資源

          注意:PUT和PATCH的區(qū)別在與PUT是對(duì)整個(gè)對(duì)象進(jìn)行更新,而PATCH是局部更新,是非冪等的,PATCH在Spring3.2以上版本支持;POST具有非冪等的特點(diǎn),所以一般情況下用作創(chuàng)建資源,當(dāng)然還可以進(jìn)行其他的操作。

          1.創(chuàng)建一個(gè)基本的Restful API

          下面這個(gè)例子就是最簡(jiǎn)單的一個(gè)控制器,通過(guò)在UserController類(lèi)上添加@Controller注解表明該類(lèi)是控制器。而@RequestMapping注解則用來(lái)處理請(qǐng)求地址映射,可以同時(shí)用在類(lèi)或者方法上。@RequestMapping注解常用的兩個(gè)屬性就是method和value,method代表接受的HTTP請(qǐng)求方法;value代表URL路徑的一部分。最后返回的時(shí)候需要加上@ResponseBody注解,該注解的作用是將Controller的方法返回的對(duì)象通過(guò)適當(dāng)?shù)霓D(zhuǎn)換器轉(zhuǎn)換為指定的格式,并寫(xiě)入返回對(duì)象的body區(qū),通常用來(lái)返回JSON數(shù)據(jù)或者是XML數(shù)據(jù)。

          @Controller

          public class UserController {

          @Autowired

          private UserSerivce userSerivce;

          @RequestMapping(method=RequestMethod.GET, value="/users")

          @ResponseBody

          public List<User> getUsers() {

          return userSerivce.getUsers();

          }

          }

          在Spring4.0中提供了很多新的注解,其中@RestController注解就是其中之一。從下面的源碼可以看到,@RestController其實(shí)就是集合了@Controller和@ResponseBody注解。這樣我們就不用在每一個(gè)API上都使用@ResponseBody注解了。

          @Target(ElementType.TYPE)

          @Retention(RetentionPolicy.RUNTIME)

          @Documented

          @Controller

          @ResponseBody

          public @interface RestController {

          @AliasFor(annotation=Controller.class)

          String value() default "";

          }

          當(dāng)然一個(gè)Restful API更有可能我們需要提供查詢(xún)的參數(shù),所以我們就需要引入@PathVariable注解。該注解可以將URL中占位符參數(shù)綁定到控制器處理方法的入?yún)⒅校篣RL中的{xxx}占位符可以通過(guò)@PathVariable("xxx") 綁定到操作方法的入?yún)⒅小J褂梅绞饺缦滤荆?/p>

          // 訪(fǎng)問(wèn)方式 GET http://localhost:8080/user/1

          @RequestMapping(method=RequestMethod.GET, value="/user/{id}")

          public User getUser(@PathVariable("id") Integer id) {

          return userSerivce.getUser(id);

          }

          2.返回錯(cuò)誤信息

          在Spring3中添加了一個(gè)ResponseEntity用于在返回時(shí)可以定義返回的HttpStatus和HttpHeaders,使用方式如下所示。

          @RequestMapping(method=RequestMethod.GET, value="/user/{id}")

          public ResponseEntity<User> getUser(@PathVariable("id") int id) {

          User user=userService.getUser(id);

          HttpStatus httpStatus=HttpStatus.OK;

          HttpHeaders httpHeaders=new HttpHeaders();

          try {

          httpHeaders.setLocation(new URI("localhost:8080/user/" + id));

          } catch (URISyntaxException e) {

          httpStatus=HttpStatus.INTERNAL_SERVER_ERROR;

          e.printStackTrace();

          }

          if(user==null) {

          httpStatus=HttpStatus.NOT_FOUND;

          }

          return new ResponseEntity<User>(user, httpStatus);

          }

          但是我個(gè)人覺(jué)得這種方式并不能解決我們平常業(yè)務(wù)中的問(wèn)題。在大多數(shù)情況下,其實(shí)我們的請(qǐng)求都是Ajax請(qǐng)求,我們?nèi)稳恍枰颓岸藴贤ǚ祷氐母袷剑热缛缦碌母袷健Mㄟ^(guò)code將后端執(zhí)行后的結(jié)果告訴前端,我們?nèi)稳恍枰约簩?xiě)公用的返回工具類(lèi)。

          {

          "code": 200,

          "msg": "成功",

          "_csrf": "6BerChUe",

          "data": {

          // 數(shù)據(jù)存放

          },

          "login": false

          }

          3.資源操作

          下面是對(duì)資源進(jìn)行增刪查改操作。

          @RestController

          public class UserController {

          @Autowired

          private UserService userService;

          // GET localhost:8080/user/{id}

          @RequestMapping(method=RequestMethod.GET, value="/user/{id}")

          public ResponseEntity<User> getUser(@PathVariable("id") int id) {

          User user=userService.getUser(id);

          HttpStatus httpStatus=HttpStatus.OK;

          if(user==null) {

          httpStatus=HttpStatus.NOT_FOUND;

          }

          return new ResponseEntity<User>(user, httpStatus);

          }

          // POST localhost:8080/user

          @RequestMapping(method=RequestMethod.POST, value="/user")

          public ResponseEntity<User> addUser(@RequestBody User user) {

          User result=userService.addUser(user);

          HttpStatus httpStatus=HttpStatus.OK;

          if(result==null) {

          httpStatus=HttpStatus.INTERNAL_SERVER_ERROR;

          }

          return new ResponseEntity<User>(result, httpStatus);

          }

          // DELETE localhost:8080/user/1

          @RequestMapping(method=RequestMethod.DELETE, value="/user")

          public ResponseEntity<User> deleteUser(@PathVariable("id") int id) {

          User result=userService.deleteUser(id);

          HttpStatus httpStatus=HttpStatus.OK;

          if(result==null) {

          httpStatus=HttpStatus.INTERNAL_SERVER_ERROR;

          }

          return new ResponseEntity<User>(result, httpStatus);

          }

          // PUT localhost:8080/user

          @RequestMapping(method=RequestMethod.PUT, value="/user")

          public ResponseEntity<User> updateUserByPUT(@RequestBody User user) {

          User result=userService.updateUser(user);

          HttpStatus httpStatus=HttpStatus.OK;

          if(result==null) {

          httpStatus=HttpStatus.INTERNAL_SERVER_ERROR;

          }

          return new ResponseEntity<User>(result, httpStatus);

          }

          // PATCH localhost:8080/user

          @RequestMapping(method=RequestMethod.PATCH, value="/user")

          public ResponseEntity<User> updateUserByPATCH(@RequestBody User user) {

          User result=userService.updateUser(user);

          HttpStatus httpStatus=HttpStatus.OK;

          if(result==null) {

          httpStatus=HttpStatus.INTERNAL_SERVER_ERROR;

          }

          return new ResponseEntity<User>(result, httpStatus);

          }

          }

          同樣對(duì)接口的測(cè)試我們可以用專(zhuān)門(mén)的工具進(jìn)行測(cè)試,我這里使用的是Postman。

          參考文檔

          《Spring In Action》第四版

          https://www.cnblogs.com/wzbinStu/p/8566367.html

          https://blog.csdn.net/maxiao124/article/details/79897229

          https://baike.baidu.com/item/rest/6330506?fr=aladdin

          https://baike.baidu.com/item/RESTful/4406165?fr=aladdin

          https://blog.csdn.net/lxbfirst/article/details/78979556

          SpringMVC概述

          SpringMVC是一種基于Java的Web框架,?用于構(gòu)建Web應(yīng)用程序。?

          SpringMVC的核心在于其設(shè)計(jì)模式——MVC(?Model-View-Controller)?,?這種設(shè)計(jì)模式將應(yīng)用程序的數(shù)據(jù)處理、?用戶(hù)接口和控制邏輯分開(kāi),?使得代碼結(jié)構(gòu)更加清晰,?便于維護(hù)和擴(kuò)展。?在SpringMVC中,?Model代表數(shù)據(jù)和相關(guān)的業(yè)務(wù)邏輯,?View負(fù)責(zé)顯示數(shù)據(jù)給用戶(hù),?而Controller則是協(xié)調(diào)Model和View的橋梁,?處理用戶(hù)請(qǐng)求并返回相應(yīng)的視圖。

          1.1 MVC模型

          MVC全稱(chēng)Model View Controller,是一種設(shè)計(jì)創(chuàng)建Web應(yīng)用程序的模式。這三個(gè)單詞分別代表Web應(yīng)用程序的三個(gè)部分:

          • Model(模型):指數(shù)據(jù)模型。用于存儲(chǔ)數(shù)據(jù)以及處理用戶(hù)請(qǐng)求的業(yè)務(wù)邏輯。在Web應(yīng)用中,JavaBean對(duì)象,業(yè)務(wù)模型等都屬于Model。
          • View(視圖):用于展示模型中的數(shù)據(jù)的,一般為jsp或html文件。
          • Controller(控制器):是應(yīng)用程序中處理用戶(hù)交互的部分。接受視圖提出的請(qǐng)求,將數(shù)據(jù)交給模型處理,并將處理后的結(jié)果交給視圖顯示。 ?



          1.2 SpringMVC主要組件

          (1)前端控制器 DispatcherServlet(不需要開(kāi)發(fā),由框架提供【核心】)

          DispatcherServlet 是 Spring MVC 的入口函數(shù)。接收請(qǐng)求,響應(yīng)結(jié)果,相當(dāng)于轉(zhuǎn)發(fā)器,中央處理器。有了 DispatcherServlet ,可以大大減少其它組件之間的耦合度。

          用戶(hù)請(qǐng)求到達(dá)前端控制器,就相當(dāng)于 mvc 模式中的 c,DispatcherServlet 是整個(gè)流程控制的中心,由它調(diào)用其它組件來(lái)處理用戶(hù)的請(qǐng)求。

          (2)處理器映射器 HandlerMapping (不需要開(kāi)發(fā),由框架提供)

          HandlerMapping 負(fù)責(zé)根據(jù)用戶(hù)請(qǐng)求(URL),找到相應(yīng)的 Handler 即處理器(Controller),SpringMVC 提供了不同映射器實(shí)現(xiàn)的不同映射方式,例如:配置文件方式,實(shí)現(xiàn)接口方式,注解方式等。

          (3)處理器適配器 HandlerAdapter (不需要開(kāi)發(fā),由框架提供)

          按照特定規(guī)則(HandlerAdapter 要求的規(guī)則)去執(zhí)行 Handler,通過(guò) HandlerAdapter 對(duì)處理器進(jìn)行執(zhí)行,這是適配器模式的應(yīng)用,通過(guò)擴(kuò)展適配器可以對(duì)更多類(lèi)型的處理器進(jìn)行處理。

          (4)處理器 Handler (需要工程師開(kāi)發(fā))

          Handler 是繼 DispatcherServlet 前端控制器的后端控制器,在 DispatcherServlet 的控制下,Handler 對(duì)具體的用戶(hù)請(qǐng)求進(jìn)行處理。由于 Handler 涉及到具體的用戶(hù)業(yè)務(wù)請(qǐng)求,所以一般情況下需要工程師根據(jù)業(yè)務(wù)需求來(lái)開(kāi)發(fā) Handler。

          (5)視圖解析器 View Resolver (不需要開(kāi)發(fā),由框架提供)

          作用:進(jìn)行視圖解析,根據(jù)邏輯視圖名解析成真正的視圖(View),View Resolver 負(fù)責(zé)將處理結(jié)果生成 View 視圖。首先,根據(jù)邏輯視圖名解析成物理視圖名(即具體的頁(yè)面地址),再生成 View 視圖對(duì)象,最后對(duì) View 進(jìn)行渲染,將處理結(jié)果通過(guò)頁(yè)面展示給用戶(hù)。

          Spring MVC 框架提供了很多的 View 視圖類(lèi)型,包括:jstlView、freemarkerView、pdfView 等。 一般情況下,需要通過(guò)頁(yè)面標(biāo)簽或頁(yè)面模版技術(shù),將模型數(shù)據(jù)通過(guò)頁(yè)面展示給用戶(hù),這需要由工程師根據(jù)業(yè)務(wù)需求開(kāi)發(fā)具體的頁(yè)面。

          (6)視圖 View (需要工程師開(kāi)發(fā))

          View 是一個(gè)接口,實(shí)現(xiàn)類(lèi)才可以支持不同的View類(lèi)型(jsp、freemarker、pdf...)

          1.2 SpringMVC執(zhí)行流程

          1.2.1 SpringMVC三大核心組件

          • HandlerMapping處理器映射器:建立地址與方法的映射。

          HandlerMapping負(fù)責(zé)根據(jù)用戶(hù)請(qǐng)求url找到Handler即處理器,springmvc提供了不同的映射器實(shí)現(xiàn)不同的映射方式,例如:配置文件方式,實(shí)現(xiàn)接口方式,注解方式等。

          • HandlerAdapter處理器適配器:根據(jù)地址調(diào)用方法。

          Handler 是繼DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler對(duì)具體的用戶(hù)請(qǐng)求進(jìn)行處理。

          • ViewResolver 視圖解析器:處理ModelAndView數(shù)據(jù)和視圖。

          ViewResolver通過(guò)HandlerAdapter對(duì)處理器進(jìn)行執(zhí)行,這是適配器模式的應(yīng)用,通過(guò)擴(kuò)展適配器可以對(duì)更多類(lèi)型的處理器進(jìn)行執(zhí)行。

          1.2.2 SpringMVC執(zhí)行流程圖



          1.2.3 Spring執(zhí)行流程具體步驟(參考圖)

          1. 用戶(hù)發(fā)送請(qǐng)求至前端控制器DispatcherServlet。
          2. DispatcherServlet收到請(qǐng)求調(diào)用HandlerMapping處理器映射器。
          3. 處理器映射器根據(jù)請(qǐng)求url找到具體的處理器,生成處理器對(duì)象及處理器攔截器(如果有則生成)一并返回給DispatcherServlet。
          4. DispatcherServlet通過(guò)HandlerAdapter處理器適配器調(diào)用處理器。
          5. 執(zhí)行處理器(Controller層,也叫后端控制器)。
          6. Controller執(zhí)行完成返回?cái)?shù)據(jù)和視圖(ModelAndView)。
          7. HandlerAdapter將controller執(zhí)行結(jié)果ModelAndView返回給DispatcherServlet。
          8. DispatcherServlet將ModelAndView傳給ViewReslover視圖解析器。
          9. ViewReslover解析后返回具體的View視圖(JSP / HTML)。
          10. DispatcherServlet對(duì)View進(jìn)行渲染視圖(即將模型數(shù)據(jù)填充至視圖中)。
          11. DispatcherServlet響應(yīng)用戶(hù),用戶(hù)看到界面和數(shù)據(jù)。

          1.2.4 總結(jié)流程

          Spring MVC所有的請(qǐng)求都經(jīng)過(guò)DispatcherServlet來(lái)統(tǒng)一分發(fā)。DispatcherServlet將請(qǐng)求分發(fā)給Controller之前,需要借助于Spring MVC提供的HandlerMapping定位到具體的Controller。 HandlerMapping接口負(fù)責(zé)完成客戶(hù)請(qǐng)求到Controller映射。 Controller接口將處理用戶(hù)請(qǐng)求,這和Java Servlet扮演的角色是一致的。一旦Controller處理完用戶(hù)請(qǐng)求,則返回ModelAndView(數(shù)據(jù)和視圖)對(duì)象給DispatcherServlet前端控制器。從宏觀角度考慮,DispatcherServlet是整個(gè)Web應(yīng)用的控制器;從微觀考慮,Controller是單個(gè)Http請(qǐng)求處理過(guò)程中的控制器,而ModelAndView是Http請(qǐng)求過(guò)程中返回的模型(Model)和視圖(View)。 返回的視圖需要通過(guò)ViewResolver接口(視圖解析器)在Web應(yīng)用中負(fù)責(zé)查找View對(duì)象,從從而將相應(yīng)結(jié)果渲染給客戶(hù)。

          2 SpringMVC項(xiàng)目快速搭建

          2.1 搭建Maven項(xiàng)目



          2.2 在pom.xml中加入相關(guān)依賴(lài)

          <!-- springmvc依賴(lài) -->
          <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-webmvc</artifactId>
              <version>5.2.5.RELEASE</version>
          </dependency>
          <!-- servlet依賴(lài) -->
          <dependency>
              <groupId>javax.servlet</groupId>
              <artifactId>javax.servlet-api</artifactId>
              <version>3.1.0</version>
          </dependency>
          <!-- jsp依賴(lài) -->
          <dependency>
              <groupId>javax.servlet.jsp</groupId>
              <artifactId>javax.servlet.jsp-api</artifactId>
              <version>2.3.1</version>
          </dependency>

          2.3 在web.xml中聲明DispatcherServlet對(duì)象

          <?xml version="1.0" encoding="UTF-8"?>
          <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
                   version="4.0">
          <!--聲明springmvc的核心對(duì)象
                  訪(fǎng)問(wèn)mymvc地址后,報(bào)錯(cuò),文件沒(méi)有找到。找到文件是/WEB-INF/springmvc-servlet.xml或者myweb-servlet.xml(這個(gè))
                  錯(cuò)誤原因:在Servlet的init()方法中,創(chuàng)建springmvc使用的容器對(duì)象WebApplicationContext
                  WebApplicationContext ctx=new ClassPathXmlApplicationContext(配置文件)
                  配置文件的默認(rèn)路徑:/WEB-INF/<servlet-name>-servlet.xml
                  DispatcherServlet作用:
                  1.在init()中創(chuàng)建springmvc的容器對(duì)象 WebApplicationContext,創(chuàng)建springmvc配置文件的所有Java對(duì)象。
                      java對(duì)象就是Controller對(duì)象
                  2.DispatcherServlet 是一個(gè)Servlet,能夠接受請(qǐng)求。
          -->
              <servlet>
                  <servlet-name>springmvc</servlet-name>
                  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
          <!--        如果是自定義的文件,需要在這寫(xiě)自定義配置文件的位置    和監(jiān)聽(tīng)器的是一樣的-->
                  <init-param>
                      <param-name>contextConfigLocation</param-name>
                      <param-value>classpath:springmvc.xml</param-value>
                  </init-param>
           
          <!--        在服務(wù)器啟動(dòng)時(shí)候創(chuàng)建對(duì)象,和容器的順序    在啟動(dòng)時(shí)裝載對(duì)象 隨意給個(gè)值要求大于等于0  數(shù)值越小,創(chuàng)建的越早-->
                  <load-on-startup>1</load-on-startup>
              </servlet>
              <servlet-mapping>
                  <servlet-name>springmvc</servlet-name>
          <!--        url-pattern 作用:把一些請(qǐng)求交給servlet處理   就例如將/mymvc交給springmvc處理
                      使用中央調(diào)度器(DispatcherServlet)  1.使用擴(kuò)展名方式,格式/*.xxx  例如:xxx.xml表示以xml結(jié)尾的都算
          -->
                  <url-pattern>*.do</url-pattern>
              </servlet-mapping>
          </web-app>

          2.4 創(chuàng)建一個(gè)發(fā)起請(qǐng)求的jsp頁(yè)面(index.jsp)

          <%@ page contentType="text/html;charset=UTF-8" language="java" %>
          <html>
          <head>
              <title>第一個(gè)springmvc</title>
          </head>
          <body>
              <a href="some.do">發(fā)起一個(gè)som.do的請(qǐng)求</a> 
          </body>
          </html>

          2.5 創(chuàng)建一個(gè)普通的類(lèi),作為控制器使用,代替之前的servlet

          import org.springframework.stereotype.Controller;
          import org.springframework.web.bind.annotation.RequestMapping;
          import org.springframework.web.servlet.ModelAndView;
           
          /**  @Controller: 創(chuàng)建控制器(處理器)對(duì)象
           *  控制器:叫做后端控制器(back controller),自定義的類(lèi)處理請(qǐng)求的。
           *  位置:在類(lèi)的上面,表示創(chuàng)建此類(lèi)的對(duì)象,對(duì)象放在springmvc的容器中
           *
           */
          @Controller
          public class MyController {
              /*
                  Springmvc框架使用 ,使用控制器類(lèi)中的方法,處理請(qǐng)求
                  方法的特點(diǎn): 1.方法的形參,表示請(qǐng)求中的參數(shù)   2.方法的返回值,表示本次請(qǐng)求的處理請(qǐng)求
               */
           
              /**
               * @RequestMapping :請(qǐng)求映射
               *  屬性:value 請(qǐng)求中的uri地址,唯一值,以"/"開(kāi)頭
               *  位置:1.在方法上面(必須) 2.在類(lèi)定義的上面(可選)
               *  作用:指定的請(qǐng)求,交給指定的方法處理,等同于url-pattern(個(gè)人理解 相當(dāng)于可以做doget相關(guān)的操作)
               *  返回值ModelAndView:表示本次請(qǐng)求的處理結(jié)果(數(shù)據(jù)和視圖) model:表示數(shù)據(jù)   view:表示視圖
               */
              //可以在一個(gè)類(lèi)中定義多個(gè)方法使用多個(gè)@RequestMapping注解
              @RequestMapping(value={"/some.do","/first.do"})  //value是一個(gè)數(shù)組,可以有多個(gè)值,相當(dāng)于將該方法起一個(gè)名字
              public ModelAndView doSome(){  //doGet()
              //使用這個(gè)方法處理請(qǐng)求,能夠處理請(qǐng)求的方法叫做控制器方法
                  //調(diào)用service對(duì)象,處理請(qǐng)求,返回?cái)?shù)據(jù)
                  ModelAndView mv=new ModelAndView();
                  //添加數(shù)據(jù)
                  mv.addObject("msg","在ModelAddView中處理了some.do的請(qǐng)求");
                  mv.addObject("fun","執(zhí)行了dosome的方法");
                  //指定視圖,setviewName("視圖路徑")  相當(dāng)于請(qǐng)求轉(zhuǎn)發(fā)request.getRequestDis...("/show.jsp").forward(..)
          //        mv.setViewName("/WEB-INF/view/show.jsp");
                  //當(dāng)配置了視圖解析器,使用文件名稱(chēng)作為視圖名使用,叫做視圖邏輯名稱(chēng)
                  //使用了邏輯名稱(chēng),框架使用配置文件中視圖解析器的前綴和后綴,拼接為完整地視圖路徑 ,例如/WEB-INF/view/ + show + .jsp
                  mv.setViewName("show");
                  /*
                  當(dāng)框架調(diào)用完dosome方法后,得到返回中modelandview  框架會(huì)在后續(xù)的處理邏輯值,處理mv對(duì)象里的數(shù)據(jù)和視圖
                  對(duì)數(shù)據(jù)執(zhí)行requert,setAttribute(“msg”,“處理了some.do請(qǐng)求”);把數(shù)據(jù)放到request作用域中
                  對(duì)視圖進(jìn)行轉(zhuǎn)發(fā)操作
                   */
                  return  mv;
              }
          }

          2.6 創(chuàng)建顯示處理結(jié)果的jsp頁(yè)面(webapp\WEB-INF\view\show.jsp)

          <%@ page contentType="text/html;charset=UTF-8" language="java" %>
          <html>
          <head>
              <title>Title</title>
          </head>
          <body>
                  /show.jsp,顯示request作用域中的數(shù)據(jù)<br>
                  <h2>msg數(shù)據(jù):<%=request.getAttribute("msg")%></h2>
                  <h2>fun數(shù)據(jù):${fun}</h2>
          </body>
          </html>

          2.7 創(chuàng)建springmvc的配置文件

          <?xml version="1.0" encoding="UTF-8"?>
          <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.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
          <!--spring的配置文件  聲明組件掃描器-->
              <context:component-scan base-package="com.aiowang.controller"/>
          <!--    聲明視圖解析器;幫助處理視圖  主要幫助我們處理重復(fù)的多余的冗余路徑等-->
              <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
          <!--        前綴:指定試圖文件的路徑-->
                  <property name="prefix" value="/WEB-INF/view/"/>
          <!--        后綴,試圖文件的擴(kuò)展名-->
                  <property name="suffix" value=".jsp"/>  <!--表示所有的jsp文件-->
              </bean>
          </beans>

          2.8 配置Tomcat并驗(yàn)證

          (1)Tomcat9.0下載 https://tomcat.apache.org/download-90.cgi



          (2)IDEA配置Tomcat

          打開(kāi)配置選項(xiàng)



          找到左側(cè)Tomcat圖標(biāo),新建,選擇下載好并解壓的Tomcat路徑



          部署



          正常運(yùn)行,成功



          2.9 日志配置并驗(yàn)證

          2.9.1 Log4j+Commons-Logging方式

          (1) 導(dǎo)入依賴(lài)

          junit是測(cè)試用的

          <dependency>
              <groupId>log4j</groupId>
              <artifactId>log4j</artifactId>
              <version>1.2.17</version>
          </dependency>
          <dependency>
              <groupId>commons-logging</groupId>
              <artifactId>commons-logging</artifactId>
              <version>1.2</version>
          </dependency>
          <dependency>
              <groupId>junit</groupId>
              <artifactId>junit</artifactId>
              <version>4.12</version>
              <scope>compile</scope>
          </dependency>

          (2)基本使用

          新建一個(gè)測(cè)試類(lèi)HelloLog.java

          import org.apache.commons.logging.Log;
          import org.apache.commons.logging.LogFactory;
          import org.junit.Test;
          
          public class LogTest {
              //日志對(duì)象
              private Log log=LogFactory.getLog(LogTest.class);
              @Test
              public void test1(){
                  log.trace("hello trace!");
                  log.debug("hello debug");
                  log.info("hello info");
                  log.warn("hello warn");
                  log.error("hello error");
                  log.fatal("hello fatal");
              }
          }

          (3)新建log4j配置文件

          寫(xiě)好了測(cè)試類(lèi)運(yùn)行發(fā)現(xiàn)沒(méi)有打印 因?yàn)榕渲米芳悠鳎芳悠?<appender>的意思是我們的日志要輸出到哪里 寫(xiě)一個(gè)log4j的配置文件 新建log4j.xml,配置信息如下

          <?xml version="1.0" encoding="UTF8" ?>
          <!DOCTYPE log4j:configuration PUBLIC "-//LOGGER"
                  "http://org/apache/log4j/xml/log4j.dtd">
          <log4j:configuration>
              <!--org.apache.log4j.ConsoleAppender 輸出到控制臺(tái)-->
              <appender name="myConsole" class="org.apache.log4j.ConsoleAppender">
                  <!--輸出格式-->
                  <layout class="org.apache.log4j.PatternLayout">
                      <param name="ConversionPattern"
                             value="%-d{yyyy-MM-dd HH:mm:ss,SSS} [%c]-[%p] %m%n"/>
                      <!--%-d{yyyy-MM-dd HH:mm:ss,SSS}是當(dāng)前時(shí)間
                                [%c]是日志出現(xiàn)的包和類(lèi)   %p是日志的級(jí)別 %m是message也就是日志的消息,%n是換行符 -->
                  </layout>
              </appender>
              <!--    輸出到文件H:/log/hello.log中-->
              <appender name="myFile1" class="org.apache.log4j.RollingFileAppender">
                  <param name="File" value="D:/log/hello.log"/><!--文件位置-->
                  <param name="Append" value="true"/><!--是否選中追加-->
                  <param name="MaxFileSize" value="1kb"/><!--文件最大字節(jié)數(shù)-->
                  <param name="MaxBackupIndex" value="2"/>
                  <!--第一個(gè)文件超出上面設(shè)置的文件最大字節(jié)數(shù)后,
                  可以新增的新文件數(shù)量,這里只能新增2個(gè),
                  當(dāng)日志文件要輸出的內(nèi)容超出3個(gè)1kb(第一個(gè)加上新增的兩個(gè)),則覆蓋新增的第一個(gè)文件,再覆蓋第二個(gè)文件-->
                  <!--日志的輸出格式-->
                  <layout class="org.apache.log4j.PatternLayout">
                      <param name="ConversionPattern"
                             value="%-d{yyyy-MM-dd HH:mm:ss,SSS} [%c]-[%p] %m%n"/>
                      <!--%-d{yyyy-MM-dd HH:mm:ss,SSS}是輸出當(dāng)前時(shí)間
                                [%c]是輸出日志出現(xiàn)的包和類(lèi)   %p是日志的級(jí)別 %m是message也就是日志的消息,%n是換行符 -->
          
                  </layout>
              </appender>
          <!--    輸出到文件,每天輸出一個(gè)文件-->
          
              <appender name="myFile2" class="org.apache.log4j.DailyRollingFileAppender">
                  <param name="File" value="h:/log/world.log"/>
                  <param name="Append" value="true"/>
                  <layout class="org.apache.log4j.PatternLayout">
                      <param name="ConversionPattern" value="%-d{yyyy-MM-dd HH:mm:ss,SSS} [%c]-[%p] %m%n"/>
                  </layout>
              </appender>
              <root>
                  <!--優(yōu)先級(jí)設(shè)置,all < trace < debug < info < warn < error < fatal < off-->
                  <priority value="all"/>
                  <appender-ref ref="myConsole"/>
                  <appender-ref ref="myFile1"/>
                  <appender-ref ref="myFile2"/>
              </root>
          </log4j:configuration>

          (4)日志輸出格式

          • %t 輸出產(chǎn)生改日志事件的線(xiàn)程名
          • %p 輸出優(yōu)先級(jí),即上面提到的DEBUG、INFO、WARN、ERROR、FATAL
          • %c 輸出所在類(lèi)的全名
          • %r 輸出自應(yīng)用啟動(dòng)到輸出該log信息耗費(fèi)的毫秒數(shù)
          • %m message也就是日志的消息
          • %n 換行符
          • %d 輸出日志時(shí)間點(diǎn)的日期或手機(jī),默認(rèn)格式為ISO8601,也可以在其后指定格式,比如%-d{yyyy-MM-dd HH:mm:ss,SSS},輸出類(lèi)似:2022-5-11 14:19:33,921

          3 SpringMVC的常用注解

          Spring MVC 框架中的常用注解主要包括在控制器層(Controller)、服務(wù)層(Service)、數(shù)據(jù)訪(fǎng)問(wèn)層(Repository)、實(shí)體類(lèi)(Entity)、請(qǐng)求參數(shù)(Request Parameters)等方面。以下是這些注解的主要含義和用例 @Autowired、@ComponentScan、@Configuration 和 @Bean 是 Spring 框架中常用的注解,用于實(shí)現(xiàn)依賴(lài)注入和配置管理。

          3.1 在控制器層(Controller)使用的注解:

          1、@Controller: 含義: 標(biāo)識(shí)一個(gè)類(lèi)為 Spring MVC 控制器。 用例:

          @Controller
          public class MyController {
              // Controller methods
          }

          2、@RequestMapping: 含義: 映射 HTTP 請(qǐng)求的 URL 到一個(gè)具體的處理方法。 用例:

          @Controller
          @RequestMapping("/example")
          public class MyController {
              @RequestMapping("/hello")
              public String hello() {
                  return "hello";
              }
          }

          3、@RequestParam: 含義: 用于提取請(qǐng)求中的參數(shù)值。 客戶(hù)端發(fā)送請(qǐng)求 /example/greet?name=John 用例:

          @Controller
          @RequestMapping("/example")
          public class MyController {
              @RequestMapping("/greet")
              public String greet(@RequestParam("name") String name) {
                  return "Hello, " + name + "!";
              }
          }

          4、@PathVariable: 含義: 用于將 URI 模板變量映射到處理方法的參數(shù)。 客戶(hù)端發(fā)送請(qǐng)求 /example/user/123 用例:

          @Controller
          @RequestMapping("/example")
          public class MyController {
              @RequestMapping("/user/{id}")
              public String getUserById(@PathVariable("id") Long userId) {
                  // Retrieve user with the specified ID
                  return "userDetails";
              }
          }

          5、@PatchMapping: 含義:用于映射PATCH請(qǐng)求到控制器方法。@PatchMapping是一個(gè)用于映射HTTP PATCH請(qǐng)求到控制器方法的注解,在SpringMVC中也可以使用。它可以用于方法級(jí)別,用于指定處理PATCH請(qǐng)求的方法。 用例:

          @Controller
          @RequestMapping("/users")
          public class UserController {
          
              @PatchMapping("/{id}")
              public String updateUser(@PathVariable Long id, @RequestBody User user) {
                  // ...
              }
          }

          3.2 CURD

          1、@GetMapping: (查詢(xún)) 含義:處理 HTTP GET 請(qǐng)求。 用例:

          @Controller
          @RequestMapping("/users")
          public class UserController {
          
              @GetMapping("/{id}")
              public String getUser(@PathVariable Long id) {
                  // ...
              }
          }

          2、@PostMapping: (新增) 含義:處理 HTTP POST 請(qǐng)求。 用例:

          @Controller
          @RequestMapping("/users")
          public class UserController {
          
              @PostMapping
              public String createUser(@ModelAttribute User user) {
                  // ...
              }
          }

          3、@PutMapping:(更新) 含義:處理 HTTP PUT 請(qǐng)求。 用例:

          @Controller
          @RequestMapping("/users")
          public class UserController {
          
              @PutMapping("/{id}")
              public String updateUser(@PathVariable Long id, @ModelAttribute User user) {
                  // ...
              }
          }

          4、@DeleteMapping:(刪除) 含義:處理 HTTP DELETE 請(qǐng)求。 用例:

          @Controller
          @RequestMapping("/users")
          public class UserController {
          
              @DeleteMapping("/{id}")
              public String deleteUser(@PathVariable Long id) {
                  // ...
              }
          }

          5、@PatchMapping: 含義:處理 HTTP PATCH 請(qǐng)求。 用例:

          @Controller
          @RequestMapping("/users")
          public class UserController {
          
              @PatchMapping("/{id}")
              public String updateUser(@PathVariable Long id, @RequestBody User user) {
                  // ...
              }
          }

          3.3 在服務(wù)層(Service)使用的注解

          1、@Service: 含義: 標(biāo)識(shí)一個(gè)類(lèi)為服務(wù)層的組件。 用例:

          @Service
          public class MyService {
              // Service methods
          }

          @Service 是 Spring Framework 中的一個(gè)注解,用于標(biāo)識(shí)一個(gè)類(lèi)為服務(wù)層(Service Layer)的組件。服務(wù)層通常包含應(yīng)用程序的業(yè)務(wù)邏輯,負(fù)責(zé)處理業(yè)務(wù)規(guī)則、調(diào)用數(shù)據(jù)訪(fǎng)問(wèn)層(Repository 或 DAO)執(zhí)行數(shù)據(jù)庫(kù)操作,并協(xié)調(diào)應(yīng)用程序的不同部分

          組件掃描: @Service 是 Spring 的組件掃描機(jī)制的一部分,標(biāo)識(shí)帶有該注解的類(lèi)為一個(gè)服務(wù)層組件。在應(yīng)用程序啟動(dòng)時(shí),Spring 會(huì)掃描包路徑下的所有組件,并注冊(cè)為 Spring 容器中的 Bean。

          依賴(lài)注入: 通過(guò)將 @Service 注解添加到類(lèi)上,Spring IoC 容器會(huì)自動(dòng)將該類(lèi)的實(shí)例注入到其他需要依賴(lài)的組件中,例如控制器(Controller)或其他服務(wù)層組件。

          事務(wù)管理: 在服務(wù)層執(zhí)行的方法通常涉及數(shù)據(jù)庫(kù)操作,@Service 注解通常與 @Transactional 注解一起使用,以啟用事務(wù)管理。這確保了在業(yè)務(wù)方法中的一系列操作要么全部成功,要么全部失敗(回滾)。

          用例:

          @Service
          public class MyService {
              @Autowired
              private MyRepository myRepository;
          
              public String performBusinessLogic() {
                  // Business logic implementation
                  return "Business logic executed successfully";
              }
          
              public List<MyEntity> getAllEntities() {
                  return myRepository.findAll();
              }
          }

          3.4 在數(shù)據(jù)訪(fǎng)問(wèn)層(Repository)使用的注解

          1、@Repository: 含義: 標(biāo)識(shí)一個(gè)類(lèi)為數(shù)據(jù)訪(fǎng)問(wèn)層的組件,通常與 Spring 的數(shù)據(jù)訪(fǎng)問(wèn)異常轉(zhuǎn)換一起使用。 用例:

          @Repository
          public class MyRepository {
              // Repository methods
          }

          3.5 在實(shí)體類(lèi)(Entity)使用的注解

          1、@Entity: 含義: 標(biāo)識(shí)一個(gè)類(lèi)為 JPA 實(shí)體類(lèi)。 用例:

          @Entity
          public class User {
              // Entity properties and methods
          }

          @Entity 注解是 Java Persistence API (JPA) 的一部分,用于標(biāo)識(shí)一個(gè)類(lèi)為 JPA 實(shí)體類(lèi)。JPA 是一種規(guī)范,用于描述如何通過(guò) Java 對(duì)象與關(guān)系型數(shù)據(jù)庫(kù)進(jìn)行映射。@Entity 注解告訴 JPA,被注解的類(lèi)將映射到數(shù)據(jù)庫(kù)中的一個(gè)表。

          數(shù)據(jù)庫(kù)映射: @Entity 注解告訴 JPA 這個(gè)類(lèi)與數(shù)據(jù)庫(kù)中的表存在映射關(guān)系。類(lèi)中的字段(成員變量)通常與表中的列相對(duì)應(yīng)。

          主鍵標(biāo)識(shí): 實(shí)體類(lèi)通常需要一個(gè)主鍵,用于唯一標(biāo)識(shí)每個(gè)實(shí)體對(duì)象。通過(guò) @Entity 注解,JPA 可以識(shí)別實(shí)體類(lèi)中的主鍵。

          實(shí)體類(lèi)識(shí)別: 當(dāng)應(yīng)用程序使用 JPA 進(jìn)行持久化操作時(shí),JPA 需要知道哪些類(lèi)是實(shí)體類(lèi)。@Entity 注解是 JPA 識(shí)別實(shí)體類(lèi)的標(biāo)志。

          持久性操作: 通過(guò)實(shí)體類(lèi),可以執(zhí)行 CRUD(Create, Read, Update, Delete)操作。JPA 提供了 EntityManager 接口,可以用于執(zhí)行這些操作。

          關(guān)系映射: 實(shí)體類(lèi)之間的關(guān)系可以通過(guò) JPA 進(jìn)行映射,包括一對(duì)一、一對(duì)多、多對(duì)一、多對(duì)多等關(guān)系。

          示例:

          @Entity
          public class User {
          
              @Id
              @GeneratedValue(strategy=GenerationType.IDENTITY)
              private Long id;
          
              @Column(name="username")
              private String username;
          
              @Column(name="email")
              private String email;
          
              // Getters and setters
          }

          3.5 請(qǐng)求參數(shù)相關(guān)注解

          1、@RequestBody: 含義: 用于將 HTTP 請(qǐng)求的正文映射到方法參數(shù)。 用例:

          @Controller
          @RequestMapping("/example")
          public class MyController {
              @RequestMapping("/processJson")
              public String processJson(@RequestBody MyJsonModel jsonModel) {
                  // Process JSON data
                  return "result";
              }
          }

          2、@ResponseBody: 含義: 表示方法的返回值直接作為響應(yīng)體,而不是視圖名稱(chēng)。 用例:

          @Controller
          @RequestMapping("/example")
          public class MyController {
              @RequestMapping("/getJson")
              @ResponseBody
              public MyJsonModel getJson() {
                  // Return JSON data directly
              }
          }

          3.7 @Autowired

          含義: 用于自動(dòng)裝配,將指定類(lèi)型的 Bean 注入到屬性、構(gòu)造函數(shù)或方法參數(shù)中。 用例:

          @Service
          public class MyService {
              private final MyRepository repository;
          
              @Autowired
              public MyService(MyRepository repository) {
                  this.repository=repository;
              }
          }

          在上例中,MyService 類(lèi)通過(guò) @Autowired 注解將 MyRepository 類(lèi)型的 Bean 自動(dòng)注入到構(gòu)造函數(shù)中。

          3.8 @ComponentScan

          含義: 掃描指定包路徑,尋找標(biāo)有 @Component、@Service、@Repository、@Controller 注解的類(lèi),并將其注冊(cè)為 Spring Bean。 用例:

          @Configuration
          @ComponentScan(basePackages="com.example")
          public class AppConfig {
              // Configuration content
          }

          在上例中,@ComponentScan 注解掃描 com.example 包路徑下的所有類(lèi),將帶有相應(yīng)注解的類(lèi)注冊(cè)為 Spring Bean。

          3.9 @Configuration

          含義: 聲明當(dāng)前類(lèi)是一個(gè)配置類(lèi),通常與 @Bean 注解一起使用,用于配置 Spring 應(yīng)用上下文。 用例:

          @Configuration
          public class AppConfig {
              // Bean declarations using @Bean
          }

          在上例中,AppConfig 被聲明為配置類(lèi),用于定義 Spring Bean。

          3.10 @Bean

          含義: 在配置類(lèi)中使用,用于聲明一個(gè) Bean。 用例:

          @Configuration
          public class AppConfig {
              @Bean
              public MyService myService() {
                  return new MyService(myRepository());
              }
          
              @Bean
              public MyRepository myRepository() {
                  return new MyRepository();
              }
          }

          在上例中,@Bean 注解用于聲明兩個(gè) Bean:MyService 和 MyRepository。

          4 SpringMVC的基本配置

          4.1 靜態(tài)資源映射

          springmvc靜態(tài)資源配置

          在javaweb項(xiàng)目中配置了DispatcherServlet的情況下,如果不進(jìn)行額外配置的話(huà),幾乎所有的請(qǐng)求都會(huì)走這個(gè)servlet來(lái)處理,默認(rèn)靜態(tài)資源按路徑是訪(fǎng)問(wèn)不到的會(huì)報(bào)404錯(cuò)誤,下面講一講如何配置才能訪(fǎng)問(wèn)到靜態(tài)資源,本文將介紹三種方法

            <servlet>
              <servlet-name>dispatcher</servlet-name>
              <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
              <async-supported>false</async-supported>
            </servlet>
          
            <servlet-mapping>
              <servlet-name>dispatcher</servlet-name>
              <url-pattern>/</url-pattern>
            </servlet-mapping>

          4.1.1 在java配置文件中配置DefaultServletHttpRequestHandler來(lái)進(jìn)行處理

          @Configuration
          @EnableWebMvc
          public class MyMvcConfigurer implements WebMvcConfigurer {
          
            @Override
            public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
              // tomcat默認(rèn)處理靜態(tài)資源的servlet名稱(chēng)為default,不指定也可以DefaultServletHttpRequestHandler.setServletContext會(huì)自動(dòng)獲取
          //    configurer.enable("default");
              configurer.enable();
            }
          }

          上述配置完成后org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#defaultServletHandlerMapping 方法會(huì)生成一個(gè)類(lèi)名為SimpleUrlHandlerMapping的bean,當(dāng)其他handlerMapping無(wú)法處理請(qǐng)求時(shí)會(huì)接著調(diào)用SimpleUrlHandlerMapping對(duì)象進(jìn)行處理。SimpleUrlHandlerMapping中有一個(gè)urlMap屬性,key為請(qǐng)求路徑匹配模式串,/**能匹配所有的路徑, value為handler匹配完成后會(huì)調(diào)用handler處理請(qǐng)求。 接著調(diào)用DefaultServletHttpRequestHandler的handleRequest方法處理請(qǐng)求,邏輯比較簡(jiǎn)單,獲取請(qǐng)求轉(zhuǎn)發(fā)器進(jìn)行請(qǐng)求轉(zhuǎn)發(fā)交給tomcat默認(rèn)的servlet來(lái)進(jìn)行處理。

          4.1.2 在java配置文件中配置ResourceHttpRequestHandler來(lái)進(jìn)行處理

          @Configuration
          @EnableWebMvc
          public class MyMvcConfigurer implements WebMvcConfigurer {
          
            @Override
            public void addResourceHandlers(ResourceHandlerRegistry registry) {
              registry.addResourceHandler("/static/**").addResourceLocations("/static/");
            }
          }

          和第一種配置幾乎一樣,其實(shí)只是換了一個(gè)handler類(lèi)型來(lái)處理請(qǐng)求罷了。

          上述配置完成后org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#resourceHandlerMapping 方法會(huì)生成一個(gè)類(lèi)名為SimpleUrlHandlerMapping的bean,當(dāng)其他handlerMapping無(wú)法處理請(qǐng)求時(shí)會(huì)接著調(diào)用SimpleUrlHandlerMapping對(duì)象進(jìn)行處理

          ResourceHttpRequestHandler比DefaultServletHttpRequestHandler的構(gòu)建稍微復(fù)雜一點(diǎn)。之后也是調(diào)用SimpleUrlHandlerMapping相同的邏輯先根據(jù)請(qǐng)求路徑匹配找到對(duì)應(yīng)處理的handler,這里對(duì)應(yīng)的是ResourceHttpRequestHandler之后調(diào)用handleRequest方法,原理是先根據(jù)請(qǐng)求的路徑找到對(duì)應(yīng)的資源文件,再獲取資源文件的輸入流寫(xiě)入到response響應(yīng)中。

          4.1.3 web.xml配置servlet映射

          就是利用容器自身的默認(rèn)Servlet, 以Tomcat為例,如下圖有一個(gè)默認(rèn)的Servlet,名稱(chēng)就是default(也可以在tomcat的配置文件中修改為其他名稱(chēng),是在tomcat的目錄/conf/web.xml中配置的)。

          我們只需要在web項(xiàng)目的web.xml中配置靜態(tài)文件是由此Servlet來(lái)映射即可。 default是容器的默認(rèn)servlet的名稱(chēng),示例為tomcat容器,其他容器根據(jù)實(shí)際情況來(lái),如果tomcat配置文件修改了默認(rèn)servlet名稱(chēng),則也要修改為實(shí)際的。

          <servlet-mapping>
              <servlet-name>default</servlet-name>
              <url-pattern>/static/*</url-pattern>
          </servlet-mapping>

          將帶有/static/xxx 路徑的請(qǐng)求直接交給tomcat默認(rèn)的servlet去進(jìn)行處理

          4.1.4 spring-mvc.xml文件中配置

          <mvc:resources mapping="/images/**" location="/images/" />
          <mvc:resources mapping="/js/**" location="/js/" />
          <mvc:resources mapping="/css/**" location="/css/" />
          <mvc:resources location="/,classpath:/META-INF/publicResources/" mapping="/resources/**"/>

          因?yàn)樯厦娴膌ocation屬性節(jié)點(diǎn)是Resource資源, 因此可以使用classpath這類(lèi)寫(xiě)法。 <mvc:resources />更進(jìn)一步,由Spring MVC框架自己處理靜態(tài)資源,并添加一些有用的附加值功能。

          首先,<mvc:resources />允許靜態(tài)資源放在任何地方,如WEB-INF目錄下、類(lèi)路徑下等,你甚至可以將JavaScript等靜態(tài)文件打到JAR包中。通過(guò)location屬性指定靜態(tài)資源的位置,由于location屬性是Resources類(lèi)型,因此可以使用諸如"classpath:"等的資源前綴指定資源位置。傳統(tǒng)Web容器的靜態(tài)資源只能放在Web容器的根路徑下,<mvc:resources />完全打破了這個(gè)限制。

          其次,<mvc:resources />依據(jù)當(dāng)前著名的Page Speed、YSlow等瀏覽器優(yōu)化原則對(duì)靜態(tài)資源提供優(yōu)化。你可以通過(guò)cacheSeconds屬性指定靜態(tài)資源在瀏覽器端的緩存時(shí)間,一般可將該時(shí)間設(shè)置為一年,以充分利用瀏覽器端的緩存。在輸出靜態(tài)資源時(shí),會(huì)根據(jù)配置設(shè)置好響應(yīng)報(bào)文頭的Expires 和 Cache-Control值。

          在接收到靜態(tài)資源的獲取請(qǐng)求時(shí),會(huì)檢查請(qǐng)求頭的Last-Modified值,如果靜態(tài)資源沒(méi)有發(fā)生變化,則直接返回303相應(yīng)狀態(tài)碼,提示客戶(hù)端使用瀏覽器緩存的數(shù)據(jù),而非將靜態(tài)資源的內(nèi)容輸出到客戶(hù)端,以充分節(jié)省帶寬,提高程序性能。

          4.2 攔截器配置

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

          第一種方式是要定義的Interceptor類(lèi)要實(shí)現(xiàn)Spring的HandlerInterceptor 接口

          第二種方式是繼承實(shí)現(xiàn)了抽象類(lèi)HandlerInterceptorAdapter

          4.2.2 實(shí)例

          4.2.2.1 編寫(xiě)攔截器

          public class UserInterceptor implements HandlerInterceptor{
          
              /**
               * 該方法在整個(gè)請(qǐng)求完成后執(zhí)行,主要用來(lái)清理資源
               * 該方法只能在當(dāng)前interceptor的preHandler方法的返回值是true時(shí)才會(huì)執(zhí)行
               */
              @Override
              public void afterCompletion(HttpServletRequest arg0,
                      HttpServletResponse arg1, Object arg2, Exception arg3)
                      throws Exception {
              }
              /**
               * 該方法在Controller的方法調(diào)用后執(zhí)行,在視圖被渲染以前被調(diào)用,所以可以用來(lái)對(duì)ModelAndView對(duì)象進(jìn)行操作
               * 該方法只能在當(dāng)前interceptor的preHandler方法的返回值是true時(shí)才會(huì)執(zhí)行
               */
              @Override
              public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1,
                      Object arg2, ModelAndView arg3) throws Exception {
              }
              /**
               * 該方法在請(qǐng)求之前被調(diào)用
               * 該方法返回為true時(shí)攔截器才會(huì)繼續(xù)往下執(zhí)行
               */
              @Override
              public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                      Object handler) throws Exception {
                  //用于判斷用戶(hù)是否登錄
                  boolean flag=false;
                  
                  User user=(User) request.getSession().getAttribute("user");
                  if(user==null){
                      request.setAttribute("message", "請(qǐng)先登錄");
                      request.getRequestDispatcher("loginPage.jsp").forward(request, response);
                  }else{
                      flag=true;
                  }
                  return flag;
              }
              
          }

          4.2.2.2 配置springmvc-config.xml,在里面配置攔截器,

          <mvc:mapping path=""/>配置攔截路徑

          <mvc:exclude-mapping path=""/>配置不進(jìn)行攔截的路徑。

           <!-- 配置攔截器 -->
           <mvc:interceptors>
          	 <mvc:interceptor>
          		 <!-- 攔截路徑 -->
          		 <mvc:mapping path="/*"/>
          		 <!-- 不攔截的路徑 -->
          		 <mvc:exclude-mapping path="/login"/>
          		 <mvc:exclude-mapping path="/loginPage"/>
          		 <bean class="com.dj.interceptor.UserInterceptor"></bean>
          	 </mvc:interceptor>
            <!-- 當(dāng)設(shè)置多個(gè)攔截器時(shí),先按**順序調(diào)用preHandle方法**,然后**逆序調(diào)用**每個(gè)攔截器的postHandle和afterCompletion方法  -->
           </mvc:interceptors>

          4.2.2.3 編寫(xiě)controller

          @Controller
          public class UserController {
          
              @RequestMapping(value="/{pagename}")
              public String pageName(@PathVariable String pagename){
                  return pagename;
                  
              }
              @RequestMapping("login")
              public ModelAndView login(String username,String password
                      ,ModelAndView mv,HttpSession session){
                  
                  if(username!=null&&username.equals("aaa")&&password!=null&&password.equals("111")){
                      User user=new User();
                      user.setUsername(username);
                      user.setPassword(password);
                      session.setAttribute("user", user);
                      mv.setViewName("success");
                  }else{
                      mv.addObject("message", "賬號(hào)或密碼錯(cuò)誤");
                      mv.setViewName("loginPage");
                  }
                  return mv;
              }
              
              @RequestMapping("success")
              public String success(){
                  return "success";
                  
              }
          }

          4.2.2.4 編寫(xiě)登錄頁(yè)面和顯示登錄成功的頁(yè)面,顯示登錄成功的頁(yè)面只有在登錄后才能訪(fǎng)問(wèn),如果沒(méi)有登錄就訪(fǎng)問(wèn)將會(huì)被攔截。

          <form action="login" method="post">
              <!-- 提示信息 -->
              <font color="red">${requestScope.message }</font><br>
              用戶(hù)名:<input type="text" name="username" /><br>
              密碼:<input type="password" name="password"/>
              <input type="submit" value="登錄"/>
          </form>

          <body> 登陸成功! </body>

          4.2.2.5 測(cè)試

          直接訪(fǎng)問(wèn)success頁(yè)面被攔截



          訪(fǎng)問(wèn)登錄頁(yè)面,因?yàn)榕渲昧瞬贿M(jìn)行攔截的路徑,所以顯示如下



          輸入賬號(hào)密碼登錄成功



          4.3 @ControllerAdvice

          對(duì)于@ControllerAdvice,我們比較熟知的用法是結(jié)合@ExceptionHandler用于全局異常的處理,但其作用不僅限于此。ControllerAdvice拆分開(kāi)來(lái)就是Controller Advice,關(guān)于Advice,前面我們講解Spring Aop時(shí)講到,其是用于封裝一個(gè)切面所有屬性的,包括切入點(diǎn)和需要織入的切面邏輯。這里ContrllerAdvice也可以這么理解,其抽象級(jí)別應(yīng)該是用于對(duì)Controller進(jìn)行“切面”環(huán)繞的,而具體的業(yè)務(wù)織入方式則是通過(guò)結(jié)合其他的注解來(lái)實(shí)現(xiàn)的。@ControllerAdvice是在類(lèi)上聲明的注解,其用法主要有三點(diǎn):

          • 結(jié)合方法型注解@ExceptionHandler,用于捕獲Controller中拋出的指定類(lèi)型的異常,從而達(dá)到不同類(lèi)型的異常區(qū)別處理的目的;
          • 結(jié)合方法型注解@InitBinder,用于request中自定義參數(shù)解析方式進(jìn)行注冊(cè),從而達(dá)到自定義指定格式參數(shù)的目的;
          • 結(jié)合方法型注解@ModelAttribute,表示其標(biāo)注的方法將會(huì)在目標(biāo)Controller方法執(zhí)行之前執(zhí)行。

          在Spring MVC進(jìn)行調(diào)用的過(guò)程中,會(huì)有很多的特殊的需求。比如全局異常,分頁(yè)信息和分頁(yè)搜索條件,請(qǐng)求時(shí)帶來(lái)返回時(shí)還得回顯頁(yè)面。

          Spring提供@ControllerAdvice對(duì)需要處理的范圍進(jìn)行配置。

          @Target(ElementType.TYPE)
          @Retention(RetentionPolicy.RUNTIME)
          @Documented
          @Component
          public @interface ControllerAdvice {
              // 控制的掃描包范圍
              @AliasFor("basePackages")
              String[] value() default {};
              // 控制的掃描包范圍
              @AliasFor("value")
              String[] basePackages() default {};
              // 控制的包類(lèi)
              Class<?>[] basePackageClasses() default {};
              // @Controller或者@RestController的類(lèi) 的數(shù)據(jù)
              Class<?>[] assignableTypes() default {};
              // 控制范圍可以用注解進(jìn)行配置
              Class<? extends Annotation>[] annotations() default {};
          }

          從上面的講解可以看出,@ControllerAdvice的用法基本是將其聲明在某個(gè)bean上,然后在該bean的方法上使用其他的注解來(lái)指定不同的織入邏輯。不過(guò)這里@ControllerAdvice并不是使用AOP的方式來(lái)織入業(yè)務(wù)邏輯的,而是Spring內(nèi)置對(duì)其各個(gè)邏輯的織入方式進(jìn)行了內(nèi)置支持。本文將對(duì)@ControllerAdvice的這三種使用方式分別進(jìn)行講解。

          4.3.1 @ExceptionHandler

          系統(tǒng)比較龐大時(shí)很多的異常是不能控制,或者未知的,不能將所有的sql異常,反射異常,類(lèi)不存在等拋到頁(yè)面上展示給用戶(hù)。

          則需要一個(gè)全局的攔截器處理,Spring 提供了@ExceptionHandler處理方式。

          1)、全局異常處理定義

          @ControllerAdvice(basePackages="com.kevin.tool")
          public class ExceptionHandlerController {
              /**
               * 錯(cuò)誤后返回json
               * 如果想跳轉(zhuǎn)到專(zhuān)門(mén)的異常界面,則可以返回{@link org.springframework.web.servlet.ModelAndView}
               *
               * @return 標(biāo)準(zhǔn)異常json
               */
              @ResponseBody
              @ExceptionHandler(Exception.class)
              public Map<String, String> handler() {
                  Map<String, String> errorMap=new HashMap<String, String>(16);
                  errorMap.put("code", "500");
                  errorMap.put("msg", "系統(tǒng)異常,請(qǐng)稍后重試");
                  return errorMap;
              }
          }

          2)、控制器方法調(diào)用異常

          @RestController
          public class ControllerAdviceDemoController {
              @ResponseBody
              @RequestMapping("bindException")
              public String bindException() {
                  getMessage();
                  return "ok";
              }
              private void getMessage() {
                  throw new RuntimeException("未知異常!");
              }
          }

          3)、訪(fǎng)問(wèn)效果



          4.3.2 InitBinder

          @InitBinder從字面意思可以看出這個(gè)的作用是給Binder做初始化的,@InitBinder主要用在@Controller中標(biāo)注于方法上(@RestController也算),表示初始化當(dāng)前控制器的數(shù)據(jù)綁定器(或者屬性綁定器),只對(duì)當(dāng)前的Controller有效。@InitBinder標(biāo)注的方法必須有一個(gè)參數(shù)WebDataBinder。所謂的屬性編輯器可以理解就是幫助我們完成參數(shù)綁定,然后是在請(qǐng)求到達(dá)controller要執(zhí)行方法前執(zhí)行!

          數(shù)據(jù)綁定有很多的場(chǎng)景,當(dāng)前比如前端傳入的日期為字符串類(lèi)型,后端按照Format進(jìn)解析為日期。

          1)、全局日期綁定定義

          @ControllerAdvice(basePackages="com.kevin.tool")
          public class ExceptionHandlerController {
              @InitBinder("date")
              public void globalInitBinder(WebDataBinder binder) {
                  binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
              }
          }

          2)、控制器方法調(diào)用日期轉(zhuǎn)換

          @RestController
          public class ControllerAdviceDemoController {
              @ResponseBody
              @RequestMapping(value="/initBind", method=RequestMethod.GET)
              public String detail(@RequestParam("id") long id, Date date) {
                  System.out.println(date);
                  System.out.println(id);
                  return "ok";
              }
          }

          3)、收到的日期類(lèi)型效果

          訪(fǎng)問(wèn)地址為://127.0.0.1:9999/initBind?id=123&date=2019-12-30



          4.3.3 ModelAttribute

          先看看@ModelAttribute的注解信息,元注解@Target指定可以修飾方法參數(shù)和方法(全局)。當(dāng)前模擬一種常見(jiàn),就是將所有輸出的信息都加上當(dāng)前的平臺(tái)信息(比如版本等公共信息,這種需求還是比較多的)。

          @Target({ElementType.PARAMETER, ElementType.METHOD})
          @Retention(RetentionPolicy.RUNTIME)
          @Documented
          public @interface ModelAttribute {
              
              @AliasFor("name")
              String value() default "";
              
              @AliasFor("value")
              String name() default "";
           
              boolean binding() default true;
          }

          1)、全局返回屬性添加

          @ControllerAdvice(basePackages="com.kevin.tool")
          public class ExceptionHandlerController {
           
              @ModelAttribute
              public void addAttributes(Model model) {
                  model.addAttribute("msg", "hello");
           
                  HashMap<String, String> map=new HashMap<>(16);
                  map.put("version", "1.0.0");
                  map.put("name", "XXX平臺(tái)");
                  model.addAttribute("platform", map);
              }
          }

          2)、控制器方法訪(fǎng)問(wèn)

          @RestController
          public class ControllerAdviceDemoController {
           
              @GetMapping("/modelAttributeTest")
              private String modelAttributeTest(@ModelAttribute("msg") String msg,
                  @ModelAttribute("platform") Map<String, String> platform) {
           
                  String result="msg:" + msg + "<br>" + "info:" + platform;
                  return result;
              }
           
          }

          3)、輸出效果



          4.4 其他配置

          4.4.1 ViewController

          在控制器方法中只需要實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn)(只設(shè)置頁(yè)面視圖名稱(chēng))功能而沒(méi)有其他業(yè)務(wù),此時(shí)可以在SpringMvc的配置文件中使用view-controller標(biāo)簽表示控制器方法

          在SpringMvC的核心配置文件中使用視圖控制器標(biāo)簽跳轉(zhuǎn)到首頁(yè)

          <?xml version="1.0" encoding="UTF-8"?>
          <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"
                 xmlns:mvc="http://www.springframework.org/schema/mvc"
                 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 					http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd 					http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
              <!--自動(dòng)掃描控制層組件-->
              <context:component-scan base-package="com.atguigu.mvc.controller"></context:component-scan>
              <!--配置Thymeleaf視圖解析器-->
              <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
              </bean>
              <!--視圖控制器,設(shè)置請(qǐng)求對(duì)應(yīng)的視圖名稱(chēng)實(shí)現(xiàn)頁(yè)面的跳轉(zhuǎn)-->
              <mvc:view-controller path="/" view-name="index"></mvc:view-controller>
              <!--開(kāi)啟MVC的注解驅(qū)動(dòng),保證視圖控制器設(shè)置的請(qǐng)求和控制器方法設(shè)置的請(qǐng)求全部都會(huì)被前端控制器處理-->
              <mvc:annotation-driven />
          </beans>

          請(qǐng)求路徑path對(duì)應(yīng)的視圖名稱(chēng)是view-name,即請(qǐng)求路徑/對(duì)應(yīng)的視圖名稱(chēng)是index。 此時(shí),可以不需要控制器方法。

          4.4.2 路徑匹配參數(shù)配置

          通過(guò)@RequestMapping注解: 匹配路徑與處理器

          @RequestMapping注解用于建立請(qǐng)求URL路徑和處理器之間的對(duì)應(yīng)關(guān)系.

          出現(xiàn)位置: 可以出現(xiàn)在類(lèi)上,也可以出現(xiàn)在方法上.

          當(dāng)它既出現(xiàn)在類(lèi)上也出現(xiàn)在方法上時(shí),類(lèi)上注解值為請(qǐng)求URL的一級(jí)目錄,方法上注解值為請(qǐng)求URL的二級(jí)目錄 當(dāng)它只出現(xiàn)在方法上時(shí),該注解值為請(qǐng)求URL的一級(jí)目錄 其屬性如下:

          path: value屬性的別名,指定請(qǐng)求的URL,支持Ant風(fēng)格表達(dá)式,通配符如下:

          通配符

          說(shuō)明

          ?

          匹配文件(路徑)名中的一個(gè)字符

          *

          匹配文件(路徑)名中的任意數(shù)量(包括0個(gè))的字符

          **

          匹配任意數(shù)量(包括0個(gè))的路徑

          例如

          路徑/project/*.a匹配項(xiàng)目根路徑下所有在/project路徑下的.a文件
          路徑/project/p?ttern匹配項(xiàng)目根路徑下的/project/pattern和/project/pXttern,但不能匹配/project/pttern
          路徑/**/example匹配項(xiàng)目根路徑下的/project/example,/project/foo/example,和/example
          路徑/project/**/dir/file.*匹配項(xiàng)目根路徑下的/project/dir/file.jsp,/project/foo/dir/file.html,/project/foo/bar/dir/file.pdf
          路徑/**/*.jsp匹配項(xiàng)目根路徑下的所有jsp文件
          另外,遵循最長(zhǎng)匹配原則,若URL請(qǐng)求了/project/dir/file.jsp,現(xiàn)在存在兩個(gè)匹配模式:/**/*.jsp和/project/dir/*.jsp,那么會(huì)根據(jù)/project/dir/*.jsp來(lái)匹配.
          • method: 指定HTTP請(qǐng)求方法(可選RequestMethod.GET,RequestMethod.HEAD,RequestMethod.POST,RequestMethod.PUT等),多個(gè)值之間是或的關(guān)系.
          • params: 指定請(qǐng)求參數(shù)的限制,支持簡(jiǎn)單的表達(dá)式,如:
          @RequestMapping(params={"param1"}),表示請(qǐng)求參數(shù)中param1必須出現(xiàn)
          @RequestMapping(params={"!param1"}),表示請(qǐng)求參數(shù)中param1不能出現(xiàn)
          @RequestMapping(params={"param1=value1"}),表示請(qǐng)求參數(shù)中param1必須出現(xiàn)且為value1
          @RequestMapping(params={"param1!value1"}),表示請(qǐng)求參數(shù)中param1必須出現(xiàn)且不為value1
          多個(gè)值之間是與的關(guān)系
          • headers: 限定HTTP請(qǐng)求中必須包含的請(qǐng)求頭,同樣支持簡(jiǎn)單的表達(dá)式 其中path和method屬性較常用

          4.4.3 WebMvcConfigurerAdapter

          WebMvcConfigurerAdapter配置類(lèi)是spring提供的一種配置方式,采用JavaBean的方式替代傳統(tǒng)的基于xml的配置來(lái)對(duì)spring框架進(jìn)行自定義的配置。因此,在spring boot提倡的基于注解的配置,采用“約定大于配置”的風(fēng)格下,當(dāng)需要進(jìn)行自定義的配置時(shí),便可以繼承WebMvcConfigurerAdapter這個(gè)抽象類(lèi),通過(guò)JavaBean來(lái)實(shí)現(xiàn)需要的配置。

          WebMvcConfigurerAdapter是一個(gè)抽象類(lèi),它只提供了一些空的接口讓用戶(hù)去重寫(xiě),比如如果想添加攔截器的時(shí)候,需要去重寫(xiě)一下addInterceptors()這個(gè)方法,去配置自定義的攔截器。我們可以看一下WebMvcConfigurerAdapter提供了哪些接口來(lái)供我們使用。

          public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {
              /*配置路徑匹配參數(shù)*/
              public void configurePathMatch(PathMatchConfigurer configurer) {}
              /*配置Web Service或REST API設(shè)計(jì)中內(nèi)容協(xié)商,即根據(jù)客戶(hù)端的支持內(nèi)容格式情況來(lái)封裝響應(yīng)消息體,如xml,json*/
              public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {}
              /*配置路徑匹配參數(shù)*/
              public void configureAsyncSupport(AsyncSupportConfigurer configurer) {}
              /* 使得springmvc在接口層支持異步*/
              public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {}
              /* 注冊(cè)參數(shù)轉(zhuǎn)換和格式化器*/
              public void addFormatters(FormatterRegistry registry) {}
              /* 注冊(cè)配置的攔截器*/
              public void addInterceptors(InterceptorRegistry registry) {}
              /* 自定義靜態(tài)資源映射*/
              public void addResourceHandlers(ResourceHandlerRegistry registry) {}
              /* cors跨域訪(fǎng)問(wèn)*/
              public void addCorsMappings(CorsRegistry registry) {}
              /* 配置頁(yè)面直接訪(fǎng)問(wèn),不走接口*/
              public void addViewControllers(ViewControllerRegistry registry) {}
              /* 注冊(cè)自定義的視圖解析器*/
              public void configureViewResolvers(ViewResolverRegistry registry) {}
              /* 注冊(cè)自定義控制器(controller)方法參數(shù)類(lèi)型*/
              public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {}
              /* 注冊(cè)自定義控制器(controller)方法返回類(lèi)型*/
              public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {}
              /* 重載會(huì)覆蓋掉spring mvc默認(rèn)注冊(cè)的多個(gè)HttpMessageConverter*/
              public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {}
              /* 僅添加一個(gè)自定義的HttpMessageConverter,不覆蓋默認(rèn)注冊(cè)的HttpMessageConverter*/
              public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {}
              /* 注冊(cè)異常處理*/
              public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {}
              /* 多個(gè)異常處理,可以重寫(xiě)次方法指定處理順序等*/
              public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {}
          }

          WebMvcConfigurerAdapter提供了很多的接口供用戶(hù)去實(shí)現(xiàn)自定義的配置項(xiàng)。下面挑幾個(gè)比較重要的介紹一下如何使用這些接口來(lái)自定義配置。

          (1)注冊(cè)攔截器

          首先,編寫(xiě)攔截器的代碼:

          public class LoginInterceptor extends HandlerInterceptorAdapter {
          
              private static final Logger logger=LoggerFactory.getLogger(LoginInterceptor.class);
          
              @Override
              public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                  logger.info("-----------------------------");
                  logger.info(request.getRequestedSessionId());
                  logger.info("-----------------------------");
                  return true;
              }
          }

          這里只打印相關(guān)信息,然后,需要寫(xiě)一個(gè)config類(lèi)去配置這個(gè)攔截器:

          @Configuration
          public class WebConfig extends WebMvcConfigurerAdapter {
              /*
              * 攔截器配置*/
              @Override
              public void addInterceptors(InterceptorRegistry registry) {
                  registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**");
          }

          配置類(lèi)繼承了WebMvcConfigurerAdapter這個(gè)類(lèi),并且重寫(xiě)了addInterceptors這個(gè)方法,在方法中,注冊(cè)了上面編寫(xiě)的攔截器,并且為此攔截器配置了攔截路徑,這樣一來(lái)就算是配置好了這個(gè)攔截器。

          (2)配置CORS跨域

          只需要在上面的webConfig里重寫(xiě)WebMvcConfigurerAdapter的addCorsMappings方法就可以獲得基于spring的跨域支持。

          /**
               * 跨域CORS配置
               * @param registry
               */
              @Override
              public void addCorsMappings(CorsRegistry registry) {
                  super.addCorsMappings(registry);
                  registry.addMapping("/**")
                          .allowedHeaders("*")
                          .allowedMethods("POST","GET")
                          .allowedOrigins("http://...")
                          .allowCredentials(true);
              }

          (3)配置ViewController

          當(dāng)首頁(yè)或者登陸頁(yè)的頁(yè)面對(duì)外暴露,不需要加載任何的配置的時(shí)候,這些頁(yè)面將不通過(guò)接口層,而是直接訪(fǎng)問(wèn),這時(shí),就需要配置ViewController指定請(qǐng)求路徑直接到頁(yè)面。

          /**
               * 視圖控制器配置
               * @param registry
               */
              @Override
              public void addViewControllers(ViewControllerRegistry registry) {
                  super.addViewControllers(registry);
                  registry.addViewController("/").setViewName("forward:/index.html");
              }

          (4)配置ViewResolver

          通常在使用jsp的項(xiàng)目中,會(huì)基于spring mvc配置的文件去配置視圖解析器,通過(guò)重寫(xiě)WebMvcConfigurerAdapter里的configureViewResolvers也可以將自己定義的InternalResourceViewResolver配置整合進(jìn)spring中。

          /**
               * 配置請(qǐng)求視圖映射
               *
               * @return
               */
              @Bean
              public InternalResourceViewResolver resourceViewResolver() {
                  InternalResourceViewResolver internalResourceViewResolver=new InternalResourceViewResolver();
                  //請(qǐng)求視圖文件的前綴地址
                  internalResourceViewResolver.setPrefix("/WEB-INF/jsp/");
                  //請(qǐng)求視圖文件的后綴
                  internalResourceViewResolver.setSuffix(".jsp");
                  return internalResourceViewResolver;
              }
          
              @Override
              public void configureViewResolvers(ViewResolverRegistry registry) {
                  super.configureViewResolvers(registry);
                  registry.viewResolver(resourceViewResolver());
              }

          可以看一下ViewResolverRegistry中的代碼:

          public UrlBasedViewResolverRegistration jsp() {
                  return this.jsp("/WEB-INF/", ".jsp");
              }
          
              public UrlBasedViewResolverRegistration jsp(String prefix, String suffix) {
                  InternalResourceViewResolver resolver=new InternalResourceViewResolver();
                  resolver.setPrefix(prefix);
                  resolver.setSuffix(suffix);
                  this.viewResolvers.add(resolver);
                  return new UrlBasedViewResolverRegistration(resolver);
              }

          可以看到,即使不去配置,spring也會(huì)新建一個(gè)默認(rèn)的視圖解析器。十分方便。

          (5)配置Formatter

          當(dāng)請(qǐng)求的參數(shù)中帶有日期的參數(shù)的時(shí)候,可以在此配置formatter使得接收到日期參數(shù)格式統(tǒng)一。

          @Override
              public void addFormatters(FormatterRegistry registry) {
                  registry.addFormatter(new Formatter<Date>() {
                      @Override
                      public Date parse(String date, Locale locale) {
                          return new Date(Long.parseLong(date));
                      }
          
                      @Override
                      public String print(Date date, Locale locale) {
                          return Long.valueOf(date.getTime()).toString();
                      }
                  });
              }

          4.4.4 WebMvcConfigurer

          WebMvcConfigurer配置類(lèi)其實(shí)是Spring內(nèi)部的一種配置方式,采用JavaBean的形式來(lái)代替?zhèn)鹘y(tǒng)的xml配置文件形式進(jìn)行針對(duì)框架個(gè)性化定制,可以自定義一些Handler,Interceptor,ViewResolver,MessageConverter。基于java-based方式的spring mvc配置,需要?jiǎng)?chuàng)建一個(gè)配置類(lèi)并實(shí)現(xiàn)WebMvcConfigurer 接口;

          在Spring Boot 1.5版本都是靠重寫(xiě)WebMvcConfigurerAdapter的方法來(lái)添加自定義攔截器,消息轉(zhuǎn)換器等。SpringBoot 2.0 后,該類(lèi)被標(biāo)記為@Deprecated(棄用)。官方推薦直接實(shí)現(xiàn)WebMvcConfigurer或者直接繼承WebMvcConfigurationSupport,方式一實(shí)現(xiàn)WebMvcConfigurer接口(推薦),方式二繼承WebMvcConfigurationSupport類(lèi),

          5 SpringMVC的高級(jí)配置

          5.1 文件上傳配置

          Spring MVC 為文件上傳提供了直接支持,這種支持是通過(guò)即插即用的 MultipartResolver 實(shí)現(xiàn)的。Spring 使用 Jakarta Commons FileUpload 技術(shù)實(shí)現(xiàn)了一個(gè) MultipartResolver 實(shí)現(xiàn)類(lèi):CommonsMultipartResolver。

          在 Spring MVC 上下文中默認(rèn)沒(méi)有裝配 MultipartResolver,因此默認(rèn)情況下不能處理文件的上傳工作。如果想使用 Spring 的文件上傳功能,則需要先在上下文中配置 MultipartResolver。

          5.1.1 配置 MultipartResolver

          下面使用 CommonsMultipartResolver 配置一個(gè) MultipartResolver 解析器。

          <!-- 文件上傳 -->
          <bean id="multipartResolver"
            class="org.springframework.web.multipart.commons.CommonsMultipartResolver"
            p:defaultEncoding="UTF-8"//①請(qǐng)求的編碼格式,默認(rèn)為ISO-8859-1
            p:maxUploadSize="5000000"//②上傳文件的大小上限,單位為字節(jié)(5MB)
            p:uploadTempDir="file://d:/temp"/>//③上傳文件的臨時(shí)路徑

          defaultEncoding 必須和用戶(hù) JSP 的 pageEncoding 屬性一致,以便正確讀取表單的內(nèi)容。uploadTempDir 是文件上傳過(guò)程中所使用的臨時(shí)目錄,文件上傳完成后,臨時(shí)目錄中的臨時(shí)文件會(huì)被自動(dòng)清除。

          為了讓 CommonsMultipartResolver 正常工作,必須先將 Jakarta Commons FileUpload 及 Jakarta Commons io 的類(lèi)包添加到類(lèi)路徑下。

          5.1.2 編寫(xiě)控制器和文件上傳表單頁(yè)面

          在 UserController 中添加一個(gè)用于處理用戶(hù)頭像上傳的方法,如下面代碼所示。

          @Controller
          @RequestMapping("/user")
          public class UserController {
              @RequestMapping(value="/uploadPage")//①
              public String updatePage() {    
                  return "uploadPage";
              }
              
              @RequestMapping(value="/upload")
              public String updateThumb(@RequestParam("name") String name,
                                        @RequestParam("file") MultipartFile file) throws Exception{
                                        //②上傳的文件自動(dòng)綁定到MultipartFile中
                  if (!file.isEmpty()) {
                      file.transferTo(new File("d:/temp/"+file.getOriginalFilename()));
                      return "redirect:success.html";
                  }else{
                      return "redirect:fail.html";
                  }
              }
          }

          Spring MVC 會(huì)將上傳文件綁定到 MultipartFile 對(duì)象中。MultipartFile 提供了獲取上傳文件內(nèi)容、文件名等方法,通過(guò)其 transferTo() 方法還可將文件存儲(chǔ)到硬件中,具體說(shuō)明如下。

          • byte[] getBytes():獲取文件數(shù)據(jù)。
          • String getContentType():獲取文件 MIME 類(lèi)型,如 image/pjpeg、text/plain 等。
          • InputStream getInputStream():獲取文件流。
          • String getName():獲取表單中文件組件的名字。
          • String getOriginalFilename():獲取上傳文件的原名。
          • long getSize():獲取文件的字節(jié)大小,單位為 Byte.
          • boolean isEmpty():是否有上傳的文件。
          • void transferTo(File dest):可以使用該文件將上傳文件保存到一個(gè)目標(biāo)文件中。

          負(fù)責(zé)上傳文件的表單和一般表單有一些區(qū)別,表單的編碼類(lèi)型必須是 multipart/form-data 類(lèi)型。

          <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
          <%@ taglib prefix="c"  uri="http://java.sun.com/jsp/jstl/core" %>
          <html>
              <head>
                  <title>請(qǐng)上傳用戶(hù)頭像</title>
              </head>
              <body>
                  <h1>
                      請(qǐng)選擇上傳的頭像文件
                  </h1>
                  <form method="post" action="<c:url value="/user/upload.html"/>" enctype="multipart/form-data">//指定表單內(nèi)容類(lèi)型,以便支持文件上傳
                      <input type="text" name="name" />
                      <input type="file" name="file" />//②上傳文件的組件名
                      <input type="submit" />
                  </form>
              </body>
          </html>

          5.2 自定義HttpMessageConverter

          5.2.1 簡(jiǎn)介

          在Spring MVC中,HttpMessageConverter主要用于將HTTP請(qǐng)求的輸入內(nèi)容轉(zhuǎn)換為指定的Java對(duì)象,以及將Java對(duì)象轉(zhuǎn)換為HTTP響應(yīng)的輸出內(nèi)容。這種靈活的消息轉(zhuǎn)換機(jī)制就是利用HttpMessageConverter來(lái)實(shí)現(xiàn)的。

          Spring MVC提供了多個(gè)默認(rèn)的HttpMessageConverter實(shí)現(xiàn),包括處理JSON、XML、文本等格式的Converter。另外,我們也可以自定義HttpMessageConverter來(lái)處理其他格式的數(shù)據(jù)。

          Spring MVC提供了兩個(gè)注解:@RequestBody和@ResponseBody,分別用于完成請(qǐng)求報(bào)文到對(duì)象和對(duì)象到響應(yīng)報(bào)文的轉(zhuǎn)換。

          然而,有時(shí)候默認(rèn)的HttpMessageConverter無(wú)法滿(mǎn)足特定的需求,例如,當(dāng)我們需要處理的數(shù)據(jù)格式?jīng)]有默認(rèn)的Converter時(shí),或者我們需要對(duì)現(xiàn)有的Converter進(jìn)行擴(kuò)展時(shí),就需要自定義HttpMessageConverter。

          自定義HttpMessageConverter可以讓我們更加靈活地控制數(shù)據(jù)轉(zhuǎn)換的過(guò)程,例如我們可以自定義轉(zhuǎn)換規(guī)則、異常處理等。

          5.2.2 實(shí)戰(zhàn)案例

          接下來(lái)我們通過(guò)一個(gè)實(shí)例講解如何自定義HttpMessageConverter。

          需求

          接口請(qǐng)求數(shù)據(jù)格式:

          xxx|yyy|zzz|...

          接口返回JSON數(shù)據(jù)格式

          {
              "xxx": xxx,
              "yyy": yyy,
              "zzz": zzz,
              ...
          }

          其實(shí)就上面的數(shù)據(jù)格式,我們完全可以不用自定義HttpMessageConverter也是完全可以實(shí)現(xiàn)的。我們這里主要就是教大家如何在特殊的需求下實(shí)現(xiàn)特定的數(shù)據(jù)轉(zhuǎn)換處理。

          (1)自定義HttpMessageConverter轉(zhuǎn)換器

          public class PackHttpMessageConverter implements HttpMessageConverter<Object> {
              
            // 設(shè)置自定義的Content-Type類(lèi)型,這樣就限定了只有請(qǐng)求的內(nèi)容類(lèi)型是該類(lèi)型才會(huì)使用該轉(zhuǎn)換器進(jìn)行處理
            private static final MediaType PACK=new MediaType("application", "pack", StandardCharsets.UTF_8) ;
          
          
            // 判斷當(dāng)前轉(zhuǎn)換器是否能夠讀取數(shù)據(jù)
            @Override
            public boolean canRead(Class<?> clazz, MediaType mediaType) {
              return PACK.equals(mediaType) ;
            }
            // 判斷當(dāng)前轉(zhuǎn)換器是否可以將結(jié)果數(shù)據(jù)進(jìn)行輸出到客戶(hù)端
            @Override
            public boolean canWrite(Class<?> clazz, MediaType mediaType) {
              return true ;
            }
            // 返回當(dāng)前轉(zhuǎn)換器只支持application/pack類(lèi)型的數(shù)據(jù)格式
            @Override
            public List<MediaType> getSupportedMediaTypes() {
              return Arrays.asList(PACK) ;
            }
          
          
            // 從請(qǐng)求中讀取數(shù)據(jù)
            @Override
            public Object read(Class<? extends Object> clazz, HttpInputMessage inputMessage)
                throws IOException, HttpMessageNotReadableException {
              InputStream is=inputMessage.getBody() ;
              String res=IOUtils.toString(is, StandardCharsets.UTF_8) ;
              // 這里簡(jiǎn)單處理只針對(duì)Users類(lèi)型的對(duì)象處理
              if (clazz==Users.class) {
                try {
                  // 創(chuàng)建實(shí)例
                  Users target=(Users) clazz.newInstance() ;
                  String[] s=res.split("\\|");
                  target.setId(Long.valueOf(s[0])) ;
                  target.setName(s[1]) ;
                  target.setAge(Integer.valueOf(s[2])) ;
                  target.setIdNo(s[3]) ;
                  return target ;
                } catch (InstantiationException | IllegalAccessException e) {
                  e.printStackTrace() ;
                }
              }
              return null ;
            }
          
          
            // 將Controller方法返回值寫(xiě)到客戶(hù)端
            @Override
            public void write(Object t, MediaType contentType, HttpOutputMessage outputMessage)
                throws IOException, HttpMessageNotWritableException {
              // 設(shè)置響應(yīng)頭為json格式
              outputMessage.getHeaders().add("Content-Type", "application/json;charset=UTF-8") ;
              ObjectMapper mapper=new ObjectMapper() ;
              OutputStream os=outputMessage.getBody();
              // 輸出結(jié)果內(nèi)容
              os.write(mapper.writeValueAsString(t).getBytes(StandardCharsets.UTF_8)) ;
              os.flush(); 
            }
            
          }

          (2)將PackHttpMessageConverter注冊(cè)到容器中

          @Configuration
          public class WebMvcConfig implements WebMvcConfigurer {
            
            @Override
            public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
              converters.add(new PackHttpMessageConverter()) ;
            }
          }

          到這里自定義HttpMessageConverter及注冊(cè)到容器中就全部完成了,開(kāi)發(fā)還是比較簡(jiǎn)單,接下來(lái)做測(cè)試

          (3)接口

          // 方法非常簡(jiǎn)單還是用的那些常用的類(lèi),@RequestBody接收請(qǐng)求body中的內(nèi)容
          @PostMapping("/i")
          public Object i(@RequestBody Users user) {
            System.out.println(handlerAdapter) ;
            return user ;
          }

          (4)通過(guò)Postman測(cè)試接口

          設(shè)置請(qǐng)求的header





          似乎沒(méi)有任何的問(wèn)題,其實(shí)你只要在寫(xiě)的方法中打印下日志,或者調(diào)試下,你會(huì)發(fā)現(xiàn)你的write方法根本就沒(méi)有被調(diào)用,也就是說(shuō)寫(xiě)數(shù)據(jù)并沒(méi)有使用到我們自定義的實(shí)現(xiàn),這是因?yàn)橛袃?yōu)先級(jí)比我們自定義的轉(zhuǎn)換器高,所以要想讓寫(xiě)消息也調(diào)用自定義的。我們需要如下修改注冊(cè)方式:

          public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
            converters.add(0, new PackHttpMessageConverter()) ;
          }

          這樣我們自定義的轉(zhuǎn)換器就排到了第一的位置,這樣就會(huì)調(diào)用我們自定義的write方法。

          5.2.3 實(shí)現(xiàn)原理

          請(qǐng)求參數(shù)由于添加了@RequestBody,所以方法的參數(shù)解析器使用的是RequestResponseBodyMethodProcessor。

          public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
            protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
                Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
              // ...
              // 讀取請(qǐng)求數(shù)據(jù);調(diào)用父類(lèi)方法
              Object arg=readWithMessageConverters(inputMessage, parameter, paramType);
              // ...
            }
          }

          AbstractMessageConverterMethodArgumentResolver

          public abstract class AbstractMessageConverterMethodArgumentResolver {
            protected <T> Object readWithMessageConverters(...) {
              // ...
              // 遍歷所有的消息轉(zhuǎn)換器
              for (HttpMessageConverter<?> converter : this.messageConverters) {
                  Class<HttpMessageConverter<?>> converterType=(Class<HttpMessageConverter<?>>) converter.getClass();
                  GenericHttpMessageConverter<?> genericConverter=(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
                  // 判斷當(dāng)前轉(zhuǎn)換器是否讀,也就上面我們自定義中實(shí)現(xiàn)的canRead方法
                  if (genericConverter !=null ? genericConverter.canRead(targetType, contextClass, contentType) :
                      (targetClass !=null && converter.canRead(targetClass, contentType))) {
                    if (message.hasBody()) {
                      HttpInputMessage msgToUse=getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
                      // 讀取具體的數(shù)據(jù)內(nèi)容
                      body=(genericConverter !=null ? genericConverter.read(targetType, contextClass, msgToUse) : ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
                      body=getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
                    }
                    else {
                      body=getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
                    }
                    break;
                  }
                }
            }
          }

          原理也比較的簡(jiǎn)單。

          自定義HttpMessageConverter是Spring MVC中一個(gè)強(qiáng)大的工具,它可以幫助開(kāi)發(fā)者更加靈活地控制數(shù)據(jù)轉(zhuǎn)換的過(guò)程,滿(mǎn)足特定的需求。

          5.2.4 MappingJackson2HttpMessageConverter

          MappingJackson2HttpMessageConverter是springboot中默認(rèn)的Json消息轉(zhuǎn)換器。這個(gè)類(lèi)的繼承圖如下:



          這個(gè)類(lèi)的主要實(shí)現(xiàn)邏輯是在AbstractJackson2HttpMessageConverter抽象類(lèi)中實(shí)現(xiàn)的。這個(gè)列實(shí)現(xiàn)序列化與反序列化的最核心組件是ObjectMapper這個(gè)類(lèi)。

          MappingJackson2HttpMessageConverter是一個(gè)Spring消息轉(zhuǎn)換器,用于在Web應(yīng)用程序中處理請(qǐng)求和響應(yīng)內(nèi)容。它的工作原理如下:

          1. 請(qǐng)求處理:當(dāng)接收到請(qǐng)求時(shí),MappingJackson2HttpMessageConverter將請(qǐng)求內(nèi)容(通常是JSON格式)轉(zhuǎn)換為Java對(duì)象。它使用Jackson庫(kù)來(lái)完成此操作。
          2. 響應(yīng)處理:當(dāng)生成響應(yīng)時(shí),MappingJackson2HttpMessageConverter將Java對(duì)象轉(zhuǎn)換為JSON格式的響應(yīng)內(nèi)容。它再次使用Jackson庫(kù)來(lái)完成此操作。

          MappingJackson2HttpMessageConverter通過(guò)實(shí)現(xiàn)HttpMessageConverter接口并重寫(xiě)相關(guān)方法,完成請(qǐng)求和響應(yīng)內(nèi)容的轉(zhuǎn)換。當(dāng)Spring處理請(qǐng)求或生成響應(yīng)時(shí),它會(huì)自動(dòng)選擇合適的消息轉(zhuǎn)換器,并使用它來(lái)處理請(qǐng)求和響應(yīng)內(nèi)容。

          MappingJackson2HttpMessageConverter如何將請(qǐng)求內(nèi)容轉(zhuǎn)換為Java對(duì)象和響應(yīng)內(nèi)容轉(zhuǎn)換為JSON

          • 請(qǐng)求內(nèi)容到Java對(duì)象的轉(zhuǎn)換:
          1. MappingJackson2HttpMessageConverter重寫(xiě)了read()方法,該方法接收請(qǐng)求內(nèi)容和Java對(duì)象類(lèi)型作為參數(shù)。
          2. 該方法使用ObjectMapper類(lèi),它是Jackson庫(kù)的主要組件,將請(qǐng)求內(nèi)容解析為Java對(duì)象。
          3. 解析的Java對(duì)象作為方法的返回值,并作為請(qǐng)求的參數(shù)傳遞到控制器方法中。
          • 響應(yīng)內(nèi)容到JSON的轉(zhuǎn)換: MappingJackson2HttpMessageConverter
          1. 重寫(xiě)了write()方法,該方法接收J(rèn)ava對(duì)象和輸出流作為參數(shù)。
          2. 該方法使用ObjectMapper類(lèi)將Java對(duì)象序列化為JSON格式的內(nèi)容。
          3. 序列化的內(nèi)容寫(xiě)入輸出流,并作為響應(yīng)內(nèi)容返回給客戶(hù)端。

          (1)簡(jiǎn)單使用:

          @Configuration
          public class MvcConfig implements WebMvcConfigurer {
            
              @Override
              public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
                  converters.add(new MappingJackson2HttpMessageConverter(objectMapper()));
              }
              
              @Bean
              public ObjectMapper objectMapper() {
                  return new Jackson2ObjectMapperBuilder()
                          .propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE)
                          .featuresToEnable(SerializationFeature.INDENT_OUTPUT)
                          .build();
              }
          }
          
          

          這樣,在控制器方法中使用@RequestBody或@ResponseBody注解時(shí),就可以通過(guò)MappingJackson2HttpMessageConverter進(jìn)行序列化/反序列化操作了。

          (2)自定義MappingJackson2HttpMessageConverter

          將時(shí)間戳序列化為L(zhǎng)ocalDateTime,將LocalDateTime反序列化為時(shí)間戳

          public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
              MappingJackson2HttpMessageConverter converter=new MappingJackson2HttpMessageConverter();
              ObjectMapper mapper=new ObjectMapper();
              mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
              // 時(shí)間對(duì)象自定義格式化
              JavaTimeModule javaTimeModule=new JavaTimeModule();
              javaTimeModule.addDeserializer(LocalDateTime.class, new JsonDeserializer<LocalDateTime>() {
                  @Override
                  public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
                      long timestamp=Long.parseLong(jsonParser.getText());
                      Instant instant=Instant.ofEpochMilli(timestamp);
                      return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
                  }
              });
              javaTimeModule.addSerializer(LocalDateTime.class, new JsonSerializer<LocalDateTime>() {
                  @Override
                  public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
                      jsonGenerator.writeNumber(localDateTime.toInstant(ZoneOffset.ofHours(8)).toEpochMilli());
                  }
              });
              javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
              javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
              javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
              javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
              // Long轉(zhuǎn)換為String傳輸
              javaTimeModule.addSerializer(Long.class, ToStringSerializer.instance);
              mapper.registerModule(javaTimeModule);
          
              converter.setObjectMapper(mapper);
              return converter;
          }
          

          如果不生效:

          • 想要生效,其實(shí)只要把自定義的MappingJackson2HttpMessageConverter位置調(diào)整一下,
          • @Bean MappingJackson2HttpMessageConverter方式,注意這里是實(shí)現(xiàn)WebMvcConfigurer接口而不是繼承WebMvcConfigurationSuppor
          • 如果是繼承WebMvcConfigurationSupport的,要重寫(xiě)extendMessageConverters方法,這里不再展開(kāi)了

          5.2.5 StringHttpMessageConverter

          StringHttpMessageConverter是Spring MVC中用于讀寫(xiě)HTTP消息的字符串轉(zhuǎn)換器。它可以將請(qǐng)求的輸入流轉(zhuǎn)換為字符串,同樣也可以將字符串寫(xiě)入HTTP響應(yīng)中

          import org.springframework.context.annotation.Bean;
          import org.springframework.context.annotation.Configuration;
          import org.springframework.http.converter.StringHttpMessageConverter;
          import org.springframework.web.servlet.config.annotation.EnableWebMvc;
          import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
           
          @Configuration
          @EnableWebMvc
          public class WebConfig implements WebMvcConfigurer {
           
              @Bean
              public StringHttpMessageConverter stringHttpMessageConverter() {
                  StringHttpMessageConverter converter=new StringHttpMessageConverter();
                  converter.setWriteAcceptCharset(false); // 設(shè)置是否在寫(xiě)入響應(yīng)時(shí)發(fā)送AcceptedCharset
                  return converter;
              }
          }

          5.3 服務(wù)器端推送技術(shù)

          5.3.1 SSE

          SSE技術(shù)是基于單工通信模式,只是單純的客戶(hù)端向服務(wù)端發(fā)送請(qǐng)求,服務(wù)端不會(huì)主動(dòng)發(fā)送給客戶(hù)端。服務(wù)端采取的策略是抓住這個(gè)請(qǐng)求不放,等數(shù)據(jù)更新的時(shí)候才返回給客戶(hù)端,當(dāng)客戶(hù)端接收到消息后,再向服務(wù)端發(fā)送請(qǐng)求,周而復(fù)始。

          注意:因?yàn)镋ventSource對(duì)象是SSE的客戶(hù)端,可能會(huì)有瀏覽器對(duì)其不支持,但谷歌、火狐、360是可以的,IE不可以。

          另外WebSocket技術(shù)是雙工模式。

          服務(wù)端代碼如下:

          //本文使用的是Spring4.x,無(wú)需其他類(lèi)庫(kù),;
          
          import java.text.SimpleDateFormat;
          import java.util.Date;
          import java.util.Locale;
          import org.springframework.stereotype.Controller;
          import org.springframework.ui.Model;
          import org.springframework.web.bind.annotation.RequestMapping;
          import org.springframework.web.bind.annotation.RequestMethod;
          import org.springframework.web.bind.annotation.ResponseBody;
          
          @Controller
          public class HomeController {
              
              @RequestMapping(value="/", method=RequestMethod.GET)
              public String home(Locale locale, Model model) {
                  return "sse";
              }
              
              @RequestMapping(value="push",produces="text/event-stream")
              public @ResponseBody String push(){
                  System.out.println("push msg..");
                  try {  
                      Thread.sleep(2000);  
                  } catch (InterruptedException e) {  
                      e.printStackTrace();  
                  }  
                  
                  //注意:返回?cái)?shù)據(jù)的格式要嚴(yán)格按照這樣寫(xiě),‘\n\n’不可少
                  return "data:current time: "+new SimpleDateFormat("YYYY-MM-dd hh:mm:ss").format(new Date())+"\n\n";  
              }
          }

          客戶(hù)端代碼如下,sse.jsp:

          <%@ page language="java" contentType="text/html; charset=UTF-8"
              pageEncoding="UTF-8"%>
          <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
          <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
          <html>
          <head>
          <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
          <title>SSE方式消息推送</title>
          </head>
          <body>
          
              <div id="msgFromPush"></div>
                <!--這里的jquery僅僅用于數(shù)據(jù)的展示,不影響消息推送-->
              <script type="text/javascript" src="<c:url value='resources/jquery-1.10.2.js'/>"></script>
              <script type="text/javascript">
                  if(!!window.EventSource){
                      var source=new EventSource('push');
                      s='';
                      source.addEventListener('message',function(e){
                          console.log("get message"+e.data);
                          s+=e.data+"<br/>";
                          $("#msgFromPush").html(s);
                      });
                      
                      source.addEventListener('open',function(e){
                          console.log("connect is open");
                      },false);
                      
                      source.addEventListener('error',function(e){
                          if(e.readyState==EventSource.CLOSE){
                              console.log("connect is close");
                          }else{
                              console.log(e.readyState);
                          }
                      },false);
                  }else{
                      console.log("web is not support");
                      
                  }
                  
              </script>
          </body>
          </html>

          運(yùn)行結(jié)果:



          5.3.2 Servlet3.0+異步方法處理

          Servlet3.0+ 異步處理方法通過(guò)設(shè)置動(dòng)態(tài)Servlet(即Dynamic)支持異步處理,在客戶(hù)端(瀏覽器)以ajax形式不斷發(fā)送請(qǐng)求,從而獲得信息。

          (1)動(dòng)態(tài)Servlet支持異步處理

          import javax.servlet.ServletContext;
          import javax.servlet.ServletException;
          import javax.servlet.ServletRegistration.Dynamic;
          
          import org.springframework.web.WebApplicationInitializer;
          import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
          import org.springframework.web.servlet.DispatcherServlet;
          
          import com.config.MvcConfig;
          
          /** Use class that implements WebApplicationInitializer interface to  substitute web.xml
           * @author apple
           *
           */
          public class WebInitializer implements WebApplicationInitializer {
          
              @Override
              public void onStartup(ServletContext servletContext) throws ServletException {
                  AnnotationConfigWebApplicationContext applicationContext=         new AnnotationConfigWebApplicationContext();
                  applicationContext.register(MvcConfig.class); // process annotated class MvcConfig
                  applicationContext.setServletContext(servletContext);  // make applicationContext and servletContext related
                  applicationContext.refresh();
          
                  Dynamic servlet=servletContext.addServlet("dispatcher", new DispatcherServlet(applicationContext));
                  servlet.addMapping("/");
                  servlet.setLoadOnStartup(1);
                  servlet.setAsyncSupported(true);
              }   
          }

          (2)Service Bean

          該類(lèi)僅僅是業(yè)務(wù)邏輯,與異步實(shí)現(xiàn)關(guān)系無(wú)關(guān)。

          import org.springframework.scheduling.annotation.Scheduled;
          import org.springframework.stereotype.Service;
          import org.springframework.web.context.request.async.DeferredResult;
          
          /** 用于異步Servlet 3.0+的服務(wù)器端推送測(cè)試,
           * 為Controller提供一個(gè)異步的定時(shí)更新的DeferredResult<String>
           * @author apple
           *
           */
          @Service
          public class PushService {
              private DeferredResult<String> deferredResult ;
          
              public DeferredResult<String> getAyncUpdateDeferredResult() {
                  this.deferredResult=new DeferredResult<>();
                  return deferredResult;
              }
          
              /**以下說(shuō)明通過(guò)查看@Scheduled注解獲得。
               * 由于@Scheduled注解的處理是通過(guò)注冊(cè)一個(gè)ScheduledAnnotationBeanPostProcessor完成的,
               * 而后者是對(duì)方法被@Scheduled注解了的Bean,按照該注解的要求,
               * 通過(guò)調(diào)用一個(gè)TaskScheduler進(jìn)行post process。
               * 因此對(duì)于實(shí)例化的Bean,必須完成@Scheduled注解的方法后才能被調(diào)用。
               */
              @Scheduled(fixedDelay=5000)
              public void refresh() {
                  if (this.deferredResult !=null) {
                      this.deferredResult.setResult(String.valueOf(System.currentTimeMillis()));
                  }
              }
          }

          (3) Controller

          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.stereotype.Controller;
          import org.springframework.web.bind.annotation.RequestMapping;
          import org.springframework.web.bind.annotation.ResponseBody;
          import org.springframework.web.context.request.async.DeferredResult;
          
          import com.service.PushService;
          
          @Controller
          public class PushController {
              @Autowired
              PushService pushService; 
          
              @RequestMapping(value="/defer")
              @ResponseBody
              public DeferredResult<String> deferredCall() {
                // 通過(guò)Service Bean獲取異步更新的DeferredReuslt<String>
                  return pushService.getAyncUpdateDeferredResult(); 
              }
          }

          (4) 測(cè)試頁(yè)面(jsp)

          測(cè)試頁(yè)面采用ajax不斷發(fā)送請(qǐng)求,這些請(qǐng)求構(gòu)成了并發(fā)請(qǐng)求。由于前面Servlet支持異步處理,請(qǐng)求到達(dá)服務(wù)端后,直到Service Bean實(shí)例化,并按照@Scheduled要求延時(shí)至設(shè)定的時(shí)間(例中5000ms)進(jìn)行了設(shè)置,才通過(guò)Controller中以@RequestMapping注解的方法發(fā)送response到瀏覽器。 測(cè)試結(jié)果: 瀏覽器頁(yè)面顯示(數(shù)據(jù)為:System.currentTimeMillis()) 1528273203260 1528273208265 1528273213271 1528273218278 1528273223282 1528273228285 1528273233290 1528273238296 1528273243298 1528273248302

          由結(jié)果可知,請(qǐng)求確實(shí)是并發(fā)的,Servlet 3.0+對(duì)請(qǐng)求進(jìn)行了異步處理。

          6 SpringMVC的測(cè)試

          6.1 Mock 測(cè)試簡(jiǎn)介

          1、什么是 mock 測(cè)試

          在測(cè)試過(guò)程中,對(duì)于某些不容易構(gòu)造或者不容易獲取的對(duì)象,用一個(gè)虛擬的對(duì)象來(lái)創(chuàng)建以便測(cè)試的測(cè)試方法,就是 mock 測(cè)試在測(cè)試過(guò)程中,對(duì)于某些不容易構(gòu)造或者不容易獲取的對(duì)象,用一個(gè)虛擬的對(duì)象來(lái)創(chuàng)建以便測(cè)試的測(cè)試方法,就是mock測(cè)試

          • 虛擬的對(duì)象就是 mock 對(duì)象。
          • mock 對(duì)象就是真實(shí)對(duì)象在調(diào)試期間的代替品。

          2、為什么使用 mock 測(cè)試

          • 避免開(kāi)發(fā)模塊之間的耦合
          • 輕量、簡(jiǎn)單、靈活

          3、MockMVC 介紹

          基于 RESTful 風(fēng)格的 SpringMVC 的測(cè)試,我們可以測(cè)試完整的 Spring MVC 流程,即從 URL請(qǐng)求到控制器處理,再到視圖渲染都可以測(cè)試。

          1)MockMvcBuilder

          MockMvcBuilder 是用來(lái)構(gòu)造 MockMvc 的構(gòu)造器,其主要有兩個(gè)實(shí)現(xiàn):StandaloneMockMvcBuilder 和 DefaultMockMvcBuilder,對(duì)于我們來(lái)說(shuō)直接使用靜態(tài)工廠(chǎng) MockMvcBuilders 創(chuàng)建即可。 MockMvcBuilder 是用來(lái)構(gòu)造 MockMvc 的構(gòu)造器,其主要有兩個(gè)實(shí)現(xiàn):StandaloneMockMvcBuilder 和 DefaultMockMvcBuilder,對(duì)于我們來(lái)說(shuō)直接使用靜態(tài)工廠(chǎng) MockMvcBuilders 創(chuàng)建即可。

          2)MockMvcBuilders

          負(fù)責(zé)創(chuàng)建 MockMvcBuilder 對(duì)象,有兩種創(chuàng)建方式:

          standaloneSetup(Object... controllers):通過(guò)參數(shù)指定一組控制器,這樣就不需要從上下文獲取了。

          webAppContextSetup(WebApplicationContext wac):指定 WebApplicationContext,將會(huì)從該上下文獲取相應(yīng)的控制器并得到相應(yīng)的 MockMvc,本章節(jié)下面測(cè)試用例均使用這種方式創(chuàng)建 MockMvcBuilder 對(duì)象。

          3)MockMvc

          對(duì)于服務(wù)器端的 SpringMVC 測(cè)試支持主入口點(diǎn)。通過(guò) MockMvcBuilder 構(gòu)造MockMvcBuilder 由 MockMvcBuilders 建造者的靜態(tài)方法去建造。

          核心方法:perform(RequestBuilder rb) -- 執(zhí)行一個(gè) RequestBuilder 請(qǐng)求,會(huì)自動(dòng)執(zhí)行SpringMVC 的流程并映射到相應(yīng)的控制器執(zhí)行處理,該方法的返回值是一個(gè) ResultActions。

          4)ResultActions

          (1)andExpect:添加 ResultMatcher 驗(yàn)證規(guī)則,驗(yàn)證控制器執(zhí)行完成后結(jié)果是否正確;

          (2)andDo:添加 ResultHandler 結(jié)果處理器,比如調(diào)試時(shí)打印結(jié)果到控制臺(tái);

          (3)andReturn:最后返回相應(yīng)的 MvcResult;然后進(jìn)行自定義驗(yàn)證/進(jìn)行下一步的異步處理;

          5)MockMvcRequestBuilders

          用來(lái)構(gòu)建請(qǐng)求的,其主要有兩個(gè)子類(lèi) MockHttpServletRequestBuilderMockMultipartHttpServletRequestBuilder(如文件上傳使用),即用來(lái) Mock 客戶(hù)端請(qǐng)求需要的所有數(shù)據(jù)。

          6)MockMvcResultMatchers

          (1)用來(lái)匹配執(zhí)行完請(qǐng)求后的結(jié)果驗(yàn)證

          (2)如果匹配失敗將拋出相應(yīng)的異常

          (3)包含了很多驗(yàn)證 API 方法

          7)MockMvcResultHandlers

          (1)結(jié)果處理器,表示要對(duì)結(jié)果做點(diǎn)什么事情

          (2)比如此處使用 MockMvcResultHandlers.print() 輸出整個(gè)響應(yīng)結(jié)果信息

          8)MvcResult

          (1)單元測(cè)試執(zhí)行結(jié)果,可以針對(duì)執(zhí)行結(jié)果進(jìn)行自定義驗(yàn)證邏輯


          6.2 測(cè)試用例演示

          1、添加依賴(lài)

          <!-- spring 單元測(cè)試組件包 -->
          <dependency>
          	<groupId>org.springframework</groupId>
          	<artifactId>spring-test</artifactId>
          	<version>5.0.7.RELEASE</version>
          </dependency>
          <!-- 單元測(cè)試Junit -->
          <dependency>
          	<groupId>junit</groupId>
          	<artifactId>junit</artifactId>
          	<version>4.12</version>
          </dependency>
          <!-- Mock測(cè)試使用的json-path依賴(lài) -->
          <dependency>
          	<groupId>com.jayway.jsonpath</groupId>
          	<artifactId>json-path</artifactId>
          	<version>2.2.0</version>
          </dependency>

          前兩個(gè) jar 依賴(lài)我們都已經(jīng)接觸過(guò)了,對(duì)于返回視圖方法的測(cè)試這兩個(gè) jar 依賴(lài)已經(jīng)足夠了,第三個(gè) jar 依賴(lài)是用于處理返回 Json 數(shù)據(jù)方法的,這里要明白每個(gè) jar 的具體作用。

          2、被測(cè)試的方法

          @RequestMapping(value="editItem")
          public String editItem(Integer id, Model model) {
          	Item item=itemService.getItemById(id);
          	model.addAttribute("item", item);
          	return "itemEdit";
          }
          
          @RequestMapping(value="getItem")
          @ResponseBody
          public Item getItem(Integer id) {
          	Item item=itemService.getItemById(id);
          	return item;
          }

          這里我們提供了兩個(gè)方法,一個(gè)是返回視圖的方法,另一個(gè)是返回 Json 數(shù)據(jù)的方法,下面我們會(huì)給出測(cè)試類(lèi),分別對(duì)這兩個(gè)方法進(jìn)行測(cè)試。

          3、測(cè)試類(lèi):ItemMockTest

          @RunWith(SpringJUnit4ClassRunner.class)
          @ContextConfiguration(locations="classpath:spring/*.xml")
          @WebAppConfiguration
          public class ItemMockTest {
          
              @Autowired
              private WebApplicationContext context;
          
              private MockMvc mockMvc;
          
              @Before
              public void init() {
                  mockMvc=MockMvcBuilders.webAppContextSetup(context).build();
              }
          }

          這里前兩個(gè)注解就不再解釋了,我們?cè)趯W(xué)習(xí) Spring 與 Junit 整合的時(shí)候已經(jīng)講解過(guò)了,這里說(shuō)一下第三個(gè)注解: @WebAppConfiguration:可以在單元測(cè)試的時(shí)候,不用啟動(dòng) Servlet 容器,就可以獲取一個(gè) Web 應(yīng)用上下文。

          1)返回視圖方法測(cè)試

          @Test
          public void test() throws Exception {
              MvcResult result=mockMvc.perform(MockMvcRequestBuilders.get("/editItem").param("id", "1"))
                      .andExpect(MockMvcResultMatchers.view().name("itemEdit"))
                      .andExpect(MockMvcResultMatchers.status().isOk())
                      .andDo(MockMvcResultHandlers.print())
                      .andReturn();
              Assert.assertNotNull(result.getModelAndView().getModel().get("item"));
          }





          這三句代碼是我們對(duì)結(jié)果的期望,最后打印出了結(jié)果,說(shuō)明執(zhí)行成功,所有期望都達(dá)到了,否則會(huì)直接報(bào)錯(cuò)。從結(jié)果中我們就可以看到這個(gè)請(qǐng)求測(cè)試的情況。

          2、返回 Json 數(shù)據(jù)方法

          @Test
          public void test1() throws Exception {
              mockMvc.perform(MockMvcRequestBuilders.get("/getItem")
                      .param("id", "1")
                      .accept(MediaType.APPLICATION_JSON))
                      .andExpect(MockMvcResultMatchers.status().isOk())
                      .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
                      .andExpect(MockMvcResultMatchers.jsonPath("$.id").value(1))
                      .andExpect(MockMvcResultMatchers.jsonPath("$.name").value("IPhone X"))
                      .andDo(MockMvcResultHandlers.print())
                      .andReturn();
          }



          在這個(gè)方法中比較特殊的就是設(shè)置 MediaType 類(lèi)型,因?yàn)槎际鞘褂?Json 格式,所以設(shè)置了 MediaType.APPLICATION_JSON,jsonPath 用于比對(duì)期望的數(shù)據(jù)是否與返回的結(jié)果一致,這里需要注意的是 "$.id" 這 key 的種形式。


          主站蜘蛛池模板: 一区二区三区四区无限乱码| 精品无码一区二区三区水蜜桃| 美女啪啪一区二区三区| 91无码人妻精品一区二区三区L| 国产拳头交一区二区| 亚洲福利一区二区| 日韩综合无码一区二区| 一区二区福利视频| 亚洲爽爽一区二区三区| 亚洲国产一区二区视频网站| 一区二区三区视频免费| 日韩一区二区在线观看| 亚洲福利精品一区二区三区| 日本强伦姧人妻一区二区| 一区二区在线播放视频| 精品国产一区二区三区AV性色| 国产A∨国片精品一区二区| 亚洲色精品aⅴ一区区三区| 久久久久人妻精品一区| 国产一区二区不卡老阿姨| 日本一区二区三区久久| 精品成人一区二区三区四区| 久久综合精品不卡一区二区| 亚洲国产成人久久一区二区三区| 国产综合无码一区二区色蜜蜜| 久久一区二区三区99| 亚洲Av高清一区二区三区| 美女免费视频一区二区三区| 亚洲无线码在线一区观看| 久久久久人妻精品一区三寸| 日韩制服国产精品一区| 国产精品久久久久一区二区三区| 精品人妻少妇一区二区三区| 日本在线视频一区二区| 国产成人无码精品一区在线观看| 中文字幕一区二区三匹| 精品人体无码一区二区三区 | 色多多免费视频观看区一区| 亚洲视频一区二区三区| 亚洲av福利无码无一区二区| 精品国产一区二区三区久|