成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

Spring之旅第八站:Spring MVC Spittr舞臺(tái)的搭建、基本的控制器、請(qǐng)求的輸入、表

maybe_009 / 3678人閱讀

摘要:請(qǐng)求旅程的第一站是的。的任務(wù)是將請(qǐng)求發(fā)送控制器控制器是一個(gè)用于處理請(qǐng)求的組件。處理映射器根據(jù)請(qǐng)求攜帶的信息來(lái)進(jìn)行決策。這樣的結(jié)果就是,只能找到顯示聲明在配置類(lèi)中的控制器。

構(gòu)建Spring Web應(yīng)用 說(shuō)明

如果你有幸能看到。

1、本文參考了《Spring 實(shí)戰(zhàn)》重點(diǎn)內(nèi)容,參考了GitHub上的代碼

2、本文只為記錄作為以后參考,要想真正領(lǐng)悟Spring的強(qiáng)大,請(qǐng)看原書(shū)。

3、在一次佩服老外,國(guó)外翻譯過(guò)來(lái)的書(shū),在GiuHub上大都有實(shí)例。看書(shū)的時(shí)候,跟著敲一遍,效果很好。

4、代碼和筆記在這里GitHub,對(duì)你有幫助的話,歡迎點(diǎn)贊。

5、每個(gè)人的學(xué)習(xí)方式不一樣,找到合適自己的就行。2018,加油。

6、Java 8 In Action 的作者M(jìn)ario Fusco

7、Spring In Action 、Spring Boot In Action的作者Craig Walls

8、知其然,也要知其所以然。

談一些個(gè)人感受

1、趕快學(xué)習(xí)Spring吧,Spring MVC 、Spring Boot 、微服務(wù)。

2、重點(diǎn)中的重點(diǎn),學(xué)習(xí)JDK 8 Lambda,Stream,Spring 5 最低要求JDK1.8.

3、還有Netty、放棄SH吧,不然你會(huì)落伍的。

4、多看一些國(guó)外翻譯過(guò)來(lái)的書(shū),例如 Xxx In Action 系列。權(quán)威指南系列。用Kindle~

5、寫(xiě)代碼之前先寫(xiě)測(cè)試,這就是老外不同之處。學(xué)到了很多技巧。

系統(tǒng)面臨的挑戰(zhàn):狀態(tài)管理、工作流、以及驗(yàn)證都是需要解決的重要特性。HTTP協(xié)議的無(wú)狀態(tài)決定了這些問(wèn)題都不是那么容易解決。

Spring的Web框架就是為了幫你解決這些關(guān)注點(diǎn)而設(shè)計(jì)的。Spring MVC基于模型-視圖-控制器(Model-View-Controller MVC)模式實(shí)現(xiàn)的,他能夠幫你構(gòu)建向Spring框架那樣靈活和松耦合的Web應(yīng)用程序。

在本章中,將會(huì)介紹Spring MVC Web框架,并使用新的Spring MVC注解來(lái)構(gòu)建處理各種Web請(qǐng)求、參數(shù)、和表單輸入的控制器。

5.1 Spring MVC起步

Spring將請(qǐng)求在調(diào)度Servlet、處理器映射(Handler Mappering)、控制器以及視圖解析器(View resolver)之間移動(dòng),每一個(gè)Spring MVC中的組件都有特定的目的,并且也沒(méi)那么復(fù)雜。

讓我們看一下,請(qǐng)求是如何從客戶端發(fā)起,經(jīng)過(guò)Spring MVC中的組件,最終返回到客戶端

5.1.1 跟蹤Spring MVC

每當(dāng)用戶在Web瀏覽器中點(diǎn)擊鏈接或提交表單的時(shí)候,請(qǐng)求就開(kāi)始工作了。請(qǐng)求是一個(gè)十分繁忙的家伙,從離開(kāi)瀏覽器開(kāi)始到獲取響應(yīng)返回,它會(huì)經(jīng)歷很多站,在每站都會(huì)留下一些信息,同時(shí)也會(huì)帶上一些信息。

Spring工作流程描述原文在這里

用戶向服務(wù)器發(fā)送請(qǐng)求,請(qǐng)求被Spring 前端控制Servelt DispatcherServlet捕獲;

DispatcherServlet對(duì)請(qǐng)求URL進(jìn)行解析,得到請(qǐng)求資源標(biāo)識(shí)符(URI)。然后根據(jù)該URI,調(diào)用HandlerMapping獲得該Handler配置的所有相關(guān)的對(duì)象(包括Handler對(duì)象以及Handler對(duì)象對(duì)應(yīng)的攔截器),最后以HandlerExecutionChain對(duì)象的形式返回;

DispatcherServlet 根據(jù)獲得的Handler,選擇一個(gè)合適的HandlerAdapter。(附注:如果成功獲得HandlerAdapter后,此時(shí)將開(kāi)始執(zhí)行攔截器的preHandler(...)方法)

提取Request中的模型數(shù)據(jù),填充Handler入?yún)ⅲ_(kāi)始執(zhí)行Handler(Controller)。 在填充Handler的入?yún)⑦^(guò)程中,根據(jù)你的配置,Spring將幫你做一些額外的工作:

HttpMessageConveter: 將請(qǐng)求消息(如Json、xml等數(shù)據(jù))轉(zhuǎn)換成一個(gè)對(duì)象,將對(duì)象轉(zhuǎn)換為指定的響應(yīng)信息

數(shù)據(jù)轉(zhuǎn)換:對(duì)請(qǐng)求消息進(jìn)行數(shù)據(jù)轉(zhuǎn)換。如String轉(zhuǎn)換成Integer、Double等

數(shù)據(jù)根式化:對(duì)請(qǐng)求消息進(jìn)行數(shù)據(jù)格式化。 如將字符串轉(zhuǎn)換成格式化數(shù)字或格式化日期等

數(shù)據(jù)驗(yàn)證: 驗(yàn)證數(shù)據(jù)的有效性(長(zhǎng)度、格式等),驗(yàn)證結(jié)果存儲(chǔ)到BindingResult或Error中

Handler執(zhí)行完成后,向DispatcherServlet 返回一個(gè)ModelAndView對(duì)象;

根據(jù)返回的ModelAndView,選擇一個(gè)適合的ViewResolver(必須是已經(jīng)注冊(cè)到Spring容器中的ViewResolver)返回給DispatcherServlet ;

ViewResolver 結(jié)合Model和View,來(lái)渲染視圖

將渲染結(jié)果返回給客戶端。

圖片參考這里

Spring工作流程描述

為什么Spring只使用一個(gè)Servlet(DispatcherServlet)來(lái)處理所有請(qǐng)求?

詳細(xì)見(jiàn)J2EE設(shè)計(jì)模式-前端控制模式

Spring為什么要結(jié)合使用HandlerMapping以及HandlerAdapter來(lái)處理Handler?

符合面向?qū)ο笾械膯我宦氊?zé)原則,代碼架構(gòu)清晰,便于維護(hù),最重要的是代碼可復(fù)用性高。如HandlerAdapter可能會(huì)被用于處理多種Handler。

1、請(qǐng)求旅程的第一站是Spring的DispatcherServlet。與大多數(shù)基于Java的Web框架一樣,Spring MVC所有的請(qǐng)求都會(huì)通過(guò)一個(gè)前端控制器(front contrller)Servlet.前端控制器是常用Web應(yīng)用程序模式。在這里一個(gè)單實(shí)例的Servlet將請(qǐng)求委托給應(yīng)用的其他組件來(lái)執(zhí)行實(shí)際的處理。在Spring MVC中,DisPatcherServlet就是前端控制器。

2、DisPactcher的任務(wù)是將請(qǐng)求發(fā)送Spring MVC控制器(controller).控制器是一個(gè)用于處理請(qǐng)求的Spring組件。在典型的應(yīng)用中可能會(huì)有多個(gè)控制器,DispatcherServlet需要知道應(yīng)該將請(qǐng)求發(fā)送給那個(gè)哪個(gè)控制器。所以Dispactcher以會(huì)查詢(xún)一個(gè)或 多個(gè)處理器映射(Handler mapping),來(lái)確定請(qǐng)求的下一站在哪里。處理映射器根據(jù)請(qǐng)求攜帶的 URL信息來(lái)進(jìn)行決策。

3、一旦選擇了合適的控制器,DispatcherServlet會(huì)將請(qǐng)求發(fā)送給選中的控制器。到了控制器,請(qǐng)求會(huì)卸下其負(fù)載(用戶提交的信息)并耐心等待控制器處理這些信息。(實(shí)際上,設(shè)計(jì)良好的控制器 本身只是處理很少,甚至不處理工作,而是將業(yè)務(wù)邏輯委托給一個(gè)或多個(gè)服務(wù)器對(duì)象進(jìn)行處理)

4、控制器在完成處理邏輯后,通常會(huì)產(chǎn)生一些信息。這些 信息需要返回給 用戶,并在瀏覽器上顯示。這些信息被稱(chēng)為模型(Model),不過(guò)僅僅給用戶返回原始的信息是不夠的----這些信息需要以用戶友好的方式進(jìn)行格式化,一般會(huì)是HTML。所以,信息需要發(fā)送一個(gè)視圖(View),通常會(huì)是JSP。

5、 控制器做的最后一件事就是將模型打包,并且表示出用于渲染輸出的視圖名。它接下來(lái)會(huì)將請(qǐng)求連同模型和視圖發(fā)送回DispatcherServlet。

6、這樣,控制器就不會(huì)與特定的視圖相耦合*傳遞給控制器的視圖名并不直接表示某個(gè)特定的jsp。實(shí)際上,它甚至并不能確定視圖就是JSP。相反,它僅僅傳遞了一個(gè)邏輯名稱(chēng),這個(gè)名字將會(huì)用來(lái)查找產(chǎn)生結(jié)果的真正視圖。DispatcherServlet將會(huì)使用視圖解析器(View resolver),來(lái)將邏輯視圖名稱(chēng)匹配為一個(gè)特定的視圖實(shí)現(xiàn),他可能也可能不是JSP

7、雖然DispatcherServlet已經(jīng)知道了哪個(gè)駛?cè)脘秩窘Y(jié)果、那請(qǐng)求的任務(wù)基本上也就完成了,它的最后一站是試圖的實(shí)現(xiàn)。在這里它交付給模型數(shù)據(jù)。請(qǐng)求的任務(wù)就結(jié)束了。視圖將使用模型數(shù)據(jù)渲染輸出。這個(gè)輸出通過(guò)響應(yīng)對(duì)象傳遞給客戶端(不會(huì)像聽(tīng)上去那樣硬編碼)

可以看到,請(qǐng)求要經(jīng)過(guò)很多步驟,最終才能形成返回給客戶端的響應(yīng),大多數(shù)的 步驟都是在Spirng框架內(nèi)部完成的。

5.1.2 搭建Spring MVC

借助于最近幾個(gè)Spring新特性的功能增強(qiáng),開(kāi)始使用SpringMVC變得非常簡(jiǎn)單了。使用最簡(jiǎn)單的方式配置Spring MVC;所要實(shí)現(xiàn)的功能僅限于運(yùn)行我們所創(chuàng)建的控制器。

配置DisPatcherServlet

DispatcherServlet是Spirng MVC的核心,在這里請(qǐng)求會(huì)第一次接觸到框架,它要負(fù)責(zé)將請(qǐng)求路由到其他組件之中。

按照傳統(tǒng)的方式,像DispatcherServlet這樣的Servlet會(huì)配置在web.xml中。這個(gè)文件會(huì)放到應(yīng)用的war包中。當(dāng)然這是配置DispatcherServlet方法之一。借助于Servlet 3規(guī)范和Spring 3.1 的功能增強(qiáng),這種方式已經(jīng)不是唯一的方案來(lái)。

我們會(huì)使用Java將DispatcherServlet配置在Servlet容器中。而不會(huì)在使用web.xml文件

public class SpitterWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected String[] getServletMappings() {             //將DispatcherServlet映射到“/”
        return new String[]{"/"};
    }

    @Override
    protected Class[] getRootConfigClasses() {
        return new Class [] {RootConfig.class};
    }

    @Override
    protected Class[] getServletConfigClasses() {
        return new Class [] { WebConfig.class};
    }
}

我們只需要知道擴(kuò)展AbstractAnnotationConfigDispatcherServletInitializer的任意類(lèi)都會(huì)自動(dòng)的配置Dispatcherservlet和Spring應(yīng)用上下文,Spirng的應(yīng)用上下文會(huì)位于應(yīng)用程序的Servlet上下文之中

在Servlet3.0環(huán)境中,容器會(huì)在類(lèi)路徑中 查找實(shí)現(xiàn)javax.servlet.ServletContainerInitialzer接口的類(lèi),如果能發(fā)現(xiàn)的話,就會(huì)用它來(lái)配置Servlet容器。

Spring提供了這個(gè)接口的實(shí)現(xiàn)名為SpringServletContainnerInitialzer,這個(gè)類(lèi)反過(guò)來(lái)又會(huì)查找實(shí)現(xiàn)WebApplicationInitialzer的類(lèi),并將配置的任務(wù)交給他們來(lái)完成。Spring 3.2引入了一個(gè)遍歷的WebApplicationInitialzer基礎(chǔ)實(shí)現(xiàn)也就是AbstractAnnotationConfigDispatcherServletInitializer因?yàn)槲覀兊?b>Spittr-WebApplicationInitialzer擴(kuò)展了AbstractAnnotationConfigDispatcherServletInitializer,(同時(shí)也就實(shí)現(xiàn)了WebApplicationInitialzer),因此當(dāng)部署Servlet3.0容器的時(shí)候,容器會(huì)自動(dòng)發(fā)現(xiàn)它,并用它來(lái)配置Servlet上下文

第一個(gè)方法getServletMappings(),它會(huì)將一個(gè)或多個(gè)路徑映射到DispatcherServlet上,在本示例中,它映射的是“/”,表示它是應(yīng)用默認(rèn)的Servlet,它會(huì)處理應(yīng)用的所有請(qǐng)求。

為了理解其他兩個(gè)方法,我們首先需要理解DispatcherServlet和一個(gè)Servlet監(jiān)聽(tīng)器(也就是ContextLoaderListener)的關(guān)系。

當(dāng)DispatcherServlet啟動(dòng)的時(shí)候,它會(huì)創(chuàng)建應(yīng)用上下文,并加載配置文件或配置類(lèi)中聲明的bean。在上面那個(gè)程序中的getServletConfigClasses()方法中,我們要求DispatcherServlet加載應(yīng)用上下文時(shí),使用定義在WebConfig配置類(lèi)(使用Java配置)中的bean

但在Spring Web應(yīng)用中,通常還會(huì)有另外一個(gè)應(yīng)用上下文。另外這個(gè)就是由ContextLoaderListener創(chuàng)建.

我們希望DispatcherServlet加載包含Web組件的bean,如控制器,視圖解析器,以及處理器映射,而ContextLoaderListener要加載應(yīng)用中的其他bean。這些bean通常 是驅(qū)動(dòng)應(yīng)用后端的中間層和數(shù)據(jù)層組件。

實(shí)際上AbstractAnnotationConfigDispatcherServletInitializer會(huì)同時(shí)創(chuàng)建DispatcherServletContextLoaderListenergetServletConfigClasses()方法會(huì)返回帶有@Configuration注解的類(lèi)將會(huì)用來(lái)定義DispatcherSerle應(yīng)用上下文中的bean,getRootConfigClasses()會(huì)返回帶有@Configuration注解的類(lèi)將會(huì)用來(lái)配置ContextLoaderListener創(chuàng)建的應(yīng)用上下文。

如果有必要兩個(gè)可以同時(shí)存在,wex.xml和 AbstractAnnotationConfigDispatcherServletInitializer,但其實(shí)沒(méi)有必要。

如果按照這種方式配置DispatcherServlet,而不是使用Web.xml的話,那么唯一的問(wèn)題在于它能部署到支持Servlet3.0的服務(wù)器上才可以正常工作,如Tomcat7或更高版本,Servlet3.0規(guī)范在2009年12月份就發(fā)布了,

如果沒(méi)有支持Servlet3.0,那別無(wú)選擇了,只能使用web.xml配置類(lèi)。

啟用Spring MVC

我們有多種方式來(lái)啟動(dòng)DispatcherServlet,與之類(lèi)似,啟用Spring MVC組件的方式也不止一種,以前Spring是XMl進(jìn)行配置的,你可以選擇啟用注解驅(qū)動(dòng)的Spring MVC。

在第七章的時(shí)候會(huì)介紹,現(xiàn)在會(huì)讓Spring MVC搭建的過(guò)程盡可能簡(jiǎn)單,并基于Java進(jìn)行配置。

我們所能創(chuàng)建最簡(jiǎn)單的Spring MVC配置就是一個(gè)帶有@EnableWebMvc注解的類(lèi)

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@EnableWebMvc
public class WebConfig {
}

這可以運(yùn)行起來(lái),它的確能夠啟用Spring MVC,但還有不少問(wèn)題要解決。

1、沒(méi)有配置視圖解析器,如果這樣的話,Spring默認(rèn)會(huì)使用BeanNameView-Resolver,這個(gè)視圖解析器會(huì)查找ID與視圖名稱(chēng)匹配的bean,并且查找的bean要實(shí)現(xiàn)View接口,它以這樣的方式來(lái)解析視圖。

2、沒(méi)有啟用組件掃描。這樣的結(jié)果就是,Spirng只能找到顯示聲明在配置類(lèi)中的控制器。

3、這樣配置的話,DispatcherServlet會(huì)映射為默認(rèn)的Servlet,所以他會(huì)處理所有的請(qǐng)求,包括對(duì)靜態(tài)資源的請(qǐng)求,如圖片 和樣式表(在大多數(shù)情況下,這可能并不是你想要的結(jié)果)。

因此我們需要在WebConfig這個(gè)最小的Spring MVC配置上再加一些內(nèi)容,從而讓他變得真正實(shí)用。

@Configuration
@EnableWebMvc                           //啟用Spring MVC
@ComponentScan("com.guo.spittr.web")    //啟用組件掃描
public class WebConfig extends WebMvcConfigurerAdapter {
    @Bean
    public ViewResolver viewResolver () {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        //配置JSP視圖解析器
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        resolver.setExposeContextBeansAsAttributes(true);
        return resolver;
    }

    @Override
    //我們要求DispatcherServlet將靜態(tài)資源的請(qǐng)求轉(zhuǎn)發(fā)到Servlet容器中默認(rèn)的Servlet上,
    //而不是使用DispatcherServlet本來(lái)來(lái)處理此類(lèi)請(qǐng)求。
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        //配置靜態(tài)資源的處理
        configurer.enable();
    }
}

第一件需要注意的是WebConfig現(xiàn)在添加了@ComponentScan注解,此時(shí)將會(huì)掃描com.guo.spittr.web包來(lái)查找組件。稍后你會(huì)看到,我們編寫(xiě)的控制器將會(huì)帶有@Controller注解,這會(huì)使其成為組件掃描時(shí)的候選bean。因此,我們不需要在配置類(lèi)中顯示聲明任何的控制器。

接下來(lái),我們添加了一個(gè)ViewResolver bean,更具體的將是InternalResourceViewResolver。將會(huì)在第6章更為詳細(xì)的討論視圖解析器。我們只需要知道他會(huì)去查找jsp文件,在查找的時(shí)候,它會(huì)在視圖名稱(chēng)上加一個(gè)特定的前綴和后綴。(例如:名為home的視圖會(huì)被解析為/WEB-INF/views/home.jsp)

最后新的WebConfig類(lèi)還擴(kuò)展里WebMvcConfigurerAdapter并重寫(xiě)了其configureDefaultServletHandling()方法,通過(guò)調(diào)用DefaultServletHandlerConfigurer的enable()方法,我們要求DispatcherServlet將靜態(tài)資源的請(qǐng)求轉(zhuǎn)發(fā)到Servlet容器中默認(rèn)的Servlet上,而不是使用DispatcherServlet本來(lái)來(lái)處理此類(lèi)請(qǐng)求。

WebConfig已經(jīng)就緒,那么RootConfig呢?因?yàn)楸菊戮劢褂赪eb開(kāi)發(fā),而Web相關(guān)的配置通過(guò)DisPatcherServlet創(chuàng)建的應(yīng)用上下文都已經(jīng)配好了,因此現(xiàn)在的RootConfig相對(duì)很簡(jiǎn)單:

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;
/**
 * Created by guo on 23/2/2018.
 */
@Configuration
@ComponentScan(basePackages = {"com.guo.spittr"},
    excludeFilters = {
        @Filter(type = FilterType.ANNOTATION,value = EnableWebMvc.class)})
public class RootConfig {

}

唯一需要注意的是RootConfig使用了@ComponentScan注解,這樣的話,我們就有很多機(jī)會(huì)用非Web的組件來(lái)完善RootConfig。

5.1.3 Spittr應(yīng)用簡(jiǎn)介

為了實(shí)現(xiàn)在線社交的功能,我們將要構(gòu)造一個(gè)簡(jiǎn)單的微博(microblogging)應(yīng)用,在很多方面,我們所構(gòu)建的應(yīng)用于最早的微博應(yīng)用Twitter很類(lèi)似,在這個(gè)過(guò)程中,我們會(huì)添加一些小的變化。當(dāng)然我們使用Spirng技術(shù)來(lái)構(gòu)建這個(gè)應(yīng)用。

因?yàn)閺腡witter借鑒了靈感并通過(guò)Spring來(lái)進(jìn)行實(shí)現(xiàn),所以它就有了一個(gè)名字:Spitter。

Spittr應(yīng)用有兩個(gè)基本的領(lǐng)域概念:Spitter(應(yīng)用的用戶)和Spittle(用戶發(fā)布的簡(jiǎn)短狀態(tài)更新)。當(dāng)我們?cè)跁?shū)中完善Spittr應(yīng)用的功能時(shí),將會(huì)介紹這兩個(gè)概念。在本章中,我們會(huì)構(gòu)建應(yīng)用的Web層,創(chuàng)建展現(xiàn)Spittle的控制器以及處理用戶注冊(cè)為Spitter的表單。

舞臺(tái)已經(jīng)搭建完成了,我們已經(jīng)配置了DispatcherServlet,啟用了基本的Spring MVC組件,并確定了目標(biāo)應(yīng)用。讓我們進(jìn)入本章的核心內(nèi)容:使用Spring MVC 控制器處理Web請(qǐng)求。

5.2 編寫(xiě) 基本的控制器

在SpringMVC中,控制器只是在方法上添加了@RequestMapping注解的類(lèi),這個(gè)注解聲明了他們所要處理的請(qǐng)求。

開(kāi)始的時(shí)候,我們盡可能簡(jiǎn)單,假設(shè)控制器類(lèi)要處理對(duì)/的請(qǐng)求,并對(duì)渲染應(yīng)用的首頁(yè)。

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * Created by guo on 24/2/2018.
 * 首頁(yè)控制器
 */
@Controller
public class HomeController {
    @RequestMapping(value = "/",method = RequestMethod.GET)           //處理對(duì)“/”的Get請(qǐng)求
    public String home() {
        return "home";                                                //視圖名為home
    }
}

寫(xiě)完測(cè)試了下,好使,

你可能注意到第一件事就是HomeController帶有@Controller注解,很顯然這個(gè)注解是用來(lái)聲明控制器的,但實(shí)際上這個(gè)注解對(duì)Spirng MVC 本身影響不大。

@Controller是一個(gè)構(gòu)造型(stereotype)的注解。它基于@Component注解。在這里,它的目的就是輔助實(shí)現(xiàn)組件掃描。因?yàn)閔omeController帶有@Controller注解,因此組件掃描器會(huì)自動(dòng)去找到HomeController,并將其聲明為Spring應(yīng)用上下文中的bean。

Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
    String value() default "";
}

其實(shí)你可以讓HomeController帶有@Component注解,它所實(shí)現(xiàn)的效果是一樣的。但是在表意性上可能差一些,無(wú)法確定HomeController是什么組件類(lèi)型。

HomeController唯一的一個(gè)方法,也就是Home方法,帶有@RequestMapping注解,他的Value屬性指定了這個(gè)方法所要處理的請(qǐng)求路徑,method屬性細(xì)化了它所能處理的HTTP方法,在本例中,當(dāng)收到對(duì)‘/’的HTTP GET請(qǐng)求時(shí),就會(huì)調(diào)用home方法。

home()方法其實(shí)并沒(méi)有做太多的事情,它返回一個(gè)String類(lèi)型的“home”,這個(gè)String將會(huì)被Spring MVC 解讀為要渲染的視圖名稱(chēng)。DispatcherServlet會(huì)要求視圖解析器將這個(gè)邏輯名稱(chēng)解析為實(shí)際的視圖。

鑒于我們配置InternalResourceViewResolver的方式,視圖名“home”將會(huì)被解析為“/WEB-INF/views/home.jsp”

Spittr應(yīng)用的首頁(yè),定義為一個(gè)簡(jiǎn)單的JSP

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>

  
    Spitter
    " >
  
  
    

Welcome to Spitter

">Spittles | ">Register

測(cè)試控制器最直接的辦法可能是構(gòu)建并部署應(yīng)用,然后通過(guò)瀏覽器對(duì)其進(jìn)行訪問(wèn),但是自動(dòng)化測(cè)試可能會(huì)給你更快的反饋和更一致的獨(dú)立結(jié)果,所以,讓我們編寫(xiě)一個(gè)針對(duì)HomeController的測(cè)試

5.2.1 測(cè)試控制器

編寫(xiě)一個(gè)簡(jiǎn)單的類(lèi)來(lái)測(cè)試HomoController。

import static org.junit.Assert.*;
import org.junit.Test;

public class HomeControllerTest {
    @Test
    public void testHomePage() throws Exception {
        HomeController controller = new HomeController();
        assertEquals("home",controller.home());
    }
}

在測(cè)試中會(huì)直接調(diào)用home()方法,并斷言返回包含 "home"值的String類(lèi)型。它完全沒(méi)有站在Spring MVC控制器的視角進(jìn)行測(cè)試。這個(gè)測(cè)試沒(méi)有斷言當(dāng)接收到針對(duì)“/”的GET請(qǐng)求時(shí)會(huì)調(diào)用home()方法。因?yàn)樗祷氐闹稻褪恰癶ome”,所以沒(méi)有真正判斷home是試圖的名稱(chēng)。

不過(guò)從Spring 3.2開(kāi)始,我們可以按照控制器的方式進(jìn)行測(cè)試Spring MVC中的控制器了。而不僅僅是POJO進(jìn)行測(cè)試。Spring現(xiàn)在包含了一種mock Spirng MVC 并針對(duì)控制器執(zhí)行 HTTP請(qǐng)求的機(jī)制。這樣的話,在測(cè)試控制器的時(shí)候,就沒(méi)有必要在啟動(dòng)Web服務(wù)器和Web瀏覽器了。

為了闡述如何測(cè)試Spirng MVC 容器,我們重寫(xiě)了HomeControllerTest并使用Spring MVC 中新的測(cè)試特性。

import org.junit.Test;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

/**
 * Created by guo on 24/2/2018.
 */
public class HomeControllerTest1  {
    @Test                                                             //大家在測(cè)試的時(shí)候注意靜態(tài)導(dǎo)入的方法
    public void testHomePage() throws Exception {
        HomeController controller = new HomeController();
        MockMvc mockMvc =  standaloneSetup(controller).build();       //搭建MockMvc
       mockMvc.perform(get("/"))                                      //對(duì)“/”執(zhí)行GET請(qǐng)求,
               .andExpect(view().name("home"));                       //預(yù)期得到home視圖
    }
}

這次我們不是直接調(diào)用home方法并測(cè)試它的返回值,而是發(fā)起了對(duì)"/"的請(qǐng)求,并斷言結(jié)果視圖的名稱(chēng)為home,它首先傳遞一個(gè)HomeController實(shí)例到MockMvcBuilders.strandaloneSetup()并調(diào)用build()來(lái)構(gòu)建MockMvc實(shí)例,然后它使用MockMvc實(shí)例執(zhí)行針對(duì)“/”的GET請(qǐng)求,并設(shè)置 期望得到的視圖名稱(chēng)。

5.2.2 定義類(lèi)級(jí)別的請(qǐng)求處理。

現(xiàn)在,已經(jīng)為HomeController編寫(xiě)了測(cè)試,那么我們可以做一些重構(gòu)。并通過(guò)測(cè)試來(lái)保證不會(huì)對(duì)功能造成什么破壞。我們可以做的就是拆分@RequestMapping,并將其路徑映射部分放到類(lèi)級(jí)別上

@Controller
@RequestMapping("/")
public class HomeController {
    @RequestMapping(method = RequestMethod.GET)           //處理對(duì)“/”的Get請(qǐng)求
    public String home() {
        return "home";                                    //視圖名為home
    }
}

在這個(gè)新版本的HomeController中,路徑被轉(zhuǎn)移到類(lèi)級(jí)別的@RequestMapping上,而HTTP方法依然映射在方法級(jí)別上。當(dāng)控制器在類(lèi)級(jí)別上添加@RequestMapping注解時(shí),這個(gè)注解會(huì)應(yīng)用到控制器的所有處理器方法上,處理器方法上的@RequestMapping注解會(huì)對(duì)類(lèi)級(jí)別上的@RequestMapping的聲明進(jìn)行補(bǔ)充。

就HomeController而言,這里只有一個(gè)控制器方法,與類(lèi)級(jí)別的@RequestMapping合并之后,這個(gè)方法的@RequestMapping表明home()將會(huì)處理對(duì) “/”路徑的GET請(qǐng)求。

有了測(cè)試,所以可以確保在這個(gè)過(guò)程中,沒(méi)有對(duì)原有的功能造成破壞。

當(dāng)我們修改@RequestMapping時(shí),還可以對(duì)HomeController做另一個(gè)變更。@RequestMapping的value接受一個(gè)String類(lèi)型的數(shù)組。到目前為止,我們給它設(shè)置的都是一個(gè)String類(lèi)型的‘/’。但是,我們還可以將它映射到對(duì)“/Homepage”的請(qǐng)求,只需要將類(lèi)級(jí)別的@RequestMapping改動(dòng)下

@Controller
@RequestMapping({"/","/Homepage"})
public class HomeController {
  ...
}

現(xiàn)在,HomeController的home()方法可以被映射到對(duì)“/”和“/homepage”的GET請(qǐng)求上。

5.2.3 傳遞模型數(shù)據(jù)到視圖中

到目前為止,就編寫(xiě)超級(jí)簡(jiǎn)單的控制器來(lái)說(shuō),HomeController已經(jīng)是一個(gè)不錯(cuò)的樣例了,但是大多數(shù)的控制器并不是那么簡(jiǎn)單。在Spring應(yīng)用中,我們需要有一個(gè)頁(yè)面展示最近提交的Spittle列表。因此,我們需要有一個(gè)新的方法來(lái)處理這個(gè)頁(yè)面。

首先需要定義一個(gè)數(shù)據(jù)訪問(wèn)的Repository,為了實(shí)現(xiàn)解耦以及避免陷入數(shù)據(jù)庫(kù)訪問(wèn)的細(xì)節(jié)中,我們將Repository定義為一個(gè)接口,并在稍后實(shí)現(xiàn)它(第十章),此時(shí),我們只需要一個(gè)能夠獲取Spittle列表的Repository,

package com.guo.spittr.data;
import com.guo.spittr.Spittle;
import java.util.List;
/**
 * Created by guo on 24/2/2018.
 */
public interface SpittleRepository {
    List finfSpittles(long max, int count);
}

findSpittles()方法接受兩個(gè)參數(shù),其中max參數(shù)代表所返回的Spittle中,Spittle ID屬性的最大值,而count參數(shù)表明要返回多少個(gè)Spittle對(duì)象,為了獲得最新的20個(gè)Spittle對(duì)象,我們可以這樣調(diào)用方法。

List recent = SpittleRepository.findSpittles(long.MAX_VALUE(),20)

它的屬性包括消息內(nèi)容,時(shí)間戳,以及Spittle發(fā)布時(shí)對(duì)應(yīng)的經(jīng)緯度。

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(null, message, time, null, null);
  }

  public Spittle(Long id, String message, Date time, Double longitude, Double latitude) {
    this.id = id;
    this.message = message;
    this.time = time;
    this.longitude = longitude;
    this.latitude = latitude;
  }

  //Getter和Setter略

  @Override
public boolean equals(Object that) {
  return EqualsBuilder.reflectionEquals(this, that, "id", "time");
}

@Override
public int hashCode() {
  return HashCodeBuilder.reflectionHashCode(this, "id", "time");
}

需要注意的是,我們使用Apache Common Lang包來(lái)實(shí)現(xiàn)equals()和hashCode()方法,這些方法除了常規(guī)的作用以外,當(dāng)我們?yōu)榭刂破鞯奶幚砥鞣椒ň帉?xiě)測(cè)試時(shí),它們也是有用的。

既然我們說(shuō)到了測(cè)試,那么我們繼續(xù)討論這個(gè)話題,并為新的控制器方法編寫(xiě)測(cè)試,

@Test
 public void houldShowRecentSpittles() throws Exception {
   List expectedSpittles = createSpittleList(20);
   SpittleRepository mockRepository = mock(SpittleRepository.class);
   when(mockRepository.findSpittles(Long.MAX_VALUE, 20))
       .thenReturn(expectedSpittles);

   SpittleController controller = new SpittleController(mockRepository);
   MockMvc mockMvc = standaloneSetup(controller)
       .setSingleView(new InternalResourceView("/WEB-INF/views/spittles.jsp"))
       .build();

   mockMvc.perform(get("/spittles"))
      .andExpect(view().name("spittles"))
      .andExpect(model().attributeExists("spittleList"))
      .andExpect(model().attribute("spittleList",
                 hasItems(expectedSpittles.toArray())));
 }
/.................佩服老外,測(cè)試代碼一大堆,省略了好多,好好研究下,..................../
 private List createSpittleList(int count) {
   List spittles = new ArrayList();
   for (int i=0; i < count; i++) {
     spittles.add(new Spittle("Spittle " + i, new Date()));
   }
   return spittles;
 }
}

測(cè)試首先會(huì)創(chuàng)建SpittleRepository接口的mock實(shí)現(xiàn),這個(gè)實(shí)現(xiàn)會(huì)從他的findSpittles()方法中返回20個(gè)Spittle對(duì)象,然后將這個(gè)Repository注入到一個(gè)新的SpittleController實(shí)例中,然后創(chuàng)建MockMvc并使用這個(gè)控制器。

需要注意的是這個(gè)測(cè)試在MockMvc構(gòu)造器上調(diào)用了setSingleView().這樣的話,mock框架就不用解析控制器中的視圖名了。在很多場(chǎng)景中,其實(shí)沒(méi)必要這么做,但是對(duì)于這個(gè)控制器方法,視圖和請(qǐng)求路徑非常相似,這樣按照默認(rèn)的駛?cè)虢馕鲆?guī)則,MockMvc就會(huì)發(fā)生失敗,因?yàn)闊o(wú)法區(qū)分視圖路徑和控制器的路徑,在這個(gè)測(cè)試中,構(gòu)建InternalResourceViewResolver時(shí)所設(shè)置的路徑是無(wú)關(guān)緊要的,但我們將其設(shè)置為InternalResourceViewResolver一致。

這個(gè)測(cè)試對(duì)“/spittles”發(fā)起Get請(qǐng)求,然后斷言視圖的名稱(chēng)為spittles并且模型中包含名為spittleList的屬性,在spittleList中包含預(yù)期的內(nèi)容。

當(dāng)然如果此時(shí)運(yùn)行測(cè)試的話,它將會(huì)失敗。他不是運(yùn)行失敗,而是編譯的時(shí)候就失敗,這是因?yàn)槲覀冞€沒(méi)編寫(xiě)SpittleController。

@Controller
@RequestMapping("/spittles")
public class SpittleController {
    private SpittleRepository spittleRepository;

    @Autowired
    public  SpittleController(SpittleRepository spittleRepository) {              //注入SpittleRepository
        this.spittleRepository = spittleRepository;
    }
    @RequestMapping(method = RequestMethod.GET)
    public String spittles(Model model) {
        model.addAttribute(spittleRepository.findSpittles(Long.MAX_VALUE,20));     // 將spittle添加到視圖
        return "spittles";                                                          // 返回視圖名
    }
}

我們可以看到SpittleController有一個(gè)構(gòu)造器,這個(gè)構(gòu)造器使用@Autowired注解,用來(lái)注入SpittleRepository。這個(gè)SpittleRepository隨后又在spittls()方法中,用來(lái)獲取最新的spittle列表。

需要注意的是我們?cè)趕pittles()方法中給定了一個(gè)Model作為參數(shù)。這樣,spittles()方法就可以將Repository中獲取到的Spittle列表填充到模型中,Model實(shí)際上就是一個(gè)Map(也就是key-value的集合)它會(huì)傳遞給視圖,這樣數(shù)據(jù)就能渲染到客戶端了。當(dāng)調(diào)用addAttribute()方法并且指定key的時(shí)候,那么key會(huì)根據(jù)值的對(duì)象類(lèi)型來(lái)推斷確定。

sittles()方法最后一件事是返回spittles作為視圖的名字,這個(gè)視圖會(huì)渲染模型。

如果你希望顯示模型的key的話,也可以指定,

@RequestMapping(method = RequestMethod.GET)
public String spittles(Model model) {
    model.addAttribute("spittleList",
        spittleRepository.findSpittles(Long.MAX_VALUE,20));     // 將spittle添加到視圖
    return "spittles";                                          // 返回視圖名
}

如果你希望使用非Spring類(lèi)型的話,那么可以使用java.util.Map來(lái)代替Model

@RequestMapping(method = RequestMethod.GET)
public String spittles(Map model) {
    model.put("spittleList",
        spittleRepository.findSpittles(Long.MAX_VALUE,20));     // 將spittle添加到視圖
    return "spittles";                                          // 返回視圖名
}

既然我們現(xiàn)在提到了各種可替代方案,那下面還有另外一種方式來(lái)編寫(xiě)spittles()方法

@RequestMapping(method = RequestMethod.GET)
public List spittles() {
  return spittleRepository.findSpittles(Long.MAX_VALUE,20));
}

這個(gè)并沒(méi)有返回值,也沒(méi)有顯示的設(shè)定模型,這個(gè)方法返回的是Spittle列表。。當(dāng)處理器方法像這樣返回對(duì)象或集合時(shí),這個(gè)值會(huì)放到模型中,模型的key會(huì)根據(jù)其類(lèi)型推斷得出。在本示例中也就是(spittleList)

邏輯視圖的名稱(chēng)也會(huì)根據(jù)請(qǐng)求的路徑推斷得出。因?yàn)檫@個(gè)方法處理針對(duì)“/spittles”的GET請(qǐng)求,因此視圖的名稱(chēng)將會(huì)是spittles,(去掉開(kāi)頭的線。)

不管使用哪種方式來(lái)編寫(xiě)spittles()方法,所達(dá)成的結(jié)果都是相同的。模型會(huì)存儲(chǔ)一個(gè)Spittle列表,ket為spittleList,然后這個(gè)列表會(huì)發(fā)送到名為spittles的視圖中。視圖的jsp會(huì)是“/WEB-INF/views/spittles.jsp”

現(xiàn)在數(shù)據(jù)已經(jīng)放到了模型中,在JSP中該如何訪問(wèn)它呢?實(shí)際上,當(dāng)視圖是JSP的時(shí)候,模型數(shù)據(jù)會(huì)作為請(qǐng)求屬性放入到請(qǐng)求之中(Request) ,因此在spittles.jsp文件中可以使用JSTL(JavaServer Pages Standard Tag Library) 的標(biāo)簽渲染spittle列表。


  
  • ">
    (, )
  • 盡管SpittleController很簡(jiǎn)單,但是它依然比homeController更進(jìn)一步,不過(guò),SpittleController和HomeController都沒(méi)有處理任何形式的輸入?,F(xiàn)在,讓我們擴(kuò)展SpittleContorller,讓它從客戶端接受一些輸入。

    5.3 接受請(qǐng)求的輸入

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

    查詢(xún)數(shù)據(jù)(Query Parameter)

    表單參數(shù)(Form Parameter)

    路徑變量(Path Variable)

    作為開(kāi)始,先來(lái)看下如何處理帶有查詢(xún)參數(shù)的請(qǐng)求,這也是客戶端往服務(wù)器發(fā)送數(shù)據(jù)時(shí),最簡(jiǎn)單和最直接的方法。

    5.3.1 處理查詢(xún)參數(shù)

    在Spittr應(yīng)用中,可能需要處理的一件事就是展現(xiàn)分頁(yè)的Spittle列表,如果你想讓用戶每次查看某一頁(yè)的Spittle歷史,那么就需要提供一種方式讓用戶傳遞參數(shù)進(jìn)來(lái),進(jìn)而確定展現(xiàn)那些Spittle列表。

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

    before參數(shù) (表明結(jié)果中所有的SPittle的ID均在這個(gè)值之前)

    count參數(shù)(彪悍在結(jié)果中要包含的Spittle數(shù)量)

    為了實(shí)現(xiàn)這個(gè)功能,我們將程序修改為spittles()方法替換為使用before參數(shù)和count參數(shù)的新spittles()方法。

    首先添加一個(gè)測(cè)試,這個(gè)測(cè)試反映了xinspittles()方法的功能

    @Test
    public void shouldShowPagedSpittles() throws Exception {
        List expectedSpittles = createSpittleList(50);
        SpittleRepository mockRepository = mock(SpittleRepository.class);
        when(mockRepository.findSpittles(238900, 50))
                .thenReturn(expectedSpittles);
    
        SpittleController controller = new SpittleController(mockRepository);
        MockMvc mockMvc = standaloneSetup(controller)
                .setSingleView(new InternalResourceView("/WEB-INF/views/spittles.jsp"))
                .build();
    
        mockMvc.perform(get("/spittles?max=238900&count=50"))
                .andExpect(view().name("spittles"))
                .andExpect(model().attributeExists("spittleList"))
                .andExpect(model().attribute("spittleList",
                        hasItems(expectedSpittles.toArray())));
    }

    這個(gè)測(cè)試方法關(guān)鍵點(diǎn)在于同時(shí)傳入了max和count參數(shù),它測(cè)試了這些參數(shù)存在時(shí)的處理方法,而另一個(gè)則測(cè)試了沒(méi)有這些參數(shù)的情景。

    在這個(gè)測(cè)試之后,我們就能確保不管控制器發(fā)生了什么樣的變化,它都能夠處理這兩種類(lèi)型的請(qǐng)求。

    @RequestMapping(method = RequestMethod.GET)
    public List spittles(
            @RequestParam(value = "max") long max,
            @RequestParam(value = "count") int count) {
        return spittleRepository.findSpittles(max, count);
    }

    SittleController中的處理器方法同時(shí)要處理有參數(shù)和沒(méi)參數(shù)的場(chǎng)景,那我們需要對(duì)其進(jìn)行修改,讓它能接受參數(shù)。同時(shí)如果這些參數(shù)在請(qǐng)求中不存在的話,就是用默認(rèn)值Long.MAX_VALUE和20.@RequestParam注解的defaultValue屬性可以完成這個(gè)任務(wù)。

    @RequestMapping(method=RequestMethod.GET)
    public List spittles(
        @RequestParam(value="max", defaultValue=MAX_LONG_AS_STRING) long max,
        @RequestParam(value="count", defaultValue="20") int count) {
      return spittleRepository.findSpittles(max, count);
    }

    現(xiàn)在如果max如果沒(méi)有參數(shù)指定的話,它將會(huì)是Long的最大值。

    因?yàn)椴樵?xún)參數(shù)都是String 類(lèi)型 ,因此defaultValue屬性需要String類(lèi)型,

    private static final String MAX_LONG_AS_STRING = long.toString(Long.MAX.VALUE)

    請(qǐng)求中的查詢(xún)參數(shù)是往控制器中傳遞信息的常用手段。另外一種方式就是將傳遞的參數(shù)作為請(qǐng)求路徑的一部分。

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

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

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

    在理想情況下,要識(shí)別資源應(yīng)用應(yīng)該通過(guò)URL路徑來(lái)標(biāo)識(shí),而不是通過(guò)查詢(xún)參數(shù)。對(duì)“/spittles/12345”發(fā)起請(qǐng)求要優(yōu)于對(duì)“/spittles/show?spittle_id=12345”發(fā)起的請(qǐng)求。前者能識(shí)別出要查詢(xún)的資源,而后者描述的是帶有參數(shù)的一個(gè)操作——本質(zhì)上是通過(guò)HTTP發(fā)起的RPC。

    既然已經(jīng)以面向資源的控制器作為目標(biāo),那我們將這個(gè)需求轉(zhuǎn)化為一個(gè)測(cè)試。

    @Test
    public void testSpittle() throws Exception {
      Spittle expectedSpittle = new Spittle("Hello", new Date());
      SpittleRepository mockRepository = 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"))                                 //斷言圖片的名稱(chēng)為spittle
        .andExpect(model().attributeExists("spittle"))                     //預(yù)期Spittle放到了模型之中
        .andExpect(model().attribute("spittle", expectedSpittle));
    }

    這個(gè)測(cè)試構(gòu)建了一個(gè)mockRepository,一個(gè)控制器和MockMvc

    到目前為止,我們所編寫(xiě)的控制器,所有的方法都映射到了靜態(tài)定義好的路徑上,還需要包含變量部分

    為了實(shí)現(xiàn)這種路徑變量,Spring MVC允許我們?cè)贎RequestMapping路徑中添加占位符,占位符的名稱(chēng)需要({..}),路徑中的其他部分要與所處理的請(qǐng)求完全匹配,但是占位符可是是任意的值。

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

    @PathVariable("spittleId") 表明在請(qǐng)求路徑中,不管占位符部分的值是什么都會(huì)傳遞給處理器方法的showSpittle參數(shù)中。

    也可以去掉這個(gè)value的值,因?yàn)榉椒ǖ膮?shù)碰巧與占位符的名稱(chēng)相同。

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

    如果傳遞請(qǐng)求中少量的數(shù)據(jù),那查詢(xún)參數(shù)和路徑變量是合適的,但通常我們還需要傳遞很多的數(shù)據(jù),(表單數(shù)據(jù)),那么查詢(xún)顯得有些笨拙和受限制了。

    5.4 處理表單

    Web應(yīng)用的功能不局限于為用戶推送內(nèi)容,大多數(shù)的應(yīng)用允許用戶填充表單,并將數(shù)據(jù)提交回應(yīng)用中,通過(guò)這種方式實(shí)現(xiàn)與用戶的交互。

    使用表單分為兩個(gè)方面:展現(xiàn)表單以及處理用戶通過(guò)表單提交的數(shù)據(jù)。在Spittr應(yīng)用中,我們需要有個(gè)表單讓用戶進(jìn)行注冊(cè),SitterController是一個(gè)新的控制器,目前只有一個(gè)請(qǐng)求處理的方法來(lái)展現(xiàn)注冊(cè)表單。

    @Controller
    @RequestMapping("/spitter")
    public class SpitterController {
        //處理對(duì)“/spitter/register”
        @RequestMapping(value = "/register",method = RequestMethod.GET)
          public String showRegistrationForm() {
              return "registerForm";
          }
    }

    測(cè)試展現(xiàn)表單的控制器方法(老外每次都測(cè)試)

    @Test
    public void shouldShowRegistration() throws Exception {
      SpitterController controller = new SpitterController();
      MockMvc mockMvc = standaloneSetup(controller).build();
      mockMvc.perform(get("/spitter/register"))
             .andExpect(view().name("registerForm"));
    }
    }

    這個(gè)JSP必須包含一個(gè)HTML

    標(biāo)簽,

    
      
      
      

    需要注意的是這里的

    標(biāo)簽中并沒(méi)有設(shè)置action屬性。在這種情況下,當(dāng)表單體提交的時(shí),它會(huì)提交到與展現(xiàn)時(shí)相同的URL路徑上,它會(huì)提交到“/spitter/reqister”上。

    這意味著需要在服務(wù)器端編寫(xiě)該HTTP POST請(qǐng)求。

    5.4.1 編寫(xiě)處理表單的處理器

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

    @Test
    public void shouldProcessRegistration() throws Exception {
      SpitterRepository mockRepository = mock(SpitterRepository.class);
      Spitter unsaved = new Spitter("jbauer", "24hours", "Jack", "Bauer", "[email protected]");
      Spitter saved = new Spitter(24L, "jbauer", "24hours", "Jack", "Bauer", "[email protected]");
      when(mockRepository.save(unsaved)).thenReturn(saved);
    
      SpitterController controller = new SpitterController(mockRepository);
      MockMvc mockMvc = standaloneSetup(controller).build();
    
      mockMvc.perform(post("/spitter/register")
             .param("firstName", "Jack")
             .param("lastName", "Bauer")
             .param("username", "jbauer")
             .param("password", "24hours")
             .param("email", "[email protected]"))
             .andExpect(redirectedUrl("/spitter/jbauer"));
    
      verify(mockRepository, atLeastOnce()).save(unsaved);
    }

    希望大家也可以學(xué)會(huì)這樣方式

    在構(gòu)建完SpitterRepository的mock實(shí)現(xiàn)以及所要執(zhí)行的控制器和MockNvc之后,shouldProcessRegistration()對(duì)“/spitter/register”發(fā)起了一個(gè)POST請(qǐng)求,作為請(qǐng)求的一部分,用戶信息以參數(shù)的形式放到request中,從而模擬提交的表單。

    /**
     * Created by guo on 24/2/2018.
     */
    @Controller
    @RequestMapping("/spitter")
    public class SpitterController {
    
        private SpitterRepository spitterRepository;
    
        @Autowired
        public SpitterController(SpitterRepository spitterRepository) {      //注入SpiterRepository
            this.spitterRepository = spitterRepository;
        }
    
        @RequestMapping(value = "/register", method = RequestMethod.GET)
        public String showRegistrationForm() {
            return "registerForm";
        }
    
        @RequestMapping(value = "/register",method = RequestMethod.POST)
        public String procesRegistration(Spitter spitter) {
            spitterRepository.save(spitter);                                //保存Spitter
            return "redirect:/spitter/" + spitter.getUsername();            //重定向到基本信息頁(yè)面
        }
    }

    返回一個(gè)String類(lèi)型,用來(lái)指定視圖。但是這個(gè)視圖格式和以前有所不同。這里不僅返回了視圖的名稱(chēng)供視圖解析器查找目標(biāo)視圖,而且返回的值還帶有重定向的格式return "redirect:/spitter/" 當(dāng)看到視圖格式中有“redirect:”前綴時(shí),它就知道要將其解析為重定向的規(guī)則,而不是試圖的名稱(chēng)。在本例中,它將會(huì)重定向到基本信息的頁(yè)面。

    需要注意的是除了可以“redirect”還可以識(shí)別“forward:”前綴,請(qǐng)求將會(huì)前(forward)往指定的URL路徑,而不再是重定向。

    在SpitterController中添加一個(gè)處理器方法,用來(lái)處理對(duì)基本信息頁(yè)面的請(qǐng)求。

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

    spitterRepository通過(guò)用戶獲取一個(gè)Spitter對(duì)象,showSpitterProfile()方法得到這個(gè)對(duì)象并將其添加到模型中,然后返回profile。也就是基本信息頁(yè)面的邏輯視圖。

    
      

    Your Profile




    注意:這里使用H2數(shù)據(jù)庫(kù),太有用了。

    @Configuration
    public class DataConfig {
    
      @Bean
      public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .addScript("schema.sql")
                .build();
      }
      @Bean
      public JdbcOperations jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
      }
    }
    

    如果表單中沒(méi)有發(fā)送username或password,會(huì)發(fā)生什么情況呢?或者名字太長(zhǎng),由會(huì)怎么樣?,接下來(lái),讓我們看一下為表單添加校驗(yàn),而從避免數(shù)據(jù)呈現(xiàn)不一致性。

    5.4.2 校驗(yàn)表單

    如果用戶在提交表單的時(shí)候,username和password為空的話,那么將會(huì)導(dǎo)致在新建Spitter對(duì)象中,username和password是空的String。如果不處理,將會(huì)出項(xiàng)安全問(wèn)題。

    同時(shí)我們應(yīng)該阻止用戶提交空的名字。限制這些輸入的長(zhǎng)度。

    從Spring 3.0 開(kāi)始,在Spring MVC中提供了java校驗(yàn)的API的支持。只需要在類(lèi)路徑下包含這個(gè)JavaAPI的實(shí)現(xiàn)即可。比如Hibernate validator.

    Java校驗(yàn)API定義了多個(gè)注解,這些注解可以用在屬性上,從而限制這些屬性的值。

    @Size :所注解的元素必須是String、集合、或數(shù)組,并且長(zhǎng)度要符合要求

    @Null :所注解的值必須為Null

    @NotNull :所注解的元素不能為Null。

    @Max :所注解的必須是數(shù)字,并且值要小于等于給定制。

    @Min

    @Past :所注解的元素必須是一個(gè)已過(guò)期的日期

    @Future :必須是一個(gè)將來(lái)的日期

    @Pattern:必須匹配給定的正則表達(dá)式

    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;
    
      @NotNull
      @Size(min=2, max=30)
      private String lastName;
    
      @NotNull
      @Email
      private String email;
    
      忽略其他方法。
    }
    @RequestMapping(value="/register", method=POST)      //老外喜歡靜態(tài)導(dǎo)入特性
    public String processRegistration(
        @Valid Spitter spitter,                          //校驗(yàn)Spitter輸入
        Errors errors) {
      if (errors.hasErrors()) {
        return "registerForm";                           //如果校驗(yàn)出現(xiàn)錯(cuò)誤,則重新返回表單
      }
    
      spitterRepository.save(spitter);
      return "redirect:/spitter/" + spitter.getUsername();
    }

    Spitter參數(shù)添加了@Valid注解,這會(huì)告訴Spring,需要確保這個(gè)對(duì)象滿足校驗(yàn)限制。

    如果表單出錯(cuò)的話,那么這些錯(cuò)誤可以通過(guò)Errors進(jìn)行反問(wèn)。

    很重要一點(diǎn)需要注意的是:Errors參數(shù)要緊跟在帶有Valid注解參數(shù)的后面。@Valid注解所標(biāo)注的就是要校驗(yàn)的參數(shù)。

    如果沒(méi)有錯(cuò)誤的話,Spitter對(duì)象將會(huì)通過(guò)Repository進(jìn)行保存,控制器會(huì)像之前那樣重定向到基本信息頁(yè)面。

    5.5 小節(jié)

    在本章中,我們?yōu)榫帉?xiě)應(yīng)用程序的Web部分開(kāi)來(lái)一個(gè)好頭,可以看到Spring有一個(gè)強(qiáng)大而靈活的Web框架。借助于注解,Spring MVC 提供了近似于POJO的開(kāi)發(fā)模式,這使得開(kāi)發(fā)處理請(qǐng)求的控制器變得簡(jiǎn)單,同時(shí)也易于測(cè)試。

    當(dāng)編寫(xiě)控制器的處理方法時(shí),Spring MVC及其靈活。概括來(lái)講,如果你的處理器方法需要內(nèi)容的話,只需將對(duì)應(yīng)的對(duì)象作為參數(shù),而他不需要的內(nèi)容,則沒(méi)有必要出現(xiàn)在參數(shù)列表中。這樣,就為請(qǐng)求帶來(lái)了無(wú)限的可能性,同時(shí)還能保持一種簡(jiǎn)單的編程模型。

    盡管本章中很多內(nèi)容都是關(guān)于控制器的請(qǐng)求處理的,但是渲染響應(yīng)也同樣重要,我們通過(guò)使用JSP的方式,簡(jiǎn)單了解了如何為控制器編寫(xiě)視圖,但是,就Spring MVC視圖來(lái)說(shuō),它并不是本章所看到的簡(jiǎn)單JSP。

    在接下來(lái)的第6章,我們將會(huì)更深入的學(xué)習(xí)Spring視圖,包括如何在JSP中使用Spring標(biāo)簽庫(kù),還會(huì)學(xué)習(xí)如何借助于Apache Tiles為視圖添加一致的結(jié)構(gòu)。同時(shí),還會(huì)了解Thymeleaf,這是一個(gè)很有意思的JSP替代方法,Spring為其提供了內(nèi)置的支持。

    真的非常期待下一章,,,,,加油

    文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

    轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/68555.html

    相關(guān)文章

    • Spring之旅第十站:MVC配置、上傳文件、異常處理、跨重定向請(qǐng)求、為制器添加通知

      摘要:依賴(lài)于對(duì)請(qǐng)求的支持。使用解析兼容的沒(méi)有構(gòu)造器參數(shù),也沒(méi)有要設(shè)置的參數(shù),這樣,在應(yīng)用上下文中,將其聲明為就會(huì)非常簡(jiǎn)單。默認(rèn)是沒(méi)有限制的整個(gè)請(qǐng)求的容量。 Spring MVC 高級(jí)的技術(shù) 本章內(nèi)容: Spring MVC配置的替代方案 處理文件上傳 在控制器中處理異常 使用flash屬性 稍等還沒(méi)結(jié)束 說(shuō)明 如果你有幸能看到。后面的章節(jié)暫時(shí)不更新了,改變學(xué)習(xí)方式了。重要理解思想,這本書(shū)...

      leanote 評(píng)論0 收藏0
    • Spring實(shí)戰(zhàn)5-基于Spring構(gòu)建Web應(yīng)用

      摘要:的框架用于解決上述提到的問(wèn)題,基于模型,可以幫助開(kāi)發(fā)人員構(gòu)建靈活易擴(kuò)展的應(yīng)用。在這一章中,將專(zhuān)注于構(gòu)建該應(yīng)用的層,創(chuàng)建控制器和顯示,以及處理用戶注冊(cè)的表單。類(lèi)有兩個(gè)靜態(tài)接口,代表兩種模擬服務(wù)的方式獨(dú)立測(cè)試和集成測(cè)試。 主要內(nèi)容 將web請(qǐng)求映射到Spring控制器 綁定form參數(shù) 驗(yàn)證表單提交的參數(shù) 寫(xiě)在前面:關(guān)于Java Web,首先推薦一篇文章——寫(xiě)給java web一年左右...

      sourcenode 評(píng)論0 收藏0
    • Spring MVC概念

      摘要:在中,就是前端控制器的任務(wù)是將請(qǐng)求發(fā)送給控制器。處理器映射會(huì)根據(jù)請(qǐng)求所攜帶的信息來(lái)進(jìn)行決策一旦選擇了合適的控制器,會(huì)將請(qǐng)求發(fā)送給選中的控制器。這些信息被稱(chēng)為模型。因此無(wú)需在配置類(lèi)中顯式聲明任何的控制器具體來(lái)講是試圖解析器。 Spring MVC基于模型-視圖-控制器(Model-View-Controller,MVC)模式實(shí)現(xiàn),能夠構(gòu)建像Spring框架那樣靈活和松耦合的Web應(yīng)用程序...

      lindroid 評(píng)論0 收藏0
    • 如何向一個(gè)WebApp引入SpringSpring MVC

      摘要:可以發(fā)現(xiàn),這兩個(gè)類(lèi)都是可以被實(shí)例化的,且構(gòu)造器不需要參數(shù)。這段代碼的后半部分其實(shí)沒(méi)有什么新意,但下半部分的第一行非常關(guān)鍵接受一個(gè)作為構(gòu)造器參數(shù)這實(shí)際上解決了我們?cè)诘谒恼聹y(cè)試失敗后反思的可能的疑惑我們配置的容器實(shí)際上并沒(méi)有和融合起來(lái)。 如何向一個(gè)WebApp引入Spring與Spring MVC 1 在Servlet 3.0環(huán)境中,容器(加載運(yùn)行webapp的軟件,如Tomcat)會(huì)在類(lèi)...

      maochunguang 評(píng)論0 收藏0

    發(fā)表評(píng)論

    0條評(píng)論

    最新活動(dòng)
    閱讀需要支付1元查看
    <