14蒲稳、構(gòu)建Spring Web應(yīng)用程序(2)(spring筆記)

三氮趋、接受請(qǐng)求的輸入

Spring MVC中,允許以多種方式將客戶端中的數(shù)據(jù)傳送到控制器的處理方法中江耀,包括:

  • 查詢參數(shù)
  • 表單參數(shù)
  • 路徑變量

3.1 處理查詢參數(shù)

如果我們向讓用戶每次都能產(chǎn)看某一頁(yè)的Spittle歷史剩胁,那么就需要提供一種方式讓用戶傳遞參數(shù)進(jìn)來(lái),進(jìn)而確定要展現(xiàn)哪些Spittle集合祥国。在確定該如何實(shí)現(xiàn)時(shí)昵观,假設(shè)我們要查看某一頁(yè)Spittle列表,這個(gè)列表會(huì)按照最新的Spittle在前的方式進(jìn)行排序(最后發(fā)布是Spittle消息的ID最大)舌稀。因此啊犬,下一頁(yè)中第一條的ID肯定會(huì)早于當(dāng)前頁(yè)最后一條的ID。因此壁查,為了顯示下一頁(yè)的Spittle觉至,需要將一個(gè)SpittleID傳入進(jìn)來(lái),這個(gè)ID要恰好小于當(dāng)前頁(yè)最后一條SpittleID睡腿。另外语御,還可以傳入一個(gè)參數(shù)確定要展現(xiàn)的Spittle數(shù)量峻贮。

為了實(shí)現(xiàn)這個(gè)分頁(yè)的功能,所編寫(xiě)的處理器方法要接受如下的參數(shù):

  • max參數(shù)(標(biāo)明結(jié)果中所有的SpittleID均應(yīng)該在這個(gè)值之前)
  • count參數(shù)(表明要查詢的Spittle數(shù)量)

下面給出SpittleController控制器中新的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);
}

說(shuō)明:可以看到這里max參數(shù)的默認(rèn)值是MAX_LONG_AS_STRING因?yàn)椴樵儏?shù)都是String類型的应闯,即通過(guò)GET方式傳遞參數(shù)都是String類型的纤控,因此這里不能使用Long.MAX_VALUE,當(dāng)MAX_LONG_AS_STRING綁定到max的時(shí)候會(huì)轉(zhuǎn)換成Long類型)碉纺,即最大值嚼黔,也就是說(shuō)所有的Spittle消息的ID都不可能大于此數(shù)。再次對(duì)其進(jìn)行測(cè)試:

@Test
public void shouldShowPagedSpittles() throws Exception {
    List<Spittle> expectedSpittles = createSpittleList(50);
    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(MockMvcRequestBuilders.get("/spittles?max=238900&count=50"))
            .andExpect(MockMvcResultMatchers.view().name("spittles"))
            .andExpect(MockMvcResultMatchers.model().attributeExists("spittleList"))
            .andExpect(MockMvcResultMatchers.model().attribute("spittleList",
                    org.hamcrest.Matchers.hasItems(expectedSpittles.toArray())));
}

3.2 通過(guò)路徑參數(shù)接受輸入

假設(shè)應(yīng)用程序需要根據(jù)給定的ID來(lái)展現(xiàn)某一個(gè)Spittle記錄惜辑。其中一種方案就是編寫(xiě)處理器方法唬涧,通過(guò)使用@RequestParam注解,讓它接受ID作為查詢參數(shù):

@RequestMapping(value="/show", method=RequestMethod.GET)
public String spittle(
    @PathVariable("spittle_id") long spittleId, Model model) {
  model.addAttribute(spittleRepository.findOne(spittleId));
  return "spittle";
}

說(shuō)明:這個(gè)處理器方法將會(huì)處理形如“/spittles/show?spittle_id=12345”這樣的請(qǐng)求盛撑。這樣可以正常工作碎节,但是從面向資源的角度來(lái)看這并不理想。在理想情況下抵卫,要識(shí)別的資源應(yīng)該通過(guò)URL路徑進(jìn)行標(biāo)識(shí)狮荔,而不是通過(guò)查詢參數(shù)。對(duì)“spittles/12345”發(fā)起GET請(qǐng)求要優(yōu)于對(duì)“/spittles/show?spittle_id=12345”發(fā)起請(qǐng)求介粘。

下面我們給出新的測(cè)試方法殖氏,它會(huì)斷言SpittleController中對(duì)面向資源的請(qǐng)求的處理。

@Test
public void testSpittle() throws Exception {
    Spittle expectedSpittle = new Spittle("Hello", new Date());
    SpittleRepository mockRepository = Mockito.mock(SpittleRepository.class);
    Mockito.when(mockRepository.findOne(12345)).thenReturn(expectedSpittle);

    SpittleController controller = new SpittleController(mockRepository);
    MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller).build();

    mockMvc.perform(MockMvcRequestBuilders.get("/spittles/12345"))
            .andExpect(MockMvcResultMatchers.view().name("spittle"))
            .andExpect(MockMvcResultMatchers.model().attributeExists("spittle"))
            .andExpect(MockMvcResultMatchers.model().attribute("spittle", expectedSpittle));
}

說(shuō)明:可以看到這里使用地址“/spittles/12345”進(jìn)行測(cè)試姻采,如果向讓測(cè)試通過(guò)雅采,需要編寫(xiě)@RequestParam要包含變量部分,這部分代表SpittleID慨亲。下面給出新的spittle()方法:

@RequestMapping(value="/{spittleId}", method=RequestMethod.GET)
public String spittle(
    @PathVariable("spittleId") long spittleId, Model model) {
  model.addAttribute(spittleRepository.findOne(spittleId));
  return "spittle";
}

說(shuō)明:這里在@RequestMapping中使用了占位符spittleId婚瓜,而方法參數(shù)中使用@PathVariable標(biāo)識(shí),這標(biāo)明不管占位符的名字(這里是spittleId)是什么刑棵,都會(huì)傳遞到處理器方法的spittleId參數(shù)中巴刻。當(dāng)然這里占位符和方法參數(shù)名一致,可以去掉@PathVariable中的值蛉签。下面給出spittle.jsp:

<div class="spittleView">
  <div class="spittleMessage"><c:out value="${spittle.message}" /></div>
  <div>
    <span class="spittleTime"><c:out value="${spittle.time}" /></span>
  </div>
</div>

四胡陪、處理表單

使用表單分為兩個(gè)方面:展現(xiàn)表單以及處理用戶通過(guò)表單提交的數(shù)據(jù)。在本應(yīng)用中需要有各表單讓新用戶進(jìn)行注冊(cè)碍舍。

package spittr.web;
import org.springframework.web.bind.annotation.RequestMethod;
import ...;

@Controller
@RequestMapping("/spitter")
public class SpitterController {

  @RequestMapping(value="/register", method=RequestMethod.GET)
  public String showRegistrationForm() {
    return "registerForm";
  }
}

說(shuō)明:這個(gè)方法非常簡(jiǎn)單柠座,就是向應(yīng)用發(fā)起請(qǐng)求,返回一個(gè)表單頁(yè)面乒验,相關(guān)測(cè)試如下:

@Test
public void shouldShowRegistration() throws Exception {
    SpitterRepository mockRepository = Mockito.mock(SpitterRepository.class);
    SpitterController controller = new SpitterController(mockRepository);
    MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
    mockMvc.perform(MockMvcRequestBuilders.get("/spitter/register"))
            .andExpect(MockMvcResultMatchers.view().name("registerForm"));
}

說(shuō)明:這里斷言視圖為“registerForm”愚隧。下面給出注冊(cè)表單registerForm.jsp

<form method="POST">
  First Name: <input type="text" name="firstName" /><br/>
  Last Name: <input type="text" name="lastName" /><br/>
  Email: <input type="email" name="email" /><br/>
  Username: <input type="text" name="username" /><br/>
  Password: <input type="password" name="password" /><br/>
  <input type="submit" value="Register" />
</form>

說(shuō)明:這里的<form>標(biāo)簽中并沒(méi)有設(shè)置action屬性蒂阱。此時(shí)锻全,它會(huì)提交到與展現(xiàn)時(shí)相同的URL路徑上(即GET請(qǐng)求地址“/spitter/register”)狂塘。當(dāng)表單填寫(xiě)無(wú)誤提交后,需要在服務(wù)器端處理該HTTP POST請(qǐng)求鳄厌。

4.1 編寫(xiě)處理表單的控制器

當(dāng)處理注冊(cè)表單的POST請(qǐng)求時(shí)荞胡,控制器需要接受表單數(shù)據(jù)并將表單數(shù)據(jù)保存為Spitter對(duì)象。最后了嚎,為了防止重復(fù)提交泪漂,應(yīng)該將瀏覽器重定向到新創(chuàng)建用戶的基本信息頁(yè)面。下面先給出測(cè)試代碼:

@Test
public void shouldProcessRegistration() throws Exception {
    SpitterRepository mockRepository = Mockito.mock(SpitterRepository.class);
    Spitter unsaved = new Spitter("jbauer", "24hours", "Jack", "Bauer", "jbauer@ctu.gov");
    Spitter saved = new Spitter(24L, "jbauer", "24hours", "Jack", "Bauer", "jbauer@ctu.gov");
    Mockito.when(mockRepository.save(unsaved)).thenReturn(saved);

    SpitterController controller = new SpitterController(mockRepository);
    MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller).build();

    mockMvc.perform(MockMvcRequestBuilders.post("/spitter/register")
            .param("firstName", "Jack")
            .param("lastName", "Bauer")
            .param("username", "jbauer")
            .param("password", "24hours")
            .param("email", "jbauer@ctu.gov"))
            .andExpect(MockMvcResultMatchers.redirectedUrl("/spitter/jbauer"));

    Mockito.verify(mockRepository, Mockito.atLeastOnce()).save(unsaved);
}

說(shuō)明:這里首先構(gòu)建了兩個(gè)相同Spitter對(duì)象(具體代碼后面會(huì)給出)歪泳,只是一個(gè)有ID(這個(gè)ID是隨意加的)萝勤,一個(gè)沒(méi)有。然后發(fā)起注冊(cè)請(qǐng)求呐伞,斷言至少保存了一個(gè)Spitter對(duì)象敌卓。在處理POST請(qǐng)求完后,為防止重復(fù)提交伶氢,最好進(jìn)行一下重定向趟径,這里重定向到此對(duì)象的顯示頁(yè)面(“/spitter/jbauer”)中。給出修改后的控制器SpitterController :

package spittr.web;
import ...

@Controller
@RequestMapping("/spitter")
public class SpitterController {

    private SpitterRepository spitterRepository;

    @Autowired
    public SpitterController(SpitterRepository spitterRepository) {
        this.spitterRepository = spitterRepository;
    }

    @RequestMapping(value="/register", method=RequestMethod.GET)
    public String showRegistrationForm() {
        return "registerForm";
    }

    @RequestMapping(value="/register", method=RequestMethod.POST)
    public String processRegistration(Spitter spitter) {
        spitterRepository.save(spitter);
        return "redirect:/spitter/" + spitter.getUsername();
    }
}

說(shuō)明:控制器在處理完注冊(cè)請(qǐng)求之后癣防,對(duì)訪問(wèn)進(jìn)行了重定向蜗巧,這里使用“redirect:”進(jìn)行重定向。當(dāng)然我們還可以使用“forward:”進(jìn)行請(qǐng)求轉(zhuǎn)發(fā)蕾盯。這里進(jìn)行請(qǐng)求重定向之后幕屹,我們?cè)诳刂破髦羞€需要針對(duì)此重定向的請(qǐng)求進(jìn)行處理:

@RequestMapping(value="/{username}", method=RequestMethod.GET)
public String showSpitterProfile(@PathVariable String username, Model model) {
    Spitter spitter = spitterRepository.findByUsername(username);
    model.addAttribute(spitter);
    return "profile";
}

說(shuō)明:下面給出相關(guān)的profile.jsp:

<h1>Your Profile</h1>
<c:out value="${spitter.username}" /><br/>
<c:out value="${spitter.firstName}" /> <c:out value="${spitter.lastName}" /><br/>
<c:out value="${spitter.email}" />

4.2 校驗(yàn)表單

在填寫(xiě)表單的時(shí)候,一般需要對(duì)填寫(xiě)的字段進(jìn)行校驗(yàn)级遭。但是手動(dòng)為每個(gè)字段編寫(xiě)校驗(yàn)顯然比較麻煩香嗓。從Spring 3.0開(kāi)始,在Spring MVC中提供了對(duì)Java校驗(yàn)的API支持装畅。這里需要導(dǎo)入兩個(gè)額外的包:hibernate-validator-6.0.0.Alpha2.jar靠娱、validation-api-2.0.0.Alpha2.jar

Java校驗(yàn)API所提供的校驗(yàn)注解

注解 描述
@AssertFalse 所注解的元素必須是Boolean類型掠兄,并且值為false
@AssertTrue 所注解的元素必須是Boolean類型像云,并且值為true
@DecimalMax 所注解的元素必須是數(shù)字,并且它的值要小于或等于給定的BigDecimalString
@DecimalMin 所注解的元素必須是數(shù)字蚂夕,并且它的值要大于或等于給定的BigDecimalString
@Digits 所注解的元素必須是數(shù)字迅诬,并且它的值必須有指定的位數(shù)
@Future 所注解的元素必須是一個(gè)將來(lái)的日期
@Max 所注解的元素必須是數(shù)字,并且它的值要小于或等于給定的值
@Min 所注解的元素必須是數(shù)字婿牍,并且它的值要大于或等于給定的值
@NotNull 所注解的元素的值必須不能為null
@Null 所注解的元素的值必須為null
@Past 所注解的元素的值必須是一個(gè)已過(guò)去的日期
@Pattern 所注解的元素的值必須匹配給定的正則表達(dá)式
@Size 所注解的元素的值必須是String侈贷、集合或數(shù)組,并且它的長(zhǎng)度要符合給定的范圍

下面給出spitter類:

package spittr;
import javax.validation.constraints.*;
import org.apache.commons.lang3.builder.*;
import org.hibernate.validator.constraints.Email;

public class Spitter {

  private Long id;
  
  @NotNull//非空等脂,5到16各字符
  @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;

  @NotNull
  @Size(min=2, max=30)
  private String lastName;
  
  @NotNull
  @Email
  private String email;

  public Spitter() {}
  
  public Spitter(String username, String password, String firstName, String lastName, String email) {
    this(null, username, password, firstName, lastName, email);
  }

  public Spitter(Long id, String username, String password, String firstName, String lastName, String email) {
    this.id = id;
    this.username = username;
    this.password = password;
    this.firstName = firstName;
    this.lastName = lastName;
    this.email = email;
  }

  //getter俏蛮、setter方法省略

  @Override
  public boolean equals(Object that) {
    return EqualsBuilder.reflectionEquals(this, that, "firstName", "lastName", "username", "password", "email");
  }
  
  @Override
  public int hashCode() {
    return HashCodeBuilder.reflectionHashCode(this, "firstName", "lastName", "username", "password", "email");
  }
}

說(shuō)明:現(xiàn)在已經(jīng)為spitter添加了校驗(yàn)注解撑蚌,接下來(lái)需要修改控制器中的注冊(cè)方法processRegistration()方法來(lái)應(yīng)用校驗(yàn)功能。

@RequestMapping(value="/register", method=RequestMethod.POST)
public String processRegistration(@Valid Spitter spitter, Errors errors) {
    if (errors.hasErrors()) {
        return "registerForm";
    }

    spitterRepository.save(spitter);
    return "redirect:/spitter/" + spitter.getUsername();
}

說(shuō)明:這里使用Spring的校驗(yàn)注解@Valid來(lái)對(duì)Spitter輸入進(jìn)行校驗(yàn)搏屑,如果校驗(yàn)出現(xiàn)錯(cuò)誤争涌,則重新放安徽表單。注意辣恋,在Spitter屬性上添加校驗(yàn)限制并不能組織表單提交亮垫。在填寫(xiě)出現(xiàn)錯(cuò)誤時(shí),需要讓相關(guān)錯(cuò)誤信息顯示在表單頁(yè)面(下一節(jié)進(jìn)行講解)伟骨。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末饮潦,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子携狭,更是在濱河造成了極大的恐慌害晦,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件暑中,死亡現(xiàn)場(chǎng)離奇詭異壹瘟,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)鳄逾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)稻轨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人雕凹,你說(shuō)我怎么就攤上這事殴俱。” “怎么了枚抵?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵线欲,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我汽摹,道長(zhǎng)李丰,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任逼泣,我火速辦了婚禮趴泌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘拉庶。我一直安慰自己嗜憔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布氏仗。 她就那樣靜靜地躺著吉捶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上呐舔,一...
    開(kāi)封第一講書(shū)人閱讀 49,792評(píng)論 1 290
  • 那天币励,我揣著相機(jī)與錄音,去河邊找鬼滋早。 笑死,一個(gè)胖子當(dāng)著我的面吹牛砌们,可吹牛的內(nèi)容都是我干的杆麸。 我是一名探鬼主播,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼浪感,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼昔头!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起影兽,我...
    開(kāi)封第一講書(shū)人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤揭斧,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后峻堰,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體讹开,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年捐名,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了旦万。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡镶蹋,死狀恐怖成艘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情贺归,我是刑警寧澤淆两,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站拂酣,受9級(jí)特大地震影響秋冰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜婶熬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一丹莲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧尸诽,春花似錦甥材、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)办成。三九已至,卻和暖如春贝或,著一層夾襖步出監(jiān)牢的瞬間渡讼,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工苛谷, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辅鲸,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓腹殿,卻偏偏與公主長(zhǎng)得像独悴,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子锣尉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容