天天看點

一步一步開發spring mvc第1部分 -設定基本應用程式和環境第2部分 -開發和配置應用程式第3部分 -為應用程式添加單元測試和表單第4部分 - 實作資料庫持久

第1部分 -設定基本應用程式和環境

先決條件:

o       Java SDK(我目前使用的是1.4.2版)

o       Ant  (使用1.6.2)

o       Apache Tomcat(使用5.0.28版)

你應該已經對使用以上軟體相當的自如了。

我不會在這篇文檔裡面涵蓋很多背景資訊或者理論——已經有很多書深入地讨論了這些東西。我們會直接投入開發程式的過程中。

第1步 - 開發目錄

我們需要一個地方用來放置所有的源代碼和其他我們将要建立的檔案,是以我建立了一個目錄,并命名為“

springapp

”。你可以把這個目錄放在你的主檔案夾或者其它一些地方。我把我的建立在我已經放在主目錄中的“

projects

”目錄下,這時我的目錄的完整路徑“

/User/trisberg/projects/springapp

”。在這個目錄中我建立了一個“src”目錄來存放所有的Java源代碼。然後我建立了另一個目錄并命名為“war”。這個目錄會存放所有将來進入WAR檔案的東西,這個檔案我們可以用來部署我們的應用程式。所有除了Java源代碼的源檔案,像JSP檔案和配置檔案,也屬于這個目錄。

第2步 – index.jsp

我将從建立一個叫做“

index.jsp

”的檔案(放在war目錄中)開始。這是我們整個應用的入口點。

springapp/war/index.jsp

隻是為了Web應用的完整性,我在war目錄中的WEB-INF目錄中建立了一個web.xml。

springapp/war/WEB-INF/web.xml

第3步 – 将應用程式部署到Tomcat

下面,我要寫一個Ant建構腳本,貫穿這個文檔我們都要使用它。一個獨立的建構腳本包含了應用伺服器特定的任務。同樣還有用于控制Tomcat下的任務。

springapp/build.xml

這個腳本現在包含了所有我們需要的目标,以便使我們開發更加容易。這裡我不會詳細解釋這個腳本,因為大部分内容都是比較标準Ant和Tomcat的東西。你可以直接複制上面的建構檔案并且把它放在你的開發目錄的根目錄中。我們還需要一個

build.properties

檔案,你需要自定這個檔案來配合你的伺服器安裝。這個檔案和

build.xml

檔案在同一個目錄中。

springapp/build.properties

如果你是在一個你不是Tomcat安裝的所有者的系統中,那麼Tomcat所有者必須給你通路webapps目錄的全部權限,或者他可以在webapps目錄下面建立一個“springapp”目錄,并且給你全部權限來把程式部署到這個建立的目錄中。在Linux上我運作

chmod a+rwx springapp

來給與所有人對目錄的通路權利。

如果你使用一個不用的Web應用伺服器,那麼你要删除在建構腳本底部的那些特定于Tomcat的任務。你還要依賴你伺服器的熱部署特定,否則你就需要手工重新啟動你的應用伺服器。

現在我運作Ant來確定所有的東西都工作正常。你應該把你目前的目錄設定到“springapp”目錄下。

[[email protected] springapp]$ ant      
Buildfile: build.xml      
usage:      
     [echo] springapp build file      
     [echo] -----------------------------------      
     [echo] Available targets are:      
     [echo] build     --> Build the application      
     [echo] deploy    --> Deploy application as directory      
     [echo] deploywar --> Deploy application as a WAR file      
     [echo] install   --> Install application in Tomcat      
     [echo] reload    --> Reload application in Tomcat      
     [echo] start     --> Start Tomcat application      
     [echo] stop      --> Stop Tomcat application      
     [echo] list      --> List Tomcat applications      
BUILD SUCCESSFUL      
Total time: 2 seconds      

這裡最後的動作是進行實際的部署。隻要運作Ant并且指明“deploy”或者“deploywar”作為目标。

[[email protected] springapp]$ ant deploy      
Buildfile: build.xml      
build:      
    [mkdir] Created dir: /Users/trisberg/projects/springapp/war/WEB-INF/classes      
deploy:      
     [copy] Copying 2 files to /Users/trisberg/jakarta-tomcat-5.0.28/webapps/springapp      
BUILD SUCCESSFUL      
Total time: 2 seconds      

第4步 – 測試應用

讓我們立刻啟動Tomcat并且確定我們可以通路這個應用程式。使用我們的建構腳本中的“

list

”任務來檢視Tomcat是否已經載入了新的應用程式。

[[email protected] springapp]$ ant list      
Buildfile: build.xml      
list:      
     [list] OK - Listed applications for virtual host localhost      
     [list] /admin:running:0:/Users/trisberg/jakarta-tomcat-5.0.28/server/webapps/admin      
     [list] /webdav:running:0:/Users/trisberg/jakarta-tomcat-5.0.28/webapps/webdav      
     [list] /servlets-examples:running:0:/Users/trisberg/jakarta-tomcat-5.0.28/webapps/servlets-examples      
     [list] /springapp:running:0:/Users/trisberg/jakarta-tomcat-5.0.28/webapps/springapp      
     [list] /jsp-examples:running:0:/Users/trisberg/jakarta-tomcat-5.0.28/webapps/jsp-examples      
     [list] /balancer:running:0:balancer      
     [list] /tomcat-docs:running:0:/Users/trisberg/jakarta-tomcat-5.0.28/webapps/tomcat-docs      
     [list] /:running:0:/Users/trisberg/jakarta-tomcat-5.0.28/webapps/ROOT      
     [list] /manager:running:0:/Users/trisberg/jakarta-tomcat-5.0.28/server/webapps/manager      
BUILD SUCCESSFUL      
Total time: 1 second      

如果他沒有被列出,使用“

install

”任務來把應用程式安裝到Tomcat中。

[[email protected] springapp]$ ant install      
Buildfile: build.xml      
install:      
  [install] OK - Installed application at context path /springapp      
BUILD SUCCESSFUL      
Total time: 2 seconds      

現在打開一個浏覽器并浏覽http://localhost:8080/springapp/index.jsp.

第5步 – 下載下傳Spring分發包

如果你還沒有下載下傳Spring Framework的釋出檔案,那現在就行動吧。我目前使用的是“

spring-framework-1.2-with-dependencies.zip

”,可以從http://www.springframework.org/download.html 下載下傳到。我把檔案解壓縮到我的主目錄中。我們後面将要用到裡面的一些檔案。

到此為止必要的環境安裝已經完成了,現在我們要開始實際開發我們的Spring Framework MVC應用了。

第6步 – 修改WEB-INF目錄中的web.xml

進入“

springapp/war/ WEB-INF

”目錄。修改我們前面建立的最小“

web.xml

”檔案。現在我們要修改它來滿足我們需求。我們定義一個将來控制我們所有請求轉向的

DispatcherServlet

,它将根據我們以後某處輸入的資訊進行工作。同時還有一個标準的用來映射到我們使用的URL模式的

servlet-mapping

條目。我決定讓所有帶“

.htm

”擴充名的URL轉向到“

springapp

” 配置設定器。

springapp/war/WEB-INF/web.xml

下面,在

springapp/war/WEB-INF

目錄下建立一個叫做“

springapp-servlet.xml

”的檔案(你可以直接從Spring分發包中複制一個範例檔案,位于

sample/skeletons/webapp-minimal

目錄中)。

DispatcherServlet

所使用的定義就要放在這個檔案中。檔案名是

web.xml

中的servlet-name并加上“-servlet”字尾。這是Spring Framework所使用的标準命名約定。現在,添加一個叫做

springappController

的bean條目并建立一個

SpringappController

類。這裡将定義我們的應用程式所使用的控制器。我們還要添加一個URL映射

urlMapping

這樣

DispatcherServlet

就會知道對于不同的URL應該調用哪個控制器。

springapp/war/WEB-INF/springapp-servlet.xml

第7步 - 把jar檔案複制到WEB-INF/lib

首先在“

war/WEB-INF

”目錄中建立一個“lib”目錄。然後,從Spring分發包中,将

spring.jar

(

spring-framework-1.2/dist/spring.jar

)複制到建立的

war/WEB-INF/lib

目錄中。同時把commons-logging的jar檔案(

spring-framework-1.2/lib/jakarta-commons/commons-logging.jar

)也複制到

war/WEB-INF/lib

中。同時我們還需要log4j.jar。把

log4j-1.2.9.jar(spring-framework-1.2/lib/log4j/log4j-1.2.9.jar)

複制到

war/WEB-INF/lib

目錄。這些jar檔案以後會被部署到伺服器上而且他們在建構過程中也會被用到。

第8步 - 建立你的控制器

建立你的控制器——我把我的控制器命名為

SpringappController.java

并把它放在

springapp/src

目錄下。

springapp/src/SpringappController.java

這是非常基本的控制器。我們稍後會對他進行擴充,同時過會兒我們還要擴充一些已經提供的抽象的基本實作。這個控制器處理請求并傳回一個

ModelAndView

。不過我們還沒有定義任何視圖,是以現在沒什麼可做的了。

第9步 - 建構應用程式

運作

build.xml

中的“

build

”任務。基本上代碼應該順利通過編譯。

[[email protected] springapp]$ ant build      
Buildfile: build.xml      
build:      
    [javac] Compiling 1 source file to /Users/trisberg/projects/springapp/war/WEB-INF/classes      
BUILD SUCCESSFUL      
Total time: 2 seconds      

第10步 – 複制并修改log4j.properties

Spring Framework使用log4j來進行日志記錄,是以我們要為log4j建立一個配置檔案。把

log4j.properties

檔案從Petclinic範例應用程式(

spring-framework-1.2/samples/petclinic/war/WEB-INF/log4j.properties

) 中複制到

war/WEB-INF/classes

目錄中(這個目錄應該在前一步中被建立了)。現在取消

log4j.rootCategory

屬性前的注釋并且更改寫入的日志檔案的名稱和位置。我決定把日志寫入與其他Tomcat日志一樣的目錄中。

springapp/war/WEB-INF/classes/log4j.properties

第11步 – 部署應用程式

運作

build.xml

中的“

deploy

”任務然後再運作“

stop

”和“

start

”任務。這将強制應用程式重新載入。我們要檢查Tomcat日志中的部署錯誤——可能在上面的XML檔案中有輸入錯誤或者也可能缺少class或者jar檔案。下面是一個日志的例子(

/Users/trisberg/jakarta-tomcat-5.0.28/logs/springapp.log

)。

2005-04-24 14:58:18,112 INFO [org.springframework.web.servlet.DispatcherServlet] - Initializing servlet 'springapp'      
2005-04-24 14:58:18,261 INFO [org.springframework.web.servlet.DispatcherServlet] - FrameworkServlet 'springapp': initialization started      
2005-04-24 14:58:18,373 INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] - Loading XML bean definitions from ServletContext resource [/WEB-INF/springapp-servlet.xml]      
2005-04-24 14:58:18,498 INFO [org.springframework.web.context.support.XmlWebApplicationContext] - Bean factory for application context [WebApplicationContext for namespace 'springapp-servlet']: org.springframework.beans.factory.support.DefaultListableBeanFactory defining beans [springappController,urlMapping]; root of BeanFactory hierarchy      
2005-04-24 14:58:18,505 INFO [org.springframework.web.context.support.XmlWebApplicationContext] - 2 beans defined in application context [WebApplicationContext for namespace 'springapp-servlet']      
2005-04-24 14:58:18,523 INFO [org.springframework.core.CollectionFactory] - JDK 1.4+ collections available      
2005-04-24 14:58:18,524 INFO [org.springframework.core.CollectionFactory] - Commons Collections 3.x available      
2005-04-24 14:58:18,537 INFO [org.springframework.web.context.support.XmlWebApplicationContext] - Unable to locate MessageSource with name 'messageSource': using default [[email protected]b]      
2005-04-24 14:58:18,539 INFO [org.springframework.web.context.support.XmlWebApplicationContext] - Unable to locate ApplicationEventMulticaster with name 'applicationEventMulticaster': using default [org.[email protected]5674a4]      
2005-04-24 14:58:18,549 INFO [org.springframework.ui.context.support.UiApplicationContextUtils] - No ThemeSource found for [WebApplicationContext for namespace 'springapp-servlet']: using ResourceBundleThemeSource      
2005-04-24 14:58:18,556 INFO [org.springframework.beans.factory.support.DefaultListableBeanFactory] - Pre-instantiating singletons in factory [org.springframework.beans.factory.support.DefaultListableBeanFactory defining beans [springappController,urlMapping]; root of BeanFactory hierarchy]      
2005-04-24 14:58:18,557 INFO [org.springframework.beans.factory.support.DefaultListableBeanFactory] - Creating shared instance of singleton bean 'springappController'      
2005-04-24 14:58:18,603 INFO [org.springframework.beans.factory.support.DefaultListableBeanFactory] - Creating shared instance of singleton bean 'urlMapping'      
2005-04-24 14:58:18,667 INFO [org.springframework.web.servlet.DispatcherServlet] - Using context class [org.springframework.web.context.support.XmlWebApplicationContext] for servlet 'springapp'      
2005-04-24 14:58:18,668 INFO [org.springframework.web.servlet.DispatcherServlet] - Unable to locate MultipartResolver with name 'multipartResolver': no multipart request handling provided      
2005-04-24 14:58:18,670 INFO [org.springframework.web.servlet.DispatcherServlet] - Unable to locate LocaleResolver with name 'localeResolver': using default [[email protected]318309]      
2005-04-24 14:58:18,675 INFO [org.springframework.web.servlet.DispatcherServlet] - Unable to locate ThemeResolver with name 'themeResolver': using default [[email protected]]      
2005-04-24 14:58:18,681 INFO [org.springframework.web.servlet.DispatcherServlet] - No HandlerAdapters found in servlet 'springapp': using default      
2005-04-24 14:58:18,700 INFO [org.springframework.web.servlet.DispatcherServlet] - No ViewResolvers found in servlet 'springapp': using default      
2005-04-24 14:58:18,700 INFO [org.springframework.web.servlet.DispatcherServlet] - FrameworkServlet 'springapp': initialization completed in 439 ms      
2005-04-24 14:58:18,704 INFO [org.springframework.web.servlet.DispatcherServlet] - Servlet 'springapp' configured successfully      

第12步 - 建立一個視圖

現在是時候建立我們第一個視圖了。我将使用一個JSP頁面,并命名為

hello.jsp

。然後我把它放在了war目錄中。

springapp/war/hello.jsp

裡面沒什麼奇特的東西,隻是為了現在試一下。下面我們要修改SpringappController來引導到這個視圖。

springapp/src/SpringappController.java

當我在修改這個類的同時,我還添加了一個

logger

這樣我們可以校對我們在這裡實際得到的值。更改的内容将以紅色标明。這個類傳回的模型将最終通過一個

ViewResolver

來進行轉換。由于我們并沒有指定一個特别的,是以我們将使用一個預設的,它僅僅引導到比對指定的視圖的名字的URL。我們稍候将修改它。

現在編譯并部署這個應用程式。在通知Tomcat重新啟動應用程式之後,所有的東西都應該被重新載入了。

讓我們在浏覽器中試一下——輸入URL http://localhost:8080/springapp/hello.htm,然後我們應該看到以下内容:

我們也可以檢查一下日志——我這裡僅列出最後的條目,我們可以看到控制器确實被調用了,然後它引導到了hello視圖。(

/Users/trisberg/jakarta-tomcat-5.0.28/logs/springapp.log

)

2005-04-24 15:01:56,217 INFO [org.springframework.web.servlet.DispatcherServlet] - FrameworkServlet 'springapp': initialization completed in 372 ms      
2005-04-24 15:01:56,217 INFO [org.springframework.web.servlet.DispatcherServlet] - Servlet 'springapp' configured successfully      
2005-04-24 15:03:57,908 INFO [SpringappController] - SpringappController - returning hello view      

總結

讓我們快速回顧一下目前我們已經建立的應用程式的各個部分:

1.     一個基本不做什麼事情的介紹頁面

index.jsp

。它隻是用來測試我們的安裝。我們以後會修改它,以便提供一個連結指向我們的應用。

2.     一個

DispatcherServlet

和一個相應的配置檔案

springapp-servlet.xml

3.     一個控制器

springappController.java

,包含了有限的功能——他僅僅把一個

ModelAndView

引導到

ViewResolver

。事實上,我們目前還隻有一個空的模型,不過我們以後會修正它。

4.     一個視圖

hello.jsp

,同樣是極其基本的。但是整個安裝工作可以運作并且我們現在已經準備好開始添加更多的功能了。

第2部分 -開發和配置應用程式

在第一部分(第1 – 12 步)我們已經配置了開發環境并建立了一個基本的應用程式。

我們已經準備好了:

1.     一個介紹頁面

index.jsp

.

2.     一個

DispatcherServlet

,以及相應的配置檔案

springapp-servlet.xml

3.     一個控制器

springappController.java

.

4.     一個視圖

hello.jsp

.

現在我們要改進這些部件來建立一個更好的應用程式。

第13步 – 改進index.jsp

我們将利用JSP标準标簽庫(JSTL),是以我要先複制我們所需的JSTL檔案到我們的

WEB-INF/lib

目錄中。複制“

spring-framework-1.2/lib/j2ee

”中的jstl.jar和“

spring-framework-1.2/lib/jakarta-taglibs

”中的

standard.jar

springapp/war/WEB-INF/lib

目錄下。我還建立了一個“header”檔案,将來會在我寫的每一個JSP頁面中包含這個檔案。這樣會令開發更加簡單同時我可以確定在所有的JSP檔案中都有同樣的定義。我将把所有的JSP檔案放在

WEB-INF

目錄下的一個

jsp

目錄中。這可以確定隻有控制器可以通路這些視圖——直接在浏覽器中輸入URL來通路這些頁面是不行的。這個政策不一定在所有的應用伺服器中都可以行得通,如果你使用的應用伺服器恰好不行的話,隻要把jsp目錄往上移一級。你可以使用

springapp/war/jsp

作為目錄來替代以後所有的例子代碼中的“

springapp/war/WEB-INF/jsp

”。

springapp/war/WEB-INF/jsp/include.jsp

現在我們可以更改

index.jsp

來使用,由于我們使用了JSTL,我們可以使用

<c:redirect>

标簽來轉向到我們的控制器。

springapp/war/index.jsp

第14步 – 改進視圖和控制器

現在我要把

hello.jsp

視圖移入

WEB-INF/jsp

目錄。

Index.jsp

裡面添加的包含檔案

include.jsp

同樣也添加到了

hello.jsp

中。我也使用JSTL

<c:out>

标簽來輸出從傳給視圖的模型裡擷取的目前的日期和時間。

springapp/war/WEB-INF/jsp/hello.jsp

對于

SpringappController.java

,我們還要做一些更改。由于我們把檔案移動到了一個新的位置,是以需要把視圖變成

WEB-INF/jsp/hello.jsp

。同時添加一個包含目前時間和日期的字元串作為模型。

springapp/src/SpringappController.java

在我們建構并部署了新的代碼之後,現在我們準備嘗試它了。我們在浏覽器中輸入http://localhost:8080/springapp,它首先會調用

index.jsp

,然後它又會重定向到

hello.htm

,這個URL又會調用控制器并把時間和日期發送給視圖。

第15步 – 解耦視圖和控制器

現在控制器是指明了視圖的完整路徑,這在控制器和視圖之間産生了一個多餘的依賴關系。理想上來說,我們要使用一個邏輯名稱來映射到視圖,這可以讓我們無需更改控制器就可以切換視圖。你可以在一個屬性檔案中設定這個映射,如果你喜歡使用

ResourceBundleViewResolver

SimpleUrlHandlerMapping

類的話。如果你的映射需求确實很簡單,那麼在

InternalResourceViewResolver

上加上字首和字尾會很友善。後一種方法就是我現在要實作的。我修改了

springapp-servlet.xml

并包含了

viewResolver

條目。我選擇使用

JstlView

,它可以讓我們使用JSTL,可以結合消息資源綁定,同時他還可以支援國際化。

springapp/war/WEB-INF/springapp-servlet.xml

是以現在我可以從控制器的視圖名稱中删除字首和字尾了。

springapp/src/SpringappController.java

編譯并部署,應用程式應該仍然可以正常運作。

第16步 – 添加一些業務邏輯的類

目前位置我們的應用還不是很有用。我想添加一些業務邏輯,一個

Product

類和一個管理所有産品的類。我把管理類命名為

ProductManager

。為了能分離依賴Web的邏輯和業務邏輯,我将在Java源代碼重建立兩個單獨的包——web和bus。如果這個應用程式是為一個真實的公司開發的,我可能會把包命名成像

com.mycompany.web

com.mycompany.bus

之類的名字,不過這隻是一個示範而已我就讓包的名稱簡短一些。

Product

類是實作為一個JavaBean——它有一個預設的構造器(如果我們沒有指明任何構造器,會自動給出),兩個執行個體變量

description

price

的擷取器(getter)和設制器(setter)。我還把它設為

Serializable

,這對我們的應用不是必需的,不過以後我們很可能要把這個類在不同的應用層中傳遞的時候,那時就可以直接使用了。

springapp/src/bus/Product.java

ProductManager

中有一個

Product

的清單List,同樣的,這個類也是實作為一個JavaBean。

springapp/src/bus/ProductManager.java

下面,我修改了

SpringappController

來存放一個指向

ProductManager

類的引用。正如你所見,它現在在一個單獨的web的包中——記得把代碼放到這個新位置中。我還要添加讓控制器将産品資訊傳送到視圖的代碼。

getModelAndView

現在傳回一個

Map

,同時包含了時間日期和産品管理的引用。

springapp/src/web/SpringappController.java

第 17 步 – 修改視圖用于現實業務資料并且添加消息綁定的支援

我使用了JSTL

<c:forEach>

标簽來添加了一個現實産品資訊的部分。我還用JSTL

<fmt:message>

标記替換了标題和歡迎文本,這樣可以從給定的“message”源中讀取文本并顯示——在後面的步驟中我會顯示這個方法。

springapp/war/WEB-INF/jsp/hello.jsp

第18步 – 添加一些測試資料來自動組裝一些業務對象

我不會添加任何用于從資料庫中載入業務對象的代碼。然和,我們可以使用Spring的bean和應用程式上下文的支援來牽線到執行個體的引用。我隻要簡單地把握需要的資料作為bean之間的偶合條目寫入

springapp-servlet.xml

。我還要添加

messageSource

條目來引入消息資源綁定(“

messages.properties

”),在下一步我将建立它。

springapp/war/WEB-INF/springapp-servlet.xml

第19步 – 添加消息綁定以及給build.xml添加“clean”目标

我在

war/WEB-INF/classes

目錄中建立了一個“

messages.properties

”檔案。這個屬性綁定檔案目前有3個條目可以比對在

<fmt:message>

标記中指定的鍵。

springapp/war/WEB-INF/classes/messages.properties

由于我們移動了一些源代碼,是以給建構腳本中添加一個“

clean

”和一個“

undeploy

”目标。我把以下内容添加到build.xml檔案。

    <target name="clean" description="Clean output directories">      
        <delete>      
            <fileset dir="${build.dir}">      
                <include name="**/*.class"/>      
            </fileset>      
        </delete>      
    </target>      
    <target name="undeploy" description="Un-Deploy application">      
        <delete>      
            <fileset dir="${deploy.path}/${name}">      
                <include name="**/*.*"/>      
            </fileset>      
        </delete>      
    </target>      

現在停止Tomcat伺服器,運作

clean

undeploy

deploy

目标。這應該會删除所有的舊檔案,重新建構應用并部署它:

第3部分 -為應用程式添加單元測試和表單

第20步 – 為

SpringappController

添加單元測試

在我們建立單元測試之前,我們要先準備Ant并讓我們的建構腳本可以處理單元測試。Ant有一個内置的JUnit目标,但是我們需要把

junit.jar

放入Ant的lib目錄中。我使用了Spring分發包中自帶的

spring-framework-1.2/lib/junit/junit.jar

。隻要把它複制到你的Ant安裝目錄的lib目錄下即可。我還将以下目标添加到我們建構腳本中:

    <target name="junit" depends="build" description="Run JUnit Tests">      
        <junit printsummary="on"      
               fork="false"      
               haltonfailure="false"      
               failureproperty="tests.failed"      
               showoutput="true">      
            <classpath refid="master-classpath"/>      
            <formatter type="brief" usefile="false"/>      
            <batchtest>      
                <fileset dir="${build.dir}">      
                    <include name="**/Test*.*"/>      
                </fileset>      
            </batchtest>      
        </junit>      
       <fail if="tests.failed">      
        tests.failed=${tests.failed}      
        ***********************************************************      
        ***********************************************************      
        ****  One or more tests failed!  Check the output ...  ****      
        ***********************************************************      
        ***********************************************************      
        </fail>      
    </target>      

現在我在

src

目錄中添加了一個新的子目錄叫做

tests

。相信大家也都猜到了,這個目錄将包含所有的單元測試。

這些工作結束之後,我們準備開始寫我們的第一個單元測試。

SpringappController

要依賴于

HttpServletRequest

HttpServletResponse

以及我們的應用程式上下文。由于控制器并沒有使用請求和響應,我們直接傳送

null

。如果不是這樣,我們要使用EasyMock建立一些模仿對象mock object,這樣就可以在測試用使用了。使用某個類,可以在Web Server環境之外載入應用程式上下文。有好幾個類可以使用,針對目前的任務我們将使用

FileSystemXmlApplicationContext

springapp/src/tests/TestSpringappController.java

唯一的測試就是調用

handleRequest

,我們檢測從模型中傳回的産品。在

setUp

方法中,我們載入應用程式上下文,之前我已經複制到了

tests

中的

WEB-INF

目錄中。我建立了一個副本這樣這個檔案可以在測試中以“

messageSource

”所需的bean的最小集來運作。這樣,複制

springapp/war/WEB-INF/springapp-servlet.xml

springapp/src/tests/WEB-INF

目錄中。你可以删除“

messageSource

”、“

urlMapping

”和“

viewResolver

”bean條目,因為這個測試不需要他們。

springapp/src/tests/WEB-INF/springapp-servlet.xml

當你運作這個測試的時候,你應該看到載入應用程式上下文時有很多日志資訊。

第21步 – 為ProductManager添加單元測試和新的功能

接下來我為

ProductManager

添加一個測試案例,同時我打算給

ProductManager

添加一個用于增加價格的新方法,并添加一個測試。

springapp/src/tests/TestProductManager .java

對于這個測試,沒有必要建立一個應用程式上下文。我隻在

setUp

方法中建立了産品資訊并且把他們添加到了産品管理對象中。我還給

getProducts

increasePrice

添加了測試。

increasePrice

方法根據傳給它的百分比對價格進行增加。我修改了

ProductManager

類來實作這個新方法。

springapp/src/bus/ProductManager.java

下面我建構并運作這些測試。正如你所見,這些測試就像一般的測試一樣——業務類不依賴于任何servlet類,是以這些類測試起來很友善。

第22步 – 添加一個表單

為了在Web應用中提供了一個接口,我添加了一個可以讓使用者輸入百分比值的表單。這個表單使用了一個叫做“spring”的标簽庫,它是由Spring Framework所提供的。我們要從Spring的分發包中把

spring-framework-1.2/dist/spring.tld

複制到

springapp/war/WEB-INF

目錄中。現在我們還要給

web.xml

添加一個

<taglib>

條目。

springapp/war/WEB-INF/web.xml

我們還需要在jsp檔案的

page

指令中申明這個taglib。我們用普通的方法通過

<form>

标簽聲明一個表單,以及一個

<input>

文本域和一個送出按鈕。

springapp/war/WEB-INF/jsp/priceincrease.jsp

<spring:bind>

标記是用于将一個

<input>

表單元素綁定到一個指令對象

PriceIncrease.java

上的。這個指令對象以後會被傳送給效驗器,同時如果它通過了檢驗,它會被繼續傳送給控制器。

${status.errorMessage}

${status.value}

是由架構聲明的特殊變量,可以用來顯示錯誤資訊和目前域的值。

springapp/src/bus/PriceIncrease.java

這是一個十分簡單的JavaBean類,同時這裡有一個屬性以及他的擷取器和設定器。在使用者按下了送出按鈕之後,

Validator

類将擷取控制。在表單中輸入的值會被架構設定在指令對象上。然後會調用方法

validate

,并傳入指令對象和一個用來存放錯誤資訊的對象。

springapp/src/bus/PriceIncreaseValidator.java

現在我們要在

springapp-servlet.xml

檔案中添加一條内容來定義新的表單和控制器。我們定義指令對象和效驗器的屬性。我們還要指明兩個視圖,一個用來顯示表單,另一個将是在成功的表單處理之後我們将看到的。後一個也叫做成功視圖,可以是兩種類型之一:它可以是一個普通的視圖引用直接引導到我們某個JSP頁面。但這種方法的一個缺點是,如果使用者重新整理頁面,那麼表單的資料就會被重新送出,然後你可能最後就做了兩次

priceIncreace

。另一種方法是使用一個重定向,它将給使用者浏覽器傳回一個應答并且訓示浏覽器重定向到一個新的URL。我們這裡使用的這個URL不可以是我們的JSP頁面之一,因為他們對于直接通路是不可見的。必須一個從外部可以擷取的URL。是以我選擇了“

hello.htm

”來作為我的重定向URL。這個URL影射到“

hello.jsp

”頁面,這個應該運作得很令人滿意。

springapp/war/WEB-INF/springapp-servlet.xml

下面,讓我們看一下這個表單的控制器。

onSubmit

方法擷取了控制并且在它調用

ProductManager

對象的

increasePrice

方法之前進行了一些日志記錄。然後它使用

successView

的url建立了

RedirectView

的一個新的執行個體,并傳遞這個執行個體給

ModelAndView

,最後傳回這個

ModelAndView

的執行個體。

springapp/src/web/PriceIncreaseFormController.java

我們還要在

message.properties

資源檔案裡面添加一些消息。

springapp/war/WEB-INF/classes/messages.properties
priceincrease.heading=Price Increase :: SpringApp      
error.not-specified=Percentage not specified!!!      
error.too-low=You have to specify a percentage higher than {0}!      
error.too-high=Don't be greedy - you can't raise prices by more than {0}%!      
required=Entry required.      
typeMismatch=Invalid data.      
typeMismatch.percentage=That is not a number!!!       

最後,我們要從

hello.jsp

中提供一個指向

priceincrease

頁面的連結。

springapp/war/WEB-INF/jsp/hello.jsp

編譯并部署,在重新載入應用之後,我們就可以再測試它了。下面是表單出錯時所顯示的樣子:

Back

第4部分 - 實作資料庫持久

在第一部分(第1 – 12步)中,我們配置了開發環境并建立了一個基本的應用程式,并以此開始。第二部分(第13 – 19步)在某些方面改進了應用程式。第三部分(第20 – 22步)為應用程式添加了一些單元測試并且我們還添加一個表單用來進行價格增加。在第四部分中,我們要處理資料庫持久的問題了。我們在前幾部分中看到了我們如何使用在配置檔案中bean定義來載入一些業務對象。很顯然在實際生活中是這個實行不同的——一旦我們重新開機了伺服器,我們就又回到了原來的價格。我們需要添加可以實際在資料中持久這些更改的代碼。

第23步 – 添加Ant任務來建立和載入測試資料

在我們開始開發持久部分的代碼之前,我們應該先建立我們開發和測試所需的資料庫表。我們當然還需要一個資料庫。我打算使用HSQL,這是一個很優秀的用Java寫的開源資料庫。這個資料庫和Spring一起分發的,是以我們隻要把jar檔案複制到web應用的庫目錄中就可以了。複制

spring-framework-1.2/lib/hsqldb/hsqldb.jar

springapp/war/WEB-INF/lib/

中。我想讓HSQL以獨立的模式運作。這樣我們就不用擔心每次啟動一個單獨資料庫伺服器了。用來連接配接到HSQL的URL可以控制資料庫運作的模式。為了能夠運作一些針對資料庫的Ant任務,我們還要在

build.properties

檔案中添加一些資料庫的屬性。

springapp/build.properties
db.driver=org.hsqldb.jdbcDriver      
db.url=jdbc:hsqldb:db/test      
db.user=sa      
db.pw=      

下面我要為建構腳本添加我需要的目标。有建立和删除表格以及載入和删除資料庫目标。Next I add the targets I need to the build script. There are targets to create and delete tables and to load and delete test data.

    <target name="createTables">      
        <echo message="CREATE TABLES USING: ${db.driver} ${db.url}"/>      
        <sql driver="${db.driver}"      
             url="${db.url}"      
             userid="${db.user}"      
             password="${db.pw}"      
             οnerrοr="continue">        
            <classpath refid="master-classpath"/>      
        CREATE TABLE products (      
          id INTEGER NOT NULL PRIMARY KEY,      
          description varchar(255),      
          price decimal(15,2)      
        );      
        CREATE INDEX products_description ON products(description);      
        </sql>       
    </target>      
    <target name="dropTables">      
        <echo message="DROP TABLES USING: ${db.driver} ${db.url}"/>      
        <sql driver="${db.driver}"      
             url="${db.url}"      
             userid="${db.user}"      
             password="${db.pw}"      
             οnerrοr="continue">        
            <classpath refid="master-classpath"/>      
        DROP TABLE products;      
        </sql>       
    </target>      
    <target name="loadData">      
        <echo message="LOAD DATA USING: ${db.driver} ${db.url}"/>      
        <sql driver="${db.driver}"      
             url="${db.url}"      
             userid="${db.user}"      
             password="${db.pw}"      
             οnerrοr="continue">        
            <classpath refid="master-classpath"/>      
        INSERT INTO products (id, description, price) values(1, 'Lamp', 5.78);      
        INSERT INTO products (id, description, price) values(2, 'Table', 75.29);      
        INSERT INTO products (id, description, price) values(3, 'Chair', 22.81);      
        COMMIT;      
        SHUTDOWN      
        </sql>       
    </target>      
    <target name="printData">      
        <echo message="PRINT DATA USING: ${db.driver} ${db.url}"/>      
        <sql driver="${db.driver}"      
             url="${db.url}"      
             userid="${db.user}"      
             password="${db.pw}"      
             οnerrοr="continue"      
             print="true">        
            <classpath refid="master-classpath"/>      
        SELECT * FROM products;      
        </sql>       
    </target>      
    <target name="clearData">      
        <echo message="CLEAR DATA USING: ${db.driver} ${db.url}"/>      
        <sql driver="${db.driver}"      
             url="${db.url}"      
             userid="${db.user}"      
             password="${db.pw}"      
             οnerrοr="continue">        
            <classpath refid="master-classpath"/>      
        DELETE FROM products;      
        </sql>       
    </target>      

現在我将執行其中一些任務來建立測試資料庫。這會在springapp目錄下建立一個db目錄。運作“

ant createTables loadData pringData

”——我把我的輸出列在下面了。

[[email protected] springapp]$ ant createTables loadData printData      
Buildfile: build.xml      
createTables:      
     [echo] CREATE TABLES USING: org.hsqldb.jdbcDriver jdbc:hsqldb:db/test      
      [sql] Executing commands      
      [sql] 2 of 2 SQL statements executed successfully      
loadData:      
     [echo] LOAD DATA USING: org.hsqldb.jdbcDriver jdbc:hsqldb:db/test      
      [sql] Executing commands      
      [sql] 3 of 3 SQL statements executed successfully      
printData:      
     [echo] PRINT DATA USING: org.hsqldb.jdbcDriver jdbc:hsqldb:db/test      
      [sql] Executing commands      
      [sql] ID,DESCRIPTION,PRICE      
      [sql] 1,Lamp,5.78      
      [sql] 2,Table,75.29      
      [sql] 3,Chair,22.81      
      [sql] 1 of 1 SQL statements executed successfully      
BUILD SUCCESSFUL      
Total time: 2 seconds      

第24步 – 為JDBC建立一個資料通路對象(DAO)的實作

我先建立一個“

springapp/src/db

”目錄來存放我用來進行資料庫通路的類。在這個目錄中我建立了一個叫做“

ProductManagerDao.java

”的接口。這是定義了DAO實作類需要提供的功能的接口——我們可以有多種實作。

springapp/src/db/ProductManagerDao.java

我們按照它建立一個叫做“

ProductManagerDaoJdbc.java

”的類,它是一個上面接口的JDBC的實作。Spring提供了一個JDBC抽象架構,我們可以利用它。直接使用JDBC和使用Spring的JDBC架構之間最大的差別是,你不必關心打開和關閉連接配接或者是任何語句。Spring的架構會幫你進行處理。另一個好處是你不用捕獲任何異常,除非你需要。Spring會把所有

SQLException

包裝在從

DataAccessException

中繼承的不檢測的異常層次中。如果需要,你也可以捕獲這個異常,但由于大多數資料庫異常是無法恢複的,是以你可能會就讓異常傳到更高的層次中去。

springapp/src/db/ProductManagerDaoJdbc.java

讓我們回顧一下這個類中的兩個DAO方法。首先,

getProductList()

建立一個擷取所有産品資訊的查詢對象。執行了查詢後,結果會作為

Products

的一個清單傳回。在類的末尾我們可以看到這個查詢類的定義。我把它作為一個内部類實作了。這個查詢類擴充了

MappingSqlQuery

,這是Spring JDBC架構的一個核心類。這個類的用法是你必須在建立類的時候指定一個SQL查詢,然後你也要負責實作

mapRow

方法來把每一行的資料映射到代表查詢中所取得的實體的類中。好了,這就是你必須要提供的東西。剩下的東西就交給架構去管吧。在

ProductQuery

類的構造器中,我傳入了資料源。這個資料源将通過一個與我們在第二部分中引入業務對象的方法類似的形式給出,是以我們無須擔心如何在DAO類中擷取一個資料源。在構造器中我同樣定義了我們要用來擷取産品的SQL查詢。它會建立一個新的

Product

對象并根據從資料集的目前行擷取的資料組裝它。你不許要對資料集調用

getNext

方法,這些都由架構來處理。這也是控制反轉(IoC)方法的另一個例子。

第二個方法

increasePrice

要利用一個

SqlUpdate

類。同時把資料源和一個帶有占位符的SQL更新語句作為參數傳給這個類。SQL語句的文法和JDBC中的預處理語句的文法是一樣的。事實上,

SqlUpdate

在幕後就會建立這個預處理語句。對于SQL語句中的參數,我們要給他們名稱并且申明他們的類型這樣架構才能在執行預處理語句之前正确設定這些參數。在聲明了所有的參數之後,我們按照一個JDO的形式對語句進行“編譯”。這會通知我們已經完成參數聲明,然後架構會檢查并確定我們對SQL語句中的每個占位符都有一個比對的聲明。下面我們聲明一個

Object

數組用來存放參數值,并把它們傳入預處理語句中。這個數組将被傳入

SqlUpdate

update

方法中。

update

方法則會傳回受影響的行數。

我需要把每一個産品的主鍵的值存儲在

Product

類中。當我持久化這個對象的更改到資料庫的時候,就會用到這個鍵。為了存放這個鍵,我在

Product.java

中添加了一個叫做“id”的私有字段,并且加入了設定器和擷取器。

springapp/src/bus/Product.java

現在可以測試整個DAO裝置了。事實上,我們也許應該首先寫這些測試,但是由于這是一個教程風格的文檔,是以我想在測試之前先介紹實際的代碼會更有意義。我決定使用一個現場的資料庫進行測試,是以我要再

build.xml

中給

junit

任務添加對

clearData

loadData

的依賴。這将確定我們的測試都會有一個一緻的起點。

下面,我給單元測試集合添加了一個

TestProductManagerDaoJdbc.java

。在啟動部分我建立了一個用于測試的資料源。

springapp/src/test/TestProductManagerDaoJdbc.java

一旦我們通過了單元測試,我們就可以繼續并修改Web應用來使用這些信的資料庫持久能力。

第25步 – 修改Web應用來使用資料庫持久

如果我們合理地建構了我們的應用程式,我們應該隻需要更改業務類就可以立用資料庫持久。視圖和控制器類不應該被修改,因為他們應該部不知道任何業務類的實作細節。是以讓我們把持久添加到

ProductManager

類中就可以了。我添加了一個

ProductManagerDao

接口的引用以及這個引用的設定器方法。我們實際上使用哪個實作對于

ProductManager

類是不相關的,同時我們将通過配置選項對他進行設定。

springapp/src/bus/ProductManager.java

我們不再依賴我們在記憶體中種産品清單。每當

getProducts

被調用的時候,我們都會去查詢一下資料庫。

increasePrice

方法現在代理調用DAO來增加價格,而新的價格會在下一次我們調用

getProducts

方法的時候反映出來。

下面我們要為Web應用修改配置檔案——

springapp-servlet.xml

springapp/war/WEB-INF/springapp-servlet.xml

我删除了原來傳給

ProductManager

的産品的集合。我把它們替換成了一個

DataSource

和一個

ProductManagerDaoJdbc

實作。指定給

datasource

的URL包含了資料庫位置的完整路徑(

/Users/trisberg/projects/springapp/db/test

)。當然你要把它調整為符合你情況的設定。

現在我們可以建構并部署修改過的應用了,運作“

ant undeploy deploy

”來清除所有的舊類并把它們替換成新的。然後啟動Tomcat并載入應用。你可以看到的差別是小數被限制為2位,這是因為這個列是這樣在資料庫中被聲明的。同時,你應該看到了價格的增加都會保持有效即時在你的應用程式周期結束之後。

第26步 – 修複損壞的測試

我們完全更改了

ProductManager

的實作——我們把所有功能推倒

ProductManagerDao

實作中,這樣原來的測試都失敗了。要修複這個問題,我将建立一個

ProductManagerDao

的模仿實作。這個實作将基本模拟原來我們在

ProductManager

中的功能。

springapp/src/tests/MockProductManagerDaoImpl.java

現在我們要配置“

TestSpringappController

”和“

TestProductManager

”測試來使用這個模仿實作。對于“

TestSpringappController

”我将修改我複制到

tests/WEB-INF

目錄下的“

springapp-servlet.xml

”檔案(我知道對這個檔案作一個副本是個很好的主意)。主意不要修改

war/WEB-INF

中的那個檔案——我們需要它保持原樣這樣我們可以在Tomcat中運作應用程式。我們把産品清單從

ProductManager

移到

ProductManagerDao

并且給

ProductManager

一個指向這個DAO的引用。

springapp/src/tests/WEB-INF/springapp-servlet.xml

對于

TestProductManager

測試案例我們将對

setUp

方法做一個類似的修改。

springapp/src/tests/TestProductManager .java

繼續閱讀