在Spring MVC中正蛙,控制器只是方法上添加了@RequestMapping注解 的類督弓,這個注解聲明了它們所要處理的請求。
-
假設控制器類要處理對“/”的請求乒验, 并渲染應用的首頁愚隧。程序清單5.3所示的HomeController可能是最 簡單的Spring MVC控制器類了。
程序清單5.3 HomeController:超級簡單的控制器* package com.spring.mvc.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import static org.springframework.web.bind.annotation.RequestMethod.GET; /** * 聲明一個控制器 * @author huyingqi */ @Controller public class HomeController { @RequestMapping(value = "/",method = GET ) public String home(){ //視圖名為home return "home"; } }
-
在上述內(nèi)容中锻全,我們可以注意到HomeController帶有@Controller注解奸攻。
- 這個注解用于聲明控制器,但它對Spring MVC本身的影響并不大虱痕。
-
HomeController是一個構(gòu)造型注解睹耐,基于@Component注解。
- 它的目的是輔助實現(xiàn)組件掃描部翘。
- 由于HomeController帶有@Controller注解硝训,組件掃描器會自動找到它,并將其聲明為Spring應用上下文中的一個bean新思。
實際上窖梁,你也可以讓HomeController帶有@Component注解,它的效果是一樣的夹囚。但在表意性上可能會有所差別纵刘,無法確定HomeController是什么類型的組件。
-
HomeController中唯一的方法是home()方法荸哟,它帶有@RequestMapping注解假哎。
- value屬性
- 指定了該方法要處理的請求路徑,
- method屬性
- 細化了它所處理的HTTP方法鞍历。
- 在這個例子中舵抹,當收到對"/"的HTTP GET請求時,將調(diào)用home()方法劣砍。
- value屬性
-
在home()方法中惧蛹,并沒有做太多的事情,它只是返回一個String類型的"home"刑枝。這個字符串將被Spring MVC解釋為要渲染的視圖名稱香嗓。DispatcherServlet會要求視圖解析器將這個邏輯名稱解析為實際的視圖。
鑒于我們配置InternalResourceViewResolver的方式装畅,視圖 名“home”將會解析為“/WEB-INF/views/home.jsp”路徑的JSP 】坑椋現(xiàn)在, 我們會讓Spittr應用的首頁相當簡單洁灵,如下所示饱岸。
程序清單5.4 Spittr應用的首頁掺出,定義為一個簡單的JSP
[圖片]
這個JSP并沒有太多需要注意的地方。它只是歡迎應用的用戶苫费,并提 供了兩個鏈接:一個是查看Spittle列表汤锨,另一個是在應用中進行 注冊。圖5.2展現(xiàn)了此時的首頁是什么樣子的百框。
在本章完成之前闲礼,我們將會實現(xiàn)處理這些請求的控制器方法。但現(xiàn)在铐维,讓我們對這個控制器發(fā)起一些請求柬泽,看一下它是否能夠正常工作。測試控制器最直接的辦法可能就是構(gòu)建并部署應用嫁蛇,然后通過瀏 覽器對其進行訪問锨并,但是自動化測試可能會給你更快的反饋和更一致 的獨立結(jié)果。所以睬棚,讓我們編寫一個針對HomeController的測 試第煮。
[圖片]
圖5 .2 當前的Spittr首頁
5.2.1 測試控制器
讓我們再審視一下HomeController。如果你眼神不太好的話抑党,你 甚至可能注意不到這些注解包警,所看到的僅僅是一個簡單的POJO 。我 們都知道測試POJO是很容易的底靠。因此害晦,我們可以編寫一個簡單的類 來測試HomeController,如下所示:
程序清單5.5 HomeControllerTest:測試HomeControllerpackage com.spring.mvc; import com.spring.mvc.controller.HomeController; import org.junit.Assert; import org.junit.Test; public class HomeControllerTest { @Test public void testHomePage() throws Exception { HomeController homeController = new HomeController(); Assert.assertEquals("home",homeController.home()); } } -
在程序清單5.5中的測試非常簡單暑中,它只測試了home()方法的行為壹瘟。測試直接調(diào)用home()方法,并斷言返回的字符串包含"home"值痒芝。這個測試并沒有從Spring MVC控制器的角度進行測試俐筋,它沒有斷言當接收到針對"/"的GET請求時會調(diào)用home()方法。由于返回的值正好是"home"严衬,所以也沒有真正判斷"home"是否是視圖的名稱。
然而笆呆,從Spring 3.2開始请琳,我們可以使用控制器的方式來測試Spring MVC中的控制器,而不僅僅作為POJO進行測試赠幕。Spring現(xiàn)在提供了一種模擬Spring MVC并執(zhí)行HTTP請求的機制俄精。這樣,在測試控制器時榕堰,就不需要啟動Web服務器和Web瀏覽器了竖慧。
-
為了演示如何測試Spring MVC控制器嫌套,我們重寫了HomeControllerTest,并使用了Spring MVC中的新測試特性圾旨。程序清單5.6展示了新的HomeControllerTest踱讨。
程序清單5.6 改進HomeControllerTest
新版本的測試相比之前的版本只多了幾行代碼,但它更完整地測試了HomeController砍的。這次測試不是直接調(diào)用home()方法并測試返回值痹筛,而是發(fā)起了對"/"的GET請求,并斷言結(jié)果視圖的名稱為"home"廓鞠。首先帚稠,它使用HomeControllerMockMvcBuilders.standaloneSetup()創(chuàng)建一個MockMvc實例,并調(diào)用build()方法進行構(gòu)建床佳。然后滋早,使用MockMvc實例執(zhí)行針對"/"的GET請求,并設置期望得到的視圖名稱砌们。
5.2.2 定義類級別的請求處理現(xiàn)在馆衔,已經(jīng)為HomeController編寫了測試,那么我們可以做一些重構(gòu)怨绣,并通過測試來保證不會對功能造成什么破壞角溃。
我們可以做的一件事就是拆分@RequestMapping,并將其路徑映射部分放到類級別 上篮撑。
-
程序清單5.7展示了這個過程减细。
程序清單5.7 拆分HomeController中的@RequestMapping- package com.spring.mvc.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import static org.springframework.web.bind.annotation.RequestMethod.GET; /** * 聲明一個控制器 * @author huyingqi */ @Controller. ——>將控制器映射到“/” @RequestMapping("/) public class HomeController { @RequestMapping(method = GET ) //處理GET請求 public String home(){ //視圖名為home return "home"; ——> 視圖名為home } }
-
在這個新版本的HomeController中,路徑被轉(zhuǎn)移到類級別的@RequestMapping上赢笨,而HTTP方法仍然映射在方法級別上未蝌。
- 當控制器在類級別上添加@RequestMapping注解時,這個注解會應用到控制器的所有處理器方法上茧妒。
- 處理器方法上的@RequestMapping注解會補充類級別上的@RequestMapping的聲明萧吠。
-
對于HomeController來說,只有一個控制器方法桐筏。
- 在與類級別的@RequestMapping合并之后纸型,該方法的@RequestMapping注解明確了它將處理對"/"路徑的GET請求。
換句話說梅忌,我們實際上沒有改變?nèi)魏喂δ苷纾皇菍⒁恍┐a移動了位置,但HomeController所做的事情與之前相同牧氮。由于我們現(xiàn)在有了測試琼腔,可以確保在這個過程中沒有破壞原有的功能。
-
當我們在修改@RequestMapping時踱葛,還可以對HomeController 做另外一個變更丹莲。
@RequestMapping的value屬性能夠接受一 個String類型的數(shù)組光坝。
到目前為止,我們給它設置的都是一個 String類型的“/” 甥材。
-
但是盯另,我們還可以將它映射到對“/homepage”的 請求,只需將類級別的@RequestMapping改為如下所示:
- - @Controller. ——>將控制器映射到“/” @RequestMapping("/","/homepage") public class HomeController { }
現(xiàn)在擂达,HomeController的home ()方法能夠映射到對“/”和“/homepage”的GET請求土铺。
5.2.3 傳遞模型數(shù)據(jù)到視圖中
- HomeController是一個簡單的控制器樣例,但大多數(shù)控制器并不是這么簡單板鬓。
- 在Spittr應用中悲敷,需要有一個頁面來展示最近提交的Spittle列表,因此需要一個新的方法來處理這個頁面俭令。
- 首先后德,需要定義一個數(shù)據(jù)訪問的Repository。為了解耦和避免陷入數(shù)據(jù)庫訪問的細節(jié)抄腔,將Repository定義為一個接口瓢湃,并在稍后實現(xiàn)它(在第10章中)。
-
目前赫蛇,只需要一個能夠獲取Spittle列表的Repository绵患,下面是一個足夠?qū)崿F(xiàn)功能的SpittleRepository的示例。
public. interface Spilttlerepository{ List<Spittle> findSpittleRepository(long max,int count); }
-
-
findSpittles ()方法接受兩個參數(shù)悟耘。
- 其中max參數(shù)代表所返回的 Spittle中落蝙,Spittle ID屬性的最大值,
- 而count參數(shù)表明要返回 多少個Spittle對象暂幼。
為了獲得最新的20個Spittle對象筏勒,我們可 以這樣調(diào)用findSpittles ():
List<Spittle> findSpittleRepository(long max,int count);
現(xiàn)在,我們讓Spittle類盡可能的簡單旺嬉,如下面的程序清單5.8所 示管行。它的屬性包括消息內(nèi)容、時間戳以及Spittle發(fā)布時對應的經(jīng)緯 度邪媳。
程序清單5.8 Spittle類:包含消息內(nèi)容捐顷、時間戳和位置信息
[圖片]
- Spittle是一個基本的POJO數(shù)據(jù)對象,沒有復雜的內(nèi)容悲酷。
- 在Spittle類中套菜,我們使用了Apache Common Lang包來實現(xiàn)equals()和hashCode()方法。
- 這些方法除了常規(guī)的作用外设易,當我們?yōu)榭刂破鞯奶幚砥鞣椒ň帉憸y試時,它們也是有用的蛹头。
既然我們說到了測試顿肺,那么我們繼續(xù)討論這個話題并為新的控制器方 法編寫測試戏溺。如下的程序清單使用Spring的MockMvc來斷言新的處理 器方法中你所期望的行為。
程序清單5.9 測試SpittleController處理針對“/spittles”的GET請求
[圖片]
- 首先屠尊,測試會創(chuàng)建一個SpittleRepository接口的mock實現(xiàn)旷祸。
- 這個mock實現(xiàn)會在其findSpittles()方法中返回20個Spittle對象。
- 接下來讼昆,將這個mock實現(xiàn)注入到一個新的SpittleController實例中托享。
- 然后,創(chuàng)建一個MockMvc并使用這個控制器進行測試浸赫。
根據(jù)您提供的內(nèi)容闰围,我將嘗試梳理一下:
- 首先,與HomeController不同既峡,這個測試在MockMvc構(gòu)造器上調(diào)用了setSingleView()方法羡榴。
- 優(yōu)點
- 這樣可以避免mock框架解析控制器中的視圖名。在很多場景中运敢,這樣做并不是必需的校仑。
- 缺點
- 但是對于這個控制器方法來說,視圖名與請求路徑非常相似传惠。按照默認的視圖解析規(guī)則迄沫,MockMvc會發(fā)生失敗,因為無法區(qū)分視圖路徑和控制器的路徑卦方。
- 在這個測試中羊瘩,設置InternalResourceView的實際路徑與InternalResourceViewResolver的配置一致是無關(guān)緊要的。
- 這個測試對"/spittles"發(fā)起GET請求愿汰,并斷言視圖的名稱為"spittles"困后,并且模型中包含名為"spittleList"的屬性,該屬性包含預期的內(nèi)容衬廷。
- 當然摇予,如果此時運行測試,它將會失敗吗跋。它不是在運行時失敗侧戴,而是在編譯時就會失敗。這是因為我們還沒有編寫SpittleController〉穑現(xiàn)在酗宋,我們需要創(chuàng)建SpittleController。
總結(jié)概要,我梳理了以下內(nèi)容:與HomeController不同疆拘,這個測試在MockMvc構(gòu)造器上調(diào)用了setSingleView()方法蜕猫,避免了視圖名的解析問題。測試對"/spittles"發(fā)起GET請求哎迄,并斷言視圖的名稱為"spittles"回右,并且模型中包含名為"spittleList"的屬性隆圆,該屬性包含預期的內(nèi)容。然而翔烁,由于還沒有編寫SpittleController渺氧,測試將在編譯時失敗。請注意蹬屹,以上內(nèi)容是用中文輸出的侣背。如果您有任何其他問題或需要進一步的解釋,請隨時告訴我慨默。
讓它滿足程序清單5.9的預期贩耐。如下的SpittleController實現(xiàn)將 會滿足以上測試的要求。
程序清單5.10 SpittleController:在模型中放入最新的spittle 列表
[圖片]
- 首先业筏,SpittleController類有一個構(gòu)造器憔杨,使用@Autowired注解來注入SpittleRepository。這個SpittleRepository隨后在spittles()方法中使用蒜胖,用于獲取最新的spittle列表消别。
- 在spittles()方法中,我們給定了一個Model作為參數(shù)台谢。這樣寻狂,spittles()方法可以將Repository中獲取到的Spittle列表填充到模型中。Model實際上是一個Map(鍵值對的集合)朋沮,它會傳遞給視圖蛇券,從而使數(shù)據(jù)能夠渲染到客戶端。
- 當調(diào)用addAttribute()方法并且不指定key時樊拓,key會根據(jù)值的對象類型進行推斷纠亚。在這個例子中,因為值是一個List<Spittle>筋夏,所以鍵會被推斷為spittleList蒂胞。
- spittles()方法的最后一步是返回spittles作為視圖的名稱,這個視圖將用于渲染模型中的數(shù)據(jù)条篷。
如果你希望顯式聲明模型的key的話骗随,那也盡可以進行指定。例如赴叹, 下面這個版本的spittles ()方法與程序清單5. 10中的方法作用是一樣的:
[圖片]
如果你希望使用非Spring類型的話鸿染,那么可以用java.util.Map來 代替Model紊选。下面這個版本的spittles ()方法與之前的版本在功能 上是一樣的:
[圖片]
既然我們現(xiàn)在提到了各種可替代的方案庆寺,那下面還有另外一種方式來 編寫spittles ()方法:
[圖片] - 首先,這個版本與其他版本有一些差別津坑。它沒有返回視圖名稱,也沒有顯式地設置模型丢烘,而是直接返回了Spittle列表柱宦。
- 當處理器方法像這樣返回對象或集合時些椒,這個值會放到模型中播瞳,模型的key會根據(jù)其類型推斷得出(在本例中,是spittleList)免糕。
- 邏輯視圖的名稱將根據(jù)請求路徑推斷得出赢乓。因為這個方法處理針對“/spittles”的GET請求,所以視圖的名稱將是spittles(去掉開頭的斜線)石窑。
- 無論選擇哪種方式編寫spittles()方法牌芋,結(jié)果都是相同的。模型中會存儲一個Spittle列表松逊,key為spittleList躺屁,然后將這個列表發(fā)送到名為spittles的視圖中。
- 根據(jù)配置的InternalResourceViewResolver经宏,視圖的JSP文件將是“/WEB-INF/views/spittles.jsp”犀暑。
- 現(xiàn)在,數(shù)據(jù)已經(jīng)放到模型中烁兰,那么在JSP中如何訪問它呢耐亏?實際上,當視圖是JSP時沪斟,模型數(shù)據(jù)會作為請求屬性放到請求(request)中广辰。因此,在spittles.jsp文件中可以使用JSTL(JavaServer Pages Standard Tag Library)的<c:forEach>標簽來渲染spittle列表主之。
[圖片]
圖5.3為顯示效果择吊,能夠讓你對它在Web瀏覽器中是什么樣子有個可視 化的印象。
盡管SpittleController很簡單槽奕,但是它依然比 HomeController更進一步了几睛。不過,SpittleController和 HomeController都沒有處理任何形式的輸入∈非蹋現(xiàn)在枉长,讓我們擴 展SpittleController,讓它從客戶端接受一些輸入琼讽。
圖5 .3 控制器中的Spittle模型數(shù)據(jù)將會作為請求參數(shù)必峰,并在Web頁面上渲染為列 表的形式
本文由博客一文多發(fā)平臺 OpenWrite 發(fā)布!