struct2 權威指南
這一節通過一個詳細的實例來講解Struct2框架的應用
1 下載和安裝Struts 2框架
(1) 登錄http://struts.apache.org/download.cgi#Struts206站點,下載Struts 2的最新GA版。在Struts 2.06下有如下幾個選項:
— Full Distribution:下載Struts 2的完整版。通常建議下載該選項。
— Example Applications:下載Struts 2的示例應用,這些示例應用對於學習Struts 2有很大的幫助,下載Struts 2的完整版時已經包含了該選項下全部應用。
— Blank Application only:僅下載Struts 2的空示例應用,這個空應用已經包含在Example Applications選項下。
— Essential Dependencies:僅僅下載Struts 2的核心庫,下載Struts 2的完整版時將包括該選項下的全部內容。
— Documentation:僅僅下載Struts 2的相關文檔,包含Struts 2的使用文檔、參考手冊和API文檔等。下載Struts 2的完整版時將包括該選項下的全部內容。
— Source:下載Struts 2的全部源代碼,下載Struts 2的完整版時將包括該選項下的全部內容。
— Alternative Java 4 JARs:下載可選的JDK 1.4的支持JAR。下載Struts 2的完整版時將包括該選項下的全部內容。
通常建議讀者下載第一個選項:下載Struts 2的完整版,將下載到的Zip文件解壓縮,該文件就是一個典型的Web結構,該文件夾包含如下文件結構:
— apps:該文件夾下包含了基於Struts 2的示例應用,這些示例應用對於學習者是非常有用的資料。
— docs:該文件夾下包含了Struts 2的相關文檔,包括Struts 2的快速入門、Struts 2的文檔,以及API文檔等內容。
— j4:該文件夾下包含了讓Struts 2支持JDK 1.4的JAR文件。
— lib:該文件夾下包含了Struts 2框架的核心類庫,以及Struts 2的第三方插件類庫。
— src:該文件夾下包含了Struts 2框架的全部源代碼。
(2)將lib文件夾下的Struts2-core-2.0.6.jar、xwork-2.0.1.jar和ognl-2.6.11.jar等必需類庫復制到Web應用的WEB-INF/lib路徑下。當然,如果你的Web應用需要使用Struts 2的更多特性,則需要將更多的JAR文件復制到Web應用的WEB-INF/lib路徑下。如果需要在DOS或者Shell窗口下手動編譯Struts 2相關的程序,則還應該將Struts2-core-2.0.6.jar和xwork-2.0.1.jar添加到系統的CLASSPATH環境變量里。
(3)編輯Web應用的web.xml配置文件
-----------------------------------------------------
<?xml version="1.0" encoding="GBK"?>
<!-- web-app是Web應用配置文件的根元素,指定Web應用的Schema信息 -->
<web-app version="2.4" 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">
<!-- 定義Struts 2的FilterDispatcher的Filter -->
<filter>
<!-- 定義核心Filter的名字 -->
<filter-name>struts2</filter-name>
<!-- 定義核心Filter的實現類 -->
<filter-class>org.apache.Struts2.dispatcher.FilterDispatcher
</ filter-class>
</filter>
<!-- FilterDispatcher用來初始化Struts 2並且處理所有的Web請求 -->
<filter-mapping>
<filter-name>Struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
2 創建一個JSP頁面
----------------------------------------------------
<%@ page language="java" contentType="text/html; charset=GBK"%>
<html>
<head>
<title>登錄頁面</title>
</head>
<body>
<!-- 提交請求參數的表單 -->
<form action="Login.action" method="post">
<table align="center">
<caption><h3>用戶登錄</h3></caption>
<tr>
<!-- 用戶名的表單域 -->
<td>用戶名:<input type="text" name="username"/></td>
</tr>
<tr>
<!-- 密碼的表單域 -->
<td>密 碼:<input type="text" name="password"/></td>
</tr>
<tr align="center">
<td colspan="2"><input type="submit" value="登錄"/><input
type="reset" value="重填" /></td>
</tr>
</table>
</form>
</body>
</html>
3. 創建WEB應用
建立一個Web應用請按如下步驟進行。
在任意目錄新建一個文件夾,筆者將以該文件夾建立一個Web應用。
(1)在第1步所建的文件夾內建一個WEB-INF文件夾。
(2)進入Tomcat,或任何Web容器內,找到任何一個Web應用,將Web應用的WEB-INF下的web.xml文件復制到第2步所建的WEB-INF文件夾下。
(3) 修改復制的web.xml文件,將該文件修改成只有一個根元素的XML文件,修改后的web.xml文件代碼如下
-------------------------------------------------
<?xml version="1.0" encoding="GBK"?>
<!-- web-app是Web應用配置文件的根元素,指定Web應用的Schema信息 -->
<web-app version="2.4" 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">
</web-app>
(4) 在第2步所建的WEB-INF路徑下,新建兩個文件夾:classes和lib,它們分別用於保存單個*.class文件和JAR文件。
(5)經過上面步驟,已經建立了一個空Web應用。將該Web應用復制到Tomcat的webapps路徑下,該Web應用將可以自動部署在Tomcat中。
將2.2節所定義的JSP頁面文件復制到第1步所建的文件夾下,該JSP頁面將成為該Web應用的一個頁面。該Web將有如下文件結構:
Struts2qs
|-WEB-INF
| |-classes
| |-lib
| |-web.xml
|-login.jsp
上面的Struts2qs是Web應用所對應文件夾的名字,可以更改;login.jsp是該Web應用下JSP頁面的名字,也可以修改。其他文件夾、配置文件都不可以修改。
4. 增加Struct2的功能
為了給Web應用增加Struts 2功能,只需要將Struts 2安裝到Web應用中即可。在Web應用中安裝Struts 2框架核心只需要經過如下三個步驟。
(1) 修改web.xml文件,在web.xml文件中配置Struts 2的核心Filter。
(2)將Struts 2框架的類庫復制到Web應用的WEB-INF/lib路徑下。
(3) 在WEB-INF/classes下增加struts.xml配置文件。
下面是增加了Struts 2功能后Web應用的文件結構:
----------------------------------------------------------
Struts2qs
|-WEB-INF
| |-classes(struts.xml)
| |-lib(commons-logging.jar,freemarker.jar,ognl.jar,struts2-core.jar,xwork.jar)
| |-web.xml
|-login.jsp
在上面的文件結構中,lib下Struts 2框架的類庫可能有版本后綴。例如commons-logging.jar,可能是commons-logging-1.1.jar;struts2-core.jar可能是struts2-core-2.0.6.jar。
修改后的web.xml文件在2.1節已經給出了,故此處不再贅述。
4.1 實現控制器
Struts 2下的控制器不再像Struts 1下的控制器,需要繼承一個Action父類,甚至可以無需實現任何接口,Struts 2的控制器就是一個普通的POJO。
實際上,Struts 2的Action就是一個包含execute方法的普通Java類,該類里包含的多個屬性用於封裝用戶的請求參數。下面是處理用戶請求的Action類的代碼:
//Struts 2的Action類就是一個普通的Java類
public class LoginAction
{
//下面是Action內用於封裝用戶請求參數的兩個屬性
private String username;
private String password;
//username屬性對應的getter方法
public String getUsername()
{
return username;
}
//username屬性對應的setter方法
public void setUsername(String username)
{
this.username = username;
}
//password屬性對應的getter方法
public String getPassword()
{
return password;
}
//password屬性對應的setter方法
public void setPassword(String password)
{
this.password = password;
}
//處理用戶請求的execute方法
public String execute() throws Exception
{
//當用戶請求參數的username等於scott,密碼請求參數為tiger時,返回success
字符串
//否則返回error字符串
if (getUsername().equals("scott")
&& getPassword().equals("tiger") )
{
return "success";
}
else
{
return "error";
}
}
}
4.2 配置Action
前面已經介紹過了,struts.xml文件應該放在classes路徑下,該文件主要放置Struts 2的Action定義。定義Struts 2 Action時,除了需要指定該Action的實現類外,還需要定義Action處理結果和資源之間的映射關系。下面是該應用的struts.xml文件的代碼:
-----------------struts.xml-------------------
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Struts 2配置文件的DTD信息 -->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<!-- struts是Struts 2配置文件的根元素 -->
<struts>
<!-- Struts 2的Action必須放在指定的包空間下定義 -->
<package name="strutsqs" extends="struts-default">
<!-- 定義login的Action,該Action的實現類為lee.Action類 -->
<action name="Login" class="lee.LoginAction">
<!-- 定義處理結果和資源之間映射關系。 -->
<result name="error">/error.jsp</result>
<result name="success">/welcome.jsp</result>
</action>
</package>
</struts>
上面映射文件定義了name為login的Action,即:該Action將負責處理向login.action URL請求的客戶端請求。該Action將調用自身的execute方法處理用戶請求,如果execute方法返回success字符串,請求將被轉發到/welcome.jsp頁面;如果execute方法返回error字符串,則請求被轉發到/error.jsp頁面。
我們再增加兩個頁面 error.jsp和welcome.jsp也就完成了基本屬性的配置
第二部分 功能擴展:
5. 改進控制器
5.1 實現Action接口
public interface Action
{
//下面定義了5個字符串常量
public static final String SUCCESS = "success";
public static final String NONE = "none";
public static final String ERROR = "error";
public static final String INPUT = "input";
public static final String LOGIN = "login";
//定義處理用戶請求的execute抽象方法
public String execute() throws Exception;
}
在上面的Action代碼中,我們發現該Action接口里已經定義了5個標准字符串常量:SUCCESS、NONE、ERROR、INPUT和LOGIN,它們可以簡化execute方法的返回值,並可以使用execute方法的返回值標准化。例如對於處理成功,則返回SUCCESS常量,避免直接返回一個success字符串(程序中應該盡量避免直接返回數字常量、字符串常量等)。
因此,借助於上面的Action接口,我們可以將原來的Action類代碼修改為如下:
//實現Action接口來實現Struts 2的Action類
public class LoginAction implements Action
{
//下面是Action內用於封裝用戶請求參數的兩個屬性
private String username;
private String password;
//username屬性對應的getter方法
public String getUsername()
{
return username;
}
//username屬性對應的setter方法
public void setUsername(String username)
{
this.username = username;
}
//password屬性對應的getter方法
public String getPassword()
{
return password;
}
//password屬性對應的setter方法
public void setPassword(String password)
{
this.password = password;
}
//處理用戶請求的execute方法
public String execute() throws Exception
{
//當用戶請求參數的username等於scott,密碼請求參數為tiger時,返回success
字符串
//否則返回error的字符串
if (getUsername().equals("scott")
&& getPassword().equals("tiger") )
{
return SUCCESS;
}
else
{
return ERROR;
}
}
}
對比前面Action和此處的Action實現類,我們發現兩個Action類的代碼基本相似,除了后面的Action類實現了Action接口。因為實現了Action接口,故Action類的execute方法可以返回Action接口里的字符串常量。
5.2 跟蹤用戶狀態
前面的Action處理完用戶登錄后,僅僅執行了簡單的頁面轉發,並未跟蹤用戶狀態信息——通常,當一個用戶登錄成功后,需要將用戶的用戶名添加為Session狀態信息。
為了訪問HttpSession實例,Struts 2提供了一個ActionContext類,該類提供了一個getSession的方法,但該方法的返回值類型並不是HttpSession,而是Map。這又是怎么回事呢?實際上,這與Struts 2的設計哲學有關,Struts 2為了簡化Action類的測試,將Action類與Servlet API完全分離,因此getSession方法的返回值類型是Map,而不是HttpSession。
雖然ActionContext的getSession返回的不是HttpSession對象,但Struts 2的系列攔截器會負責該Session和HttpSession之間的轉換。
為了可以跟蹤用戶信息,我們修改Action類的execute方法,在execute方法中通過ActionContext訪問Web應用的Session。修改后的execute方法代碼如下:
//處理用戶請求的execute方法
public String execute() throws Exception
{
//當用戶請求參數的username等於scott,密碼請求參數為tiger時,返回success字符串
//否則返回error的字符串
if (getUsername().equals("scott")
&& getPassword().equals("tiger") )
{
//通過ActionContext對象訪問Web應用的Session
ActionContext.getContext().getSession().put("user" , getUsername());
return SUCCESS;
}
else
{
return ERROR;
}
}
上面的代碼僅提供了Action類的execute方法,該Action類的其他部分與前面的Action類代碼完全一樣。在上面的Action類通過ActionContext設置了一個Session屬性:user。為了檢驗我們設置的Session屬性是否成功,我們修改welcome.jsp頁面,在welcome.jsp頁面中使用JSP 2.0表達式語法輸出Session中的user屬性。下面是修改后的welcome.jsp頁面代碼:
<%@ page language="java" contentType="text/html; charset=GBK"%>
<html>
<head>
<title>成功頁面</title>
</head>
<body>
歡迎,${sessionScope.user},您已經登錄!
</body>
</html>
上面的JSP頁面與前面的JSP頁面沒有太大改變,除了使用了JSP 2.0語法來輸出Session中的user屬性。關於JSP 2.0表達式的知識,請參看筆者所著的《輕量級J2EE企業應用實戰》一書的第2章。
在如圖2.1所示頁面的“用戶名”輸入框中輸入scott,在“密碼”輸入框中輸入tiger,然后單擊“登錄”按鈕,將看到如圖2.4所示的頁面。
在上面登錄成功的頁面中,已經輸出登錄所用的用戶名:scott,可見在Action通過ActionContext設置Session是成功的。
5.3 添加處理信息
到目前為止,Action僅僅控制轉發用戶請求,JSP頁面並未獲得Action的處理結果。對於大部分Web應用而言,用戶需要獲得請求Action的處理結果,例如,在線購物系統需要查詢某個種類下的商品,則Action調用業務邏輯組件的業務邏輯方法得到該種類下的全部商品,而JSP頁面則獲取該Action的處理結果,並將全部結果迭代輸出。
下面將為應用增加一個Action,該Action負責獲取某個系列的全部書籍。為了讓該Action可以獲取這系列的書籍,我們增加一個業務邏輯組件,它包含一個業務邏輯方法,該方法可以獲取某個系列的全部書籍。
下面是系統所用的業務邏輯組件的代碼:
public class BookService
{
//以一個常量數組模擬了從持久存儲設備(數據庫)中取出的數據
private String[] books =
new String[]{
"Spring2.0寶典" ,
"輕量級J2EE企業應用實戰",
"基於J2EE的Ajax寶典",
"Struts,Spring,Hibernate整合開發"
};
//業務邏輯方法,該方法返回全部圖書
public String[] getLeeBooks()
{
return books;
}
}
上面的業務邏輯組件實際上就是MVC模式中的Model,它負責實現系統業務邏輯方法。理論上,業務邏輯組件實現業務邏輯方法時,必須依賴於底層的持久層組件,但此處的業務邏輯組件則只是返回一個靜態的字符串數組——因為這只是一種模擬。
注意 此處的業務邏輯組件只是模擬實現業務邏輯方法,並未真正調用持久層組件來獲取數據庫信息。
在系統中增加如下Action類,該Action類先判斷Session中user屬性是否存在,並且等於scott字符串——這要求查看圖書之前,用戶必須已經登錄本系統。如果用戶已經登錄本系統,則獲取系統中全部書籍,否則返回登錄頁面。
新增的Action類的代碼如下:
public class GetBooksAction implements Action
{
//該屬性並不用於封裝用戶請求參數,而用於封裝Action需要輸出到JSP頁面信息
private String[] books;
//books屬性的setter方法
public void setBooks(String[] books)
{
this.books = books;
}
//books屬性的getter方法
public String[] getBooks()
{
return books;
}
//處理用戶請求的execute方法
public String execute() throws Exception
{
//獲取Session中的user屬性
String user = (String)ActionContext.getContext().getSession().
get("user");
//如果user屬性不為空,且該屬性值為scott
if (user != null && user.equals("scott"))
{
//創建BookService實例
BookService bs = new BookService();
//將業務邏輯組件的返回值設置成該Action的屬性
setBooks(bs.getLeeBooks());
return SUCCESS;
}
else
{
return LOGIN;
}
}
}
通過上面的Action類,我們發現Action類中的成員屬性,並不一定用於封裝用戶的請求參數,也可能是封裝了Action需要傳入下一個JSP頁面中顯示的屬性。
提示 Action中的成員屬性,並一定用於封裝用戶的請求參數,也可能是封裝了Action需要傳入下一個頁面顯示的值。實際上,這些值將被封裝在ValueStack對象中。
當我們的控制器需要調用業務邏輯方法時,我們直接創建了一個業務邏輯組件的實例,這並不是一種好的做法,因為控制器不應該關心業務邏輯組件的實例化過程。比較成熟的做法可以利用工廠模式來管理業務邏輯組件;當然,目前最流行的方式是利用依賴注入——這將在后面章節里介紹。
注意 實際項目中不會在控制器中直接創建業務邏輯組件的實例,而是通過工廠模式管理業務邏輯組件實例,或者通過依賴注入將業務邏輯組件實例注入控制器組件。
該Action處理用戶請求時,無需獲得用戶的任何請求參數。將該Action配置在struts.xml文件中,配置該Action的配置片段如下:
<!-- 定義獲取系統中圖書的Action,對應實現類為lee.GetBooksAction -->
<action name="GetBooks" class="lee.GetBooksAction">
<!-- 如果處理結果返回login,進入login.jsp頁面 -->
<result name="login">/login.jsp</result>
<!-- 如果處理結果返回success,進入showBook.jsp頁面 -->
<result name="success">/showBook.jsp</result>
</action>
當用戶向getBooks.action發送請求時,該請求將被轉發給lee.GetBooksAction處理。