以前一直在看《Spring實戰》第三版,看到第五章時發現不少東西已通過時被廢棄了,因而如今開始讀《Spring實戰》第四版了,章節安排與以前不一樣了,裏面應用的應該是最新的技術。javascript
本章中,將會接觸到Spring MVC基礎,以及如何編寫控制器來處理web請求,如何通明地綁定請求參數到業務對象上,同時還能夠提供數據校驗和錯誤處理的功能。css
在請求離開瀏覽器時,會帶有用戶所請求內容的信息,例如請求的URL、用戶提交的表單信息。html
請求旅程的第一站是Spring的DispatcherServlet。Spring MVC全部的請求都會經過一個前端控制器Servlet。前端控制器是經常使用的Web應用程序模式,在這裏一個單實例的Servlet將請求委託給應用程序的其餘組件來執行實際的處理。在Spring MVC中,DispatcherServlet 就是前端控制器。前端
DispatcherServlet的任務是將請求發送給Spring MVC控制器。控制器是一個用於處理請求的Spring組件。在典型的應用程序中可能會有多個控制器, Dispatcher Servlet須要知道應該將請求發送給哪一個控制器。因此DispatcherServlet會查詢一個或多個處理器映射來肯定請求的下一站在哪裏。處理器映射會根據請求所攜帶的URL信息來進行決策。java
一旦選擇了合適的控制器,DispatcherServlet會將請求發送給選中的控制器。到達了控制器,請求會卸下其負載(用戶提交的信息)並等待控制器處理這些信息(實際上,設計良好的控制器自己只處理不多甚至不處理工做,而是將業務邏輯委託給個或多個服務對象)。git
控制器在完成邏輯處理後一般會產生一些信息,這些信息須要返回給用戶並在瀏覽器上顯示。這些信息被稱爲模型(Model)。不過僅僅給用戶返回原始的信息是不夠的--這些信息須要以用戶友好的方式進行格式化,通常是HTML。因此,信息須要發送給—個視圖(View),一般會是JSP。github
控制器所作的最後一件事是將模型數據打包,而且標示出用於渲染輸出的視圖名稱。它接下來會將請求連同模型和視圖名稱發送回DispatcherServlet。web
這樣,控制器就不會與特定的視圖相耦合,傳遞給DispatcherServlet的視圖名稱並不直接表示某個特定的JSP。它僅僅傳遞了一個邏輯名,這個名字將會用來查找用來產生結果的真正視圖。DispatcherServlet將會使用視圖解析器來將邏輯視圖名匹配爲一個特定的視圖實現。正則表達式
既然DispatcherServlet已經知道由哪一個視圖渲染結果,那麼請求的任務基本上也就完成了。它的最後一站是視圖的實現(多是JSP),在這裏它交付模型數據。請求的任務就完成了。視圖將使用模型數據渲染輸出,並經過這個輸出將響應對象傳遞給客戶端。spring
DispatcherServlet是Spring MVC的核心,它負責將請求分發到其餘各個組件。
在舊版本中,DispatcherServlet之類的servlet通常在web.xml
文件中配置,該文件通常會打包進最後的war包種;可是Spring3引入了註解,咱們在這一章將展現如何基於註解配置Spring MVC。
注意:
在使用maven構建web工程時,因爲缺乏web.xml文件,可能會出現web.xml is missing and <failOnMissingWebXml> is set to true
這樣的錯誤,那麼能夠經過在pom.xml文件中添加以下配置來避免這種錯誤:
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.6</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </build>
既然不適用web.xml
文件,你須要在servlet容器中使用Java配置DispatcherServlet,具體的代碼列舉以下:
package spittr.config; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; public class SpittrWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected String[] getServletMappings() { return new String[] { "/" }; } @Override protected Class<?>[] getRootConfigClasses() { return new Class<?>[] { RootConfig.class }; } @Override protected Class<?>[] getServletConfigClasses() { return new Class<?>[] { WebConfig.class }; } }
任意繼承自AbstractAnnotationConfigDispatcherServletInitializer
的類都會被自動用來配置DispatcherServlet,這個類負責配置DispatcherServlet、初始化Spring MVC容器和Spring容器。
SpittrWebAppInitializer重寫了三個方法,getRootConfigClasses()
方法用於獲取Spring應用容器的配置文件,這裏咱們給定預先定義的RootConfig.class;getServletConfigClasses()
負責獲取SpringMVC應用容器,這裏傳入預先定義好的WebConfig.class;getServletMappings()
方法負責指定須要由DispatcherServlet映射的路徑,這裏給定的是"/",意思是由DispatcherServlet處理全部向該應用發起的請求。
當DispatcherServlet啓動時,會建立一個Spring應用上下文而且會加載配置文件中聲明的bean,經過getServletConfigClasses()
方法,DispatcherServlet會加載WebConfig
配置類中所配置的bean。
在Spring web應用中,一般還有另一種應用上下文:ContextLoaderListener
。
DispatcherServlet用來加載web組件bean,如控制器(controllers)、視圖解析器(view resolvers)以及處理器映射(handler mappings)等。而ContextLoaderListener則用來加載應用中的其餘bean,如運行在應用後臺的中間層和數據層組件。
AbstractAnnotationConfigDispatcherServletInitializer會同時建立DispatcherServlet和ContextLoaderListener。getServletConfigClasses()
方法返回的@Configuration
類會定義DispatcherServlet應用上下文的bean。同時,getRootConfigClasses()
返回的@Configuration
類用來配置ContextLoaderListener上下文建立的bean。
相對於傳統的web.xml
文件配置的方式,經過AbstractAnnotationConfigDispatcherServletInitializer來配置DispatcherServlet是一種替代方案。須要注意的是,這種配置只適用於Servlet 3.0,例如Apache Tomcat 7或者更高。
正若有多種方式能夠配置DispatcherServlet,激活Spring MVC組件也有不止一種方法。通常的,都會經過XML配置文件的方式來配置Spring,例如能夠經過<mvc:annotation-driven>
來激活基於註解的Spring MVC。
最簡單的配置Spring MVC的一種方式是經過@EnableWebMvc
註解:
package spittr.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; @Configuration @EnableWebMvc public class WebConfig { }
@Configuration
表示這是Java配置類;@EnableWebMvc
註解用於啓動Spring MVC特性。
這樣就能夠激活Spring MVC了,可是還有其餘一些問題:
BeanNameViewResolver
,它會經過尋找那些與視圖id匹配的bean以及實現了View接口的類進行視圖解析;所以,須要爲WebConfig增長一些配置:
package spittr.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.view.InternalResourceViewResolver; @Configuration @EnableWebMvc @ComponentScan("spitter.web") // 激活Spring MVC public class WebConfig extends WebMvcConfigurerAdapter { // 配置一個JSP視圖解析器 @Bean public ViewResolver viewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix("/WEB_INF/views/"); resolver.setSuffix(".jsp"); resolver.setExposeContextBeansAsAttributes(true); return resolver; } @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable(); } }
首先須要注意的是,WebConfig
使用了@ComponentScan
註解,所以會在spitter.web
包下掃描尋找組件,這些組件包括使用@Controller
進行註解的控制器。這樣就再也不須要在配置類中顯式地聲明其餘控制器。
接下來,添加了一個ViewResolver
bean,即InternalResourceViewResolver
。它經過匹配符合設置的前綴和後綴的視圖來用來尋找對應的JSP文件,好比視圖home會被解析爲/WEB-INF/views/home.jsp。這裏的三個函數的含義依次是:setPrefix()
方法用於設置視圖路徑的前綴;setSuffix()
用於設置視圖路徑的後綴,即若是給定一個邏輯視圖名稱——"home",則會被解析成"/WEB-INF/views/home.jsp"; setExposeContextBeansAsAttributes(true)
使得能夠在JSP頁面中經過${}訪問容器中的bean。
而後,WebConfig
繼承自WebMvcConfigurerAdapter
,而且重寫了configureDefaultServletHandling()
方法,經過調用enable()
方法從而可讓DispatcherServlet將靜態資源的請求轉發給默認的servlet。
package spittr.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.web.servlet.config.annotation.EnableWebMvc; @Configuration @ComponentScan(basePackages = { "spitter" }, excludeFilters = { @Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class) }) public class RootConfig { }
須要注意的一點是,RootConfig 使用了@ComponentScan
註解。
這一章要用的例子應用,從Twitter獲取了一些靈感,所以最開始叫Spitter;而後又借鑑了最近比較流行的網站Flickr,所以咱們也把e去掉,最終造成Spittr這個名字。這也有利於區分領域名稱(相似於twitter,這裏用spring實現,所以叫spitter)和應用名稱。
Spittr相似於Twitter,用戶能夠經過它添加一些推文。Spittr有兩個重要的概念:spitter(應用的用戶)和spittle(用戶發佈簡單狀態)。本章將會構建該應用的web層、建立用於展現spittle的控制器以及用戶註冊流程。
Spring MVC中,控制器僅僅是擁有@RequestMapping
註解方法的類,從而能夠聲明它們能夠處理何種請求。
在開始以前,咱們先假設一個控制器,它能夠處理匹配/
的請求並會跳轉到主頁面。
package spittr.web; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller // 聲明一個控制器 public class HomeController { @RequestMapping(value = "/", method = RequestMethod.GET) // 處理GET請求 public String home() { return "home"; } }
@Controller
是一個構造型註解,它基於@Component
,組件掃描器會自動地將HomeController聲明爲Spring上下文的一個bean。
home()方法採用了@RequestMapping
註解,屬性value
指定了該方法處理的請求路徑,method
方法指定了能夠處理的HTTP方法。這種狀況下,當一個來自/
的GET方法請求時,就會調用home()方法。
home()方法僅僅返回了一個"home"的String值,Spring MVC會對這個String值進行解析並跳轉到指定的視圖上。DispatcherServlet
則會請求視圖解析器將這個邏輯視圖解析到真實視圖上。
咱們已經配置了InternalResourceViewResolver,「home」視圖會被解析到/WEB-INF/views/home.jsp
。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> <html> <head> <meta charset="utf-8"> <title>Spittr</title> <link rel="stylesheet" type="text/css" href="<c:url value="/resources/style.css" />"> </head> <body> <h1>Welcome to Spittr</h1> <a href="<c:url value="/spittles" />">Spittles</a> | <a href="<c:url value="/spitter/register" />">Register</a> </body> </html>
下面對HomeController進行測試。
通常的web測試須要將工程發佈到一個web容器中,啓動後才能觀察運行結果。
如:
從另外的角度來看,HomeController實際上是一個簡單的POJO對象,那麼可使用下面的方法對其進行測試:
package spittr.web; import org.junit.Test; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import org.springframework.test.web.servlet.setup.MockMvcBuilders; public class HomeControllerTest { @Test public void testHomePage() throws Exception { HomeController controller = new HomeController(); // 設置MockMvc MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); mockMvc.perform(MockMvcRequestBuilders.get("/")).andExpect(MockMvcResultMatchers.view().name("home")); } }
相對於直接調用home()方法測試它的返回值,上面的測試中發起了一個來自/
的 GET 請求,而且對其結果視圖進行斷言。將HomeController的實例傳送給MockMvcBuilders.standaloneSetup
,而且調用build()
方法來建立一個MockMvc
實例。而後,使用MockMvc
實例產生了一個GET請求,而且設置了視圖的指望。
package spittr.web; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller // 聲明一個控制器 @RequestMapping("/") // 控制器匹配路徑 public class HomeController { @RequestMapping(method = RequestMethod.GET) // 處理GET請求 public String home() { return "home";// 視圖名稱 } }
在這個新版的HomeController中,將請求匹配路徑移到了類層級,HTTP方法的匹配仍處在方法層級。當有控制類中有一個類層級的@RequestMapping
,該類中全部的用@RequestMapping
註解的處理方法共同組成了類層級的@RequestMapping
。
@RequestMapping
的value屬性接受String數組,那麼就可使用以下配置:
@Controller // 聲明一個控制器 @RequestMapping("/", "/homepage") // 控制器匹配路徑 public class HomeController { ... }
這種狀況下,home()方法就能夠處理來自/
和/homepage
的GET請求。
在Spittr應用中,須要一個頁面,用來顯示最近提交的spittle清單。首先須要定義一個數據訪問的倉庫,用來抓取spittle:
package spittr.data; import java.util.List; import spittr.Spittle; public interface SpittleRepository { /** * @param max * 待返回的最大的Spittle ID * @param count * 返回Spittle對象的個數 * @return */ List<Spittle> findSpittles(long max, int count); }
若是要獲取最近的20個Spittle對象,那麼只需調用這樣調用:
List<Spittle> recent = spittleRepository.findSpittles(Long.MAX_VALUE, 20);
下面對Spittle進行定義:
package spittr; import java.util.Date; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; public class Spittle { private final Long id; private final String message;// 消息 private final Date time;// 時間戳 private Double latitude; private Double longitude; public Spittle(String message, Date time) { this(message, time, null, null); } public Spittle(String message, Date time, Double latitude, Double longitude) { this.id = null; this.message = message; this.time = time; this.latitude = latitude; this.longitude = longitude; } @Override public boolean equals(Object that) { return EqualsBuilder.reflectionEquals(this, that, "id", "time"); } @Override public int hashCode() { return HashCodeBuilder.reflectionHashCode(this, "id", "time"); } //getters and setters }
Spittle對象中如今包含信息、時間戳、位置這幾個屬性。
下面利用Spring的MockMvc
來斷言新的控制器的行爲是否正確:
上面的測試經過建立一個SpittleRepository接口的mock實現,該實現會經過findSpittles()方法返回一個包含20個Spittle對象的集合。而後將這個bean注入到SpittleController實例中,並設置MockMvc使用該實例。
不一樣於HomeControllerTest,該測試使用了setSingleView()
,發起一個/spittles
的GET請求,並斷言視圖是否爲spittles以及model是否含有一個spittleList的屬性值。
固然,如今運行這個測試代碼確定是會出錯的,由於尚未SpittleController。
package spittr.web; import java.util.ArrayList; import java.util.Date; import java.util.List; import org.hamcrest.core.IsCollectionContaining; import org.junit.Test; import org.mockito.Mockito; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.servlet.view.InternalResourceView; import spittr.Spittle; import spittr.data.SpittleRepository; public class SpittleControllerTest { @Test public void shouldShowRecentSpittles() throws Exception { List<Spittle> expectedSpittles = createSpittleList(20); SpittleRepository mockRepository = Mockito.mock(SpittleRepository.class); Mockito.when(mockRepository.findSpittles(Long.MAX_VALUE, 20)).thenReturn(expectedSpittles); SpittleController controller = new SpittleController(mockRepository); MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller) .setSingleView(new InternalResourceView("/WEB_INF/views/spittles.jsp")).build(); // 調用MockMvc.perform(RequestBuilder requestBuilder)發起一個http請求,而後將獲得ResultActions mockMvc.perform(MockMvcRequestBuilders.get("/spittles"))// 添加驗證斷言來判斷執行請求後的結果是不是預期的; .andExpect(MockMvcResultMatchers.view().name("spittles"))// view():獲得視圖驗證器; // 獲得相應的***ResultMatchers後,接着再調用其相應的API獲得ResultMatcher, // 如ModelResultMatchers.attributeExists(final String... names)判斷Model屬性是否存在。 .andExpect(MockMvcResultMatchers.model().attributeExists("spittleList"))// model():獲得模型驗證器; .andExpect(MockMvcResultMatchers.model().attribute("spittleList", IsCollectionContaining.hasItems(expectedSpittles.toArray()))); } private List<Spittle> createSpittleList(int count) { List<Spittle> spittles = new ArrayList<Spittle>(); for (int i = 0; i < count; i++) { spittles.add(new Spittle("Spittle ", new Date())); } return spittles; } }
SpittleController中,使用@Autowired註解注入了spittleRepository屬性。
須要注意的是spittles()
方法使用了Model(控制器和視圖之間傳遞的數據)做爲入參,Model本質上是一個map,它會被傳送至view,所以數據能夠提供給客戶端。若是在調用addAttribute()
方法時沒有指定key,那麼就會從傳入的對象中獲取,好比代碼中傳入的參數屬性是List<Spittle>,那麼key就是spittleList。最後,該方法返回spittles做爲傳動給model的視圖名稱。
也能夠顯示的指定key:
model.addAttribute(spittleRepository.findSpittles(Long.MAX_VALUE, 20));
也能夠直接採用map的方式:
@RequestMapping(method = RequestMethod.GET) public String spittles(Map model) { // 將spittles添加到model中 model.put("spittles", spittleRepository.findSpittles(Long.MAX_VALUE, 20)); // 返回視圖名稱 return "spittles"; }
無論採用何種方式實現spittles()方法,結果都是同樣的。一個Spittle對象集合會存儲在model中,並分配到名爲spittles的view中,根據測試方法中的配置,該視圖就是/WEB-INF/views/spittles.jsp。
如今model已經有數據了,那麼JSP頁面中如何獲取數據呢?當視圖是一個JSP頁面時,model數據會做爲請求屬性被拷貝到請求中,所以能夠經過JSTL(JavaServer Pages Standard Tag Library)<c:forEach>
來獲取:
<c:forEach items="${spittleList}" var="spittle"> <li id="spittle_<c:out value="spittle.id"/>"> <div class="spittleMessage"> <c:out value="${spittle.message}" /> </div> <div> <span class="spittleTime"><c:out value="${spittle.time}" /></span> <span class="spittleLocation"> (<c:out value="${spittle.latitude}" />, <c:out value="${spittle.longitude}" />) </span> </div> </li> </c:forEach>
下面對SpittleController進行擴展,讓它能夠處理一些輸入。
Spring MVC提供了以下方式供客戶端傳遞數據到控制器處理方法:
Spittr應用的一個需求就是要對spittle列表分頁展現,可是SpittleController僅僅展現最近的spittle。若是要讓用戶能夠每次獲得一頁的spittle記錄,那麼就須要讓用戶能夠經過某種方式將他們想看的spittle記錄的參數傳遞到後臺。
在瀏覽spittle時,若是想要查看下一頁的spittle,那麼就須要傳遞比當前頁的最後一個spittle的id小一位的id,也能夠傳遞想要展現的spittle的數量。
爲了實現分頁,須要編寫一個控制器知足:
before
參數,結果中的spittle的id都要在這個參數以前;count
參數,結果中要包含的spittle的個數下面咱們對上面的spittles()
方法進行小小的改動,讓它可使用before和count參數。首先對測試方法進行改動:
@Test public void shouldShowRecentSpittles() throws Exception { List<Spittle> expectedSpittles = createSpittleList(20); SpittleRepository mockRepository = Mockito.mock(SpittleRepository.class); Mockito.when(mockRepository.findSpittles(238900, 50)).thenReturn(expectedSpittles); SpittleController controller = new SpittleController(mockRepository); MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller) .setSingleView(new InternalResourceView("/WEB_INF/views/spittles.jsp")).build(); // 調用MockMvc.perform(RequestBuilder requestBuilder)發起一個http請求,而後將獲得ResultActions mockMvc.perform(MockMvcRequestBuilders.get("/spittles?max=238900&count=50"))// 添加驗證斷言來判斷執行請求後的結果是不是預期的; .andExpect(MockMvcResultMatchers.view().name("spittles"))// view():獲得視圖驗證器; // 獲得相應的***ResultMatchers後,接着再調用其相應的API獲得ResultMatcher, // 如ModelResultMatchers.attributeExists(final String... names)判斷Model屬性是否存在。 .andExpect(MockMvcResultMatchers.model().attributeExists("spittleList"))// model():獲得模型驗證器; .andExpect(MockMvcResultMatchers.model().attribute("spittleList", IsCollectionContaining.hasItems(expectedSpittles.toArray()))); }
這個測試方法的主要改動就是它發起的GET請求傳遞了兩個參數:max和count。對spittles()
進行修改:
@RequestMapping(method=RequestMethod.GET) public List<Spittle> spittles( @RequestParam(value="max", defaultValue=MAX_LONG_AS_STRING) long max, @RequestParam(value="count", defaultValue="20") int count) { return spittleRepository.findSpittles(max, count); }
這種狀況下,若是沒有max參數沒有指定,那麼就會使用默認的設置。因爲查詢參數是String類型的,所以defaultValue
屬性值也須要設置爲String類型,須要對Long.MAX_VALUE進行設置:
private static final String MAX_LONG_AS_STRING = "9223372036854775807";
雖然,這裏defaultValue的屬性爲String類型,當運行到函數時,將會根據函數的參數類型進行轉換。
查詢參數是請求中傳送信息給控制器的最經常使用方式,另一種流行的方式就是將參數做爲請求路徑的一部分。
假設如今應用須要展現單獨的一篇Spittle,那麼就須要一個id做爲查詢參數,對應的處理方法能夠是:
@RequestMapping(value="show", method=RequestMethod.GET) public String showSpittle( @RequestParam("spittle_id") long spittleId, Model model ) { model.addAttribute(spittleRepository.findOne(spittleId)); return "spittle"; }
這個handler方法將會處理形如/spittles/show?spittle_id=12345
的請求,可是這並不符合資源導向的觀點。理想狀況下,應該使用URL路徑對資源進行區分,而不是查詢參數,即應該使用/spittles/12345
這種形式。
爲了實現資源導向的控制器,咱們先在測試中得到這個需求(使用了靜態引入):
@Test public void testSpittle() throws Exception { Spittle expectedSpittle = new Spittle("Hello", new Date()); SpittleRepository mockRepository = Mockito.mock(SpittleRepository.class); when(mockRepository.findOne(12345)).thenReturn(expectedSpittle); SpittleController controller = new SpittleController(mockRepository); MockMvc mockMvc = standaloneSetup(controller).build(); mockMvc.perform(get("/spittles/12345")) .andExpect(view().name("spittle")) .andExpect(model().attributeExists("spittle")) .andExpect(model().attribute("spittle", expectedSpittle)); }
該測試中發起了一個/spittles/12345
的GET請求,而且對其返回結果視圖進行斷言。
爲了知足路徑參數,Spring MVC容許在@RequestMapping
路徑中使用佔位符(須要用大括號包圍),下面是使用佔位符來接受一個id做爲路徑的一部分:
@RequestMapping(value="/{spittleId}", method=RequestMethod.GET) public String spittle( @PathVariable("spittleId") long spittleId, Model model ) { model.addAttribute(spittleRepository.findOne(spittleId)); return "spittle"; }
spittle()方法的spittleId入參使用了@PathVariable("spittleId")
註解,代表請求中佔位符位置的值都會被傳送到handler的spittleId參數。@RequestMapping中value屬性的佔位符必須和@PathVariable包裹的參數一致。若是@PathVariable中沒有給定參數,那麼將默認使用入參的冊數參數名。便可以使用下面的方法:
@RequestMapping(value="/{spittleId}", method=RequestMethod.GET) public String spittle( @PathVariable long spittleId, Model model ) { model.addAttribute(spittleRepository.findOne(spittleId)); return "spittle"; }
spittle()方法會將接收的參數值傳遞給spittleRepository的findOne()方法並查找到一個Spittle,並將其放置到model中,model的key值會是spittle,接下來就能夠在視圖中引用這個Spittle:
<div class="spittleView"> <div class="spittleMessage"> <c:out value="${spittle.message}" /> </div> <div> <span class="spittleTime"><c:out value="${spittle.time}" /></span> </div> </div>
查詢參數和路徑參數能夠處理一些少許的請求數據,可是當請求數據過大時,它們就再也不適用,下面就來說解一下如何處理表單數據。
Web應用不只僅是將內容推送給用戶,它同時也會讓用戶填寫表單並將數據提交給應用。
對於表單有兩種處理方式:展現表單以及處理用戶提交的表單數據。在Spittr中,須要提供一個供新用戶進行註冊的表單。
SpitterController
:展現用戶註冊表單
package spittr.web; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller @RequestMapping("/spitter") public class SpitterController { // 處理來自/spitter/register的get請求 @RequestMapping(value = "/register", method = RequestMethod.GET) public String showRegistrationForm() { return "registerForm"; } }
showRegistrationForm
方法的@RequestMapping
註解,以及類級別的註解@RequestMapping
,代表了這個方法會處理來自/spitter/register的get請求,該方法僅僅返回了一個名爲registerForm的邏輯視圖。根據以前在InternalResourceViewResolver
中的配置,這個邏輯視圖會導向到/WEB-INF/views/registerForm.jsp
該界面。
對應的測試方法:
package spittr.web; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*; import org.junit.Test; import org.springframework.test.web.servlet.MockMvc; public class SpitterControllerTest { @Test public void shouldShowRegistration() throws Exception { SpitterController controller = new SpitterController(); MockMvc mockMvc = standaloneSetup(controller).build(); mockMvc.perform(get("/spitter/register")).andExpect(view().name("registerForm")); } }
也能夠經過啓動項目訪問界面的方式驗證:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ page session="false" %> <html> <head> <title>Spitter</title> <link rel="stylesheet" type="text/css" href="<c:url value="/resources/style.css" />" > </head> <body> <h1>Register</h1> <form method="POST"> First Name: <input type="text" name="firstName" /><br/> Last Name: <input type="text" name="lastName" /><br/> Username: <input type="text" name="username" /><br/> Password: <input type="password" name="password" /><br/> <input type="submit" value="Register" /> </form> </body> </html>
接下來須要對提交的表單進行處理。
在處理POST請求時,控制器須要接受表單數據而且將這些數據存儲爲一個Spitter對象。爲了不重複提交,應該重定向到一個新的界面:用戶信息頁。在處理post請求時,一個聰明的作法就是在處理完成後發送一個重定向的請求,從而能夠避免重複提交。
下面來實現控制器方法,從而能夠處理註冊請求。
private SpitterRepository spitterRepository; public SpitterController() { } // 注入SpitterRepository @Autowired public SpitterController(SpitterRepository spitterRepository) { this.spitterRepository = spitterRepository; } public String processRegistration(Spitter spitter) { // 保存Spitter spitterRepository.save(spitter); // 重定向到新的頁面 return "redirect:/spitter/" + spitter.getUsername(); }
processRegistration方法使用Spitter對象做爲入參,該對象的屬性會從請求中填充。該方法中調用了spitterRepository的save方法對Spitter對象進行存儲。最後返回了一個帶有redirect:
的字符串。
當InternalResourceViewResolver遇到redirect:
時,它會自動地將其當作一個重定向請求,從而能夠重定向到用戶詳情頁面,如/spitter/xiaoming。
同時,InternalResourceViewResolver也能夠識別前綴forward:
,這種狀況下,請求會被轉向到給定的URL地址。
下面須要編寫處理處理用戶詳情頁面的方法:
@RequestMapping(value = "/{username}", method = RequestMethod.GET) public String showSpitterProfile(@PathVariable("username") String username, Model model) { Spitter spitter = spitterRepository.findByUsername(username); model.addAttribute(spitter); return "profile"; }
從Spring3.0開始,Spring支持Java校驗api,從而能夠從而能夠不須要添加其餘配置,僅僅須要有一個Java API 的實現,如Hibernate Validator。
Java Validation API定義了許多註解,可使用這些註解來約束參數的值,全部的註解都在包javax.validation.constraints
中。
註解 | 描述 |
---|---|
@AssertFalse(@AssertTrue) | 對象必須是布爾類型,而且必須爲false(true) |
@DecimalMax(value)、@DecimalMin(value) | 限制對象必須是一個數字,其值不大於(不小於)指定的BigDecimalString值 |
@Digits(integer,fraction) | 對象必須爲一個小數,且整數部分的位數不能超過integer,小數部分的位數不能超過fraction |
@Future | 必須是一個未來的日期 |
@Max(value)、@Min(value) | 必須爲一個不大於(不小於)指定值的數字 |
@NotNull | 限制對象不能爲空 |
@Null | 限制對象必須爲空 |
@Past | 必須是一個過去的日期 |
@Pattern(value) | 必須符合指定的正則表達式 |
@Size(min,max) | 限制字符長度必須在min到max之間 |
使用示例:
public class Spitter { private Long id; @NotNull @Size(min = 5, max = 16) private String username; @NotNull @Size(min = 5, max = 25) private String password; @NotNull @Size(min = 2, max = 30) private String firstName; ...
既然已經對Spitter的參數添加了約束,那麼就須要改動processRegistration方法來應用校驗:
@RequestMapping(value = "/register", method = RequestMethod.POST) public String processRegistration(@Valid Spitter spitter, Errors errors) { // 若校驗中出現錯誤,那麼就返回到註冊界面 if (errors.hasErrors()) { return "registerForm"; } // 保存Spitter spitterRepository.save(spitter); // 重定向到新的頁面 return "redirect:/spitter/" + spitter.getUsername(); }
這一章比較適合Spring MVC的入門學習資料。涵蓋了Spring MVC處理web請求的處理過程、如何寫簡單的控制器和控制器方法來處理Http請求、如何使用mockito框架測試控制器方法。
基於Spring MVC的應用有三種方式讀取數據:查詢參數、路徑參數和表單輸入。本章用兩節介紹了這些內容,並給出了相似錯誤處理和參數驗證等關鍵知識點。
因爲缺乏真正的入庫操做,所以本章節的一些方法不能真正的運做。
在接下來的章節中,咱們會對Spring視圖進行深刻了解,對如何在JSP頁面中使用Spring標籤庫進行展開。