1. 程式人生 > 其它 >Spring基礎知識(12)- Spring MVC (二)

Spring基礎知識(12)- Spring MVC (二)

Spring MVC執行流程、檢視解析器(ViewResolver)、@Controller和@RequestMapping註解


1. Spring MVC執行流程

    1)  HTTP request 請求的執行流程

        (1) 使用者點選某個請求路徑,發起一個 HTTP request 請求,該請求會被提交到 DispatcherServlet(前端控制器);
        (2) 由 DispatcherServlet 請求一個或多個 HandlerMapping(處理器對映器),並返回一個執行鏈(HandlerExecutionChain);
        (3) DispatcherServlet 將執行鏈返回的 Handler 資訊傳送給 HandlerAdapter(處理器介面卡);
        (4) HandlerAdapter 根據 Handler 資訊找到並執行相應的 Handler(常稱為 Controller);
        (5) Handler 執行完畢後會返回給 HandlerAdapter 一個 ModelAndView 物件(Spring MVC的底層物件,包括 Model 資料模型和 View 檢視資訊);
        (6) HandlerAdapter 接收到 ModelAndView 物件後,將其返回給 DispatcherServlet ;
        (7) DispatcherServlet 接收到 ModelAndView 物件後,會請求 ViewResolver(檢視解析器)對檢視進行解析;
        (8) ViewResolver 根據 View 資訊匹配到相應的檢視結果,並返回給 DispatcherServlet;
        (9) DispatcherServlet 接收到具體的 View 檢視後,進行檢視渲染,將 Model 中的模型資料填充到 View 檢視中的 request 域,生成最終的 View(檢視);
        (10) 檢視負責將結果顯示到瀏覽器(客戶端)。

    2)  元件或介面



        Spring MVC 涉及到的元件或介面如下。

        (1) DispatcherServlet(前端控制器)

            DispatcherServlet 是前端控制器,從圖 1 可以看出,Spring MVC 的所有請求都要經過 DispatcherServlet 來統一分發。DispatcherServlet 相當於一個轉發器或中央處理器,控制整個流程的執行,對各個元件進行統一排程,以降低元件之間的耦合性,有利於元件之間的拓展。

        (2) HandlerMapping(處理器對映器)

            HandlerMapping 是處理器對映器,其作用是根據請求的 URL 路徑,通過註解或者 XML 配置,尋找匹配的處理器(Handler)資訊。

        (3) HandlerAdapter(處理器介面卡)

            HandlerAdapter 是處理器介面卡,其作用是根據對映器找到的處理器(Handler)資訊,按照特定規則執行相關的處理器(Handler)。

        (4) Handler(處理器)

            Handler 是處理器,和 Java Servlet 扮演的角色一致。其作用是執行相關的請求處理邏輯,並返回相應的資料和檢視資訊,將其封裝至 ModelAndView 物件中。

        (5) ViewResolver(檢視解析器)

            View Resolver 是檢視解析器,其作用是進行解析操作,通過 ModelAndView 物件中的 View 資訊將邏輯檢視名解析成真正的檢視 View(如通過一個 JSP 路徑返回一個真正的 JSP 頁面)。

        (6) View(檢視)

            View 是檢視,其本身是一個介面,實現類支援不同的 View 型別(JSP、FreeMarker、Excel 等)。

        以上元件中,需要開發人員進行開發的是處理器(Handler,常稱Controller)和檢視(View)。通俗的說,要開發處理該請求的具體程式碼邏輯,以及最終展示給使用者的介面。


2. 檢視解析器(ViewResolver)

    在 “Spring基礎知識(11)- Spring MVC (一)” 裡建立的 SpringmvcBasic 專案中,TestController 的 handleRequest() 方法裡 ModelAndView 載入了 demo.jsp, 程式碼如下:

 1         package com.example.controller;
 2 
 3         import javax.servlet.http.HttpServletRequest;
 4         import javax.servlet.http.HttpServletResponse;
5 import org.springframework.ui.ModelMap; 6 import org.springframework.web.servlet.ModelAndView; 7 import org.springframework.web.servlet.mvc.Controller; 8 9 public class TestController implements Controller { 10 public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) { 11 ModelMap modelMap = new ModelMap(); 12 modelMap.addAttribute("message", "Spring MVC Demo Page"); 13 return new ModelAndView("/WEB-INF/jsp/demo.jsp", modelMap); 14 } 15 }


    這裡的 demo.jsp 作為 View 實體檔案,使用全路徑 "/WEB-INF/jsp/demo.jsp", "/" 指 webapp 的根目錄,檔案型別是 JSP。

    在 controller 的方法,如果不想關心 View 檔案的路徑和檔案型別,只通過 View 的名字(比如,"demo")就載入實體 View 檔案,就需要用到檢視解析器(ViewResolver)。Spring MVC 提供了多個檢視解析器,下面介紹一些常用的檢視解析器。

    1) URLBasedViewResolver

        UrlBasedViewResolver 是對 ViewResolver 的一種簡單實現,主要提供了一種拼接 URL 的方式來解析檢視。

        UrlBasedViewResolver 通過 prefix 屬性指定字首,suffix 屬性指定字尾。當 ModelAndView 物件返回具體的 View 名稱時,它會將字首 prefix 和字尾 suffix 與具體的檢視名稱拼接,得到一個檢視資原始檔的具體載入路徑,從而載入真正的檢視檔案並反饋給使用者。

        使用 UrlBasedViewResolver 除了要配置字首和字尾屬性之外,還需要配置“viewClass”,表示解析成哪種檢視。示例程式碼如下。

1             <bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">            
2                 <property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceViewResolver"/> <!--不能省略-->
3                 <!--字首-->
4                 <property name="prefix" value="/WEB-INF/jsp/"/>
5                 <!--字尾-->
6                 <property name="suffix" value=".jsp"/>  
7             </bean>


        上述 viewClass 值為 InternalResourceViewResolver,它用來展示 JSP 頁面。如果需要使用 jstl 標籤展示資料,將 viewClass 屬性值指定為 JstlView 即可。

        另外,存放在 /WEB-INF/ 目錄下的內容不能直接通過 request 請求得到,所以為了安全性考慮,通常把 jsp 檔案放在 WEB-INF 目錄下。

    2) InternalResourceViewResolver

        InternalResourceViewResolver 為“內部資源檢視解析器”,是日常開發中最常用的檢視解析器型別。它是 URLBasedViewResolver 的子類,擁有 URLBasedViewResolver 的一切特性。

        InternalResourceViewResolver 能自動將返回的檢視名稱解析為 InternalResourceView 型別的物件。InternalResourceView 會把 Controller 處理器方法返回的模型屬性都存放到對應的 request 屬性中,然後通過 RequestDispatcher 在伺服器端把請求 forword 重定向到目標 URL。
        
        也就是說,使用 InternalResourceViewResolver 檢視解析時,無需再單獨指定 viewClass 屬性。示例程式碼如下。

1             <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
2                 <property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceViewResolver"/> <!--可以省略-->
3                 <!--字首-->
4                 <property name="prefix" value="/WEB-INF/jsp/"/>
5                 <!--字尾-->
6                 <property name="suffix" value=".jsp"/>  
7             </bean>


    3) FreeMarkerViewResolver

        FreeMarkerViewResolver 是 UrlBasedViewResolver 的子類,可以通過 prefix 屬性指定字首,通過 suffix 屬性指定字尾。

        FreeMarkerViewResolver 最終會解析邏輯檢視配置,返回 freemarker 模板。不需要指定 viewClass,配置如下。

1             <bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
2                 <property name="prefix" value="fm_"/>
3                 <property name="suffix" value=".ftl"/>
4             </bean>


        下面指定 FreeMarkerView 型別最終生成的實體檢視(模板檔案)的路徑以及其他配置。需要給 FreeMarkerViewResolver 設定一個 FreeMarkerConfig 的 bean 物件來定義 FreeMarker 的配置資訊,程式碼如下。

1             <bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
2                 <property name="templateLoaderPath" value="/WEB-INF/ftl" />
3             </bean>


        定義了 templateLoaderPath 屬性後,Spring 可以通過該屬性找到 FreeMarker 模板檔案的具體位置。當有模板位於不同的路徑時,可以配置 templateLoaderPath 屬性,來指定多個資源路徑。

        然後定義一個 Controller,讓其返回 ModelAndView,同時定義一些返回引數和檢視資訊。

1             public class TestController implements Controller {
2                 public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
3                     ModelAndView mv = new ModelAndView();
4                     mv.addObject("username", "Tester");
5                     mv.setViewName("freemarker");
6                     return mv;
7                 }
8             }


        當 FreeMarkerViewResolver 解析邏輯檢視資訊時,會生成一個 URL 為“字首+檢視名+字尾”(這裡即“fm_freemarker.ftl”)的 FreeMarkerView 物件,然後通過 FreeMarkerConfigurer 的配置找到 templateLoaderPath 對應文字檔案的路徑,在該路徑下找到該文字檔案,從而 FreeMarkerView 就可以利用該模板檔案進行檢視的渲染,並將 model 資料封裝到即將要顯示的頁面上,最終展示給使用者。

        在 /WEB-INF/ftl 資料夾下建立 fm_freemarker.ftl,程式碼如下。

1             <html>
2             <head>
3             <title>FreeMarker</title>
4             </head>
5             <body>
6                 <b>Welcome!</b>
7                 <i>${username }</i>
8             </body>
9             </html>

 

3. @Controller 和 @RequestMapping 註解

    上文中是使用 org.springframework.web.servlet.mvc.Controller,這個 Controller 介面僅僅定義了一個方法 handleRequest() 用於負責處理客戶請求,並返回適當的模型和檢視,即一個 action 對應於一個 Controller。

    SpringMVC 提供一個多 action 控制器  org.springframework.web.servlet.mvc.multiaction.MultiActionController,在 Spring 4.3.9.RELEASE 版本下,MultiActionController 處於 Deprecated 狀態,所以不建議使用這個控制器。

    SpringMVC 提供的處理多 action 的解決方案是 @Controller 和 @RequestMapping 註解。

    1) @Controller 註解

        @Controller 註解用於宣告某類的例項是一個控制器。例如,在 com.example.controller 包中建立控制器類 IndexController,示例程式碼如下。

1             package com.example.controller;
2             
3             import org.springframework.stereotype.Controller;
4 
5             @Controller
6             public class IndexController {
7 
8             }


        Spring MVC 使用掃描機制,需要在配置檔案中使用 <context:component-scan/> 元素指定要掃描的包路徑。

        在配置檔案 springmvc-beans.xml 中新增以下程式碼:

            <!-- 掃描 com.example 包 -->
            <context:component-scan base-package="com.example" />

    2) @RequestMapping 註解

        一個控制器內有多個處理請求的方法,每個方法負責不同的請求操作,而 @RequestMapping 就負責將請求對映到對應的控制器方法上。

        在基於註解的控制器類中可以為每個請求編寫對應的處理方法。使用 @RequestMapping 註解將請求與處理方法一 一對應即可。

        @RequestMapping 註解可用於類或方法上。用於類上,表示類中的所有響應請求的方法都以該地址作為父路徑。

        @RequestMapping 註解常用屬性如下。

屬性 描述
value 是 @RequestMapping 註解的預設屬性,因此如果只有 value 屬性時,可以省略該屬性名,如果有其它屬性,則必須寫上 value 屬性名稱,支援萬用字元匹配,如 "test/*"。
path 和 value 屬性都是用來作為對映使用,支援萬用字元匹配,如 "test/*"。
name 相當於方法的註釋,使方法更易理解。
method 用於表示該方法支援哪些 HTTP 請求。如果省略 method 屬性,則說明該方法支援全部的 HTTP 請求。method = RequestMethod.GET 表示該方法只支援 GET 請求。也可指定多個 HTTP 請求,可以同時支援 GET 和 POST 請求。
params 用於指定請求中規定的引數。
header 表示請求中必須包含某些指定的 header 值。
consumers 用於指定處理請求的提交內容型別(Content-Type),例如:application/json、text/html。
produces 用於指定返回的內容型別,返回的內容型別必須是 request 請求頭(Accept)中所包含的型別。也可以指定返回值的編碼。如:produces = "application/json,charset=utf-8"

 

    3) 通過請求 URL 進行對映

        (1) 方法級別註解

            方法級別註解的示例程式碼如下。

 1             package com.example.controller;
 2             
 3             import org.springframework.stereotype.Controller;
 4             import org.springframework.web.bind.annotation.RequestMapping;
 5 
 6             @Controller
 7             public class IndexController {
 8                 
 9                 @RequestMapping(value = "/index/login")
10                 public String login() {
11                     return "login";
12                 }
13             }


            上述示例中有兩個 RequestMapping 註解語句,它們都作用在處理方法上。在整個 Web 專案中,@RequestMapping 對映的請求 URL 必須保證全域性唯一。

            使用者可以使用如下 URL 訪問 login 方法(請求處理方法),在訪問 login 方法之前需要事先在 /WEB-INF/jsp/ 目錄下建立 login.jsp。

            http://localhost:9090/index/login

            注:本系列文章的示例程式碼,所用埠都是 9090

        (2) 類級別註解

            類級別註解的示例程式碼如下。

 1             package com.example.controller;
 2 
 3             import org.springframework.stereotype.Controller;
 4             import org.springframework.web.bind.annotation.RequestMapping;
 5             
 6             @Controller
 7             @RequestMapping("/index")
 8             public class IndexController {
 9 
10                 @RequestMapping("/login")
11                 public String login() {
12                     return "login";
13                 }
14 
15             }


            在類級別註解的情況下,控制器類中的所有方法都將對映為類級別的請求。使用者可以使用如下 URL 訪問 login 方法。

              http://localhost:9090/index/login

            為了方便維護程式,建議開發者採用類級別註解,將相關處理放在同一個控制器類中。

    4) 通過請求引數、請求方法進行對映

        @RequestMapping 除了可以使用請求 URL 對映請求之外,還可以使用請求引數、請求方法來對映請求,通過多個條件可以讓請求對映更加精確。

 1             package com.example.controller;
 2 
 3             import org.springframework.stereotype.Controller;
 4             import org.springframework.web.bind.annotation.RequestMapping;
 5             import org.springframework.web.bind.annotation.RequestParam;
 6             import org.springframework.web.bind.annotation.RequestMethod;
 7 
 8             @Controller
 9             public class IndexController {
10 
11                 @RequestMapping(value = "/index/success" method=RequestMethod.GET, params="username")
12                 public String success(@RequestParam String username) {
13                     return "success";
14                 }
15 
16             }


        上述程式碼中,@RequestMapping 的 value 表示請求的 URL;method 表示請求方法,此處設定為 GET 請求,若是 POST 請求,則無法進入 success 這個處理方法中。params 表示請求引數,此處引數名為 username。

    5) 請求處理方法

        在控制類中每個請求處理方法可以有多個不同型別的引數,以及一個多種型別的返回結果。

        (1) 請求處理方法中常出現的引數型別

            常用的引數型別有 Servlet 物件型別(比如 HttpSession、HttpServletRequest) 、輸入輸出流、表單實體類、註解型別、與 Spring 框架相關的型別等。

            其中特別重要的型別是 org.springframework.ui.Model 型別,該型別是一個包含 Map 的 Spring MVC型別。在每次呼叫請求處理方法時 Spring MVC 都將建立 org.springframework.ui.Model 物件。
            
            示例程式碼如下:

 1                 package com.example.controller;
 2 
 3                 import org.springframework.stereotype.Controller;
 4                 import org.springframework.web.bind.annotation.RequestMapping;
 5                 import org.springframework.ui.Model;
 6                 import javax.servlet.http.HttpSession;
 7                 import javax.servlet.http.HttpServletRequest;
 8 
 9                 @Controller
10                 @RequestMapping("/index")
11                 public class IndexController {
12 
13                     @RequestMapping("/test1")
14                     public String test1(HttpSession session, HttpServletRequest request) {
15                         session.setAttribute("key1", "session's value");
16                         request.setAttribute("key2", "request's value");
17                         return "test1";
18                     }
19 
20                     @RequestMapping("/test2")
21                     public String test2(Model model) {
22                         model.addAttribute("message", "It is OK");
23                         return "test2";
24                     }
25 
26                 }


        (2) 請求處理方法常見的返回型別

            a) ModelAndView
            b) Model
            c) 包含模型屬性的 Map
            d) View
            e) 代表邏輯檢視名的 String
            f) void
            g) 其它任意 Java 型別

            最常見的返回型別就是代表邏輯檢視名稱的 String 型別。

示例

    在 “Spring基礎知識(11)- Spring MVC (一)” 裡建立的 SpringmvcBasic 專案基礎上,修改如下。

    1)修改配置檔案 springmvc-beans.xml

 1         <?xml version="1.0" encoding="UTF-8"?>
 2         <beans xmlns="http://www.springframework.org/schema/beans"
 3             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4             xmlns:context="http://www.springframework.org/schema/context"
 5             xmlns:mvc="http://www.springframework.org/schema/mvc"
 6             xsi:schemaLocation="http://www.springframework.org/schema/beans
 7                                 http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
 8                                 http://www.springframework.org/schema/context
 9                                 http://www.springframework.org/schema/context/spring-context-4.0.xsd
10                                 http://www.springframework.org/schema/mvc
11                                 http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
12 
13             <!-- 掃描 com.example 包 -->
14             <context:component-scan base-package="com.example" />
15 
16             <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
17                 <property name="prefix" value="/WEB-INF/jsp/" />
18                 <property name="suffix" value=".jsp" />
19             </bean>
20             
21             <!-- TestController 類對映到 "/demo" -->
22             <bean name="/demo" class="com.example.controller.TestController"/>
23 
24         </beans>


    2) 建立 View

        (1) 建立 src/main/webapp/WEB-INF/jsp/login.jsp 檔案

 1             <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"  isELIgnored="false" %>
 2             <html>
 3             <head>
 4                 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 5                 <title>Login</title>
 6             </head>
 7             <body>
 8                 <h3>Login Page</h3>
 9             </body>
10             </html>


        (2) 建立 src/main/webapp/WEB-INF/jsp/success.jsp 檔案

 1             <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"  isELIgnored="false" %>
 2             <html>
 3             <head>
 4                 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 5                 <title>Success</title>
 6             </head>
 7             <body>
 8                 <h3>Success Page</h3>
 9                 <p>&nbsp;</p>
10                 <p>Username: ${username}</p>
11             </body>
12             </html>


        (3) 建立 src/main/webapp/WEB-INF/jsp/test.jsp 檔案

 1             <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"  isELIgnored="false" %>
 2             <html>
 3             <head>
 4                 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 5                 <title>Test</title>
 6             </head>
 7             <body>
 8                 <h3>Test Page</h3>
 9                 <p>&nbsp;</p>
10                 <p>key1: ${key1}</p>
11                 <p>key2: ${key2}</p>
12             </body>
13             </html>


    3) 建立 Controller

        建立 src/main/java/com/example/controller/IndexController.java 檔案

 1             package com.example.controller;
 2 
 3             import javax.servlet.http.HttpSession;
 4             import javax.servlet.http.HttpServletRequest;
 5 
 6             import org.springframework.ui.Model;
 7             import org.springframework.stereotype.Controller;
 8             import org.springframework.web.bind.annotation.RequestMapping;
 9             import org.springframework.web.bind.annotation.RequestParam;
10             import org.springframework.web.bind.annotation.RequestMethod;
11 
12             @Controller
13             @RequestMapping("/index")
14             public class IndexController {
15 
16                 @RequestMapping("/login")
17                 public String login() {
18                     return "login";
19                 }
20 
21                 // URL和GET引數都要匹配,/index/success?username=xxx
22                 @RequestMapping(value = "/success" method=RequestMethod.GET, params="username")
23                 public String success(@RequestParam String username, Model model) {
24                     model.addAttribute("username",username);
25                     return "success";
26                 }
27 
28                 @RequestMapping("/test")
29                 public String test(HttpSession session, HttpServletRequest request) {
30                     session.setAttribute("key1", "session's value");
31                     request.setAttribute("key2", "request's value");
32                     return "test";
33                 }
34 
35             }