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字串等,會在以後的博文慢慢更新。