複習寶典之SpringMVC

查看更多寶典,請點擊《金三銀四,你的專屬面試寶典》css

第七章:SpringMVC

MVC全名是Model View Controller,是模型(model)-視圖(view)-控制器(controller)的縮寫,一種軟件設計典範,用一種業務邏輯、數據、界面顯示分離的方法組織代碼,將業務邏輯彙集到一個部件裏面,在改進和個性化定製界面及用戶交互的同時,不須要從新編寫業務邏輯。html

最簡單的、最經典就是Jsp(view) +Servlet(controller) + JavaBean(model)。前端

mvc框架是爲了解決傳統MVC模式(Jsp +Servlet + JavaBean)的一些問題而出現的框架。java

傳統MVC模式問題:web

一、全部的Servlet和Servlet映射都要配置在web.xml中,若是項目太大,web.xml就太龐大,而且不能實現模塊化管理。面試

二、Servlet的主要功能就是接受參數、調用邏輯、跳轉頁面,好比像其餘字符編碼、文件上傳等功能也要寫在Servlet中,不能讓Servlet主要功能而須要作處理一下特例。ajax

三、接受參數比較麻煩(String name = request.getParameter(「name」),User user=new User user.setName(name)),不能經過model接收,只能單個接收,接收完成後轉換封裝model。spring

四、跳轉頁面方式比較單一(forword,redirect),而且當個人頁面名稱發生改變時須要修改Servlet源代碼。數據庫

 

1)前端控制器

前端控制器是整個MVC框架中最爲核心的一塊,它主要用來攔截符合要求的外部請求,並把請求分發到不一樣的控制器去處理,根據控制器處理後的結果,生成相應的響應發送到客戶端。前端控制器既可使用Filter實現(Struts2採用這種方式),也可使用Servlet來實現(spring MVC框架)。後端

spring MVC中的前段控制器就是DsipatcherServlet,在web.xml中配置好DispatcherServlet後,容器啓動時會去WEB-INF文件夾下去找(默認[servlet-name]-servlet.xml)dispatcherServlet-servlet.xml,解析文件初始化裏面中的bean等,DispatcherServlet繼承自抽象類:FrameworkServlet,間接繼承了HttpServlet (FrameworkServlet繼承自HttpServletBean,而HttpServletBean繼承自HttpServlet )。

在使用springmvc時,前端控制器會默認加載一些組件,具體配置在springwebmvc下DispatcherServlet.properties中

從如上配置能夠看出DispatcherServlet在啓動時會自動註冊這些特殊的Bean,無需咱們註冊,若是咱們註冊了,默認的將不會註冊。 所以BeanNameUrlHandlerMapping、SimpleControllerHandlerAdapter是不須要註冊的,DispatcherServlet默認會註冊這兩個Bean。

Controller:處理器/頁面控制器,作的是MVC中的C的事情,但控制邏輯轉移到前端控制器了,用於對請求進行處理;

HandlerMapping:請求處處理器的映射,若是映射成功返回一個HandlerExecutionChain對象(包含一個Handler處理器(頁面控制器)對象、多個HandlerInterceptor攔截器)對象;如BeanNameUrlHandlerMapping將URL與Bean名字映射,映射成功的Bean就是此處的處理器;

HandlerAdapter:HandlerAdapter將會把處理器包裝爲適配器,從而支持多種類型的處理器,即適配器模式的應用,從而很容易支持不少類型的處理器;如SimpleControllerHandlerAdapter將對實現了Controller接口的Bean進行適配,而且調處理器的handleRequest方法進行功能處理;

ViewResolver:ViewResolver將把邏輯視圖名解析爲具體的View,經過這種策略模式,很容易更換其餘視圖技術;如InternalResourceViewResolver將邏輯視圖名映射爲jsp視圖;

LocalResover:本地化解析,由於Spring支持國際化,所以LocalResover解析客戶端的Locale信息從而方便進行國際化;

ThemeResovler:主題解析,經過它來實現一個頁面多套風格,即常見的相似於如軟件皮膚效果;

MultipartResolver:文件上傳解析,用於支持文件上傳;

HandlerExceptionResolver:處理器異常解析,能夠將異常映射到相應的統一錯誤界面,從而顯示用戶友好的界面(而不是給用戶看到具體的錯誤信息);

RequestToViewNameTranslator:當處理器沒有返回邏輯視圖名等相關信息時,自動將請求URL映射爲邏輯視圖名;

FlashMapManager:用於管理FlashMap的策略接口,FlashMap用於存儲一個請求的輸出,當進入另外一個請求時做爲該請求的輸入,一般用於重定向場景。

 

2)springmvc的實現原理

概述一:

一、 用戶發送請求至前端控制器DispatcherServlet

二、 DispatcherServlet收到請求調用HandlerMapping處理器映射器

三、 處理器映射器找到具體的處理器(能夠根據xml配置、註解進行查找),生成處理器對象及處理器攔截器(若是有則生成)一併返回給DispatcherServlet。

四、 DispatcherServlet調用HandlerAdapter處理器適配器

五、 HandlerAdapter通過適配調用具體的處理器(Controller,也叫後端控制器)。

六、 Controller執行完成返回ModelAndView。

七、 HandlerAdapter將controller執行結果ModelAndView返回給DispatcherServlet。

八、 DispatcherServlet將ModelAndView傳給ViewReslover視圖解析器

九、 ViewReslover解析後返回具體View。

十、DispatcherServlet根據View進行渲染視圖(即將模型數據填充至視圖中)。

十一、 DispatcherServlet響應用戶。

 

概述二:

一、 用戶向服務器發送請求,請求被Spring前端控制器DispatcherServlet捕獲(捕獲)

二、 DispatcherServlet對請求URL進行解析,獲得請求資源標識符(URI)。而後根據該URI,調用HandlerMapping得到該Handler配置的全部相關的對象(包括Handler對象以及Handler對象對應的攔截器),最後以HandlerExecutionChain對象的形式返回;(查找handler)

三、 DispatcherServlet 根據得到的Handler,選擇一個合適的HandlerAdapter。 提取Request中的模型數據,填充Handler入參,開始執行Handler(Controller), Handler執行完成後,向DispatcherServlet 返回一個ModelAndView對象(執行handler)

四、DispatcherServlet 根據返回的ModelAndView,選擇一個適合的ViewResolver(必須是已經註冊到Spring容器中的ViewResolver) (選擇ViewResolver)

五、經過ViewResolver 結合Model和View,來渲染視圖,DispatcherServlet 將渲染結果返回給客戶端。(渲染返回)

 

3)sturts2的實現原理

 

一、客戶端瀏覽器發送請求

二、這個請求通過一系列的過濾器(Filter)(這些過濾器中有一個叫作ActionContextCleanUp的可選過濾器,這個過濾器對於Struts2和其餘框架的集成頗有幫助,例如:SiteMesh Plugin);

三、接着FilterDispatcher(StrutsPrepareAndExecuteFilter)被調用,FilterDispatcher

(StrutsPrepareAndExecuteFilter)詢問ActionMapper來決定這個請求是否須要調用某個Action;

四、若是ActionMapper決定須要調用某個Action,FilterDispatcher

(StrutsPrepareAndExecuteFilter)把請求的處理交給ActionProxy

五、ActionProxy經過Configuration Manager詢問框架的配置文件,找到須要調用的Action類;

六、ActionProxy建立一個ActionInvocation的實例。

七、ActionInvocation實例使用命名模式來調用,在調用Action的過程先後,涉及到相關攔截器(Intercepter)的調用

八、一旦Action執行完畢,ActionInvocation負責根據struts.xml中的配置找到對應的返回結果。返回結果一般是(但不老是,也多是另外的一個Action鏈)一個須要被表示的JSP或者FreeMarker的模版。在表示的過程當中可使用Struts2框架中繼承的標籤。在這個過程當中須要涉及到ActionMapper。

 

面試:

一、瀏覽器發送請求,通過一系列的過濾器後,到達核心過濾器(StrutsPrepareAndExecuteFilter).

二、StrutsPrepareAndExecuteFilter經過ActionMapper判斷當前的請求是否須要某個Action處理,若是不須要,則走原來的流程。若是須要則把請求交給ActionProxy來處理

三、ActionProxy經過Configuration Manager詢問框架的配置文件(Struts.xml),找到須要調用的Action類;

四、建立一個ActionInvocation實例,來調用Action的對應方法來獲取結果集的name,在調用先後會執行相關攔截器。

五、經過結果集的Name知道對應的結果集來對瀏覽器進行響應。

 

4)struts2與springmvc的區別

目前企業中使用SpringMvc的比例已經遠遠超過Struts2,那麼二者到底有什麼區別,是不少初學者比較關注的問題,下面咱們就來對SpringMvc和Struts2進行各方面的比較:

1.核心控制器(前端控制器、預處理控制器):對於使用過mvc框架的人來講這個詞應該不會陌生,核心控制器的主要用途是處理全部的請求,而後對那些特殊的請求 (控制器)統一的進行處理(字符編碼、文件上傳、參數接受、異常處理等等),spring mvc核心控制器是Servlet,而Struts2是Filter。

2.控制器實例:Spring Mvc會比Struts快一些(理論上)。Spring Mvc是基於方法設計,而Sturts是基於對象,每次發一次請求都會實例一個action,每一個action都會被注入 屬性,而Spring更像Servlet同樣,只有一個實例,每次請求執行對應的方法便可(注意:因爲是單例實例,因此應當避免全局變量的修改,這樣會產生線程安全問題)。

3.管理方式:大部分的公司的核心架構中,就會使用到spring,而spring mvc又是spring中的一個模塊,因此spring對於spring mvc的控制器管理更加簡單方便,並且提供了全 註解方式進行管理,各類功能的註解都比較全面,使用簡單,而struts2須要採用XML不少的配置參數來管理(雖然也能夠採用註解,可是幾乎沒有公司那 樣使用)。

4.參數傳遞:Struts2中自身提供多種參數接受,其實都是經過(ValueStack)進行傳遞和賦值,而SpringMvc是經過方法的參數進行接收。

5.學習難度:Struts更加不少新的技術點,好比攔截器、值棧及OGNL表達式,學習成本較高,springmvc 比較簡單,很較少的時間都能上手。

6.intercepter 的實現機制:struts有以本身的interceptor機制,spring mvc用的是獨立的AOP方式。這樣致使struts的配置文件量仍是比spring mvc大,雖然struts的配置能繼承,因此我以爲論使用上來說,spring mvc使用更加簡潔,開發效率Spring MVC確實比struts2高。spring mvc是方法級別的攔截,一個方法對應一個request上下文,而方法同時又跟一個url對應,因此說從架構自己上spring3 mvc就容易實現restful url。struts2是類級別的攔截,一個類對應一個request上下文;實現restful url要費勁,由於struts2 action的一個方法能夠對應一個url;而其類屬性卻被全部方法共享,這也就沒法用註解或其餘方式標識其所屬方法了。spring3 mvc的方法之間基本上獨立的,獨享request response數據,請求數據經過參數獲取,處理結果經過ModelMap交回給框架方法之間不共享變量,而struts2搞的就比較亂,雖然方法之間 也是獨立的,但其全部Action變量是共享的,這不會影響程序運行,卻給咱們編碼,讀程序時帶來麻煩。

7.spring mvc處理ajax請求,直接經過返回數據,方法中使用註解@ResponseBody,spring mvc自動幫咱們對象轉換爲JSON數據。而struts2是經過插件的方式進行處理。

在SpringMVC流行起來以前,Struts2在MVC框架中佔核心地位,隨着SpringMVC的出現,SpringMVC慢慢的取代struts2,可是不少企業都是原來搭建的框架,使用Struts2較多。

 

5)WebApplicationInitializer接口

概述一:

自從Servlet3.0+的出現,Spring-web模塊提供了一種免配置文件,在如Tomcat(支持Servlet3.0+)這樣的Servlet容器啓動時,自動調用了實現的WebApplicationInitializer(全路徑org.springframework.web.WebApplicationInitializer)接口的類的onStartup方法,並將容器的ServletContext往下傳遞。

從Spring-web模塊的API中,找到子類AbstractAnnotationConfigDispatcherServletInitializer,它間接實現了WebApplicationInitializer接口,父類onStartup方法,首先建立RootWebApplicationContext並設置ContextLoaderListner監聽器;其次,註冊往servletContext註冊DispatcherServlet實例。

因爲AbstractAnnotationConfigDispatcherServletInitializer是抽象類,Spring容器不能注入。它重寫了父類建立根ApplicationContext與ServletApplicationContext方法,並抽象出兩個方法(用來建立根ApplicationContext和ServletApplicationContext容器時,須要注入的組件字類節數組)。

所以 咱們只須要編寫SpringMVCInitializer來繼承AbstractAnnotationConfigDispatcherServletInitializer類,讓它來幫咱們完成對DispatcherServlet實例的加載便可。

 

概述二:

如今JavaConfig配置方式在逐步取代xml配置方式。而WebApplicationInitializer能夠看作是Web.xml的替代,它是一個接口。經過實現WebApplicationInitializer,在其中能夠添加servlet,listener等,在加載Web項目的時候會加載這個接口實現類,從而起到web.xml相同的做用。

爲了支持能夠不使用web.xml。提供了ServletContainerInitializer,它能夠經過SPI機制,當啓動web容器的時候,會自動到添加的相應jar包下找到META-INF/services下以ServletContainerInitializer的全路徑名稱命名的文件,它的內容爲ServletContainerInitializer實現類的全路徑,將它們實例化。既然這樣的話,那麼SpringServletContainerInitializer做爲ServletContainerInitializer的實現類,它的jar包下也應該有相應的文件。

首先,SpringServletContainerInitializer做爲ServletContainerInitializer的實現類,經過SPI機制,在web容器加載的時候會自動的被調用。(這個類上還有一個註解@HandlesTypes,它的做用是將感興趣的一些類注入到ServletContainerInitializerde), 而這個類的方法又會掃描找到WebApplicationInitializer的實現類,調用它的onStartup方法,從而起到啓動web.xml相同的做用。

 

6)servlet3.0新特性

Servlet 3.0 做爲 Java EE 6 規範體系中一員,隨着 Java EE 6 規範一塊兒發佈。該版本在前一版本(Servlet 2.5)的基礎上提供了若干新特性用於簡化 Web 應用的開發和部署。其中有幾項特性的引入讓開發者感到很是興奮,同時也得到了 Java 社區的一片讚譽之聲:

1.異步處理支持:有了該特性,Servlet 線程再也不須要一直阻塞,直到業務處理完畢才能再輸出響應,最後才結束該 Servlet 線程。在接收到請求以後,Servlet 線程能夠將耗時的操做委派給另外一個線程來完成,本身在不生成響應的狀況下返回至容器。針對業務處理較耗時的狀況,這將大大減小服務器資源的佔用,而且提升併發處理速度。

2.新增的註解支持:該版本新增了若干註解,用於簡化 Servlet、過濾器(Filter)和監聽器(Listener)的聲明,這使得 web.xml 部署描述文件從該版本開始再也不是必選的了。

3.可插性支持:熟悉 Struts2 的開發者必定會對其經過插件的方式與包括 Spring 在內的各類經常使用框架的整合特性記憶猶新。將相應的插件封裝成 JAR 包並放在類路徑下,Struts2 運行時便能自動加載這些插件。如今 Servlet 3.0 提供了相似的特性,開發者能夠經過插件的方式很方便的擴充已有 Web 應用的功能,而不須要修改原有的應用。

 

7)RequestMapping參數類型與返回類型

@RequestMapping方法方法所支持的常見參數類型:

  • 請求或響應對象(Servlet API)。能夠是任何具體的請求或響應類型的對象,好比,ServletRequest或HttpServletRequest對象。

  • HttpSession類型的會話對象(Servlet API)。使用該類型的參數將要求這樣一個session的存在,所以這樣的參數永不爲null。

  • 當前請求的地區信息java.util.Locale,由已配置的最相關的地區解析器解析獲得。在MVC的環境下,就是應用中配置的LocaleResolver或LocaleContextResolver

  • 與當前請求綁定的時區信息java.util.TimeZone(java 6以上的版本)/java.time.ZoneId(java 8),由LocaleContextResolver解析獲得

  • org.springframework.http.HttpMethod。能夠拿到HTTP請求方法

  • 包裝了當前被認證用戶信息的java.security.Principal

  • 帶@PathVariable標註的方法參數,其存放了URI模板變量中的值。

  • 帶@RequestParam標註的方法參數,其存放了Servlet請求中所指定的參數。參數的值會被轉換成方法參數所聲明的類型。

  • 帶@RequestHeader標註的方法參數,其存放了Servlet請求中所指定的HTTP請求頭的值。參數的值會被轉換成方法參數所聲明的類型。

  • 帶@RequestBody標註的參數,提供了對HTTP請求體的存取。參數的值經過HttpMessageConverter被轉換成方法參數所聲明的類型。

  • 帶@RequestPart標註的參數,提供了對一個"multipart/form-data請求塊(request part)內容的存取。

  • HttpEntity<?>類型的參數,其提供了對HTTP請求頭和請求內容的存取。請求流是經過HttpMessageConverter被轉換成entity對象的。

  • java.util.Map/org.springframework.io.Model/org.springframework.ui.ModelMap類型的參數,用以加強默認暴露給視圖層的模型(model)的功能

  • org.springframework.web.servlet.mvc.support.RedirectAttributes類型的參數,用以指定重定向下要使用到的屬性集以及添加flash屬性(暫存在服務端的屬性,它們會在下次重定向請求的範圍中有效)。

  • 命令或表單對象,它們用於將請求參數直接綁定到bean字段(多是經過setter方法)。你能夠經過@InitBinder標註和/或HanderAdapter的配置來定製這個過程的類型轉換。RequestMappingHandlerAdapter類webBindingInitializer屬性的文檔。這樣的命令對象,以及其上的驗證結果,默認會被添加到模型model中,鍵名默認是該命令對象類的類名——好比,some.package.OrderAddress類型的命令對象就使用屬性名orderAddress類獲取。ModelAttribute標註能夠應用在方法參數上,用以指定該模型所用的屬性名

  • org.springframework.validation.Errors / org.springframework.validation.BindingResult驗證結果對象,用於存儲前面的命令或表單對象的驗證結果(緊接其前的第一個方法參數)。

  • org.springframework.web.bind.support.SessionStatus對象,用以標記當前的表單處理已結束。這將觸發一些清理操做:@SessionAttributes在類級別標註的屬性將被移除

  • org.springframework.web.util.UriComponentsBuilder構造器對象,用於構造當前請求URL相關的信息,好比主機名、端口號、資源類型(scheme)、上下文路徑、servlet映射中的相對部分(literal part)等

 

BindingResult的特殊性:

在參數列表中,Errors或BindingResult參數必須緊跟在其所綁定的驗證對象後面。這是由於,在參數列表中容許有多於一個的模型對象,Spring會爲它們建立不一樣的BindingResult實例。所以,下面這樣的代碼是不能工做的:

BindingResult與@ModelAttribute錯誤的參數次序

@RequestMapping(method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, Model model, BindingResult result) { ... }

上例中,由於在模型對象Pet和驗證結果對象BindingResult中間還插了一個Model參數,這是不行的。要達到預期的效果,必須調整一下參數的次序:

@RequestMapping(method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result, Model model) { ... }

對於一些帶有required屬性的標註(好比@RequestParam、@RequestHeader等),JDK 1.8的java.util.Optional能夠做爲被它們標註的方法參數。在這種狀況下,使用java.util.Optional與required=false的做用是相同的。

 

@RequestMapping方法方法支持的常見返回類型:

  • ModelAndView對象,其中model隱含填充了命令對象,以及標註了@ModelAttribute字段的存取器被調用所返回的值。

  • Model對象,其中視圖名稱默認由RequestToViewNameTranslator決定,model隱含填充了命令對象以及標註了@ModelAttribute字段的存取器被調用所返回的值

  • Map對象,用於暴露model,其中視圖名稱默認由RequestToViewNameTranslator決定,model隱含填充了命令對象以及標註了@ModelAttribute字段的存取器被調用所返回的值

  • View對象。其中model隱含填充了命令對象,以及標註了@ModelAttribute字段的存取器被調用所返回的值。handler方法也能夠增長一個Model類型的方法參數來加強model

  • String對象,其值會被解析成一個邏輯視圖名。其中,model將默認填充了命令對象以及標註了@ModelAttribute字段的存取器被調用所返回的值。handler方法也能夠增長一個Model類型的方法參數來加強model

  • void。若是處理器方法中已經對response響應數據進行了處理(好比在方法參數中定義一個ServletResponse或HttpServletResponse類型的參數並直接向其響應體中寫東西),那麼方法能夠返回void。handler方法也能夠增長一個Model類型的方法參數來加強model

  • 若是處理器方法標註了ResponseBody,那麼返回類型將被寫到HTTP的響應體中,而返回值會被HttpMessageConverters轉換成所方法聲明的參數類型。

  • HttpEntity<?>或ResponseEntity<?>對象,用於提供對Servlet HTTP響應頭和響應內容的存取。對象體會被HttpMessageConverters轉換成響應流。

  • HttpHeaders對象,返回一個不含響應體的response

  • 若是返回類型不是Spring MVC默認識別的類型,則會被處理成model的一個屬性並返回給視圖,該屬性的名稱爲方法級的@ModelAttribute所標註的字段名(或者以返回類型的類名做爲默認的屬性名)。model隱含填充了命令對象以及標註了@ModelAttribute字段的存取器被調用所返回的值

 

總結:

最經常使用的參數和返回類型應該是Spring MVC給你提供的抽象,好比Model、View和ModelView等概念,以及用來識別請求或者表示返回類型的標準,如@PathVariable和ResponseBody等,這些能夠說時Spring MVC的核心。

另外有些參數類型可能會常用,好比須要訪問HttpSession或者HttpServletRequest等原生的Servlet API,則直接聲明該類型的一個參數便可。

以上類型即便在一個大型的Spring MVC項目中也不必定都須要用到。可是其中一些特性在特定的場景下很是有用。

好比,若是控制器但願獲取當前URL的主機名、端口號、資源類型(scheme)、上下文路徑等信息,則在@RequestMapping方法的參數中增長一個org.springframework.web.util.UriComponentsBuilder類型便可。Spring MVC會將相關信息自動填充好,你直接使用便可。

 

8)模型綁定(數據綁定)

使用@ModelAttribute、Model、Map、@SessionAttributes能便捷地將咱們的業務數據封裝到模型裏並交由視圖解析調用。

1.@RequestParam綁定單個請求參數值

@RequestParam用於將請求參數區數據映射到功能處理方法的參數上。

public String requestparam1(@RequestParam String username)

請求中包含username參數(如/requestparam1?username=zhang),則自動傳入。

public String requestparam2(@RequestParam("username") String username)

經過@RequestParam("username")明確告訴Spring Web MVC使用username進行入參。

接下來咱們看一下@RequestParam註解主要有哪些參數:

value:參數名字,即入參的請求參數名字,如username表示請求的參數區中的名字爲username的參數的值將傳入;

required:是否必須,默認是true,表示請求中必定要有相應的參數,不然將報404錯誤碼;

defaultValue:默認值,表示若是請求中沒有同名參數時的默認值,默認值能夠是SpEL表達式,如「#{systemProperties['java.vm.version']}」。

public String requestparam4(@RequestParam(value="username",required=false) String username) 

表示請求中能夠沒有名字爲username的參數,若是沒有默認爲null,此處須要注意以下幾點:

原子類型:必須有值,不然拋出異常,若是容許空值請使用包裝類代替。

Boolean包裝類型類型:默認Boolean.FALSE,其餘引用類型默認爲null。

public String requestparam5(
@RequestParam(value="username", required=true, defaultValue="zhang") String username)
       

表示若是請求中沒有名字爲username的參數,默認值爲「zhang」。

若是請求中有多個同名的應該如何接收呢?如給用戶受權時,可能授予多個權限,首先看下以下代碼:

public String requestparam7(@RequestParam(value="role") String roleList)

若是請求參數相似於url?role=admin&rule=user,則實際roleList參數入參的數據爲「admin,user」,即多個數據之間使用「,」分割;咱們應該使用以下方式來接收多個請求參數:

public String requestparam7(@RequestParam(value="role") String[] roleList)   

public String requestparam8(@RequestParam(value="list") List<String> list)   

 

2.@PathVariable綁定URI模板變量值

@PathVariable用於將請求URL中的模板變量映射到功能處理方法的參數上。

@RequestMapping(value="/users/{userId}/topics/{topicId}")
public String test(
      @PathVariable(value="userId") int userId,
      @PathVariable(value="topicId") int topicId)      

如請求的URL爲「控制器URL/users/123/topics/456」,則自動將URL中模板變量{userId}和{topicId}綁定到經過@PathVariable註解的同名參數上,即入參後userId=12三、topicId=456。

 

3.@CookieValue綁定Cookie數據值

@CookieValue用於將請求的Cookie數據映射到功能處理方法的參數上。

public String test(@CookieValue(value="JSESSIONID", defaultValue="") String sessionId) 

如上配置將自動將JSESSIONID值入參到sessionId參數上,defaultValue表示Cookie中沒有JSESSIONID時默認爲空。

public String test2(@CookieValue(value="JSESSIONID", defaultValue="") Cookie sessionId)       

傳入參數類型也能夠是javax.servlet.http.Cookie類型。

@CookieValue也擁有和@RequestParam相同的三個參數,含義同樣。

 

4.@RequestHeader綁定請求頭數據

@RequestHeader用於將請求的頭信息區數據映射到功能處理方法的參數上。

@RequestMapping(value="/header")
public String test(
      @RequestHeader("User-Agent") String userAgent,
      @RequestHeader(value="Accept") String[] accepts)
       

如上配置將自動將請求頭「User-Agent」值入參到userAgent參數上,並將「Accept」請求頭值入參到accepts參數上。測試代碼在HeaderValueTypeController中。

@RequestHeader也擁有和@RequestParam相同的三個參數,含義同樣。

 

5.@ModelAttribute綁定請求參數到命令對象

@ModelAttribute一個具備以下三個做用:

①綁定請求參數到命令對象:放在功能處理方法的入參上時,用於將多個請求參數綁定到一個命令對象,從而簡化綁定流程,並且自動暴露爲模型數據用於視圖頁面展現時使用;

②暴露表單引用對象爲模型數據:放在處理器的通常方法(非功能處理方法)上時,是爲表單準備要展現的表單引用對象,如註冊時須要選擇的所在城市等,並且在執行功能處理方法(@RequestMapping註解的方法)以前,自動添加到模型對象中,用於視圖頁面展現時使用;

③暴露@RequestMapping方法返回值爲模型數據:放在功能處理方法的返回值上時,是暴露功能處理方法的返回值爲模型數據,用於視圖頁面展現時使用。

1、綁定請求參數到命令對象

如用戶登陸,咱們須要捕獲用戶登陸的請求參數(用戶名、密碼)並封裝爲用戶對象,此時咱們可使用@ModelAttribute綁定多個請求參數到咱們的命令對象。

public String test1(@ModelAttribute("user") UserModel user)

它的做用是將該綁定的命令對象以「user」爲名稱添加到模型對象中供視圖頁面展現使用。咱們此時能夠在視圖頁面使用${user.username}來獲取綁定的命令對象的屬性。

綁定請求參數到命令對象支持對象圖導航式的綁定,如請求參數包含「?username=zhang&password=123&workInfo.city=bj」自動綁定到user中的workInfo屬性的city屬性中。

@RequestMapping(value="/model2/{username}")
public String test2(@ModelAttribute("model") DataBinderTestModel model) {

URI模板變量也能自動綁定到命令對象中,當你請求的URL中包含「bool=yes&schooInfo.specialty=computer&hobbyList[0]=program&hobbyList[1]=music&map[key1]=value1&map[key2]=value2&state=blocked」會自動綁定到命令對象上。

當URI模板變量和請求參數同名時,URI模板變量具備高優先權。

2、暴露表單引用對象爲模型數據

@ModelAttribute("cityList")
public List<String> cityList() {
  return Arrays.asList("北京", "山東");
}

如上代碼會在執行功能處理方法以前執行,並將其自動添加到模型對象中,在功能處理方法中調用Model 入參的containsAttribute("cityList")將會返回true。

@ModelAttribute("user")  //①
public UserModel getUser(@RequestParam(value="username", defaultValue="") String username) {
//TODO 去數據庫根據用戶名查找用戶對象
UserModel user = new UserModel();
user.setRealname("zhang");
    return user;
}

如你要修改用戶資料時通常須要根據用戶的編號/用戶名查找用戶來進行編輯,此時能夠經過如上代碼查找要編輯的用戶。

也能夠進行一些默認值的處理。

@RequestMapping(value="/model1") //②
public String test1(@ModelAttribute("user") UserModel user, Model model)

此處咱們看到①和②有同名的命令對象,那Spring Web MVC內部如何處理的呢:

(一、首先執行@ModelAttribute註解的方法,準備視圖展現時所須要的模型數據;@ModelAttribute註解方法形式參數規則和@RequestMapping規則同樣,如能夠有@RequestParam等;

(二、執行@RequestMapping註解方法,進行模型綁定時首先查找模型數據中是否含有同名對象,若是有直接使用,若是沒有經過反射建立一個,所以②處的user將使用①處返回的命令對象。即②處的user等於①處的user。

3、暴露@RequestMapping方法返回值爲模型數據

public @ModelAttribute("user2") UserModel test3(@ModelAttribute("user2") UserModel user)

你們能夠看到返回值類型是命令對象類型,並且經過@ModelAttribute("user2")註解,此時會暴露返回值到模型數據(名字爲user2)中供視圖展現使用。那哪一個視圖應該展現呢?此時Spring Web MVC會根據RequestToViewNameTranslator進行邏輯視圖名的翻譯。

此時又有問題了,@RequestMapping註解方法的入參user暴露到模型數據中的名字也是user2,其實咱們能猜到:

(三、@ModelAttribute註解的返回值會覆蓋@RequestMapping註解方法中的@ModelAttribute註解的同名命令對象。

4、匿名綁定命令參數

public String test4(@ModelAttribute UserModel user, Model model)

public String test5(UserModel user, Model model)

此時咱們沒有爲命令對象提供暴露到模型數據中的名字,此時的名字是什麼呢?Spring Web MVC自動將簡單類名(首字母小寫)做爲名字暴露,如「cn.javass.chapter6.model.UserModel」暴露的名字爲「userModel」。

public @ModelAttribute List<String> test6()

public @ModelAttribute List<UserModel> test7()

對於集合類型(Collection接口的實現者們,包括數組),生成的模型對象屬性名爲「簡單類名(首字母小寫)」+「List」,如List<String>生成的模型對象屬性名爲「stringList」,List<UserModel>生成的模型對象屬性名爲「userModelList」。

其餘狀況一概都是使用簡單類名(首字母小寫)做爲模型對象屬性名,如Map<String, UserModel>類型的模型對象屬性名爲「map」。

 

6.@SessionAttributes綁定命令對象到session

有時候咱們須要在屢次請求之間保持數據,通常狀況須要咱們明確的調用HttpSession的API來存取會話數據,如多步驟提交的表單。Spring Web MVC提供了@SessionAttributes進行請求間透明的存取會話數據。

//一、在控制器類頭上添加@SessionAttributes註解
@SessionAttributes(value = {"user"})   //①
public class SessionAttributeController

//二、@ModelAttribute註解的方法進行表單引用對象的建立
@ModelAttribute("user")   //②
public UserModel initUser()

//三、@RequestMapping註解方法的@ModelAttribute註解的參數進行命令對象的綁定
@RequestMapping("/session1")   //③
public String session1(@ModelAttribute("user") UserModel user)

//四、經過SessionStatus的setComplete()方法清除@SessionAttributes指定的會話數據
@RequestMapping("/session2")   //③
public String session(@ModelAttribute("user") UserModel user, SessionStatus status) {
  if(true) { //④
      status.setComplete();
  }
  return "success";
}

@SessionAttributes(value = {"user"})含義:

@SessionAttributes(value = {"user"}) 標識將模型數據中的名字爲「user」 的對象存儲到會話中(默認HttpSession),此處value指定將模型數據中的哪些數據(名字進行匹配)存儲到會話中,此外還有一個types屬性表示模型數據中的哪些類型的對象存儲到會話範圍內,若是同時指定value和types屬性則那些名字和類型都匹配的對象才能存儲到會話範圍內。

包含@SessionAttributes的執行流程以下所示:

① 首先根據@SessionAttributes註解信息查找會話內的對象放入到模型數據中;

② 執行@ModelAttribute註解的方法:若是模型數據中包含同名的數據,則不執行@ModelAttribute註解方法進行準備表單引用數據,而是使用①步驟中的會話數據;若是模型數據中不包含同名的數據,執行@ModelAttribute註解的方法並將返回值添加到模型數據中;

③ 執行@RequestMapping方法,綁定@ModelAttribute註解的參數:查找模型數據中是否有@ModelAttribute註解的同名對象,若是有直接使用,不然經過反射建立一個;並將請求參數綁定到該命令對象;

此處須要注意:若是使用@SessionAttributes註解控制器類以後,③步驟必定是從模型對象中取得同名的命令對象,若是模型數據中不存在將拋出HttpSessionRequiredException Expected session attribute ‘user’(Spring3.1)

或HttpSessionRequiredException Session attribute ‘user’ required - not found in session(Spring3.0)異常。

④ 若是會話能夠銷燬了,如多步驟提交表單的最後一步,此時能夠調用SessionStatus對象的setComplete()標識當前會話的@SessionAttributes指定的數據能夠清理了,此時當@RequestMapping功能處理方法執行完畢會進行清理會話數據。

 

咱們經過Spring Web MVC的源代碼驗證一下吧,此處咱們分析的是Spring3.1的RequestMappingHandlerAdapter,讀者能夠自行驗證Spring3.0的AnnotationMethodHandlerAdapter,流程同樣:

(一、RequestMappingHandlerAdapter.invokeHandlerMethod

//一、RequestMappingHandlerAdapter首先調用ModelFactory的initModel方法準備模型數據:
modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);
//二、調用@RequestMapping註解的功能處理方法
requestMappingMethod.invokeAndHandle(webRequest, mavContainer);
//三、更新/合併模型數據
modelFactory.updateModel(webRequest, mavContainer);

(二、ModelFactory.initModel

Map<String, ?> attributesInSession = this.sessionAttributesHandler.retrieveAttributes(request);
//1.一、將與@SessionAttributes註解相關的會話對象放入模型數據中
mavContainer.mergeAttributes(attributesInSession);
//1.二、調用@ModelAttribute方法添加表單引用對象
invokeModelAttributeMethods(request, mavContainer);
//1.三、驗證模型數據中是否包含@SessionAttributes註解相關的會話對象,不包含拋出異常
for (String name : findSessionAttributeArguments(handlerMethod)) {
if (!mavContainer.containsAttribute(name)) {
      //1.四、此處防止在@ModelAttribute註解方法又添加了會話對象
      //如在@ModelAttribute註解方法調用session.setAttribute("user", new UserModel());
Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
if (value == null) {
throw new HttpSessionRequiredException("Expected session attribute '" + name + "'");
}
mavContainer.addAttribute(name, value);
}

(三、ModelFactory.invokeModelAttributeMethods

for (InvocableHandlerMethod attrMethod : this.attributeMethods) {
  String modelName = attrMethod.getMethodAnnotation(ModelAttribute.class).value();
  //1.2.一、若是模型數據中包含同名數據則再也不添加
if (mavContainer.containsAttribute(modelName)) {
  continue;
}
//1.2.二、調用@ModelAttribute註解方法並將返回值添加到模型數據中,此處省略實現代碼
}

(四、requestMappingMethod.invokeAndHandle 調用功能處理方法,此處省略

(五、ModelFactory.updateMode 更新模型數據

//3.一、若是會話被標識爲完成,此時從會話中清除@SessionAttributes註解相關的會話對象
if (mavContainer.getSessionStatus().isComplete()){
this.sessionAttributesHandler.cleanupAttributes(request);
}
//3.二、若是會話沒有完成,將模型數據中的@SessionAttributes註解相關的對象添加到會話中
else {
this.sessionAttributesHandler.storeAttributes(request, mavContainer.getModel());
}
//省略部分代碼

到此@SessionAtrribute介紹完畢。

多步驟提交表單須要考慮會話超時問題,這種方式可能對用戶不太友好,咱們能夠採起隱藏表單(即當前步驟將其餘步驟的表單隱藏)或表單數據存數據庫(每步驟更新下數據庫數據)等方案解決。

 

7.@Value綁定SpEL表示式

@Value用於將一個SpEL表達式結果映射到到功能處理方法的參數上。

public String test(@Value("#{systemProperties['java.vm.version']}") String jvmVersion)

到此數據綁定咱們就介紹完了。

 

9)視圖解析器

當咱們對SpringMVC控制的資源發起請求時,這些請求都會被SpringMVC的DispatcherServlet處理,接着spring會分析看哪個HandlerMapping定義的全部請求映射中存在對該請求的最合理的映射。而後經過該HandlerMapping取得其對應的Handler,接着再經過相應的HandlerAdapter處理該Handler。HandlerAdapter在對Handler進行處理以後會返回一個ModelAndView對象。在得到了ModelAndView對象以後,Spring就須要把該View渲染給用戶,即返回給瀏覽器。在這個渲染的過程當中,發揮做用的就是ViewResolver和View。當Handler返回的ModelAndView中不包含真正的視圖,只返回一個邏輯視圖名稱的時候,ViewResolver就會把該邏輯視圖名稱解析爲真正的視圖View對象。View是真正進行視圖渲染,把結果返回給瀏覽器的。

SpringMVC用於處理視圖最重要的兩個接口是ViewResolver和View。ViewResolver的主要做用是把一個邏輯上的視圖名稱解析爲一個真正的視圖,SpringMVC中用於把View對象呈現給客戶端的是View對象自己,而ViewResolver只是把邏輯視圖名稱解析爲對象的View對象。View接口的主要做用是用於處理視圖,而後返回給客戶端。

Spring爲咱們提供了很是多的視圖解析器,下面將列舉一些視圖解析器:

AbstractCachingViewResolver:這是一個抽象類,這種視圖解析器會把它曾經解析過的視圖保存起來,而後每次要解析視圖的時候先從緩存裏面找,若是找到了對應的視圖就直接返回,若是沒有就建立一個新的視圖對象,而後把它放到一個用於緩存的map中,接着再把新建的視圖返回。使用這種視圖緩存的方式能夠把解析視圖的性能問題降到最低。

UrlBasedViewResolver:它是對ViewResolver的一種簡單實現,並且繼承了AbstractCachingViewResolver,主要就是提供的一種拼接URL的方式來解析視圖,它可讓咱們經過prefix屬性指定一個指定的前綴,經過suffix屬性指定一個指定的後綴,而後把返回的邏輯視圖名稱加上指定的前綴和後綴就是指定的視圖URL了。如prefix=/WEB-INF/jsps/,suffix=.jsp,返回的視圖名稱viewName=test/indx,則UrlBasedViewResolver解析出來的視圖URL就是/WEB-INF/jsps/test/index.jsp。默認的prefix和suffix都是空串。URLBasedViewResolver支持返回的視圖名稱中包含redirect:前綴,這樣就能夠支持URL在客戶端的跳轉,如當返回的視圖名稱是」redirect:test.do」的時候,URLBasedViewResolver發現返回的視圖名稱包含」redirect:」前綴,因而把返回的視圖名稱前綴」redirect:」去掉,取後面的test.do組成一個RedirectView,RedirectView中將把請求返回的模型屬性組合成查詢參數的形式組合到redirect的URL後面,而後調用HttpServletResponse對象的sendRedirect方法進行重定向。一樣URLBasedViewResolver還支持forword:前綴,對於視圖名稱中包含forword:前綴的視圖名稱將會被封裝成一個InternalResourceView對象,而後在服務器端利用RequestDispatcher的forword方式跳轉到指定的地址。使用UrlBasedViewResolver的時候必須指定屬性viewClass,表示解析成哪一種視圖,通常使用較多的就是InternalResourceView,利用它來展示jsp,可是當咱們使用JSTL的時候咱們必須使用JstlView。下面是一段UrlBasedViewResolver的定義,根據該定義,當返回的邏輯視圖名稱是test的時候,UrlBasedViewResolver將把邏輯視圖名稱加上定義好的前綴和後綴,即「/WEB-INF/test.jsp」,而後新建一個viewClass屬性指定的視圖類型予以返回,即返回一個url爲「/WEB-INF/test.jsp」的InternalResourceView對象。

InternalResourceViewResolver:它是URLBasedViewResolver的子類,因此URLBasedViewResolver支持的特性它都支持。在實際應用中InternalResourceViewResolver也是使用的最普遍的一個視圖解析器。那麼InternalResourceViewResolver有什麼本身獨有的特性呢?單從字面意思來看,咱們能夠把InternalResourceViewResolver解釋爲內部資源視圖解析器,這就是InternalResourceViewResolver的一個特性。InternalResourceViewResolver會把返回的視圖名稱都解析爲InternalResourceView對象,InternalResourceView會把Controller處理器方法返回的模型屬性都存放到對應的request屬性中,而後經過RequestDispatcher在服務器端把請求forword重定向到目標URL。好比在InternalResourceViewResolver中定義了prefix=/WEB-INF/,suffix=.jsp,而後請求的Controller處理器方法返回的視圖名稱爲test,那麼這個時候InternalResourceViewResolver就會把test解析爲一個InternalResourceView對象,先把返回的模型屬性都存放到對應的HttpServletRequest屬性中,而後利用RequestDispatcher在服務器端把請求forword到/WEB-INF/test.jsp。這就是InternalResourceViewResolver一個很是重要的特性,咱們都知道存放在/WEB-INF/下面的內容是不能直接經過request請求的方式請求到的,爲了安全性考慮,咱們一般會把jsp文件放在WEB-INF目錄下,而InternalResourceView在服務器端跳轉的方式能夠很好的解決這個問題。下面是一個InternalResourceViewResolver的定義,根據該定義當返回的邏輯視圖名稱是test的時候,InternalResourceViewResolver會給它加上定義好的前綴和後綴,組成「/WEB-INF/test.jsp」的形式,而後把它當作一個InternalResourceView的url新建一個InternalResourceView對象返回。

XmlViewResolver:它繼承自AbstractCachingViewResolver抽象類,因此它也是支持視圖緩存的。XmlViewResolver須要給定一個xml配置文件,該文件將使用和Spring的bean工廠配置文件同樣的DTD定義,因此其實該文件就是用來定義視圖的bean對象的。在該文件中定義的每個視圖的bean對象都給定一個名字,而後XmlViewResolver將根據Controller處理器方法返回的邏輯視圖名稱到XmlViewResolver指定的配置文件中尋找對應名稱的視圖bean用於處理視圖。該配置文件默認是/WEB-INF/views.xml文件,若是不使用默認值的時候能夠在XmlViewResolver的location屬性中指定它的位置。XmlViewResolver還實現了Ordered接口,所以咱們能夠經過其order屬性來指定在ViewResolver鏈中它所處的位置,order的值越小優先級越高。

ResourceBundleViewResolver:它和XmlViewResolver同樣,也是繼承自AbstractCachingViewResolver,可是它緩存的不是視圖,這個會在後面有說到。和XmlViewResolver同樣它也須要有一個配置文件來定義邏輯視圖名稱和真正的View對象的對應關係,不一樣的是ResourceBundleViewResolver的配置文件是一個屬性文件,並且必須是放在classpath路徑下面的,默認狀況下這個配置文件是在classpath根目錄下的views.properties文件,若是不使用默認值的話,則能夠經過屬性baseName或baseNames來指定。baseName只是指定一個基名稱,Spring會在指定的classpath根目錄下尋找以指定的baseName開始的屬性文件進行View解析,如指定的baseName是base,那麼base.properties、baseabc.properties等等以base開始的屬性文件都會被Spring當作ResourceBundleViewResolver解析視圖的資源文件。

FreeMarkerViewResolver、VolocityViewResolver:這兩個視圖解析器都是UrlBasedViewResolver的子類。FreeMarkerViewResolver會把Controller處理方法返回的邏輯視圖解析爲FreeMarkerView,而VolocityViewResolver會把返回的邏輯視圖解析爲VolocityView。由於這兩個視圖解析器相似,因此這裏我就只挑FreeMarkerViewResolver來作一個簡單的講解。FreeMarkerViewResolver和VilocityViewResolver都繼承了UrlBasedViewResolver。對於FreeMarkerViewResolver而言,它會按照UrlBasedViewResolver拼接URL的方式進行視圖路徑的解析。可是使用FreeMarkerViewResolver的時候不須要咱們指定其viewClass,由於FreeMarkerViewResolver中已經把viewClass定死爲FreeMarkerView了。

視圖解析鏈:

在SpringMVC中能夠同時定義多個ViewResolver視圖解析器,而後它們會組成一個ViewResolver鏈。當Controller處理器方法返回一個邏輯視圖名稱後,ViewResolver鏈將根據其中ViewResolver的優先級來進行處理。全部的ViewResolver都實現了Ordered接口,在Spring中實現了這個接口的類都是能夠排序的。在ViewResolver中是經過order屬性來指定順序的,默認都是最大值。因此咱們能夠經過指定ViewResolver的order屬性來實現ViewResolver的優先級,order屬性是Integer類型,order越小,對應的ViewResolver將有越高的解析視圖的權利,因此第一個進行解析的將是ViewResolver鏈中order值最小的那個。當一個ViewResolver在進行視圖解析後返回的View對象是null的話就表示該ViewResolver不能解析該視圖,這個時候若是還存在其餘order值比它大的ViewResolver就會調用剩餘的ViewResolver中的order值最小的那個來解析該視圖,依此類推。當ViewResolver在進行視圖解析後返回的是一個非空的View對象的時候,就表示該ViewResolver可以解析該視圖,那麼視圖解析這一步就完成了,後續的ViewResolver將不會再用來解析該視圖。當定義的全部ViewResolver都不能解析該視圖的時候,Spring就會拋出一個異常。

基於Spring支持的這種ViewResolver鏈模式,咱們就能夠在SpringMVC應用中同時定義多個ViewResolver,給定不一樣的order值,這樣咱們就能夠對特定的視圖特定處理,以此來支持同一應用中有多種視圖類型。注意:像InternalResourceViewResolver這種能解析全部的視圖,即永遠能返回一個非空View對象的ViewResolver必定要把它放在ViewResolver鏈的最後面。

 

10)redirectAttributes與flashAttributes

Spring MVC 3.1版本加了一個頗有用的特性,Flash屬性,它能解決一個長久以來缺乏解決的問題,一個POST/Redirect/GET模式問題。

一般當咱們生成一次http重定向請求的時候,被存儲到請求數據會丟失,使得下一次GET請求不可能訪問到此次請求中的一些有用的信息。

Flash attributes 的到來就是爲了處理這一狀況. Flash attributes 爲一個請求存儲意圖爲另一個請求所使用的屬性提供了一條途徑. Flash attributes 在對請求的重定向生效以前被臨時存儲(一般是在session)中,而且在重定向以後被當即移除。

爲了這樣作, Flash 特性使用了兩個集合. FlashMap 被用來管理 flash attributes 而 FlashMapManager 則被用來存儲,獲取和管理 FlashMap 實體。

對於每一次請求一個 「input」 flash map 會被建立,來存儲來自任何以前請求的 flash attribute 還有一個 「output」 flash map 會被建立,來存儲任何咱們存儲在這個請求中的,以後的請求參數。

要想在你的 Spring MVC 應用中使用 Flash attribute,要用 3.1 版本或以上。而且要在 spring-servlet.xml 文件中加入 mvc:annotation-driven。

<mvc:annotation-driven />

這些都完成以後,Flash attribute 就會自動設爲「開啓」,以供使用了。只需在你的 Spring controller 方法中加入RedirectAttributes redirectAttributes。

import org.springframework.web.servlet.mvc.support.RedirectAttributes;
//...

@RequestMapping(value="addcustomer", method=RequestMethod.POST)
public String addCustomer(@ModelAttribute("customer") Customer customer,
final RedirectAttributes redirectAttributes) {
//...
redirectAttributes.addFlashAttribute("message", "Successfully added..");
//...

return "redirect:some_other_request_name";
}

addFlashAttribute 方法會自動向 output flash map 中添加給定的參數,並將它傳遞給後續的請求。

 

11)springmvc異常處理

Spring MVC處理異常有3種方式: (1) 使用Spring MVC提供的簡單異常處理器 SimpleMappingExceptionResolver; (2) 實現Spring的異常處理接口HandlerExceptionResolver 自定義本身的異常處理器; (3) 使用@ExceptionHandler註解實現異常處理;

自定義登陸異常:

/**
* @author: 肖德子裕
* @date: 2018/11/05 11:26
* @description: 自定義登陸異常
*/
public class UserException extends RuntimeException{
  public UserException(String message) {
      super(message);
  }

  public UserException(String message, Throwable cause) {
      super(message, cause);
  }

  public UserException(Throwable cause) {
      super(cause);
  }
}

/**
* @author: 肖德子裕
* @date: 2018/11/05 11:34
* @description: 全局異常處理
*/
@ControllerAdvice("com.ex.controller")
public class ExceptionAdvice {
  /**
    * 全局異常處理方法
    * 用於處理用戶登錄異常
    * @param e
    * @return
    */
  @ExceptionHandler(UserException.class)
  @ResponseBody
  public ResponseVO handlerLoginException(RuntimeException e){
      ResponseVO vo = new ResponseVO();
      vo.setCode(401);
      vo.setMessage(e.getMessage());
      return vo;
  }

  /**
    * 全局異常處理方法
    * 用於處理crud操做
    * @param e
    * @return
    */
  @ExceptionHandler(DataAccessException.class)
  @ResponseBody
  public ResponseVO handlerDataAccessException(RuntimeException e){
      ResponseVO vo = new ResponseVO();
      vo.setCode(500);
      vo.setMessage(e.getMessage());
      return vo;
  }
}

/**
* @author: 肖德子裕
* @date: 2018/11/05 11:21
* @description: 用戶操做Servie
*/
@Service
public class UserServiceImpl implements UserServie {
  @Autowired
  private UserDao userDao;

  @Override
  public User login(User user) {
      User newUser = userDao.getUserByName(user.getUserName());
      if(newUser == null){
          throw new UserException("用戶不存在");
      }
      if(!user.getPassWord().equals(newUser.getPassWord())){
          throw new UserException("密碼錯誤");
      }
      return newUser;
  }

  @Override
  public void addUser(User user) {
      userDao.saveUser(user);
  }
}

 

12)過濾器與攔截器

過濾器的實現:

/**
* @author: 肖德子裕
* @date: 2018/8/21 10:29
* @description: 權限過濾器
*/
public class UserLoginPrivilegeFilter implements Filter {
  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
          throws IOException, ServletException {
      HttpServletRequest req=(HttpServletRequest)request;
      HttpServletResponse resp=(HttpServletResponse)response;

      //效驗用戶是否登錄,即檢查session是否存在用戶對象
      HttpSession session=req.getSession();

      //判斷用戶是否登錄,沒登錄不能提交訂單
      User user=(User)session.getAttribute("user");
      if (user==null){
          //跳轉到登錄頁
          resp.sendRedirect(req.getContextPath()+"/login.jsp");
          return;
      }
      //放行
      chain.doFilter(req, response);
  }

  @Override
  public void init(FilterConfig filterConfig) throws ServletException {

  }

  @Override
  public void destroy() {

  }
}

須要在xml文件中配置:

<!-- 用戶是否登錄 -->
<filter>
  <filter-name>UserLoginPrivilegeFilter</filter-name>
  <filter-class>com.itheima.web.filter.UserLoginPrivilegeFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>UserLoginPrivilegeFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

攔截器的實現:

/**
* @author: 肖德子裕
* @date: 2018/9/12 15:25
* @description: 攔截器類1
*/
public class Interceptor1 implements HandlerInterceptor{

  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object arg2) throws Exception {
    // TODO Auto-generated method stub
    System.out.println("方法前 1");
    //判斷用戶是否登錄 若是沒有登錄 重定向到登錄頁面   不放行   若是登錄了 就放行了
    // URL http://localhost:8080/springmvc-mybatis/login.action
    //URI /login.action
    String requestURI = request.getRequestURI();
    if(!requestURI.contains("/login")){
        String username = (String) request.getSession().getAttribute("USER_SESSION");
        if(null == username){
          response.sendRedirect(request.getContextPath() + "/item/login.action");
          //不放行
          return false;
        }
    }
    //放行
    return true;
  }

  @Override
  public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
        throws Exception {
    // TODO Auto-generated method stub
    System.out.println("方法後 1");
  }

  @Override
  public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
        throws Exception {
    // TODO Auto-generated method stub
    System.out.println("頁面渲染後 1");
  }
}

須要在springmvc中配置:

<!-- Springmvc的攔截器 -->
<mvc:interceptors>
  <!-- 多個攔截器;運行時及其複雜 -->
  <mvc:interceptor>
      <mvc:mapping path="/**"/>
      <!-- 自定義的攔截器類 -->
      <bean class="com.interceptor.Interceptor1"/>
  </mvc:interceptor>
  <!--     <mvc:interceptor>
              <mvc:mapping path="/**"/>
              自定義的攔截器類
              <bean class="com.interceptor.Interceptor2"/>
          </mvc:interceptor> -->
</mvc:interceptors>

攔截規範:

  1. /* 攔截全部 jsp js png css 真的全攔截 建議不使用

  2. *.action *.do 攔截以do action 結尾的請求 確定能使用

  3. / 攔截全部 (不包括jsp) (包含.js .png.css) 強烈建議使用

相關文章
相關標籤/搜索