JAVA半桶水

          BlogJava 首頁 新隨筆 聯系 聚合 管理
            4 Posts :: 3 Stories :: 2 Comments :: 0 Trackbacks

          一步一步開發

          Spring Framework MVC

          應用程序

          作者:Thomas Risberg

          20037月(20054月修訂)

          翻譯 Shining Ray @ Nirvana Studio

          這是一個關于如何使用Spring Framework從無到有開發一個Web應用的逐步的指南。本文分成幾個部分。你可以按順序閱讀或者根據你對他們的熟悉程度,跳過某些章節。



           

          目錄

          第1部分 - 設置基本應用程序和環境

          第1 - 開發目錄

          第2 – index.jsp

          第3將應用程序部署到Tomcat

          第4測試應用

          第5下載Spring分發包

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

          第7 - jar文件復制到WEB-INF/lib

          第8 - 創建你的控制器

          第9 - 構建應用程序

          第10復制并修改log4j.properties

          第11部署應用程序

          第12 - 創建一個視圖

          總結

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

          第13改進index.jsp

          第14改進視圖和控制器

          第15解耦視圖和控制器

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

          第 17 修改視圖用于現實業務數據并且添加消息綁定的支持

          第18添加一些測試數據來自動組裝一些業務對象

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

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

          第20SpringappController添加單元測試

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

          第22添加一個表單

          第4部分 - 實現數據庫持久

          第23添加Ant任務來創建和載入測試數據

          第24JDBC創建一個數據訪問對象(DAO)的實現

          第25修改Web應用來使用數據庫持久

          第26修復損壞的測試


           

          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

          <html>
          <head><title>Example :: Spring Application</title></head>
          <body>
          <h1>Example - Spring Application</h1>
          <p>This is my test.</p>
          </body>
          </html>

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

          springapp/war/WEB-INF/web.xml

          <?xml version="1.0" encoding="UTF-8"?>
          <!DOCTYPE web-app PUBLIC '-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN' 'http://java.sun.com/dtd/web-app_2_3.dtd'>
          <web-app>
          </web-app>

          3步 – 將應用程序部署到Tomcat

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

          springapp/build.xml

          <?xml version="1.0"?>
          <project name="springapp" basedir="." default="usage">
              <property file="build.properties"/>
              <property name="src.dir" value="src"/>
              <property name="web.dir" value="war"/>
              <property name="build.dir" value="${web.dir}/WEB-INF/classes"/>
              <property name="name" value="springapp"/>
              <path id="master-classpath">
                  <fileset dir="${web.dir}/WEB-INF/lib">
                      <include name="*.jar"/>
                  </fileset>
                  <!-- We need the servlet API classes:        -->
                  <!--   for Tomcat 4.1 use servlet.jar        -->
                  <!--   for Tomcat 5.0 use servlet-api.jar    -->
                  <!--   for Other app server - check the docs -->
                  <fileset dir="${appserver.home}/common/lib">
                      <include name="servlet*.jar"/>
                  </fileset>
                  <pathelement path="${build.dir}"/>
              </path>
              <target name="usage">
                  <echo message=""/>
                  <echo message="${name} build file"/>
                  <echo message="-----------------------------------"/>
                  <echo message=""/>
                  <echo message="Available targets are:"/>
                  <echo message=""/>
                  <echo message="build     --> Build the application"/>
                  <echo message="deploy    --> Deploy application as directory"/>
                  <echo message="deploywar --> Deploy application as a WAR file"/>
                  <echo message="install   --> Install application in Tomcat"/>
                  <echo message="reload    --> Reload application in Tomcat"/>
                  <echo message="start     --> Start Tomcat application"/>
                  <echo message="stop      --> Stop Tomcat application"/>
                  <echo message="list      --> List Tomcat applications"/>
                  <echo message=""/>
              </target>
              <target name="build" description="Compile main source tree java files">
                  <mkdir dir="${build.dir}"/>
                  <javac destdir="${build.dir}" target="1.3" debug="true"
                         deprecation="false" optimize="false" failonerror="true">
                      <src path="${src.dir}"/>
                      <classpath refid="master-classpath"/>
                  </javac>
              </target>
              <target name="deploy" depends="build" description="Deploy application">
                  <copy todir="${deploy.path}/${name}" preservelastmodified="true">
                      <fileset dir="${web.dir}">
                          <include name="**/*.*"/>
                      </fileset>
                  </copy>
              </target>
              <target name="deploywar" depends="build" description="Deploy application as a WAR file">
                  <war destfile="${name}.war"
                       webxml="${web.dir}/WEB-INF/web.xml">
                      <fileset dir="${web.dir}">
                          <include name="**/*.*"/>
                      </fileset>
                  </war>
                  <copy todir="${deploy.path}" preservelastmodified="true">
                      <fileset dir=".">
                          <include name="*.war"/>
                      </fileset>
                  </copy>
              </target>
          <!-- ============================================================== -->
          <!-- Tomcat tasks - remove these if you don't have Tomcat installed -->
          <!-- ============================================================== -->
              <taskdef name="install" classname="org.apache.catalina.ant.InstallTask">
                  <classpath>
                      <path location="${appserver.home}/server/lib/catalina-ant.jar"/>
                  </classpath>
              </taskdef>
              <taskdef name="reload" classname="org.apache.catalina.ant.ReloadTask">
                  <classpath>
                      <path location="${appserver.home}/server/lib/catalina-ant.jar"/>
                  </classpath>
              </taskdef>
              <taskdef name="list" classname="org.apache.catalina.ant.ListTask">
                  <classpath>
                      <path location="${appserver.home}/server/lib/catalina-ant.jar"/>
                  </classpath>
              </taskdef>
              <taskdef name="start" classname="org.apache.catalina.ant.StartTask">
                  <classpath>
                      <path location="${appserver.home}/server/lib/catalina-ant.jar"/>
                  </classpath>
              </taskdef>
              <taskdef name="stop" classname="org.apache.catalina.ant.StopTask">
                  <classpath>
                      <path location="${appserver.home}/server/lib/catalina-ant.jar"/>
                  </classpath>
              </taskdef>
              <target name="install" description="Install application in Tomcat">
                  <install url="${tomcat.manager.url}"
                           username="${tomcat.manager.username}"
                           password="${tomcat.manager.password}"
                           path="/${name}"
                           war="${name}"/>
              </target>
              <target name="reload" description="Reload application in Tomcat">
                  <reload url="${tomcat.manager.url}"
                           username="${tomcat.manager.username}"
                           password="${tomcat.manager.password}"
                           path="/${name}"/>
              </target>
              <target name="start" description="Start Tomcat application">
                  <start url="${tomcat.manager.url}"
                           username="${tomcat.manager.username}"
                           password="${tomcat.manager.password}"
                           path="/${name}"/>
              </target>
              <target name="stop" description="Stop Tomcat application">
                  <stop url="${tomcat.manager.url}"
                           username="${tomcat.manager.username}"
                           password="${tomcat.manager.password}"
                           path="/${name}"/>
              </target>
              <target name="list" description="List Tomcat applications">
                  <list url="${tomcat.manager.url}"
                           username="${tomcat.manager.username}"
                           password="${tomcat.manager.password}"/>
              </target>
          <!-- End Tomcat tasks -->
          </project>

          這個腳本現在包含了所有我們需要的目標,以便使我們開發更加容易。這里我不會詳細解釋這個腳本,因為大部分內容都是比較標準AntTomcat的東西。你可以直接復制上面的構建文件并且把它放在你的開發目錄的根目錄中。我們還需要一個build.properties文件,你需要自定這個文件來配合你的服務器安裝。這個文件和build.xml文件在同一個目錄中。

          springapp/build.properties

          # Ant properties for building the springapp
          appserver.home=${user.home}/jakarta-tomcat-5.0.28
          deploy.path=${appserver.home}/webapps
          tomcat.manager.url=http://localhost:8080/manager
          tomcat.manager.username=admin
          tomcat.manager.password=tomcat

          如果你是在一個你不是Tomcat安裝的所有者的系統中,那么Tomcat所有者必須給你訪問webapps目錄的全部權限,或者他可以在webapps目錄下面新建一個“springapp”目錄,并且給你全部權限來把程序部署到這個新建的目錄中。在Linux上我運行chmod a+rwx springapp來給與所有人對目錄的訪問權利。

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

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

          [trisberg@localhost 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”作為目標。

          [trisberg@localhost 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是否已經載入了新的應用程序。

          [trisberg@localhost 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中。

          [trisberg@localhost 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

          <?xml version="1.0" encoding="UTF-8"?>
          <!DOCTYPE web-app PUBLIC '-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN' 'http://java.sun.com/dtd/web-app_2_3.dtd'>
          <web-app>
            <servlet>
              <servlet-name>springapp</servlet-name>
              <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
              <load-on-startup>1</load-on-startup>
            </servlet>
           
            <servlet-mapping>
              <servlet-name>springapp</servlet-name>
              <url-pattern>*.htm</url-pattern>
            </servlet-mapping>
           
            <welcome-file-list>
              <welcome-file>
                index.jsp
              </welcome-file>
            </welcome-file-list>
          </web-app>

          下面,在springapp/war/WEB-INF目錄下創建一個叫做“springapp-servlet.xml的文件(你可以直接從Spring分發包中復制一個范例文件,位于sample/skeletons/webapp-minimal目錄中)。DispatcherServlet所使用的定義就要放在這個文件中。文件名是web.xml中的servlet-name并加上“-servlet”后綴。這是Spring Framework所使用的標準命名約定。現在,添加一個叫做springappControllerbean條目并創建一個SpringappController類。這里將定義我們的應用程序所使用的控制器。我們還要添加一個URL映射 urlMapping這樣DispatcherServlet就會知道對于不同的URL應該調用哪個控制器。

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

          <?xml version="1.0" encoding="UTF-8"?>
          <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
          <!--
            - Application context definition for "springapp" DispatcherServlet.
            -->
          <beans>
              <bean id="springappController" class="SpringappController"/>
              <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
                  <property name="mappings">
                      <props>
                          <prop key="/hello.htm">springappController</prop>
                      </props>
                  </property>
              </bean>
          </beans>

          第7步 - 把jar文件復制到WEB-INF/lib

          首先在“war/WEB-INF目錄中創建一個“lib”目錄。然后,從Spring分發包中,將spring.jar(spring-framework-1.2/dist/spring.jar)復制到新建的war/WEB-INF/lib目錄中。同時把commons-loggingjar文件(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

          import org.springframework.web.servlet.mvc.Controller;
          import org.springframework.web.servlet.ModelAndView;
          import javax.servlet.ServletException;
          import javax.servlet.http.HttpServletRequest;
          import javax.servlet.http.HttpServletResponse;
          import java.io.IOException;
          public class SpringappController implements Controller {
              public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
                      throws ServletException, IOException {
                  return new ModelAndView("");
              }
          }

          這是非常基本的控制器。我們稍后會對他進行擴充,同時過會兒我們還要擴展一些已經提供的抽象的基本實現。這個控制器處理請求并返回一個ModelAndView。不過我們還沒有定義任何視圖,所以現在沒什么可做的了。

          第9步 - 構建應用程序

          運行build.xml中的“build任務。基本上代碼應該順利通過編譯。

          [trisberg@localhost 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

          # For JBoss: Avoid to setup Log4J outside $JBOSS_HOME/server/default/deploy/log4j.xml!
          # For all other servers: Comment out the Log4J listener in web.xml to activate Log4J.
          log4j.rootLogger=INFO, stdout, logfile
          log4j.appender.stdout=org.apache.log4j.ConsoleAppender
          log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
          log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
          log4j.appender.logfile=org.apache.log4j.RollingFileAppender
          log4j.appender.logfile.File=/Users/trisberg/jakarta-tomcat-5.0.28/logs/springapp.log
          log4j.appender.logfile.MaxFileSize=512KB
          # Keep three backup files.
          log4j.appender.logfile.MaxBackupIndex=3
          # Pattern to output: date priority [category] - message
          log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
          log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n

          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 [org.springframework.context.support.DelegatingMessageSource@8dacb]
          2005-04-24 14:58:18,539 INFO [org.springframework.web.context.support.XmlWebApplicationContext] - Unable to locate ApplicationEventMulticaster with name 'applicationEventMulticaster': using default [org.springframework.context.event.SimpleApplicationEventMulticaster@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 [org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver@318309]
          2005-04-24 14:58:18,675 INFO [org.springframework.web.servlet.DispatcherServlet] - Unable to locate ThemeResolver with name 'themeResolver': using default [org.springframework.web.servlet.theme.FixedThemeResolver@c11e94]
          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

          <html>
          <head><title>Example :: Spring Application</title></head>
          <body>
          <h1>Hello - Spring Application</h1>
          <p>Greetings.</p>
          </body>
          </html>

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

          springapp/src/SpringappController.java

          import org.springframework.web.servlet.mvc.Controller;
          import org.springframework.web.servlet.ModelAndView;
          import javax.servlet.ServletException;
          import javax.servlet.http.HttpServletRequest;
          import javax.servlet.http.HttpServletResponse;
          import java.io.IOException;
          import org.apache.commons.logging.Log;
          import org.apache.commons.logging.LogFactory;
           
          public class SpringappController implements Controller {
           
              /** Logger for this class and subclasses */
              protected final Log logger = LogFactory.getLog(getClass());
           
              public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
                      throws ServletException, IOException {
           
                  logger.info("SpringappController - returning hello view");
           
                  return new ModelAndView("hello.jsp");
              }
          }

          當我在修改這個類的同時,我還添加了一個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.jarspringapp/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

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

          現在我們可以更改index.jsp來使用,由于我們使用了JSTL,我們可以使用<c:redirect>標簽來轉向到我們的控制器。

          springapp/war/index.jsp

          <%@ include file="/WEB-INF/jsp/include.jsp" %>
           
          <%-- Redirected because we can't set the welcome page to a virtual URL. --%>
          <c:redirect url="/hello.htm"/>

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

          現在我要把hello.jsp視圖移入WEB-INF/jsp 目錄。Index.jsp里面添加的包含文件include.jsp同樣也添加到了hello.jsp中。我也使用JSTL<c:out>標簽來輸出從傳給視圖的模型里獲取的當前的日期和時間。

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

          <%@ include file="/WEB-INF/jsp/include.jsp" %>
           
          <html>
          <head><title>Hello :: Spring Application</title></head>
          <body>
          <h1>Hello - Spring Application</h1>
          <p>Greetings, it is now <c:out value="${now}"/>
          </p>
          </body>
          </html>

          對于SpringappController.java,我們還要做一些更改。由于我們把文件移動到了一個新的位置,所以需要把視圖變成WEB-INF/jsp/hello.jsp。同時添加一個包含當前時間和日期的字符串作為模型。

          springapp/src/SpringappController.java

          import org.springframework.web.servlet.mvc.Controller;
          import org.springframework.web.servlet.ModelAndView;
           
           
          import javax.servlet.ServletException;
          import javax.servlet.http.HttpServletRequest;
          import javax.servlet.http.HttpServletResponse;
           
          import java.io.IOException;
           
          import org.apache.commons.logging.Log;
          import org.apache.commons.logging.LogFactory;
           
          public class SpringappController implements Controller {
           
              /** Logger for this class and subclasses */
           
              protected final Log logger = LogFactory.getLog(getClass());
           
              public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
                      throws ServletException, IOException {
           
                    String now = (new java.util.Date()).toString(); 
                  logger.info("returning hello view with " + now);
           
                  return new ModelAndView("WEB-INF/jsp/hello.jsp", "now", now);
              }
          }

          在我們構建并部署了新的代碼之后,現在我們準備嘗試它了。我們在瀏覽器中輸入http://localhost:8080/springapp,它首先會調用index.jsp,然后它又會重定向到hello.htm,這個URL又會調用控制器并把時間和日期發送給視圖。

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

          現在控制器是指明了視圖的完整路徑,這在控制器和視圖之間產生了一個多余的依賴關系。理想上來說,我們要使用一個邏輯名稱來映射到視圖,這可以讓我們無需更改控制器就可以切換視圖。你可以在一個屬性文件中設置這個映射,如果你喜歡使用ResourceBundleViewResolverSimpleUrlHandlerMapping類的話。如果你的映射需求確實很簡單,那么在InternalResourceViewResolver上加上前綴和后綴會很方便。后一種方法就是我現在要實現的。我修改了springapp-servlet.xml并包含了viewResolver條目。我選擇使用JstlView,它可以讓我們使用JSTL,可以結合消息資源綁定,同時他還可以支持國際化。

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

          <?xml version="1.0" encoding="UTF-8"?>
          <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
           
          <!--
            - Application context definition for "springapp" DispatcherServlet.
            -->
           
          <beans>
              <bean id="springappController" class="SpringappController"/>
              <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
           
                  <property name="mappings">
                      <props>
                          <prop key="/hello.htm">springappController</prop>
                      </props>
                  </property>
              </bean>
           
                <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
                  <property name="viewClass">
          <value>org.springframework.web.servlet.view.JstlView</value>
          </property>
                  <property name="prefix"><value>/WEB-INF/jsp/</value></property>
                  <property name="suffix"><value>.jsp</value></property>
              </bean>
          </beans>        

          所以現在我可以從控制器的視圖名稱中刪除前綴和后綴了。

          springapp/src/SpringappController.java

          import org.springframework.web.servlet.mvc.Controller;
          import org.springframework.web.servlet.ModelAndView;
           
          import javax.servlet.ServletException;
          import javax.servlet.http.HttpServletRequest;
          import javax.servlet.http.HttpServletResponse;
           
          import java.io.IOException;
           
          import org.apache.commons.logging.Log;
          import org.apache.commons.logging.LogFactory;
           
           
          public class SpringappController implements Controller {
           
              /** Logger for this class and subclasses */
           
              protected final Log logger = LogFactory.getLog(getClass());
           
              public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
           
                      throws ServletException, IOException {
           
           
                  String now = (new java.util.Date()).toString();
                  logger.info("returning hello view with " + now);
           
                  return new ModelAndView("hello", "now", now);
              }
          }

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

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

          目前位置我們的應用還不是很有用。我想添加一些業務邏輯,一個Product類和一個管理所有產品的類。我把管理類命名為ProductManager。為了能分離依賴Web的邏輯和業務邏輯,我將在Java源代碼重創建兩個單獨的包——web和bus。如果這個應用程序是為一個真實的公司開發的,我可能會把包命名成像com.mycompany.webcom.mycompany.bus之類的名字,不過這只是一個演示而已我就讓包的名稱簡短一些。Product類是實現為一個JavaBean——它有一個默認的構造器(如果我們沒有指明任何構造器,會自動給出),兩個實例變量descriptionprice的獲取器(getter)和設制器(setter)。我還把它設為Serializable,這對我們的應用不是必需的,不過以后我們很可能要把這個類在不同的應用層中傳遞的時候,那時就可以直接使用了。

          springapp/src/bus/Product.java

          package bus;
           
          import java.io.Serializable;
           
          public class Product implements Serializable {
           
              private String description;
              private Double price;
           
              public void setDescription(String s) {
                  description = s;
              }
           
              public String getDescription() {
                  return description;
              }
           
              public void setPrice(Double d) {
                  price = d;
              }
           
              public Double getPrice() {
                  return price;
              }
          }

          ProductManager中有一個Product的列表List,同樣的,這個類也是實現為一個JavaBean。

          springapp/src/bus/ProductManager.java

          package bus;
           
          import java.io.Serializable;
          import java.util.List;
           
          public class ProductManager implements Serializable {
              private List products;
           
              public void setProducts(List p) {
                  products = p;
              }
           
              public List getProducts() {
                  return products;
              }
          }

          下面,我修改了SpringappController來存放一個指向ProductManager類的引用。正如你所見,它現在在一個單獨的web的包中——記得把代碼放到這個新位置中。我還要添加讓控制器將產品信息傳送到視圖的代碼。getModelAndView現在返回一個Map,同時包含了時間日期和產品管理的引用。

          springapp/src/web/SpringappController.java

          package web;
           
          import org.springframework.web.servlet.mvc.Controller;
          import org.springframework.web.servlet.ModelAndView;
           
          import javax.servlet.ServletException;
          import javax.servlet.http.HttpServletRequest;
          import javax.servlet.http.HttpServletResponse;
           
          import java.io.IOException;
           
          import java.util.Map;
          import java.util.HashMap;
           
          import org.apache.commons.logging.Log;
          import org.apache.commons.logging.LogFactory;
           
          import bus.Product;
          import bus.ProductManager;
           
          public class SpringappController implements Controller {
           
              /** Logger for this class and subclasses */
              protected final Log logger = LogFactory.getLog(getClass());
           
              private ProductManager prodMan;
           
              public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
                      throws ServletException, IOException {
           
                  String now = (new java.util.Date()).toString();
                  logger.info("returning hello view with " + now);
           
                  Map myModel = new HashMap();
                  myModel.put("now", now);
                  myModel.put("products", getProductManager().getProducts());
           
                  return new ModelAndView("hello", "model", myModel);
              }
           
              public void setProductManager(ProductManager pm) {
                  prodMan = pm;
              }
           
              public ProductManager getProductManager() {
                  return prodMan;
              }
          }

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

          我使用了JSTL<c:forEach>標簽來添加了一個現實產品信息的部分。我還用JSTL<fmt:message>標記替換了標題和歡迎文本,這樣可以從給定的“message”源中讀取文本并顯示——在后面的步驟中我會顯示這個方法。

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

          <%@ include file="/WEB-INF/jsp/include.jsp" %>
           
           
          <html>
          <head><title><fmt:message key="title"/></title></head>
          <body>
          <h1><fmt:message key="heading"/></h1>
          <p><fmt:message key="greeting"/> <c:out value="${model.now}"/>
          </p>
           
          <h3>Products</h3>
          <c:forEach items="${model.products}" var="prod">
            <c:out value="${prod.description}"/> <i>$<c:out value="${prod.price}"/></i><br><br>
          </c:forEach>
          </body>
          </html>

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

          我不會添加任何用于從數據庫中載入業務對象的代碼。然和,我們可以使用Spring的bean和應用程序上下文的支持來牽線到實例的引用。我只要簡單地把握需要的數據作為bean之間的偶合條目寫入springapp-servlet.xml。我還要添加messageSource條目來引入消息資源綁定(“messages.properties”),在下一步我將創建它。

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

          <?xml version="1.0" encoding="UTF-8"?>
          <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
           
          <!--
            - Application context definition for "springapp" DispatcherServlet.
            -->
           
           
          <beans>
              <bean id="springappController" class="web.SpringappController">
                  <property name="productManager">
                      <ref bean="prodMan"/>
                  </property>
              </bean>
           
              <bean id="prodMan" class="bus.ProductManager">
                  <property name="products">
                      <list>
                          <ref bean="product1"/>
                          <ref bean="product2"/>
                          <ref bean="product3"/>
                      </list>
                  </property>
              </bean>
           
              <bean id="product1" class="bus.Product">
                  <property name="description"><value>Lamp</value></property>
                  <property name="price"><value>5.75</value></property>
              </bean>
                  
              <bean id="product2" class="bus.Product">
                  <property name="description"><value>Table</value></property>
                  <property name="price"><value>75.25</value></property>
              </bean>
           
              <bean id="product3" class="bus.Product">
                  <property name="description"><value>Chair</value></property>
                  <property name="price"><value>22.79</value></property>
              </bean>
           
              <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
                  <property name="basename"><value>messages</value></property>
              </bean>
           
              <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
                  <property name="mappings">
                      <props>
                          <prop key="/hello.htm">springappController</prop>
                      </props>
                  </property>
              </bean>
           
              <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
                  <property name="viewClass">
          <value>org.springframework.web.servlet.view.JstlView</value>
          </property>
                  <property name="prefix"><value>/WEB-INF/jsp/</value></property>
                  <property name="suffix"><value>.jsp</value></property>
              </bean>
          </beans>        

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

          我在war/WEB-INF/classes目錄中創建了一個“messages.properties”文件。這個屬性綁定文件目前有3個條目可以匹配在<fmt:message>標記中指定的鍵。

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

          title=SpringApp
           
          heading=Hello :: SpringApp
           
          greeting=Greetings, it is now

          由于我們移動了一些源代碼,所以給構建腳本中添加一個“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服務器,運行cleanundeploydeploy目標。這應該會刪除所有的舊文件,重新構建應用并部署它:


           

          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要依賴于HttpServletRequestHttpServletResponse以及我們的應用程序上下文。由于控制器并沒有使用請求和響應,我們直接傳送null。如果不是這樣,我們要使用EasyMock創建一些模仿對象mock object,這樣就可以在測試用使用了。使用某個類,可以在Web Server環境之外載入應用程序上下文。有好幾個類可以使用,針對當前的任務我們將使用FileSystemXmlApplicationContext

          springapp/src/tests/TestSpringappController.java

          package tests;
           
          import java.util.Map;
          import java.util.List;
          import java.io.IOException;
          import javax.servlet.http.HttpServletRequest;
          import javax.servlet.http.HttpServletResponse;
          import javax.servlet.ServletException;
          import junit.framework.TestCase;
          import org.springframework.context.ApplicationContext;
          import org.springframework.context.support.FileSystemXmlApplicationContext;
          import org.springframework.web.servlet.ModelAndView;
          import web.SpringappController;
          import bus.ProductManager;
          import bus.Product;
           
          public class TestSpringappController extends TestCase {
           
              private ApplicationContext ac;
           
              public void setUp() throws IOException {
                  ac = new FileSystemXmlApplicationContext("src/tests/WEB-INF/springapp-servlet.xml");
              }
           
              public void testHandleRequest() throws ServletException, IOException {
                  SpringappController sc = (SpringappController) ac.getBean("springappController");
                  ModelAndView mav = sc.handleRequest((HttpServletRequest) null, (HttpServletResponse) null);
                  Map m = mav.getModel();
                  List pl = (List) ((Map) m.get("model")).get("products");
                  Product p1 = (Product) pl.get(0);
                  assertEquals("Lamp", p1.getDescription());
                  Product p2 = (Product) pl.get(1);
                  assertEquals("Table", p2.getDescription());
                  Product p3 = (Product) pl.get(2);
                  assertEquals("Chair", p3.getDescription());
              }
          }

          唯一的測試就是調用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

          <?xml version="1.0" encoding="UTF-8"?>
          <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
           
          <!--
            - Application context definition for "springapp" DispatcherServlet.
            -->
           
           
          <beans>
              <bean id="springappController" class="web.SpringappController">
                  <property name="productManager">
                      <ref bean="prodMan"/>
                  </property>
              </bean>
           
              <bean id="prodMan" class="bus.ProductManager">
                  <property name="products">
                      <list>
                          <ref bean="product1"/>
                          <ref bean="product2"/>
                          <ref bean="product3"/>
                      </list>
                  </property>
              </bean>
           
              <bean id="product1" class="bus.Product">
                  <property name="description"><value>Lamp</value></property>
                  <property name="price"><value>5.75</value></property>
              </bean>
                  
              <bean id="product2" class="bus.Product">
                  <property name="description"><value>Table</value></property>
                  <property name="price"><value>75.25</value></property>
              </bean>
           
              <bean id="product3" class="bus.Product">
                  <property name="description"><value>Chair</value></property>
                  <property name="price"><value>22.79</value></property>
              </bean>
           
          </beans>        

          當你運行這個測試的時候,你應該看到載入應用程序上下文時有很多日志信息。

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

          接下來我為ProductManager添加一個測試案例,同時我打算給ProductManager添加一個用于增加價格的新方法,并添加一個測試。

          springapp/src/tests/TestProductManager .java

          package tests;
           
          import java.util.List;
          import java.util.ArrayList;
          import junit.framework.TestCase;
          import bus.ProductManager;
          import bus.Product;
           
          public class TestProductManager extends TestCase {
              private ProductManager pm;
           
              public void setUp() {
                  pm = new ProductManager();
                  Product p = new Product();
                  p.setDescription("Chair");
                  p.setPrice(new Double("20.50"));
                  ArrayList al = new ArrayList();
                  al.add(p);
                  p = new Product();
                  p.setDescription("Table");
                  p.setPrice(new Double("150.10"));
                  al.add(p);
                  pm.setProducts(al);
              }
           
              public void testGetProducs() {
                  List l = pm.getProducts();
                  Product p1 = (Product) l.get(0);
                  assertEquals("Chair", p1.getDescription());
                  Product p2 = (Product) l.get(1);
                  assertEquals("Table", p2.getDescription());
              }
           
           
              public void testIncreasePrice() {
                  pm.increasePrice(10);
                  List l = pm.getProducts();
                  Product p = (Product) l.get(0);
                  assertEquals(new Double("22.55"), p.getPrice());
                  p = (Product) l.get(1);
                  assertEquals(new Double("165.11"), p.getPrice());
              }
           
          }

          對于這個測試,沒有必要創建一個應用程序上下文。我只在setUp方法中建立了產品信息并且把他們添加到了產品管理對象中。我還給getProductsincreasePrice添加了測試。increasePrice方法根據傳給它的百分比對價格進行增加。我修改了ProductManager類來實現這個新方法。

          springapp/src/bus/ProductManager.java

          package bus;
           
          import java.io.Serializable;
          import java.util.ListIterator;
          import java.util.List;
           
          public class ProductManager implements Serializable {
              private List products;
           
              public void setProducts(List p) {
                  products = p;
              }
           
              public List getProducts() {
                  return products;
              }
           
              public void increasePrice(int pct) {
                  ListIterator li = products.listIterator();
                  while (li.hasNext()) {
                      Product p = (Product) li.next();
                      double newPrice = p.getPrice().doubleValue() * (100 + pct)/100;
                      p.setPrice(new Double(newPrice));
                  }
                  
              }
           
          }

          下面我構建并運行這些測試。正如你所見,這些測試就像一般的測試一樣——業務類不依賴于任何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

          <?xml version="1.0" encoding="UTF-8"?>
          <!DOCTYPE web-app PUBLIC '-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN' 'http://java.sun.com/dtd/web-app_2_3.dtd'>
           
          <web-app>
           
              <servlet>
              <servlet-name>springapp</servlet-name>
              <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
              <load-on-startup>1</load-on-startup>
            </servlet>
           
            <servlet-mapping>
              <servlet-name>springapp</servlet-name>
              <url-pattern>*.htm</url-pattern>
            </servlet-mapping>
           
            <welcome-file-list>
              <welcome-file>
                index.jsp
              </welcome-file>
            </welcome-file-list>
           
            <taglib>
              <taglib-uri>/spring</taglib-uri>
              <taglib-location>/WEB-INF/spring.tld</taglib-location>
            </taglib>
           
          </web-app>

          我們還需要在jsp文件的page指令中申明這個taglib。我們用普通的方法通過<form>標簽聲明一個表單,以及一個<input>文本域和一個提交按鈕。

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

          <%@ include file="/WEB-INF/jsp/include.jsp" %>
          <%@ taglib prefix="spring" uri="/spring" %>
           
          <html>
          <head><title><fmt:message key="title"/></title></head>
          <body>
          <h1><fmt:message key="priceincrease.heading"/></h1>
           
          <form method="post">
            <table width="95%" bgcolor="f8f8ff" border="0" cellspacing="0" cellpadding="5">
              <tr>
                <td alignment="right" width="20%">Increase (%):</td>
                <spring:bind path="priceIncrease.percentage">
                  <td width="20%">
                    <input type="text" name="percentage" value="<c:out value="${status.value}"/>">
                  </td>
                  <td width="60%">
                    <font color="red"><c:out value="${status.errorMessage}"/></font>
                  </td>
                </spring:bind>
              </tr>
            </table>
            <br>
            <spring:hasBindErrors name="priceIncrease">
              <b>Please fix all errors!</b>
            </spring:hasBindErrors>
            <br><br>
            <input type="submit" alignment="center" value="Execute">
          </form>
          <a href="<c:url value="hello.htm"/>">Home</a>
          </body>
          </html>

          <spring:bind>標記是用于將一個<input>表單元素綁定到一個命令對象PriceIncrease.java上的。這個命令對象以后會被傳送給效驗器,同時如果它通過了檢驗,它會被繼續傳送給控制器。${status.errorMessage}${status.value}是由框架聲明的特殊變量,可以用來顯示錯誤信息和當前域的值。

          springapp/src/bus/PriceIncrease.java

          package bus;
           
           
          import org.apache.commons.logging.Log;
          import org.apache.commons.logging.LogFactory;
           
          public class PriceIncrease {
           
              /** Logger for this class and subclasses */
           
              protected final Log logger = LogFactory.getLog(getClass());
           
              private int percentage;
           
              public void setPercentage(int i) {
                  percentage = i;
                  logger.info("Percentage set to " + i);
              }
           
              public int getPercentage() {
                  return percentage;
              }
           
          }

          這是一個十分簡單的JavaBean類,同時這里有一個屬性以及他的獲取器和設置器。在用戶按下了提交按鈕之后,Validator類將獲取控制。在表單中輸入的值會被框架設置在命令對象上。然后會調用方法validate,并傳入命令對象和一個用來存放錯誤信息的對象。

          springapp/src/bus/PriceIncreaseValidator.java

          package bus;
           
           
           
          import java.io.Serializable;
          import org.springframework.validation.Validator;
          import org.springframework.validation.Errors;
           
          import org.apache.commons.logging.Log;
          import org.apache.commons.logging.LogFactory;
           
          public class PriceIncreaseValidator implements Validator {
              private int DEFAULT_MIN_PERCENTAGE = 0;
              private int DEFAULT_MAX_PERCENTAGE = 50;
              private int minPercentage = DEFAULT_MIN_PERCENTAGE;
              private int maxPercentage = DEFAULT_MAX_PERCENTAGE;
           
              /** Logger for this class and subclasses */
           
              protected final Log logger = LogFactory.getLog(getClass());
           
              public boolean supports(Class clazz) {
                  return clazz.equals(PriceIncrease.class);
              }
           
              public void validate(Object obj, Errors errors) {
                  PriceIncrease pi = (PriceIncrease) obj;
           
                  if (pi == null) {
                      errors.rejectValue("percentage", "error.not-specified", null, "Value required.");
                  }
                  else {
                      logger.info("Validating with " + pi + ": " + pi.getPercentage());
           
                      if (pi.getPercentage() > maxPercentage) {
                          errors.rejectValue("percentage", "error.too-high",
                              new Object[] {new Integer(maxPercentage)}, "Value too high.");
                      }
           
                      if (pi.getPercentage() <= minPercentage) {
                          errors.rejectValue("percentage", "error.too-low",
                              new Object[] {new Integer(minPercentage)}, "Value too low.");
                      }
                  }
              }
           
           
              public void setMinPercentage(int i) {
                  minPercentage = i;
              }
           
              public int getMinPercentage() {
                  return minPercentage;
              }
           
              public void setMaxPercentage(int i) {
                  maxPercentage = i;
              }
           
              public int getMaxPercentage() {
                  return maxPercentage;
              }
          }

          現在我們要在springapp-servlet.xml文件中添加一條內容來定義新的表單和控制器。我們定義命令對象和效驗器的屬性。我們還要指明兩個視圖,一個用來顯示表單,另一個將是在成功的表單處理之后我們將看到的。后一個也叫做成功視圖,可以是兩種類型之一:它可以是一個普通的視圖引用直接引導到我們某個JSP頁面。但這種方法的一個缺點是,如果用戶刷新頁面,那么表單的數據就會被重新提交,然后你可能最后就做了兩次priceIncreace。另一種方法是使用一個重定向,它將給用戶瀏覽器返回一個應答并且指示瀏覽器重定向到一個新的URL。我們這里使用的這個URL不可以是我們的JSP頁面之一,因為他們對于直接訪問是不可見的。必須一個從外部可以獲取的URL。所以我選擇了“hello.htm”來作為我的重定向URL。這個URL影射到“hello.jsp”頁面,這個應該運行得很令人滿意。

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

          <?xml version="1.0" encoding="UTF-8"?>
          <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
           "http://www.springframework.org/dtd/spring-beans.dtd">
           
          <!--
            - Application context definition for "springapp" DispatcherServlet.
            -->
           
          <beans>
           
              <!--  Controller for the initial "Hello" page -->
              <bean id="springappController" class="web.SpringappController">
                  <property name="productManager">
                      <ref bean="prodMan"/>
                  </property>
              </bean>
           
              <!--  Validator and Form Controller for the "Price Increase" page -->
              <bean id="priceIncreaseValidator" class="bus.PriceIncreaseValidator"/>
              <bean id="priceIncreaseForm" class="web.PriceIncreaseFormController">
                  <property name="sessionForm"><value>true</value></property>
                  <property name="commandName"><value>priceIncrease</value></property>
                  <property name="commandClass"><value>bus.PriceIncrease</value></property>
                  <property name="validator"><ref bean="priceIncreaseValidator"/></property>
                  <property name="formView"><value>priceincrease</value></property>
                  <property name="successView"><value>hello.htm</value></property>
                  <property name="productManager">
                      <ref bean="prodMan"/>
                  </property>
              </bean>
           
              <bean id="prodMan" class="bus.ProductManager">
                  <property name="products">
                      <list>
                          <ref bean="product1"/>
                          <ref bean="product2"/>
                          <ref bean="product3"/>
                      </list>
                  </property>
              </bean>
           
              <bean id="product1" class="bus.Product">
                  <property name="description"><value>Lamp</value></property>
                  <property name="price"><value>5.75</value></property>
              </bean>
           
              <bean id="product2" class="bus.Product">
                  <property name="description"><value>Table</value></property>
                  <property name="price"><value>75.25</value></property>
              </bean>
           
              <bean id="product3" class="bus.Product">
                  <property name="description"><value>Chair</value></property>
                  <property name="price"><value>22.79</value></property>
              </bean>
           
              <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
                  <property name="basename"><value>messages</value></property>
              </bean>
           
              <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
                  <property name="mappings">
                      <props>
                          <prop key="/hello.htm">springappController</prop>
                          <prop key="/priceincrease.htm">priceIncreaseForm</prop>
                      </props>
                  </property>
              </bean>
           
              <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
                  <property name="viewClass">
                     <value>org.springframework.web.servlet.view.JstlView</value>
                  </property>
                  <property name="prefix"><value>/WEB-INF/jsp/</value></property>
                  <property name="suffix"><value>.jsp</value></property>
              </bean>
          </beans>

          下面,讓我們看一下這個表單的控制器。onSubmit方法獲取了控制并且在它調用ProductManager對象的increasePrice方法之前進行了一些日志記錄。然后它使用successView的url創建了RedirectView的一個新的實例,并傳遞這個實例給ModelAndView,最后返回這個ModelAndView的實例。

          springapp/src/web/PriceIncreaseFormController.java

          package web;
           
           
          import org.springframework.web.servlet.mvc.SimpleFormController;
          import org.springframework.web.servlet.ModelAndView;
          import org.springframework.web.servlet.view.RedirectView;
           
          import javax.servlet.ServletException;
          import javax.servlet.http.HttpServletRequest;
          import javax.servlet.http.HttpServletResponse;
           
          import java.io.IOException;
          import java.util.Map;
          import java.util.HashMap;
           
          import org.apache.commons.logging.Log;
          import org.apache.commons.logging.LogFactory;
           
          import bus.Product;
          import bus.ProductManager;
          import bus.PriceIncrease;
           
          public class PriceIncreaseFormController extends SimpleFormController {
           
              /** Logger for this class and subclasses */
           
              protected final Log logger = LogFactory.getLog(getClass());
           
          private ProductManager prodMan;
           
          public ModelAndView onSubmit(Object command)
                      throws ServletException {
           
                  int increase = ((PriceIncrease) command).getPercentage();
                  logger.info("Increasing prices by " + increase + "%.");
           
                  prodMan.increasePrice(increase);
                  String now = (new java.util.Date()).toString();
                  logger.info("returning from PriceIncreaseForm view to " + getSuccessView() +
                              " with " + now);
           
                  Map myModel = new HashMap();
                  myModel.put("now", now);
                  myModel.put("products", getProductManager().getProducts());
           
                  return new ModelAndView(new RedirectView(getSuccessView()));
           
              }
           
              protected Object formBackingObject(HttpServletRequest request) throws ServletException {
           
                  PriceIncrease priceIncrease = new PriceIncrease();
                  priceIncrease.setPercentage(20);
           
                  return priceIncrease;
              }
           
              public void setProductManager(ProductManager pm) {
                  prodMan = pm;
              }
           
              public ProductManager getProductManager() {
                  return prodMan;
              }
           
          }

          我們還要在message.properties資源文件里面添加一些消息。

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

          title=SpringApp
           
          heading=Hello :: SpringApp
           
          greeting=Greetings, it is now
           
          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頁面的鏈接。

          posted on 2010-04-16 21:37 cart 閱讀(1966) 評論(1)  編輯  收藏 所屬分類: SPRING

          Feedback

          # re: 一步一步開發Spring Framework MVC應用程序[未登錄] 2012-05-15 17:30 1
          1  回復  更多評論
            


          只有注冊用戶登錄后才能發表評論。


          網站導航:
          博客園   IT新聞   Chat2DB   C++博客   博問  
           
          主站蜘蛛池模板: 甘洛县| 元谋县| 桃江县| 米林县| 岗巴县| 清水县| 沁源县| 舞钢市| 赤水市| 安康市| 阳曲县| 黎城县| 阿城市| 建湖县| 长泰县| 从化市| 慈利县| 普陀区| 秦皇岛市| 建宁县| 垦利县| 滕州市| 元江| 浦北县| 上虞市| 和静县| 安福县| 沙雅县| 济阳县| 宾川县| 山西省| 获嘉县| 离岛区| 海安县| 澄迈县| 崇信县| 德惠市| 即墨市| 宜兴市| 永吉县| 本溪市|