1. 程式人生 > >Spring MVC體系結構(二)

Spring MVC體系結構(二)

目錄

前言

總結

前言

在上一篇博文中,我們學習了Spring MVC的原理、處理流程及其體系結構,完成了請求與處理器之間的對映。今天,我們繼續深入學習Spring MVC的一些知識,主要是引數傳遞(View到Controller、Controller到View)、檢視解析器。

一、引數傳遞

引數傳遞在專案中是必不可少的,只要是動態頁面,肯定會涉及資料傳遞,想都不用想。接下來我們就學習一下View與Controller之間的引數傳遞。

一、View to Controller

頁面往控制器傳參,最粗暴的方式是直接入參,改造我們之前的WelcomeController.java如下:

package com.smbms.controller;


import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class WelcomeController{
    @RequestMapping(value = "/welcome")
    public String welcome(@RequestParam String name){
        //如果方法的入參名與傳來的引數名一致,可省略@RequestParam註解
        System.out.println(name);
        return "welcome";
    }
}

修改我們的main.jsp如下,多傳一個引數:

<%@ page language="java" import="java.util.*" contentType="text/html; charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <a href="../welcome?name=MVC大爺">去拜訪Spring MVC大爺</a>  <%--因為是在jsp目錄下,所以先返回上一級目錄,回到根目錄--%>
</body>
</html>

執行專案,會在控制檯輸出“MVC大爺”字串。

我們在以上示例中使用了@RequestParam在處理方法的的入參進行了標註,說明這是一個從View傳來的引數(這裡只簡單的使用此註解,不進行任何設定,下文會講述)。這種情況下我們必須保證方法的入參名與View傳來的引數名一致,可以發現,如果我們傳來的引數名不是name而是userName,那麼會報錯400,錯誤資訊是“請求中引數name不存在”。但是在實際開發中,由於業務需求,可能有些引數並不是必需的,那麼我們該如何解決這個問題呢?

這就需要詳細瞭解@RequestMapping和@RequestParam,兩者結合使用,實現靈活的引數傳遞。

1、@RequestMapping

通過之前的學習,我們知道@RequestMapping的主要職責就是將不同的請求對映到對應的控制器處理方法。HTTP請求資訊除了最基本的URL地址之外,還包括請求方法、HTTP協議及版本、報文頭、報文體等,所以我們除了使用URL進行對映,還可以應用其他的HTTP資訊來完成更精確的對映。

使用@RequestMapping完成對映,具體包含4個方面的資訊:請求URL、請求引數、請求方法、請求頭。

1)通過請求URL進行對映

我們之前的寫法就是通過請求URL對映,即@RequestMapping("/welcome"),等同於@RequestMapping(value="/welcome")

此外,拓展一點東西,@RequestMapping可以在控制器的類定義處指定URL,即是這種的:

package com.smbms.controller;


import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
@RequestMapping("/user")
public class WelcomeController{
    @RequestMapping(value = "/welcome")
    public String welcome(@RequestParam String name){
        //如果方法的入參名與傳來的引數名一致,可省略@RequestParam註解
        System.out.println(name);
        return "welcome";
    }
}

在以上程式碼中,我們使用@RequestMapping註解在WelcomeController的類定義處進行了標註,定義URL為“/user”,此時這個URL相對於Web應用的部署路徑,而welcome()方法處指定的URL則相對於類定義處的URL而言的,什麼意思呢?就是說,最後我們發過來的請求得是"http://localhost:8088/工程名/user/welcome"。所以修改main.jsp:

<%@ page language="java" import="java.util.*" contentType="text/html; charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <a href="../user/welcome?name=MVC大爺">去拜訪Spring MVC大爺</a>  <%--因為是在jsp目錄下,所以先返回上一級目錄,回到根目錄--%>
</body>
</html>

 執行,依然可以訪問。

這種是很常用的方式,在不同的Controller的類定義處指定相應的@RequestMapping,以便區分不同的請求,不易出錯,也更規範。比如/user/add,/user/update,/user/delete,等等。

此外,分析原始碼可以看出來,@RequestMapping的返回值是一個String型別的陣列,也就是說,我們還可以這樣寫:

@RequestMapping({"/index","/welcome"})

這句話說明URL請求為/index的、/welcome的都可以進入該處理方法。

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

@RequestMapping除了可以使用請求URL進行對映請求之外,還可以使用請求引數、請求方法加以限制,通過多條件可以讓請求對映更加準確。改造WelcomeController.java,如下:

@RequestMapping(value = "/welcome",method = RequestMethod.GET,params = "name")
public String welcome(String name){
    //如果方法的入參名與傳來的引數名一致,可省略@RequestParam註解
    System.out.println(name);
    // model.addAttribute("userName",name);
    return "welcome";
}

在上述程式碼中,@RequestMapping的value表示請求的URL,method表示請求方法,此次設定了GET,所以POST請求是進不來的,param表示請求引數,此處我們的引數名為name。執行我們的專案,同樣可以訪問此方法,下面分析一下:

首先,value(請求路徑)匹配,method(超連結是GET請求)匹配,請求引數(name)也匹配,所以進入了處理方法。

下面分析幾種錯誤情況。

如果將main.jsp頁面的引數改為?userName=MVC大爺,則訪問會出現400,控制檯也會報出異常;

如果將處理方法的入參改為String userName,即這種的,與傳參名不匹配:

@RequestMapping(value = "/welcome",method = RequestMethod.GET,params = "name")
public String welcome(String userName){
    //如果方法的入參名與傳來的引數名一致,可省略@RequestParam註解
    System.out.println(userName);
    // model.addAttribute("userName",name);
    return "welcome";
}

此時不會報錯,只是後臺會獲取不到引數值,輸出null。所以我們若選擇方法引數直接入參,方法入參名必須與請求中的引數名一致

簡單的學習@RequestMapping之後,一開始的問題依然沒有解決,如果沒有傳遞引數依然報錯。接下來學習@RequestParam,兩者結合完成靈活的請求傳參。

2、@RequestParam

在方法入參處使用@RequestParam註解指定其對應的請求引數。@RequestParam有以下三個引數:

  • value:引數名
  • required:是否是必需的,預設為true,表示請求中必須包含對應的引數名,否則丟擲異常
  • defaultValue:預設引數名,不推薦使用,比較混亂

接下來就改造以上的示例:

@RequestMapping(value = "/welcome",method = RequestMethod.GET)
public String welcome(@RequestParam(value = "name",required = false) String userName){
    //如果方法的入參名與傳來的引數名一致,可省略@RequestParam註解
    System.out.println(userName);
    // model.addAttribute("userName",name);
    return "welcome";
}

測試執行,沒問題。

這種情況下不要求方法的入參名與請求中的引數名一致,保證@RequestParam註解能夠將二者關聯上即可。但是為了規範起見,建議一致。

二、Controller to View

瞭解完從View到Controller的傳參,接下來學習Controller到View的傳參。

這就需要模型資料的處理了,對於MVC框架來說,模型資料是最重要的一部分,因為控制層是為了產生模型資料(Model),而檢視(View)最終也是為了渲染模型資料並進行輸出。那麼如何將模型資料傳遞給檢視?Spring MVC提供了多種方式,介紹一下比較常用的:

1、ModelAndView

顧名思義,控制器的處理方法的返回值若是ModelAndView,即既包含Model,也包含View。那麼拿到該物件之後,Spring MVC就可以使用檢視對模型資料進行渲染。改造以上示例,拿到引數之後,再返回給頁面:

@RequestMapping(value = "/welcome")  //表示與哪個URL請求相對應
public ModelAndView welcome(@RequestParam(value = "name",required = true) String name){
    System.out.println(name);
    ModelAndView modelAndView=new ModelAndView();
    modelAndView.addObject("userName",name);
    modelAndView.setViewName("welcome");
    return modelAndView;
}

通過以上程式碼可以看出在welcome處理方法中,返回了ModelAndView物件,並通過addObject()方法新增模型資料,通過setViewName()方法設定邏輯檢視名。

ModelAndView物件的常用方法如下:

  • addObject(String attributeName,Object attributeValue);  該方法的第一個引數為key值,第二個引數為key對應的value。其中key的值可以隨便指定,只要保證在該Model內唯一即可。
  • addAllObjects(Map<String,?> modelMap);  從此方法可以看出,模型資料也是一個Map物件,我們可以新增Map到Model。
  • setView(View view);  指定一個具體的檢視物件。
  • setViewName(String viewName);  指定一個邏輯檢視名。

然後在welcome.jsp頁面使用EL表示式展現從Controller返回的ModelAndView物件中的Model(至於返回其他格式的資料,在以後介紹)。

<%@ page language="java" import="java.util.*" contentType="text/html; charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<h1>Hello World</h1>
<p>來自<span style="font-weight: bold;color: red">${userName}</span>的回覆</p>
</body>
</html>

我們在main.jsp頁面傳入引數“張三”,執行結果如下:

 2、Model

除了可以使用ModelAndView物件來返回模型資料外,我們還可以使用Spring MVC提供的Model物件來完成模型資料的傳遞。

其實,Spring MVC在呼叫方法前會建立一個隱含的模型物件,作為模型資料的儲存容器,一般稱為“隱含模型”。若處理方法的入參為Model型別,Spring MVC會將這個隱含模型的引用傳遞給這些入參,這樣開發者就可以通過一個Model型別的入參,訪問到模型中的所有資料,當然也可以做修改、新增等。繼續修改上面的示例,通過傳入Model引數的方式完成引數返回。

@RequestMapping(value = "/welcome",method = RequestMethod.GET)
public String welcome(@RequestParam(value = "name",required = false) String name,Model model){
    System.out.println(name);
    model.addAttribute("userName",name);
    return "welcome";
}

其他的都不需要做修改,執行專案是沒問題的。此處直接使用Model物件入參,把需要傳遞的模型資料放入Model,返回字串型別的邏輯檢視名即可。

不管是ModelAndView還是Model,它們的用法很類似,運用起來也比較靈活。Model物件的addAttribute()方法和ModelView物件的新增模型資料的方法的用法是一樣的,即Model物件也是一個Map型別的資料結構。

另外,對於Model物件的addAttribute()方法,key的指定並不是必須的,可以不指定。那麼既然它是一個Map結構,如果不指定它會以什麼為key呢?這種情況下,會預設使用物件的型別作為key。比如上面的示例中,我們的name是字串型別的,我們如果不指定key,則“string”就是它的key,訪問的時候即是${string}。不過個人不推薦此用法,容易混亂。

除此之外,addAttribute()方法可以新增任何資料型別,JavaBean、List、Map等都可以。

3、Map

我們之前說過,Spring MVC的Model其實就是一個Map資料結構,故我們使用Map作為處理方法的入參也是可行的。

@RequestMapping(value = "/welcome",method = RequestMethod.GET)
public String welcome(@RequestParam(value = "name",required = false) String name,Map<String,Object> model){
    //如果方法的入參名與傳來的引數名一致,可省略@RequestParam註解
    System.out.println(name);
    model.put("userName",name);
    return "welcome";
}

這種方式依然可以達到我們想要的結果。

Spring MVC控制器的處理方法若有Map或者Model型別的入參,就會將請求內的隱含模型物件傳遞給這些入參,因此可以在方法體內對模型資料進行讀寫。推薦使用Model。

4、@ModelAttribute

若希望將入參的資料物件放入資料模型中,就需要在相應入參前使用此註解。在以後的博文講解,此處知道有這麼個東西即可。

5、@SessionAttributes

此註解可以將模型中的屬性存入HttpSession中,以便在一次會話中共享該屬性。以後會講解。

二、檢視解析器

請求處理方法執行完成之後,最終返回一個ModelAndView物件。對於那些返回String型別的處理方法,Spring MVC內部也會將它們裝配成一個ModelAndView物件,它包含邏輯檢視名和資料模型,那麼接下來的工作就交給檢視解析器(ViewResolver)了。

ViewResolver是Spring MVC處理檢視的重要介面,通過它可以將控制器返回的邏輯檢視名解析成一個真正的檢視物件。

Spring MVC預設提供了多種檢視解析器,所有的檢視解析器都實現了ViewResolver介面,如圖:

 對於JSP這種常見的檢視技術,通常使用InternalResourceViewResolver作為檢視解析器。那麼它是如何工作的?

InternalResourceViewResolver是最常用的檢視解析器,通常用於查詢JSP和JSTL等檢視。通過原始碼就可以看出,它是URLBasedViewResolver的子類,會將返回的邏輯檢視名都解析成InternalResourceViewResolver物件,該物件會把Controller的處理方法返回的模型屬性都放在對應的請求作用域中,然後通過RequestDispatcher在伺服器端把請求轉發到目標URL。

我們只需要在配置檔案中進行如下配置:

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/jsp/" />
    <property name="suffix" value=".jsp" />
</bean>

如果控制器的處理方法返回字串“welcome”,那麼經過此檢視解析器的解析,即為:/jsp/welcome.jsp。

總結

最後,結合前一篇博文,我們對Spring MVC做一下簡單的總結:

  • MVC設計模式在各種成熟框架中都得到了良好的運用,它將Model、View、Controller三層清晰地分開,搭建一個鬆耦合、高可用性、高可適用性的完美架構。
  • Spring MVC框架是典型的MVC框架,是一個結構清晰的JSP Model2實現。它基於Servlet,核心是DispatcherServlet。
  • Spring MVC的處理器對映(HandlerMapping)可配置為註解式的處理器,只需配置<mvc:annotation-driven />標籤即可。
  • Spring MVC的控制器的處理方法返回的ModelAndView物件,包括模型資料和檢視資訊。
  • Spring MVC通過檢視解析器來完成檢視解析工作,把控制器的處理方法返回的邏輯檢視名解析成一個真正的檢視物件。

對於Spring MVC框架的更多內容,以及控制器返回JSON資料、HTML字串等,會在以後的博文慢慢更新。