Transcript
Page 1: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring Web MVC框架

Web MVC 架構是長久以來開發 Web 應用程式時,受多數開發人員所推薦的架構,現今有許多開放原始碼(OpenSource)專案的 Web

MVC 框架(Framework),Spring 也提供自己的 Web MVC 框架方案,希望能解決現存的 Web MVC 框架問題,結合 Spring 的 IoC 容器功能,在許多功能的整合上更為方便。 除了提供 Web MVC 框架之外, Spring 還提供了一個完善的Controller 繼承架構,讓您可以依自己的需求使用適當的 Controller類別。

7

Page 2: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�2

7.1 Spring Web MVC入門 在這個小節中,將從一個最簡單的 Spring Web應用程式,來了解 Web

MVC架構的運作方式,以及 Spring的 Web框架如何實現 Web MVC架構。

7.1.1 使用 Eclipse Web Tools Platform 本章的 Web應用程式,將使用 Eclipse Web Tools Platform(WTP)來進行開發,您可以在 http://www.eclipse.org/webtools/的「downloads」區下載「WebTools Platform; All-in-one」的版本。本章將使用 Tomcat 5.5作為 Servlet 容器(Container),所以也請至 http://tomcat.apache.org/下載 Tomcat 5.5。 如果打算使用 Eclipse 的線上更新方式安裝 WTP,則可以參考下列網址的影音教學:

http://eclipse.cdpa.nsysu.edu.tw/technology/phoenix/demos/i

nstall-wtp/install-wtp.html 在執行 WTP之後,為了撰寫 Web應用程式時,可以使用一些方便的工具視窗組合,請執行「Window/Open Perspective/Other…」,選擇開啟「J2EE Perspective」:

Page 3: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�3

圖 7.1 選擇開啟 J2EE Perspective 首先要設定 Server執行環境,如此就可以在 WTP中直接啟動 Tomcat容器,以測試在 WTP 中所撰寫的 Web 應用程式,請在 WTP 視窗下方選擇「Servers」,並於視窗中按滑鼠右鍵,執行「New/Server」:

圖 7.2 新增 Server 執行環境 因為本章將使用 Tomcat 5.5,所以接著在「New Server」視窗中選擇「Apache」中的「Tomcat v5.5 Server」:

Page 4: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�4

圖 7.3 設定 Server 類型 按下「Next」按鈕之後,請選擇 Tomcat安裝資料夾,接著按下「Finish」按鈕,即可完成 Server執行環境的新增,完成後如下圖所示:

圖 7.4 新增了 Server 執行環境 在 WTP中要撰寫 Web應用程式,可以在執行「File/New/Project…」新增專案時,選擇「Web/Dynamic Web Project」專案,在按下「Next」按鈕之後,輸入專案名稱,假設第一個 Spring Web MVC 專案名稱為FirstSpringMVC:

Page 5: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7��

圖 7.5 新增動態 Web 專案 按下「Finish」按鈕之後,即可完成專案的新增。這次為了資源管理上的方便,直接使用 spring.jar,以及其相依的 commons-logging.jar,請將這兩個 jar放到 WEB-INF/lib資料夾下。

圖 7.6 動態 Web 專案結構

Page 6: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�6

在 WTP 中的 Java 原始碼檔案,或是必須放置在 Web 應用程式Classpath 下的檔案,可以新增在 src 目錄之下,其它網頁或 Web 相關資源,可以新增在 WebContent 目錄之下,以後要匯出(Export)為 WAR(Web Application Archive)檔案時,WTP會自動將相關檔案組織為 Web應用程式的檔案系統結構。

7.1.2 第一個 Spring Web MVC 程式 在 Web MVC架構中,使用者並不直接連接至所需的資源,而必須先連接至前端控制器(Front controller),由前端控制器判斷使用者的請求要分派(Dispatch)給哪一個控制物件(Controller)來處理請求,藉此達到控制使用者請求資源之目的。 在 Spring的 Web MVC框架中,擔任前端控制器角色的是 org.spring-

framework.web.servlet.DispatcherServlet,DispatcherServlet 負責將客戶的請求分配給控制物件,所以使用 Spring Web MVC的第一步,就是在 web.xml中定義 DispatcherServlet:

FirstSpringMVC web.xml

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns="http://java.sun.com/xml/ns/j2ee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee

→ http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4">

<session-config>

<session-timeout>

30

</session-timeout>

</session-config>

Page 7: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�7

<servlet>

<servlet-name>dispatcherServlet</servlet-name>

<servlet-class>

org.springframework.web.servlet.DispatcherServlet

</servlet-class>

<init-param>

<param-name>contextConfigLocation</param-name>

<param-value>/WEB-INF/mvc-config.xml</param-value>

</init-param>

<load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>dispatcherServlet</servlet-name>

<url-pattern>*.do</url-pattern>

</servlet-mapping>

</web-app> 在 web.xml 中定義了一個 DispatcherServlet 的實例 "dispatcher-

Servlet",從設定中可以看到,所有連接至 *.do 結尾的請求都會由"dispatcherServlet"來處理,"contextConfigLocation" 初始參數用來設定Bean 定義檔的位置與名稱,如果不設置 "contextConfigLocation" 初始參數,則 DispatcherServlet預設會使用 Servlet的名稱為前置,讀取「Servlet名稱 -servlet.xml」作為其 Bean 定義檔,在上面的設定中則會讀取mvc-config.xml中的定義。 您也可以定義多個 Bean定義檔的來源,像是:

...

<servlet>

<servlet-name>hello</servlet-name>

<servlet-class>

org.springframework.web.servlet.DispatcherServlet

</servlet-class>

<init-param>

<param-name>contextConfigLocation</param-name>

<param-value>/WEB-INF/mvc-config.xml,

Page 8: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�8

→ /WEB-INF/other-service.xml</param-value>

</init-param>

<load-on-startup>1</load-on-startup>

</servlet>

...

DispatcherServlet負責分配請求至控制物件(Controller),在 Spring

Web MVC 框架中,控制物件要實作 org.springframework.web.servlet.

mvc.Controller介面,Controller介面有一個必須實作的 handleRequest() 方法,其定義如下所示:

package org.springframework.web.servlet.mvc;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.ModelAndView;

public interface Controller {

ModelAndView handleRequest(HttpServletRequest request,

HttpServletResponse response) throws Exception;

} 當 Controller 收到 DispatcherServlet 分配的請求,會執行 handle-

Request() 方法來處理請求,處理完畢後回傳一個 org.springframework.

web.servlet.ModelAndView 的實例,ModelAndView 包括了要呈現在View層(例如 JSP網頁)的 Model資料,以及其它 View層的相關訊息。 在第一個 Spring Web MVC中,使用者的請求將由一個HelloController物件來處理,其實作如下所示:

FirstSpringMVC HelloController.java

package onlyfun.caterpillar;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.mvc.Controller;

Page 9: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7��

import org.springframework.web.servlet.ModelAndView;

public class HelloController implements Controller {

private String viewPage;

public ModelAndView handleRequest(HttpServletRequest req,

HttpServletResponse res)

throws Exception {

String user = req.getParameter("user");

return new ModelAndView(viewPage, "user", user);

}

public void setViewPage(String viewPage) {

this.viewPage = viewPage;

}

} 在這個 Controller中,取得了來自使用者的"user"請求參數,並設定在ModelAndView的實例中,在這個例子中,建構 ModelAndView的第一個引數為要呈現的目標網頁(或資源)路徑,第二個引數是設定用來取得Model物件的"鍵(Key)",而第三個引數是給 View層呈現資料的 Model物件。 在 Web MVC 架構下,控制物件的作用為收集使用者的請求,進行與 Web 層相關的動作,不應在控制物件中執行商務邏輯,也不應讓 Servlet 相關的 API 侵入至商務層,這會讓商務層的物件與 Servlet API 產生耦合,例如不能讓 HttpServletRequest 物件直接設定至商務層物件之中。 使用 Spring Web MVC的好處是,Spring的 Controller在其 IoC容器管理下,可以如同一般的 Bean 來加以管理,並利用其依賴注入來完成相關物件的注入,以這邊的例子來說,您可以在 XML檔案中設定 Controller請求處理完畢之後,所要呈現資料的網頁路徑,來看一下 Bean 定義檔的

Page 10: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�1�

內容,依 web.xml中的設定,請在 WEB-INF目錄下建立 mvc-config.xml檔案:

FirstSpringMVC mvc-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="viewResolver"

class="org.springframework.web.servlet.

→ view.InternalResourceViewResolver">

<property name="prefix" value="/WEB-INF/jsp/"/>

<property name="suffix" value=".jsp"/>

</bean>

<bean name="/hello.do"

class="onlyfun.caterpillar.HelloController">

<property name="viewPage" value="hello"/>

</bean>

</beans> 實際上 DispatcherServlet 必須根據一個 HandlerMapping 物件,決定請求由哪一個 Controller來處理,DispatcherServlet預設使用 org.spring-

framework.web.servlet.handler.BeanNameUrlHandlerMapping,也就是根據 Bean在定義時的 "name" 屬性及使用者請求的 URL,決定使用哪一個 Controller實例,例如在這個例子中,請求 /FirstSpringMVC/hello.do時,DispatcherServlet根據 "hello.do" 名稱,決定要使用 "name" 為 "hello.do" 的 Bean實例,所以就是將請求交由 HelloController的實例來處理。 當 Controller 傳回 ModelAndView 後,DispatcherServlet 會交由ViewResolver 物件來作 View 層的相關解析,因而您需要設置一個ViewResolver實例,在這個範例中將以 JSP作為 View層技術,所以使用

Page 11: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�11

org.springframework.web.servlet.view.InternalResourceViewResolver,InternalResourceViewResolver需要設置 "viewClass",預設是 org.spring-

framework.web.servlet.view.InternalResourceView,這個類別支援Servlet技術的相關資源(像是 JSP、Servlet)。

InternalResourceViewResolver的 "prefix"、"suffix" 屬性會與 Model-

AndView傳回的路徑資訊結合,例如若路徑資訊傳回為 "hello" 字串,則與以上的例子設定結合,實際的路徑就是 /WEB-INF/jsp/hello.jsp。 最後可以在 /WEB-INF/jsp/ 中寫一個測試的 hello.jsp:

FirstSpringMVC hello.jsp

<%@ page language="java" contentType="text/html; charset=BIG5"

pageEncoding="BIG5"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=BIG5">

<title>First Spring MVC</title>

</head>

<body>

<h1>Hello, ${user}!!</h1>

</body>

</html> 在 ModelAndView中設置的 Model物件,經由 InternalResourceView-

Resolver及 InternalResourceView的解析,將設定為 JSP技術中可存取的屬性(Attribute),因此可以使用 JSP技術中的 Expression Language來取得資料,依以上所撰寫的程式,如果您在請求 hello.do 時附帶了"user"參數,則最後的 JSP會出現"user"訊息,可以執行 WTP的「Run/Run As/Run

On Server」指令,WTP會自動啟動 Tomcat並啟動 Web瀏覽器,請將網址指向 http://localhost:8080/FirstSpringMVC/hello.do?user=Justin,一個執行的參考畫面如下所示:

Page 12: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�12

圖 7.7 FirstSpringMVC 執行參考畫面

7.1.3 WebApplicationContext 如果想要在自己所定義的 Servlet 類別中使用 Spring 的容器功能,則也可以使用 org.springframework.web.context.ContextLoaderListener,例如在 web.xml中使用 <listener> 標籤加以定義:

...

<listener>

<listener-class>

org.springframework.web.context.ContextLoaderListener

</listener-class>

</listener>

...

ContextLoaderListener 預設會讀取 applicationContext.xml,您可以指定自己的定義檔,只要在 <context-param> 中指定 "contextConfig-

Location" 參數,例如:

...

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>/WEB-INF/beans-config.xml,

→ /WEB-INF/demo-service.xml</param-value>

</context-param>

...

Page 13: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�13

接著可以在自定義的 Servlet 中使用 org.springframework.web.

context.support.WebApplicationContextUtils,從 ServletContext中取得org.springframework.web.context.WebApplicationContext,例如:

WebApplicationContext ctx =

WebApplicationContextUtils.

getRequiredWebApplicationContext(

this.getServletContext());

WebApplicationContext 實作了 ApplicationContext 介面,是 Spring專為 Servlet 的 Web 應用程式設計的 ApplicationContext 實作類別,在取得 WebApplicationContext之後,可以利用它來取得 Bean定義檔中定義的Bean實例,例如:

Date date = (Date) ctx.getBean("dateBean"); 在不支援 Listener設定的容器上(例如 Servlet 2.2以更早的版本),可以使用 org.springframework.web.context.ContextLoaderServlet 來取代上面的 ContextLoaderListener的設定,例如:

...

<servlet>

<servlet-name>contextLoader</servlet-name>

<servlet-class>

org.springframework.web.context.ContextLoaderServlet

</servlet-class>

<load-on-startup>1</load-on-startup>

</servlet>

... 綜合以上的敘述,撰寫一個簡單的範例來示範完整的組態方式,假設您撰寫了一個 Servlet程式,作用為提供報時功能,如下所示:

Page 14: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�14

WebApplicationContextDemo TimeServlet.java

package onlyfun.caterpillar;

import java.io.*;

import javax.servlet.*;

import javax.servlet.http.*;

import org.springframework.web.context.WebApplicationContext;

import org.springframework.web.context.

support.WebApplicationContextUtils;

public class TimeServlet extends HttpServlet {

public void doGet(HttpServletRequest req,

HttpServletResponse res)

throws ServletException, IOException {

WebApplicationContext ctx =

WebApplicationContextUtils.

getRequiredWebApplicationContext(

this.getServletContext());

PrintWriter out = res.getWriter();

out.println(ctx.getBean("dateBean"));

}

} 這 個 Servlet 中 使 用 了 WebApplicationContext 來 嘗 試 取 得"dateBean",可以在 web.xml中定義 ContextLoaderListener與 Bean定義檔的位置,例如:

WebApplicationContextDemo web.xml

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns="http://java.sun.com/xml/ns/j2ee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee

→ http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4">

<session-config>

<session-timeout>

30

</session-timeout>

Page 15: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�1�

</session-config>

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>/WEB-INF/beans-config.xml</param-value>

</context-param>

<listener>

<listener-class>

org.springframework.web.context.ContextLoaderListener

</listener-class>

</listener>

<servlet>

<servlet-name>time</servlet-name>

<servlet-class>

onlyfun.caterpillar.TimeServlet

</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>time</servlet-name>

<url-pattern>/time.do</url-pattern>

</servlet-mapping>

</web-app> 在 <context-param> 的 "contextConfigLocation" 屬性中,設定了 Bean定義檔的位置與名稱,Bean定義檔的內容如下所示:

WebApplicationContextDemo beans-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="dateBean"

class="java.util.Date" scope="singleton"/>

</beans>

Page 16: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�16

可以使用瀏覽器連接至 TimeServlet,網頁會顯示連接的時間,一個執行的畫面如下所示:

圖 7.8 WebApplicationContextDemo 的執行參考畫面

7.1.4 Handler Mapping 當客戶的請求來到時,DispatcherServlet根據 Handler mapping物件決定,如何將請求分配至對應的 Controller,對於 Web應用程式來說,通常就是以 URL路徑對應至 Controller。

DispatcherServlet 預設的 Handler mapping 是 org.springframework.

web.servlet.handler.BeanNameUrlHandlerMapping,所以即使在 Bean定義檔中沒有明確宣告 BeanNameUrlHandlerMapping,也會使用BeanNameUrlHandlerMapping依 Bean定義的 "name" 屬性之 URL,來決定使用哪一個 Controller,例如:

...

<bean id="viewResolver"

class="org.springframework.web.servlet.

→ view.InternalResourceViewResolver">

<property name="prefix" value=”/WEB-INF/jsp/”/>

<property name="suffix" value=”.jsp”/>

</bean>

<bean name="/hello.do"

class="onlyfun.caterpillar.HelloController">

<property name="viewPage" value=”hello”/>

</bean>

...

Page 17: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�17

在上面的設定中,使用者對 /hello.do路徑的請求,DispatchServlet都會交由 HelloController物件處理。

BeanNameUrlHandlerMapping是簡單的 Handler mapping,僅適用於小型的應用程式,因為它使得 URL 與 Controller 的類別名稱直接建立關係,在 Spring的 Handler mapping中最常使用的是 org.springframework.

web.servlet.handler.SimpleUrlHandlerMapping,一個使用的例子如下所示:

...

<bean id="urlHandlerMapping"

class="org.springframework.web.servlet.

→ handler.SimpleUrlHandlerMapping">

<property name="mappings">

<props>

<prop key="/hello.do">helloController</prop>

<prop key="/welcome.do">welcomeController</prop>

</props>

</property>

</bean>

<bean id="viewResolver"

class="org.springframework.web.servlet.

→ view.InternalResourceViewResolver">

<property name="prefix" value=”/WEB-INF/jsp/”/>

<property name="suffix" value=”.jsp”/>

</bean>

<bean id="helloController"

class="onlyfun.caterpillar.HelloController">

<property name="viewPage" value=”hello”/>

</bean>

<bean id="welcomeController"

class="onlyfun.caterpillar.WelcomeController">

...

</bean>

...

Page 18: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�18

在 "mappings" 屬性的設置中,<prop> 的 "key" 是請求的 URL,而

<prop> 與 </prop> 間設定實際上處理請求的 Bean名稱,<prop> 的 "key" 設置還可以使用 Wildcard,例如:

...

<bean id="urlHandlerMapping"

class="org.springframework.web.servlet.

→ handler.SimpleUrlHandlerMapping">

<property name="mappings">

<props>

<prop key="/hello*.do">helloController</prop>

<prop key="/welcome.do">welcomeController</prop>

</props>

</property>

</bean>

.... 在上面的設置中,以 hello開頭及 .do結尾的 URL請求,都會交給 Bean定義檔中的"helloController"來處理。 若定義 Controller名稱時遵守命名規範,則可使用 Spring 2.0 新增的org.springframework.web.servlet.mvc.support.ControllerClassName

HandlerMapping,例如可以改寫 FirstSpringMVC的mvc-config.xml如下:

...

<bean id="viewResolver"

class="org.springframework.web.servlet.

→ view.InternalResourceViewResolver">

<property name="prefix" value="/WEB-INF/jsp/"/>

<property name="suffix" value=".jsp"/>

</bean>

<bean id=”controllerClassNameHandlerMapping”

class="org.springframework.web.servlet.mvc.

→ support.ControllerClassNameHandlerMapping"/>

<bean name="helloController"

class="onlyfun.caterpillar.HelloController">

Page 19: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�1�

<property name="viewPage" value="hello"/>

</bean>

...

ControllerClassNameHandlerMapping 會 自 動 將 hello* 的 請 求 與Controller 類別名稱比對,例如 hello.do 的請求將會嘗試找尋 Hello-

Controller 實例來處理,同樣的,若是 welcome*的請求,則 Controller-

ClassNameHandlerMapping會嘗試找尋 WelcomeController實例來處理。

7.1.5 Handler Interceptor 如果在 Controller執行的前後,或者是在 View繪製之後打算作一些記錄或攔截請求等動作,可以實作 org.springframework.web.servlet.Hand-

lerInterceptor 介面,在這個介面中規範了三個方法,其定義如下 所示:

package org.springframework.web.servlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

public interface HandlerInterceptor {

boolean preHandle(HttpServletRequest request,

HttpServletResponse response,

Object handler) throws Exception;

void postHandle(HttpServletRequest request,

HttpServletResponse response,

Object handler,

ModelAndView modelAndView) throws Exception;

void afterCompletion(HttpServletRequest request,

HttpServletResponse response,

Object handler,

Exception ex) throws Exception;

}

Page 20: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�2�

preHandler() 會在 Controller 處理請求之前被執行,傳回的 boolean值決定是否讓 Handler Interceptor 或是 Controller 來處理請求,如果傳回false,則接下來的 Interceptor或 Controller就不處理請求,postHandler() 則會在 Controller 處理完請求之後被執行,afterCompletion() 方法會在View繪製完成之後被執行。 您可以繼承 org.springframework.web.servlet.handler.HandlerInter-

ceptorAdapter,它實作了 HandlerInterceptor介面,您只要針對必要的方法重新定義,例如可以修改 FirstSpringMVC 專案,在當中實作 Logging-

Interceptor,在請求被控制物件處理的前、後來作記錄:

HandlerInterceptorDemo LoggingInterceptor.java

package onlyfun.caterpillar;

import java.util.logging.Logger;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.ModelAndView;

import org.springframework.web.servlet.

handler.HandlerInterceptorAdapter;

public class LoggingInterceptor

extends HandlerInterceptorAdapter {

private Logger logger =

Logger.getLogger(this.getClass().getName());

public boolean preHandle(HttpServletRequest request,

HttpServletResponse response,

Object handler) throws Exception {

logger.info(

handler.getClass().getName() + " 開始執行...");

return true;

}

public void postHandle(HttpServletRequest request,

HttpServletResponse response, Object handler,

Page 21: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�21

ModelAndView modelAndView) throws Exception {

logger.info(

handler.getClass().getName() + " 執行完畢...");

}

public void afterCompletion(HttpServletRequest request,

HttpServletResponse response, Object handler,

Exception ex) throws Exception {

logger.info("請求處理完畢...");

}

} 要使用這一個 HandlerInterceptor,必須在 Bean定義檔中進行定義,例如:

HandlerInterceptorDemo mvc-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="loggingInterceptor"

class="onlyfun.caterpillar.LoggingInterceptor"/>

<bean id="viewResolver"

class="org.springframework.web.servlet.

→ view.InternalResourceViewResolver">

<property name="prefix" value="/WEB-INF/jsp/"/>

<property name="suffix" value=".jsp"/>

</bean>

<bean id="urlHandlerMapping"

class="org.springframework.web.servlet.

→ handler.SimpleUrlHandlerMapping">

<property name="interceptors">

<list>

<ref bean="loggingInterceptor"/>

</list>

Page 22: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�22

</property>

<property name="mappings">

<props>

<prop key="/hello.do">helloController</prop>

</props>

</property>

</bean>

<bean id="helloController"

class="onlyfun.caterpillar.HelloController">

<property name="viewPage" value="hello"/>

</bean>

</beans> 注意所使用的 UrlHandlerMapping 是 SimpleUrlHandlerMapping;當透過 DispatcherServlet請求時,在請求被處理的前後都會進行記錄,在控制台(Console)所看到的資訊如下所示:

圖 7.9 HandlerInterceptorDemo 的執行參考畫面

7.1.6 Controller 繼承架構 當使用者送出請求之後,實際上處理請求的是 Controller,您可以實作 Controller介面來處理請求,在 Spring中,Controller是 Spring IoC容

Page 23: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�23

器管理的一個 Bean實例。Spring提供豐富的 Controller繼承架構,可以根據實際上的需求來繼承某個類別,以完成所需的 Controller處理。 以下列出 Spring中 Controller的繼承架構:

圖 7.10 Spring 中 Controller 繼承架構 以下分別簡介 Controller相關類別之作用:

� AbstractController

AbstractController 實作了 Controller 介面,並繼承了 WebContentGene-

rator,提供了 Session 快取與同步化(synchronized)的處理。您可以直接繼承 AbstractController,並重新定義 handleRequestInternal() 方法來處理請求,例如:

public class SomeController extends AbstractController {

public ModelAndView handleRequestInternal(

HttpServletRequest request,

HttpServletResponse response) throws Exception {

....

return new ModelAndView("view", "modelName", model);

Page 24: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�24

}

..

}

� MultiActionController 如果為每一個請求撰寫一個 Controller 類別,在程式規模增大時,您的Controller 類別將會迅速增加。可以繼承或直接使用 MultiActionController類別,將數個相關的請求處理集中於同一個物件,而不用每一個請求撰寫一個控制物件。

� BaseCommandController、AbstractCommandController 如果需要將請求參數值擷取至一個 Command 物件中加以管理,則可以使用 BaseCommandController 類別的子類別。在 Spring 中,Command 是一個 JavaBean,BaseCommandController 負責將請求參數擷取並設定至Command 物件中,AbstractCommandController 則定義了後續的處理流程,通常會繼承 AbstractCommandController 來定義 CommandController,一個例子如下:

public class SomeCommandController

extends AbstractCommandController {

public SomeCommandController() {

setCommandClass(YourCommand.class);

}

public ModelAndView handle(HttpServletRequest request,

HttpServletResponse response,

Object command,

BindException exception) throws Exception {

YourCommand your = (YourCommand) command;

...

return new ModelAndView(..);

}

..

}

� AbstractFormController

Page 25: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�2�

對於表單處理,AbstractFormController 中定義了處理請求參數,及表單提交成功或失敗時所要進行的頁面流程。

� SimpleFormController 對於簡單的表單處理,可以繼承 SimpleFormController 類別,可以重新定義 doSubmitAction() 方法,如果要自己處理 ModelAndView,則可以重新定義 onSubmit() 方法。

� AbstractWizardFormController 對於需要數個表單畫面來完成資料收集的網頁來說,可以繼承 Abstract-

WizardFormController,您可以像桌面應用程式的精靈(Wizard)功能來製作表單。 在這邊對於以上的 Controller 先作個簡單的介紹,每一個 Controller類別在下一節中都會有詳細的說明,以下將繼續說明 Spring Web MVC的主要 API。

7.1.7 ModelAndView

ModelAndView類別代表了 Spring Web MVC程式中,呈現畫面時所使用 Model資料物件與 View資料物件,由於 Java程式的方法中一次只能回傳一個物件,所以 ModelAndView的作用是封裝這兩個物件,方便您同時傳回 Model與 View。 最簡單的 ModelAndView 是只包括 View 的名稱,之後 View 名稱被View resolver,也就是實作 org.springframework.web.servlet.View介面的實例解析,例如 InternalResourceView或 JstlView等,最簡單的 Model-

AndView建構方式如下:

ModelAndView(String viewName)

Page 26: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�26

如果要返回呈現畫面時所需的 Model資料物件,則可以使用 Map物件來收集資料,然後在建構 ModelAndView作為建構時的引數,可以使用下面這個版本的 ModelAndView建構方法:

ModelAndView(String viewName, Map model)

Map物件中設定"鍵(Key)"與"值(Value)",之後可以在要呈現的畫面中取出加以顯示(例如在 JSP網頁中),如果您要傳回一個 Model資料物件並指定 Model的名稱,則可以使用下面這個 ModelAndView版本:

ModelAndView(String viewName, String modelName, Object modelObject) 藉由 modelName,您可以在 View 的實作類別中取出 Model 資料物件,並根據 View所使用的技術來加以顯示(可能是 JSP網頁、Pdf等呈現技術)。

ModelAndView 類別也可使用 View 介面的物件來作為建構時的引數,以下是三個可用的建構方法:

ModelAndView(View view)

ModelAndView(View view, Map model)

ModelAndView(View view, String modelName, Object modelObject) 一個實作 View 的實例是 org.springframework.web.servlet.view.

RedirectView,ModelAndView 預設是使用轉發(Forward)方式來完成請求的結果畫面,使用 RedirectView的話,則會使用重新導向(Redirect)將請求重導至指定的結果畫面位置,以呈現請求的結果,例如:

Page 27: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�27

...

public ModelAndView handleRequest(....) ... {

...

return new ModelAndView(

new RedirectView(this.getViewPage()));

}

... 在這邊,viewPage的設定必須從伺服器網頁根目錄開始,而不是 Web應用程式的根目錄,所以 getViewPage() 傳回的位址必須像 /FirstSpring-

MVC/pages/index.htm 這樣的位址,其中 FirstSpringMVC 是 Web 應用程式目錄。 使用轉發(Forward)方式的話,網址列上並不會出現被轉發的位址,而且轉發是在 Web 應用程式之內進行,可以訪問 Web 應用程式所設定的內部目錄,像是 WEB-INF 目錄,因此可以將一些要控管存取的資源放到WEB-INF 下,如此使用者就無法直接請求這些資源,而必須透過DispatcherServlet 與 Controller 的處理,才可以取得這些資源,轉發方式只能在 Web應用程式中進行,不能指定至其它的 Web應用程式位址。 使用重新導向(Redirect)的話,Web 應用程式會要求客戶端瀏覽器重新發出請求位址,客戶端會重新連接至所指定的位址,因此瀏覽器的位址列上會出現重新導向的資訊,重新導向後的請求是由瀏覽器發出,所以不能訪問 Web應用程式中的隱藏目錄,像是 WEB-INF,重新導向是由瀏覽器重新要求一個網頁,可以指定至其它的 Web應用程式位址。 在 Spring 2.0中,ModelAndView新增了 addObject()方法,您可以僅加入 Model物件,addObject()方法會自動產生一個"鍵",例如:

...

public ModelAndView handleRequest(HttpServletRequest req,

HttpServletResponse res)

throws Exception {

Page 28: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�28

User user = new User();

...

ModelAndView modelAndView = new ModelAndView(viewPage);

modelAndView.addObject(user);

return modelAndView;

}

... 如果設定給 addObject()的是 onlyfun.caterpillar.User實例,則會自動產生"鍵"為"user",也就是說按照類別或介面名稱來產生名稱,如果加入null,則會發生 IllegalArgumentException。

Spring 2.0 的 ModelAndView 還 可 以 不 指 定 View , 由RequestToViewNameTranslator 來根據請求提供 View 名稱,例如可以在設定檔中如下撰寫:

<bean id="viewResolver"

class="org.springframework.web.servlet.

→ view.InternalResourceViewResolver">

<property name="prefix" value="/WEB-INF/jsp/"/>

<property name="suffix" value=".jsp"/>

</bean>

<bean id=”controllerClassNameHandlerMapping”

class="org.springframework.web.servlet.mvc.

→ support.ControllerClassNameHandlerMapping"/>

<bean id="viewNameTranslator"

class="org.springframework.web.servlet.

→ view.DefaultRequestToViewNameTranslator"/>

<bean name="helloController"

class="onlyfun.caterpillar.HelloController">

<property name="viewPage" value="hello"/>

</bean>

... 在 HelloController中實例化 ModelAndView時,若不指定 View,例如:

Page 29: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�2�

...

public ModelAndView handleRequest(HttpServletRequest req,

HttpServletResponse res)

throws Exception {

ModelAndView modelAndView = new ModelAndView();

...

return modelAndView;

}

... 由於設定了 ControllerClassNameHandlerMapping,所以如果請求hello.do,則會讓 HelloController 來處理請求,由於設定了 RequestTo-

ViewNameTranslator,雖然 handleRequest()中產生 ModelAndView時,並沒有指定 View,但會由 hello.jsp 來處理(結合了 InternalResource-

ViewResolver的前置與後置設定)。

7.1.8 View Resolver

DispatcherServlet 根據傳回的 ModelAndView 實例來解析 View 名稱,並處理呈現於畫面的 Model 物件。View 名稱的解析是委託給實作org.springframework.web.servlet.ViewResolver 介面的實例, View-

Resolver介面定義如下:

package org.springframework.web.servlet;

import java.util.Locale;

public interface ViewResolver {

View resolveViewName(String, Locale locale)

throws ServletException;

}

ViewResolver 的一個實例是 InternalResourceViewResolver,您可以在 viewResolver中設定 "prefix" 與 "suffix" 屬性,分別設定路徑的前置與

Page 30: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�3�

後置文字,這樣 viewPage屬性就可以只打路徑的主要區別名稱,直接給個設定的示範:

...

<bean id="viewResolver"

class="org.springframework.web.servlet.

→ view.InternalResourceViewResolver">

<property name="prefix" value=”/WEB-INF/jsp/”/>

<property name="suffix" value=”.jsp”/>

</bean>

...

ViewResolve 在名稱解析完後,實際的 View 繪製與 Model 物件的轉換處理是交給實作 org.springframework.web.servlet.View介面的實例,View介面定義如下:

package org.springframework.web.servlet;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

public interface View {

void render(Map model, HttpServletRequest request,

HttpServletResponse response) throws Exception;

} 例如 InternalResourceViewResolver預設的 "viewClass" 是 Internal-

ResourceView,也可以設定 "viewClass" 屬性為其它的 View 層技術相關類別,例如設定為 org.springframework.web.servlet.view.JstlView,另外還有 TilesView、VelocityView等實作類別,可適用不同的 View技術。 您可以使用 org.springframework.web.servlet.view.BeanNameView-

Resolver,例如:

Page 31: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�31

...

<bean id="viewResolver"

class="org.springframework.web.servlet.

→ view.BeanNameViewResolver"/>

<bean id="hello"

class="org.springframework.web.servlet.view.JstlView">

<property name="url" value=”/WEB-INF/jsp/hello.jsp”/>

</bean>

... 在以上的設定中,當 ModelAndView返回的 view名稱為 "hello" 時,會去尋找 "id" 屬性為"hello"的 View Bean。 可以將 ViewResolver 的定義獨立出來至一個屬性檔中,這可以使用org.springframework.web.servlet.view.ResourceBundleViewResolver 來達成,好處是可以根據實際的 URL設定不同的表示層技術,例如:

...

<bean id="viewResolver"

class="org.springframework.web.servlet.

→ view.ResourceBundleViewResolver">

<property name="basename" value=”views”/>

</bean>

... 在 ResourceBundleViewResolver的 "basename" 屬性設定了 "views" 名稱("views" 也是 "basename" 的預設值),這將使用 views.properties來設定表示層技術的 "viewClass" 與 "url",例如一個 views.properties的撰寫範例如下所示:

hello.class=org.springframework.web.servlet.

→ view.InternalResourceView

hello.url=/WEB-INF/jsp/hello.jsp

welcome.class=org.springframework.web.servlet.view.JstlView

welcome.url=/WEB-INF/jsp/welcome.jsp

Page 32: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�32

other.class=org.springframework.web.servlet.view.VelocityView

other.url=/WEB-INF/vm/other.vm 在傳回的 ModelAndView實例中的 view名稱若為 "hello",則會尋找hello.class及 hello.url來得到對應的 ViewClass實例及指定的 URL,您可以為不同的國家或地區設置不同的 View,在 "basename" 上設置為

"views",表示預設會讀取 views.properties,如果語系設定是 en_US,可以為它準備 views_en_US.properties,如果語系設定是 zh_TW,可以為它準備 views_zh_TW.properties,而對應的檔案中可以設置與區域設定 Locale相關的 View。 也可以使用 org.springframework.web.servlet.view.XmlViewRe-

solver,一個設定的範例如下所示:

...

<bean id="viewResolver"

class="org.springframework.web.servlet.view.XmlViewResolver">

<property name="location" value=”/WEB-INF/demo-views.xml”/>

</bean>

... 在上面的設定中, "location" 屬性預設是 views.xml,這邊設定為demo-views.xml,表示到 demo-views.xml中尋找 View的 Bean定義,例如在 demo-views.xml中這麼定義:

...

<bean id="hello"

class="org.springframework.web.servlet.view.JstlView">

<property name="url" value=”/WEB-INF/jsp/hello.jsp”/>

</bean>

...

Page 33: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�33

其運作的機制與設定 BeanNameViewResolver時是類似的,當回傳的ModelAndView實例中的 view名稱為 "hello" 時,則會去尋找 Bean定義中的 "id" 為 "hello" 的 View設定。

7.1.9 Exception Resolver 如果 JSP網頁或 Servlet在運行過程中丟出了例外,而您想捕捉這個例外,除了使用容器與 JSP 網頁中有關例外的捕捉設定方法外(例如設定web.xml 的 <error-page> 標籤,或是設定 JSP 網頁 "page" 指令元素的

"errorPage" 屬性),也可以在 DispatcherServlet的定義檔中設定錯誤處理網 頁 , 設 定 一 個 ExceptionResolver 的 Bean 實 例 , 例 如 使 用org.springframework.web.servlet.handler.SimpleMappingException-

Resolver,一個設定的例子如下所示:

...

<bean id="viewResolver"

class="org.springframework.web.servlet.

→ view.InternalResourceViewResolver">

<property name="prefix" value=”/WEB-INF/jsp/”/>

<property name="suffix" value=”.jsp”/>

</bean>

<bean id="exceptionResolver"

class="org.springframework.web.servlet.

→ handler.SimpleMappingExceptionResolver">

<property name="exceptionMappings">

<props>

<prop key="java.sql.SQLException">sqlexception</prop>

<prop key="java.sql.IOException">ioexception</prop>

</props>

</property>

</bean>

...

Page 34: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�34

DispatcherServlet會自動應用 ExceptionResolver,並在例外發生時使用指定的網頁來顯示錯誤訊息,例如在上面的設定中,只要發生了SQLException,就會連接至 /WEB-INF/jsp/sqlexception.jsp,而發生了IOException,就會連接至 /WEB-INF/jsp/ioexception.jsp。

7.2 Controller實作類別 Spring 提供實作 Controller 介面的相關類別,可以依需求來製作所需的 Controller 物件,無論是簡單的請求參數收集、表單網頁的請求處理,甚至是精靈(Wizard)網頁的設計等,在 Spring 中都有對應的類別可以 應用。

7.2.1 AbstractController

org.springframework.web.servlet.mvc.AbstractController 類別是Controller介面的簡單實作,使用 Template Method模式實作了使用者的請求處理的流程,包括了對快取標頭(Caching header)的處理、檢驗對請求方法(GET、POST)的支援、Session的取得與同步化(synchronized)等,如果需要對這些議題作處理,則可以使用 AbstractController類別。

AbstractController 從 handleRequest() 方法被 DispatcherServlet 執行開始的工作流程如下所示:

1. DispatcherServlet 執行 handleRequest() 方法。

2. 根據 "supportedMethods" 的設定來檢驗支援的請求方法,如果方法不支援則丟出 ServletException。

3. 根據 "requireSession" 的設定決定請求是否需要使用 Session,嘗試取得Session,如果沒有取得 Session 則丟出 ServletException。

Page 35: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�3�

4. 根據 "cacheSeconds" 的設定決定是否設定快取標頭(Caching header)。

5. 呼叫 handleRequestInternal() 方法,根據 "synchronizeOnSession" 決定是否對 Session 進行同步化(synchronized)。 對於 AbstractController類別的工作流程,只要有個基本的了解就可以了,可以直接繼承 AbstractController類別,並重新定義 handleRequest-

Internal() 方法,例如:

public class SomeController extends AbstractController {

protected ModelAndView handleRequestInternal(

HttpServletRequest request,

HttpServletResponse response) throws Exception {

....

return new ModelAndView("view", "modelName", model);

}

..

} 使用 AbstractController類別的話,可以直接操作已經定義好的一些方法,例如可以呼叫 setSupportedMethods() 方法,設定所允許的請求方式,例如 Controller 實作時若如下撰寫的話,則只支援 GET、POST、HEAD的請求:

package onlyfun.caterpillar;

...

public class HelloController extends AbstractController {

...

public HelloController() {

this.setSupportedMethods(

new String[] {"GET", "POST", "HEAD"});

}

protected ModelAndView handleRequestInternal(

HttpServletRequest req, HttpServletResponse res)

throws Exception {

...

}

}

Page 36: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�36

如果以不允許的方式請求的話,則 Controller 在處理的時候會丟出org.springframework.web.servlet.support.RequestMethodNotSupported-

Exception的例外。

7.2.2 MultiActionController 與 ParameterMethodName-

Resolver 如果應用程式的規模越來越大,若為每一個請求的處理撰寫一個Controller 類別,很快的,您發現到 Web 應用程式中有越來越多的Controller 類別,而事實上當中的一些職責是可以組織在一個類別,例如與資料庫操作相關的 list()、add()、delete() 等方法,就可以組織在一個 類別。 您可以使用 AbstractController類別的一個子類別:org.springframe-

work.web.servlet.mvc.multiaction.MultiActionController。它可以讓您在一個 Controller 類別中定義多個方法,並根據使用者的請求來執行當中的某個方法。 要使用 MultiActionController,您要配合一個 org.springframework.

web.servlet.mvc.multiaction.MethodNameResolver的實例,MultiAction-

Controller預設使用的MethodNameResolver類別是 org.springframework.

web.servlet.mvc.multiaction.InternalPathMethodNameResolver,這個類別可以根據所給的網址中,最後請求的檔案名稱來判斷執行 Controller中的哪一個方法,例如 /test.do 的請求會執行 test (HttpServletRequest,

HttpServletResponse) 方法。但通常不會使用 InternalPathMethodName-

Resolver,因為這就失去了使用 MultiActionController 的一些優點,像是依使用者的請求參數來決定所要執行的方法。

Page 37: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�37

通常在使用 MultiActionController 時會搭配 org.springframework.

web.servlet.mvc.multiaction.ParameterMethodNameResolver或是 org.

springframework.web.servlet.mvc.multiaction.PropertiesMethodName-

Resolver,兩個類別都是根據一個請求參數,以參數決定要執行 Controller類別中的哪一個方法,這邊先以 ParameterMethodNameResolver為例,可以在 Bean定義檔中這麼撰寫:

MultiActionDemo mvc-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="urlMapping"

class="org.springframework.web.servlet.

→ handler.SimpleUrlHandlerMapping">

<property name="mappings">

<props>

<prop key="/book.do">bookController</prop>

</props>

</property>

</bean>

<bean id="viewResolver"

class="org.springframework.web.servlet.

→ view.InternalResourceViewResolver">

<property name="prefix" value="/WEB-INF/jsp/"/>

<property name="suffix" value=".jsp"/>

</bean>

<bean id="paraMethodResolver"

class="org.springframework.web.servlet.mvc.

→ multiaction.ParameterMethodNameResolver">

<property name="paramName" value="action"/>

<property name="defaultMethodName" value="list"/>

</bean>

Page 38: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�38

<bean id="bookController"

class="onlyfun.caterpillar.BookController">

<property name="methodNameResolver"

ref="paraMethodResolver"/>

<property name="testPage" value="test"/>

</bean>

</beans> 在"paraMethodResolver"的 Bean定義中,使用 "paramName" 屬性設定在 HTTP請求中使用 "action" 參數指定所要執行的方法,若是請求中沒有指定 "action" 參數,則會使用 "defaultMethodName" 屬性所設定的方法,這邊設定的是 list() 方法。

BookController繼承 MultiActionController類別,當中定義在 "action" 參數指定下所要執行的方法,例如:

MultiActionDemo BookController.java

package onlyfun.caterpillar;

import javax.servlet.http.*;

import org.springframework.web.servlet.ModelAndView;

import org.springframework.web.servlet.mvc.

multiaction.MultiActionController;

public class BookController extends MultiActionController {

private String testPage;

public ModelAndView list(HttpServletRequest req,

HttpServletResponse res) {

return new ModelAndView(

this.getTestPage(),"executed", "list");

}

public ModelAndView add(HttpServletRequest req,

HttpServletResponse res) {

Page 39: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�3�

return new ModelAndView(

this.getTestPage(),"executed", "add");

}

public ModelAndView delete(HttpServletRequest req,

HttpServletResponse res) {

return new ModelAndView(

this.getTestPage(),"executed", "delete");

}

public String getTestPage() {

return testPage;

}

public void setTestPage(String testPage) {

this.testPage = testPage;

}

} 注意到定義的方法中,必須包括 HttpServletRequest 與 HttpServlet-

Response的參數,也可以使用帶有第三個參數 HttpSession的版本。 以上所定義的 BookController 類別只是一個簡單的範例,用於測試MultiActionController 的運作,它所作的處理只是設定一個字串,表示目前所執行的方法名稱,可以設計一個測試用的 JSP網頁,用以顯示使用者所請求的方法,例如:

MultiActionDemo test.jsp

<%@ page language="java" contentType="text/html; charset=BIG5"

pageEncoding="BIG5"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=BIG5">

<title>MultiAction Test Page</title>

</head>

<body>

<H1> "${executed}" method is executed.</H1>

Page 40: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�4�

</body>

</html> 來實際看個發出請求的例子,可以在網址中附上請求參數,一個執行的參考畫面如下所示:

圖 7.11 MultiActionDemo 專案的執行結果

7.2.3 MultiActionController 與PropertiesMethodNameResolver 您也可以將所有方法組織在一個委託(delegate)物件中,而不是撰寫在 Controller 類別中,當請求來到時,委託給這個物件執行指定的方法,您只要設定 MultiActionController的 "delegate" 屬性參考至委託物件,例如委託物件可以設計如下:

MultiActionDemo2 BookDelegate.java

package onlyfun.caterpillar;

import javax.servlet.http.*;

import org.springframework.web.servlet.ModelAndView;

public class BookDelegate {

private String testPage;

public ModelAndView list(HttpServletRequest req,

HttpServletResponse res) {

Page 41: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�41

return new ModelAndView(

this.getTestPage(),"executed", "list");

}

public ModelAndView add(HttpServletRequest req,

HttpServletResponse res) {

return new ModelAndView(

this.getTestPage(),"executed", "add");

}

public ModelAndView delete(HttpServletRequest req,

HttpServletResponse res) {

return new ModelAndView(

this.getTestPage(),"executed", "delete");

}

public String getTestPage() {

return testPage;

}

public void setTestPage(String testPage) {

this.testPage = testPage;

}

} 現在不用繼承 MultiActionController類別來定義 Controller類別,而是在定義檔中直接定義 MultiActionController的實例:

MultiActionDemo2 mvc-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="urlMapping"

class="org.springframework.web.servlet.

→ handler.SimpleUrlHandlerMapping">

<property name="mappings">

Page 42: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�42

<props>

<prop key="/book.do">bookController</prop>

</props>

</property>

</bean>

<bean id="viewResolver"

class="org.springframework.web.servlet.

→ view.InternalResourceViewResolver">

<property name="prefix" value="/WEB-INF/jsp/"/>

<property name="suffix" value=".jsp"/>

</bean>

<bean id="paraMethodResolver"

class="org.springframework.web.servlet.mvc.

→ multiaction.ParameterMethodNameResolver">

<property name="paramName" value="action"/>

<property name="defaultMethodName" value="list"/>

</bean>

<bean id="bookDelegate"

class="onlyfun.caterpillar.BookDelegate">

<property name="testPage" value="test"/>

</bean>

<bean id="bookController"

class="org.springframework.web.servlet.mvc.

→ multiaction.MultiActionController">

<property name="methodNameResolver"

ref="paraMethodResolver"/>

<property name="delegate" ref="bookDelegate"/>

</bean>

</beans> 您可以使用 MultiActionDemo 專案中的 test.jsp 網頁來呈現結果,這個程式的執行結果與 MultiActionDemo 專案是相同的,可以參考圖 7.11的畫面。

Page 43: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�43

上面的程式,也可以使用 PropertiesMethodNameResolver,例如將Bean定義檔改寫為以下的內容:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="urlMapping"

class="org.springframework.web.servlet.

→ handler.SimpleUrlHandlerMapping">

<property name="mappings">

<props>

<prop key="/*book.do">bookController</prop>

</props>

</property>

</bean>

<bean id="viewResolver"

class="org.springframework.web.servlet.

→ view.InternalResourceViewResolver">

<property name="prefix" value=”/WEB-INF/jsp/”/>

<property name="suffix" value=”.jsp”/>

</bean>

<bean id="propMethodResolver"

class="org.springframework.web.servlet.mvc.

→ multiaction.PropertiesMethodNameResolver">

<property name="mappings">

<props>

<prop key="/listbook.do">list</prop>

<prop key="/addbook.do">add</prop>

<prop key="/deletebook.do">delete</prop>

</props>

</property>

</bean>

<bean id="bookDelegate"

class="onlyfun.caterpillar.BookDelegate">

<property name="testPage" value=”test”/>

Page 44: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�44

</bean>

<bean id="bookController"

class="org.springframework.web.servlet.mvc.

→ multiaction.MultiActionController">

<property name="methodNameResolver" ref="propMethodResolver"/>

<property name="delegate" ref="bookDelegate"/>

</bean>

</beans> 根據以上的設定,所有 book.do為結尾的請求都會交給 bookController來處理,而請求 /listbook.do時,會執行 bookDelegate的 list() 方法,依同樣的道理,可以分別使用 /addbook.do或 /deletebook.do來執行 add() 或 list() 方法,一個執行的參考畫面如下所示:

圖 7.12 使用 PropertiesMethodNameResolver 的執行結果

7.2.4 ParameterizableViewController

ParameterizableViewController類別是 AbstractController類別的子類別,可以直接在 Bean定義檔中定義,設定它的 "viewName" 屬性,當這個 Controller執行時,會根據 "viewName" 的設定直接導向指定的網頁,使用它的好處是可以用來避免直接對 JSP 網頁請求,例如可以設計一個index.jsp,為了讓所有的請求都透過 DispatcherServlet 控制,可以將index.jsp放置在 WEB-INF下,並直接在 Bean定義檔中這麼定義:

Page 45: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�4�

...

<beans>

<bean id="urlMapping"

class="org.springframework.web.servlet.

→ handler.SimpleUrlHandlerMapping">

<property name="mappings">

<props>

<prop key="/index.do">indexController</prop>

....

</props>

</property>

</bean>

<bean id="viewResolver"

class="org.springframework.web.servlet.

→ view.InternalResourceViewResolver">

<property name="prefix" value=”/WEB-INF/jsp/”/>

<property name="suffix" value=”.jsp”/>

</bean>

<bean id="indexController"

class="org.springframework.web.servlet.

→ mvc.ParameterizableViewController">

<property name="viewName" value=”index”/>

</bean>

<beans> 依以上的設定,當連接的網址請求是 /index.do時,則會連接至所指定的 /WEB-INF/jsp/index.jsp網頁。

7.2.5 AbstractCommandController 使用 Controller或是 AbstractController,當遇到使用者提供請求參數時,必須自行從 HttpServletRequest 物件中取得請求參數,如果打算將這些請求參數設定至某個物件,必須自行撰寫相關邏輯或進行型態轉換等 動作。

Page 46: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�46

可 以 繼 承 org.springframework.web.servlet.mvc.AbstractCom-

mandController來實作 Controller類別,並提供一個 Command物件,當使用者請求來到時,AbstractCommandController會試著比對請求參數名稱與 Command上的 Setter方法名稱(也就是 setXXX方法名稱),並呼叫對應的 Setter方法設定請求參數值,如果 Setter上的參數是基本型態,也會自動轉換請求參數值為對應的型態。 舉個例子來說,假設您設計了一個 User類別作為 Command類別:

AbstractCommandDemo User.java

package onlyfun.caterpillar;

public class User {

private String name;

private String password;

public void setName(String name) {

this.name = name;

}

public void setPassword(String password) {

this.password = password;

}

public String getName() {

return name;

}

public String getPassword() {

return password;

}

} 如果請求中包括了 "name" 與 "password" 參數,而這些參數想要封裝為User實例,則繼承 AbstractCommandController實作 Controller時,可以使

Page 47: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�47

用 setCommandClass() 來指定 Command類別,並重新定義它的 handle() 方法,例如:

AbstractCommandDemo UserController.java

package onlyfun.caterpillar;

import java.util.HashMap;

import java.util.Map;

import javax.servlet.http.*;

import org.springframework.web.servlet.ModelAndView;

import org.springframework.web.servlet.

mvc.AbstractCommandController;

import org.springframework.validation.BindException;

public class UserController extends AbstractCommandController {

private String userPage;

public UserController() {

setCommandClass(User.class);

}

protected ModelAndView handle(HttpServletRequest request,

HttpServletResponse response,

Object command,

BindException exception) throws Exception {

User user = (User) command;

Map model = new HashMap();

model.put("name", user.getName());

model.put("password", user.getPassword());

return new ModelAndView(getUserPage(), "userInfo", model);

}

public void setUserPage(String userPage) {

this.userPage = userPage;

}

public String getUserPage() {

Page 48: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�48

return userPage;

}

}

UserController的 handle() 方法中,直接取得了 Command的實例,並在轉換操作介面為 User之後,直接操作 getName() 與 getPassword() 方法來取得請求參數,在以上使用了一個 Map物件來收集資訊,以在 JSP網頁中呈現這些資訊,假設 JSP網頁設計如下:

AbstractCommandDemo userInfo.jsp

<%@ page language="java" contentType="text/html; charset=BIG5"

pageEncoding="BIG5"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=BIG5">

<title>AbstractCommandController Demo</title>

</head>

<body>

<h1>名稱:${userInfo['name']}</h1>

<h1>密碼:${userInfo['password']}</h1>

</body>

</html> 接著可以在 Bean定義檔中設定相關的 Bean資訊,如下所示:

AbstractCommandDemo mvc-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="urlMapping"

class="org.springframework.web.servlet.

→ handler.SimpleUrlHandlerMapping">

Page 49: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�4�

<property name="mappings">

<props>

<prop key="/user.do">userController</prop>

</props>

</property>

</bean>

<bean id="viewResolver"

class="org.springframework.web.servlet.

→ view.InternalResourceViewResolver">

<property name="prefix" value="/WEB-INF/jsp/"/>

<property name="suffix" value=".jsp"/>

</bean>

<bean id="userController"

class="onlyfun.caterpillar.UserController">

<property name="userPage" value="userInfo"/>

</bean>

</beans> 來看看一個執行的結果畫面,在請求時直接在網址列附上請求參數:

圖 7.13 AbstractCommandDemo 專案的執行結果

7.2.6 AbstractFormController 對於簡單的表單需求,通常透過繼承 org.springframework.web.servlet.

mvc.SimpleFormController來定義 Controller,並重新定義 onSubmit() 或doSubmitAction() 方法來完成表單功能,而實際上真正的表單處理流程定

Page 50: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7���

義在 org.springframework.web.servlet.mvc.AbstractFormController,在 API文件中,有說明其一連串的方法之作用,這邊整理一下當中的說明:

1. 執行 formBackingObject() 方法,要回傳一個 commandClass 的實例。

2. 執行 initBinder() 方法,允許您對 Command 特定的 Field 作格式化等動作,例如日期格式、金錢格式等,提供非基本型態或 String 的設定。

3. 執行 showForm() 方法,返回一個 View 物件,用於表單的展現。

4. 如果 "bindOnNewForm" 屬性被設定為 true,則應用 ServletRequest-

DataBinder 根據初始請求參數來填入一個新的表單物件,並且執行onBindOnNewForm() 方法。

5. showForm() 方法會執行 referenceData() 方法,如果在展現表單的過程中,需要一些相關資料,可以回傳一個 Map 物件,例如:

protected Map referenceData(

HttpServletRequest request) throws Exception {

Map ref = new HashMap();

ref.put("defalutUser", "guest");

return ref;

} 這樣一來,就可以在 JSP 網頁中使用 ${defaultUser} 的寫法來呈現資料,例如所取得的值會是 "guest"。

6. Model 物件被展現,使用者可以看到表單並進行填寫。 使用者填寫完表單之後送出表單,將會依以下的流程進行:

1. 接受使用者的表單送出,通常使用 POST,如果不想用 POST 這樣的方式來判斷使用者是否送出表單,可以重新定義 isFormSubmission() 方法。

2. 如果 "sessionForm" 屬性沒有設定,則會執行 formBackingObject() 方法以取得表單物件,否則嘗試從 session 中取得表單物件,如果沒有找到則執

Page 51: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7��1

行 handleInvalidSubmit() 方法,預設的動作是嘗試建立新的表單物件並重新填寫表單。

3. ServletRequestDataBinder 根據目前的請求參數填入表單物件。

4. 執行 onBind() 方法,在綁定資料之後,驗證資料之前可以對表單物件進行一些自訂的修改動作。

5. 如果 "validateOnBinding" 屬性被設定,則使用驗證器。

6. 執行 onBindAndValidate() 方法,允許您在驗證資料之後對表單物件作一些處理。

7. 表單物件處理完畢,最後執行 processFormSubmission() 方法,子類別要重新定義這個方法,以完成使用者的請求。

7.2.7 SimpleFormController

org.springframework.web.servlet.mvc.SimpleFormController 是AbstractFormController 的子類別,對於簡單的表單可以直接繼承這個類別,並重新定義 onSubmit() 或 doSubmitAction() 方法以處理使用者的請求,SimpleFormController的處理流程是這樣的:

1. 執行 processFormSubmission() 以檢驗 Errors 物件,看看在綁定或驗證時有無任何的錯誤。

2. 如果有錯誤發生,回傳 "formView" 所設定的頁面。

3. 如果 isFormChangeRequest() 根據 request 被重新定義並回傳 true,則也會回到 "formView",在回到 "formView" 之前會執行 onFormChange(),讓您有機會修正表單物件。

4. 如果沒有錯誤發生,則會執行帶有 HttpServletRequest、HttpServlet-

Response、Object、BindException 參數的 onSubmit(),預設是執行僅帶Object 及 BindException 的 onSubmit(),而後者預設是執行只有 Object

Page 52: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7��2

參數的 onSubmit(),預設是導向設定的 "successView",可以考慮重新定義doSubmitAction(),不用返回 ModelAndView,預設會導向 "success-

View",在不需要向 "successView" 傳送任何 Model 資料物件時可以使用。 以下實作一個表單登入程式,以示範 SimpleFormController的作用,首先設計一個 form.jsp:

SimpleFormDemo form.jsp

<%@ page language="java" contentType="text/html; charset=BIG5"

pageEncoding="BIG5"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=BIG5">

<title>Login Form</title>

</head>

<body>

<h1>登入表單</h1>

請輸入使用者名稱與密碼:<p>

<form name="loginform"

action="/SimpleFormDemo/login.do" method="POST">

名稱 <input type="text" name="username"/><br>

密碼 <input type="password" name="password"/><br>

<input type="submit" value="確定"/>

</form>

注意:輸入錯誤會再回到這個頁面中。

</body>

</html> 表單中傳遞兩個參數 "username" 與 "password",注意表單的 "method" 屬性設定為 "POST",這樣 Spring才會將請求參數填入表單物件中。先看看 LoginController的撰寫:

Page 53: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7��3

SimpleFormDemo LoginController.java

package onlyfun.caterpillar;

import org.springframework.web.servlet.

mvc.SimpleFormController;

import org.springframework.web.servlet.*;

public class LoginController extends SimpleFormController {

public LoginController() {

setCommandClass(LoginForm.class);

}

protected ModelAndView onSubmit(

Object command) throws Exception {

LoginForm form = (LoginForm) command;

if("caterpillar".equals(form.getUsername()) &&

"123456".equals(form.getPassword())) {

return new ModelAndView(

getSuccessView(),"user", form.getUsername());

}

else {

return new ModelAndView(getFormView());

}

}

}

onSumit() 方法接收 command 物件,這個物件代表了表單物件,getSuccessView() 與 getFormView() 是 SimpleFormController中定義的兩個方法,分別表示通過驗證與失敗的目標網頁,待會可以直接在 Bean定義檔中定義,先來看看表單物件,它只是個簡單的 JavaBean:

SimpleFormDemo LoginForm.java

package onlyfun.caterpillar;

public class LoginForm {

Page 54: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7��4

private String username;

private String password;

public void setUsername(String username) {

this.username = username;

}

public void setPassword(String password) {

this.password = password;

}

public String getUsername() {

return username;

}

public String getPassword() {

return password;

}

} 接下來看看 Bean定義檔是如何撰寫的:

SimpleFormDemo mvc-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="urlMapping"

class="org.springframework.web.servlet.

→ handler.SimpleUrlHandlerMapping">

<property name="mappings">

<props>

<prop key="/login.do">loginController</prop>

</props>

</property>

</bean>

<bean id="viewResolver"

Page 55: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7���

class="org.springframework.web.servlet.

→ view.InternalResourceViewResolver">

<property name="prefix" value="/WEB-INF/jsp/"/>

<property name="suffix" value=".jsp"/>

</bean>

<bean id="loginController"

class="onlyfun.caterpillar.LoginController">

<property name="successView" value="success"/>

<property name="formView" value="form"/>

</bean>

</beans> 定義檔中定義了 "successView" 與 "formView",其中 form.jsp之前已經撰寫完成,來看一下 success.jsp的撰寫:

SimpleFormDemo success.jsp

<%@ page language="java" contentType="text/html; charset=BIG5"

pageEncoding="BIG5"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=BIG5">

<title>Login Success</title>

</head>

<body>

<H1>哈囉! ${user}!!</H1>

這是您的神秘禮物!^o^

</body>

</html> 直接請求 login.do的話,由於未通過登入檢查,所以會回傳表單登入畫面,如下所示:

Page 56: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7��6

圖 7.14 SimpleFormDemo 專案的登入表單 登入成功的話,會進入 success.jsp並顯示使用者的名稱,登入失敗的話,依 "formView" 的設定,會回到原來的登入表單,登入成功的畫面如下所示:

圖 7.15 SimpleFormDemo 專案的登入成功畫面

7.2.8 AbstractWizardFormController

org.springframework.web.servlet.mvc.AbstractWizardFormCont-

roller 可以讓您設計出如桌面應用程式的精靈(Wizard),通常用於問卷表單內容相當長時,與其使用一個表單呈現所有的問卷內容,不如將問卷內容分作數個畫面,讓使用者一頁一頁完成問卷,您可以繼承 Abstract-

WizardFormController,並重新定義 processFinish() 方法,當中定義的是全部問卷提交後的處理,例如:

Page 57: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7��7

AbstractWizardDemo WizardController.java

package onlyfun.caterpillar;

import java.util.*;

import javax.servlet.http.*;

import org.springframework.web.servlet.ModelAndView;

import org.springframework.web.servlet.

mvc.AbstractWizardFormController;

import org.springframework.validation.BindException;

public class WizardController

extends AbstractWizardFormController {

private String successView;

public WizardController() {

setCommandClass(Questionnaire.class);

}

protected ModelAndView processFinish(

HttpServletRequest request,

HttpServletResponse response,

Object command,

BindException exception) throws Exception {

Questionnaire questionnaire = (Questionnaire) command;

Map model = new HashMap();

model.put("q1", questionnaire.getQuestion1());

model.put("q2", questionnaire.getQuestion2());

model.put("q3", questionnaire.getQuestion3());

return new ModelAndView(getSuccessView(), "ans", model);

}

public void setSuccessView(String successView) {

this.successView = successView;

}

public String getSuccessView() {

return successView;

}

}

Page 58: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7��8

所有的問卷結果收集在一個 Command 物件中,上面的程式只是將結果取得並設定在 Model物件中,以在 View上呈現資料,Command的類別設計如下:

AbstractWizardDemo Questionnaire.java

package onlyfun.caterpillar;

public class Questionnaire {

private String question1;

private String question2;

private String question3;

public void setQuestion1(String question1) {

this.question1 = question1;

}

public void setQuestion2(String question2) {

this.question2 = question2;

}

public void setQuestion3(String question3) {

this.question3 = question3;

}

public String getQuestion1() {

return question1;

}

public String getQuestion2() {

return question2;

}

public String getQuestion3() {

return question3;

}

}

Page 59: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7���

問卷的流程是規範在 Bean定義檔中,藉由設定 AbstractWizardForm-

Controller的 "pages" 屬性來決定,例如:

AbstractWizardDemo mvc-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="urlMapping"

class="org.springframework.web.servlet.

→ handler.SimpleUrlHandlerMapping">

<property name="mappings">

<props>

<prop key="/wizard.do">wizardController</prop>

</props>

</property>

</bean>

<bean id="viewResolver"

class="org.springframework.web.servlet.

→ view.InternalResourceViewResolver">

<property name="prefix" value="/WEB-INF/jsp/"/>

<property name="suffix" value=".jsp"/>

</bean>

<bean id="wizardController"

class="onlyfun.caterpillar.WizardController">

<property name="successView" value="success"/>

<property name="pages">

<list>

<value>start</value>

<value>question1</value>

<value>question2</value>

<value>question3</value>

</list>

</property>

</bean>

</beans>

Page 60: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�6�

依以上的設定,問卷將依 start.jsp、question1.jsp、question2.jsp、ques-

tion3.jsp的順序來完成,首先看看 start.jsp的撰寫:

AbstractWizardDemo start.jsp

<%@ page language="java" contentType="text/html; charset=BIG5"

pageEncoding="BIG5"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=BIG5">

<title>Insert title here</title>

</head>

<body>

<form name="questionform"

action="/AbstractWizardDemo/wizard.do" method="POST">

歡迎您填寫問卷!

<input type="submit" value="Start" name="_target1"/>

</form>

</body>

</html> 當您連接至 Wizard 控制器時,預設會取得設定檔中索引 0 位置的網頁,第一個頁面通常是歡迎或問卷說明頁面,決定下一個要展示的頁面之方法,在於請求中有無一個 "_target" 開頭,並跟著一個號碼的請求參數,例如 "_target1" 這樣的設定,號碼表示在設定檔中 <list> 的順序,上面的網頁在按下 Start按鈕後,會顯示第二個頁面:

AbstractWizardDemo question1.jsp

<%@ page language="java" contentType="text/html; charset=BIG5"

pageEncoding="BIG5"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=BIG5">

<title>Question 1</title>

Page 61: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�61

</head>

<body>

<form name="questionform1"

action="/AbstractWizardDemo/wizard.do" method="POST">

問題一 <input type="text" name="question1"/><br>

<input type="submit" value="Previous" name="_target0"/>

<input type="submit" value="Next" name="_target2"/>

</form>

</body>

</html> 依 "_target" 與號碼來決定顯示的頁面是 getTaretPage() 方法所預設的,必要的話,也可以重新定義這個方法以決定下一個頁面由哪一個參數決定,在 question1.jsp的設計中,按下 Previous按鈕的話,會返回問卷的上一頁,按下 Next的按鈕的話,會返回問卷的下一頁,question2.jsp的設計也是類似,為節省篇幅,請自行觀看光碟中 AbstractWizardDemo 專案的 question2.jsp檔案。 決定是否完成問卷的方式,在於檢驗請求中是否有請求參數 "_finish",如果檢查到這個值,就會執行 processFinish() 方法,question3.jsp 是最後一個問卷,來看看它是如何撰寫的:

AbstractWizardDemo question3.jsp

<%@ page language="java" contentType="text/html; charset=BIG5"

pageEncoding="BIG5"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=BIG5">

<title>Question 3</title>

</head>

<body>

<form name="questionform3"

action="/AbstractWizardDemo/wizard.do" method="POST">

問題三 <input type="text" name="question3"/><br>

<input type="submit" value="Previous" name="_target2"/>

Page 62: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�62

<input type="submit" value="Finish" name="_finish"/>

</form>

</body>

</html> 記得最後一個送出的問卷要帶有一個 "_finish" 參數,才會執行AbstractWizardFormControlle 的 processFinish() 方法,最後設計一個success.jsp來顯示結果:

AbstractWizardDemo success.jsp

<%@ page language="java" contentType="text/html; charset=BIG5"

pageEncoding="BIG5"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=BIG5">

<title>Success</title>

</head>

<body>

<H1>Q1: ${ans.q1}</H1> <br>

<H1>Q2: ${ans.q2}</H1> <br>

<H1>Q3: ${ans.q3}</H1> <br>

</body>

</html> 一個問卷提交後的結果畫面如下所示:

圖 7.16 AbstractWizardDemo 專案的提交結果畫面

Page 63: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�63

您也可以在問卷中設計一個 Cancel 按鈕,在按下該按鈕後,送出

"_cancel" 請求參數,例如:

....

<form name="questionform3"

action="/AbstractWizard/wizard.do" method="POST">

....

<input type="submit" value="Cancel" name="_cancel"/>

</form>

.... 當請求中帶有 "_cancel" 請求參數時,則會執行 processCancel() 方法,您可以重新定義這個方法,例如將使用者送回問卷開始時的頁面:

...

protected ModelAndView processCancel(

HttpServletRequest request,

HttpServletResponse response,

Object command,

BindException exception) throws Exception {

return new ModelAndView("start");

}

...

7.2.9 ThrowawayController

org.springframework.web.servlet.mvc.throwaway.ThrowawayCon-

troller並不在 Spring的 Controller繼承架構中,而是一個獨立的介面定義,實作該介面的類別不是以單例的方式產生並重複使用,Throwaway-

Controller 在每一次請求時都要產生新的物件,且用了就丟,不會重複使用,這也是名稱上帶有"Throwaway"的原因(ThrowawayController提供的是類似 Webwork形式的 Action物件)。

Page 64: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�64

ThrowawayController介面的定義如下:

package org.springframework.web.servlet.mvc.throwaway;

import org.springframework.web.servlet.ModelAndView;

public interface ThrowawayController {

ModelAndView execute() throws Exception;

} 實作 ThrowawayController 介面的類別必須實作 execute() 方法,可以看到 execute() 方法中不帶任何的參數,這使得 ThrowawayController易於測試,不需要像 Controller 必須有 HttpServletRequest、HttpServlet-

Response的實例。

execute() 方法上沒有任何的參數,如果要收集請求參數的話,ThrowawayController 本身也兼具 Command 的作用(這點熟悉 Webwork的人並不陌生),例如可以如下實作一個 ThrowawayController:

....

public class SomeController implements ThrowawayController {

private String someParam;

public ModelAndView execute() throws Exception {

....

return new ModelAndView(...);

}

public void setSomeParam(String someParam) {

this.someParam = someParam;

}

....

}

Page 65: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�6�

當請求參數中帶有 "someParam" 的參數時,會將之設定至 Throwa-

wayController對應的 setSomeParam() 方法中。 在設定 Bean 定義檔中必須注意的是,ThrowawayController 必須將

"scope" 屬性設定為 "singleton",每一次的請求要產生一個新的實例,用過即丟,例如:

...

<bean id="someController"

class="onlyfun.caterpillar.SomeController"

scope="singleton">

....

</bean>

...

DispatcherServlet 預設會使用 org.springframework.web.servlet.mvc.

SimpleControllerHandlerAdapter,這使得 Controller繼承體系下的 Controller類別都可以直接為 DispatcherServlet使用,但為了要告訴 DispatchServlet使用 ThrowawayController,必須在 Bean定義檔中宣告,例如:

...

<bean id="throwawayHandler"

class="org.springframework.web.servlet.mvc.

→ throwaway.ThrowawayControllerHandlerAdapter"/>

<bean id="simpleHandler"

class="org.springframework.web.servlet.

→ mvc.SimpleControllerHandlerAdapter"/>

... 在上面的設定中,DispatcherServlet可以使用 ThrowawayController,如果您要混合 Controller 介面的繼承體系,則再加入 SimpleController-

HandlerAdapter,如此兩種 Handler就可以混合使用。

Page 66: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�66

7.3 搭配 Controller的相關類別 在這個小節中,將介紹如何在 Controller上搭配使用驗證器(Validator)、如何實作 Command資料的型態轉換,以及如何使用 Spring的 API來實作檔案上傳功能。

7.3.1 實作 Validator 使用 SimpleFormController時,可以搭配一個 org.springframework.

validation.Validator介面的實作物件,幫助您作基本的伺服端資料驗證工作,可以設計一個通用的 Validator,將之作為 Bean注入給需要進行表單驗證的其它 Bean物件。

Validator介面有兩個必須實作的方法,其定義如下所示:

package org.springframework.validation;

public interface Validator {

boolean supports(Class clazz);

void validate(Object obj, Errors errors);

}

supports() 方法回傳一個 boolean 值,表示是否支援對所傳入的物件進行驗證,只有在傳回 true的情況下,才會使用 validate() 方法進行驗證工作,在 validate() 方法的參數中,obj表示傳入的表單物件,您可以對它進行一些驗證,如果有錯誤的話,可以使用 Errors 的 reject() 或 reject-

Value() 等方法加入錯誤訊息,在後續的處理中,若 errors 物件中包括錯誤訊息,會回到 getViewPage() 所設定的頁面。

Page 67: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�67

來看一個簡單的實作:

package onlyfun.caterpillar;

import org.springframework.validation.Validator;

import org.springframework.validation.Errors;

public class LoginValidator implements Validator {

public boolean supports(Class clazz) {

return clazz.equals(LoginForm.class);

}

public void validate(Object obj, Errors errors) {

LoginForm form = (LoginForm) obj;

if(form.getPassword().length() < 4 ) {

errors.rejectValue("password",

"less-than-four", null, "密碼不得小於四個字元");

}

}

} 這個 Validator類別可以搭配 7.2.7介紹的 SimpleFormDemo專案來使用,驗證通常是在檢查資料的完備性、安全性等條件,像是檢查密碼的格式就是一例,至於使用者名稱與密碼是否正確,這應該是 Controller 的處理工作。 搭配 SimpleFormController來使用 Validator,只要在 Bean定義檔中加入 Validator的 Bean定義,以及讓 SimpleFormController參考到它即可,例如:

...

<bean id="loginValidator"

class="onlyfun.caterpillar.LoginValidator"/>

<bean id="loginAction"

class="onlyfun.caterpillar.LoginAction">

<property name="commandClass"

Page 68: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�68

value=”onlyfun.caterpillar.LoginForm”/>

<property name="validator" ref="loginValidator"/>

<property name="successView" value=”success”/>

<property name="formView" value=”form”/>

</bean>

...

"validator" 屬性參考至 loginValidator實例,只要有設定 "validator",SimpleFormController就會使用它。 在進行驗證時,您可以使用 org.springframework.validation.Valida-

tionUtils,上面有一些方便的靜態方法,像是 rejectIfEmpty()、rejectIf-

EmptyOrWhitespace() 等,可以查查線上 API文件看如何使用。 另一方面,對於 AbstractWizardFormController,可以重新定義 vali-

datePage() 方法來進行驗證,例如:

...

protected void validatePage(Object command,

Errors errors, int page) {

(YourCommand) your = (YourCommand) command;

switch(page) {

case 1:

validator.validateSome(your, errors);

break;

case 2:

validator.validateOther(your, errors);

break;

....

}

}

... 每進行一頁 Wizard表單,都會呼叫該方法一次,讓您針對該次送出的數據進行驗證,"page" 參數表示該次送出表單的頁數,與 SimpleForm-

Controller的 Validator不同的是,AbstractWizardFormController不會自動

Page 69: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�6�

執行 Validator的 validate() 方法,您要根據當次頁面的資料進行對應的方法驗證,通常採取如上的委託物件,將驗證交由一個 Validator物件來執行。

7.3.2 使用 PropertyEditor 對於 BaseCommandController及其子類別來說,它的 Command物件並不一定要接受基本型態或是 String 型態,您可以撰寫一個實作 java.

beans.PropertyEditor的類別,在當中進行轉換,例如將接收到的字串轉換為 User類別的實例。 舉個實例來說,假設 Command物件如下設計:

PropertyEditorDemo SomeForm.java

package onlyfun.caterpillar;

public class SomeForm {

private User user;

public void setUser(User user) {

this.user = user;

}

public User getUser() {

return user;

}

}

SomeForm為網頁表單的物件代表,而其中出現的 User類別如下設計:

PropertyEditorDemo User.java

package onlyfun.caterpillar;

public class User {

private String firstName;

Page 70: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�7�

private String lastName;

public void setFirstName(String firstName) {

this.firstName = firstName;

}

public void setLastName(String lastName) {

this.lastName = lastName;

}

public String getFirstName() {

return firstName;

}

public String getLastName() {

return lastName;

}

}

Command物件將接收一個自訂型態的 User實例,然而從 HTTP接收到的參數值是 String 型態,您可以撰寫一個 UserPropertyEditor 類別做轉換,通常直接繼承 java.beans.PropertyEditorSupport 並重新定義getAsText() 及 setAsText() 方法,例如:

PropertyEditorDemo UserPropertyEditor.java

package onlyfun.caterpillar;

import java.beans.PropertyEditorSupport;

public class UserPropertyEditor extends PropertyEditorSupport {

public String getAsText() {

Object o = this.getValue();

if(o == null || !(o instanceof User)) {

return null;

}

User user = (User) o;

String name = user.getFirstName()

Page 71: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�71

+ "," + user.getLastName();

return name;

}

public void setAsText(String text) {

String[] tokens = text.split(",");

User user = new User();

user.setFirstName(tokens[0]);

user.setLastName(tokens[1]);

setValue(user);

}

} 當必須從指定的物件轉換為字串時,會執行 getAsText() 方法,而接收到參數要將之轉換為指定的物件時,會執行 setAsText() 方法,接著來撰寫一個測試的 Controller:

PropertyEditorDemo SomeFormController.java

package onlyfun.caterpillar;

import org.springframework.web.servlet.

mvc.SimpleFormController;

import org.springframework.web.servlet.*;

import java.util.*;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.

bind.ServletRequestDataBinder;

public class SomeFormController extends SimpleFormController {

public SomeFormController() {

setCommandClass(SomeForm.class);

}

protected ModelAndView onSubmit(

Object command) throws Exception {

SomeForm form = (SomeForm) command;

Page 72: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�72

Map model = new HashMap();

model.put("firstName",

form.getUser().getFirstName());

model.put("lastName",

form.getUser().getLastName());

return new ModelAndView(this.getSuccessView(), model);

}

protected void initBinder(HttpServletRequest request,

ServletRequestDataBinder binder)

throws Exception {

super.initBinder(request, binder);

binder.registerCustomEditor(

User.class, new UserPropertyEditor());

}

} 注意到這邊重新定義了 initBinder() 方法,並在當中使用 Servlet-

RequestDataBinder 的 registerCustomEditor() 方法註冊自訂的 Property-

Editor,來看看定義檔的內容:

PropertyEditorDemo mvc-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="urlMapping"

class="org.springframework.web.servlet.

→ handler.SimpleUrlHandlerMapping">

<property name="mappings">

<props>

<prop key="/index.do">indexController</prop>

<prop key="/someForm.do">someFormController</prop>

</props>

</property>

</bean>

Page 73: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�73

<bean id="viewResolver"

class="org.springframework.web.servlet.

→ view.InternalResourceViewResolver">

<property name="prefix" value="/WEB-INF/jsp/"/>

<property name="suffix" value=".jsp"/>

</bean>

<bean id="indexController"

class="org.springframework.web.servlet.

→ mvc.ParameterizableViewController">

<property name="viewName" value="index"/>

</bean>

<bean id="someFormController"

class="onlyfun.caterpillar.SomeFormController">

<property name="successView" value="hello"/>

</bean>

</beans> 現在假設撰寫有一個測試網頁:

PropertyEditorDemo index.jsp

<%@ page language="java" contentType="text/html; charset=BIG5"

pageEncoding="BIG5"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=BIG5">

<title>Form</title>

</head>

<body>

<form name="someForm"

action="/PropertyEditorDemo/someForm.do" method="POST">

使用者 <input type="text" name="user"/><br>

<input type="submit" value="Submit"/>

</form>

</body>

</html>

Page 74: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�74

在輸入欄位中設定了"user"參數,當資料送出後,會經由 PropertyEditor的轉換,假設呈現處理結果的 hello.jsp網頁如下撰寫:

PropertyEditorDemo hello.jsp

<%@ page language="java" contentType="text/html; charset=BIG5"

pageEncoding="BIG5"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=BIG5">

<title>User Info</title>

</head>

<body>

<h1>${firstName} - ${lastName}</h1>

</body>

</html> 如果在 index.jsp填入 "Justin,Lin",則結果會顯示以下的內容:

圖 7.17 PropertyEditorDemo 專案的結果畫面

7.3.3 檔案上傳 如果想要進行檔案上傳的動作,則可以使用實作 org.springframe-

work.web.multipart.MultipartResolver 介面的類別,Spring 提供 org.

springframework.web.multipart.commons.CommonsMultipartResolver 與org.springframework.web.multipart.cos.CosMultipartResolver,分別支援 Commands FileUpload及 COS FileUpload。

Page 75: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�7�

舉個實際的例子,假設您設計了一個 FileForm類別:

FileUploadDemo FileForm.java

package onlyfun.caterpillar;

public class FileForm {

private String name;

private byte[] contents;

public void setName(String name) {

this.name = name;

}

public void setContents(byte[] contents) {

this.contents = contents;

}

public String getName() {

return name;

}

public byte[] getContents() {

return contents;

}

}

FileForm中接受檔案上傳的屬性型態是 byte陣列,使用者上傳的檔案可以藉由 org.springframework.web.multipart.support.ByteArrayMulti-

partFileEditor 轉換為 byte 陣列並設定給 FileForm,這必須在定義Controller時註冊,例如:

Page 76: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�76

FileUploadDemo UploadController.java

package onlyfun.caterpillar;

import java.io.BufferedOutputStream;

import java.io.FileOutputStream;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import org.springframework.validation.BindException;

import org.springframework.web.bind.ServletRequestDataBinder;

import org.springframework.web.multipart.

support.ByteArrayMultipartFileEditor;

import org.springframework.web.servlet.ModelAndView;

import org.springframework.web.servlet.

mvc.SimpleFormController;

public class UploadController extends SimpleFormController {

private String path;

public UploadController() {

setCommandClass(FileForm.class);

}

protected ModelAndView onSubmit(

HttpServletRequest request,

HttpServletResponse response,

Object command,

BindException errors)

throws Exception {

FileForm form = (FileForm) command;

String storedPath = path +

System.getProperty("file.separator") +

form.getName();

BufferedOutputStream bufferedOutputStream =

new BufferedOutputStream(

new FileOutputStream(storedPath));

bufferedOutputStream.write(form.getContents());

bufferedOutputStream.close();

Page 77: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�77

return new ModelAndView(getSuccessView(),

"filename", form.getName());

}

protected void initBinder(HttpServletRequest request,

ServletRequestDataBinder binder)

throws Exception {

super.initBinder(request, binder);

binder.registerCustomEditor(

byte[].class, new ByteArrayMultipartFileEditor());

}

public void setPath(String path) {

this.path = path;

}

} 依以上的設定,檔案上傳後會儲存在指定的目錄下,要使用檔案上傳的功能,必須在定義檔中加入 MultipartResolver的定義,可以選擇使用 Com-

monsMultipartResolver 或 CosMultipartResolver,例如以下使用 Commons-

MultipartResolver來定義:

FileUploadDemo mvc-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="urlMapping"

class="org.springframework.web.servlet.

→ handler.SimpleUrlHandlerMapping">

<property name="mappings">

<props>

<prop key="/index.do">indexController</prop>

<prop key="/upload.do">uploadController</prop>

</props>

Page 78: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�78

</property>

</bean>

<bean id="viewResolver"

class="org.springframework.web.servlet.

→ view.InternalResourceViewResolver">

<property name="prefix" value="/WEB-INF/jsp/"/>

<property name="suffix" value=".jsp"/>

</bean>

<bean id="indexController"

class="org.springframework.web.servlet.

→ mvc.ParameterizableViewController">

<property name="viewName" value="index"/>

</bean>

<bean id="multipartResolver"

class="org.springframework.web.multipart.

→ commons.CommonsMultipartResolver">

<property name="maxUploadSize" value="1000000"/>

</bean>

<bean id="uploadController"

class="onlyfun.caterpillar.UploadController">

<property name="formView" value="form"/>

<property name="successView" value="success"/>

<property name="path" value="c:\\upload"/>

</bean>

</beans> 在定義檔中,藉由設定 UploadController的 "path" 屬性為 "C:\upload",表示上傳後的檔案會儲存在 C:\upload目錄之中,接著設計一個 index.jsp:

FileUploadDemo index.jsp

<%@ page language="java" contentType="text/html; charset=BIG5"

pageEncoding="BIG5"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

Page 79: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Chapter 7 Spring Web MVC 框架

7�7�

<head>

<meta http-equiv="Content-Type" content="text/html; charset=BIG5">

<title>Upload Page</title>

</head>

<body>

<form name="uploadForm" enctype="multipart/form-data"

action="/FileUploadDemo/upload.do" method="POST">

上載後檔案名稱: <input name="name" type="text"/><br>

選擇檔案: <input name="contents" type="file"/><br>

<input type="submit" value="Submit"/>

</form>

</body>

</html> 在這個頁面中可以設定上傳後的檔案名稱,並可以選擇所要上傳的檔案,success.jsp則設計如下:

FileUploadDemo success.jsp

<%@ page language="java" contentType="text/html; charset=BIG5"

pageEncoding="BIG5"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=BIG5">

<title>Upload Success</title>

</head>

<body>

<h1>File: ${filename} upload successfully.</h1>

</body>

</html>

這個專案使用到相依的 Commands FileUpload,所以要在 l ib目錄中包括 commons-fileupload.jar 以及 commons-io.jar 檔案。

Page 80: Spring 2.0 技術手冊第七章 - Spring Web MVC 框架

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

7�8�

7.4 接下來的主題 這一個章節對於 Spring Web MVC的主要架構,與各種的 Controller及相應 API作了說明,Spring在 Web層所提供的解決方案還不只有這些,下一個章節將來看一下 Spring如何在 View層技術上提供多種不同的解決方案,以及如何在 Spring中整合其它的 Web框架,像是 Struts、JSF等。

圖 7.18 Spring 讓 MVC 架構的實現更為簡單