08 00Struts 2.x基礎

1 Struts 2.x簡介

前提:Struts 1.x和Struts 2.x沒有任何的關係,屬於兩個獨立的版本。那麼爲什麼會出現兩個獨立的版本呢?主要的原因還是在於Struts 1.x的設計結構上。在成名的MVC開發框架裏面,Struts是最早出現的,但是由於最初的設計環境沒有考慮到這麼複雜,所以來講到了今天與其它框架的整合會出現非常麻煩的問題,而且也存在有性能問題,同時自己本身的設計結構也出現了混亂,例如:如果要想使用驗證框架(無用)那麼必須使用第三方擴展包的形式,或者說如果要將對象與實例交由Spring管理,那麼也需要以插件的形式出現。

這種種的一切幾乎都在暗示着,Struts 1.x無法勝任於今天的開發,但是這個時候Apache也並沒有開發Struts 2.x。任何事物的發展都是相生相剋的。Struts 1.x發展帶了框架的繁榮,那麼繁榮之後,在某一個“角落”裏面慢慢生長出一個新的框架——WebWork(ww),最早在整個Java行業裏面有一個特別著名的開源項目——JIVE論壇,它融合了一個很有意思的特點——使用當前最新的開發技術,而WebWork就正好應用在此處,而後隨着Struts 1.x的缺陷越來越明顯,那麼WebWork的優勢越來越突出,長此以往下去,Struts 1.x已經丟失了大量的開發者,很多的開發者都轉向了WebWork,它比Struts 1.x優秀在於以下幾點:
(1)避免了Struts 1.x之中ActionForm類與Action類必須同時存在的窘境。
(2)在WebWork發展的時候考慮到了與第三方框架的整合,所以它可以輕鬆的實現與Spring的整合;
(3)WebWork的·配置更加的靈活;
(4)在驗證框架部分要比Struts 1.x更加容易(這個也沒什麼用)。

在2005年的時候,Apache收購了WebWork項目,因爲WebWork足夠好用,但是沒有Struts的名氣大,而Struts 1.x本身問題很多,那麼需要重新開發,所以Struts 1.x的名氣+WebWork的實幹=Struts 2.x,而在收購初期,Struts 2.x並沒有對WebWork做更多的修改,但是千萬要記住從2005年開始JDK 1.5出現了,而JDK 1.5出現所帶來的行業內容的巨大的變革在於Annotation的技術應用,在Struts 2.x裏面全面支持Annotation的配置以簡化程序的編寫。

Struts 2.x的基礎是WebWork,但是請千萬要記住一件事情,WebWork的起源也很早,只不過它憑藉着自己超前的結構設計,纔可以在今天繼續發揮預熱,但是Struts 1.x、Struts 2.x、JSF等等,現在隨着時間的沉澱,會發現依然不如SpringMVC的設計更加優秀。

所有的框架設計思想只來源一點——反射機制,如果再有其它的核心技術也就是XML+DOM4J。

2 開發第一個Struts 2.x程序(搭建開發環境)

如果要想開發Struts 2.x的話最早的時候是沒有開發工具的,從MyEclipse2012開始就支持了Struts 2.x開發,可以直接利用此工具自動進行Struts 2.x開發包的配置。
1、建立一個新的項目——MyStruts2Project
建立的時候一定要建立web.xml文件,因爲幾乎所有的MVC開發框架都必須有Servlet的支持或者是Filter支持,而Struts 1.x使用的是一個ActionServlet,在Struts 2.x裏面使用的是一個Filter。所以考慮到配置問題必鬚生成web.xml。

2、框架的開發一定是一堆jar文件的配置過程
|——隨後會詢問你當前要使用的Struts 2.x版本。
|——Struts 1.x有一個特別著名的標誌那麼就是*.do,所以當Apache將WebWork項目吸收進來之後,那麼也提供了多種映射路徑,當然,我們現在認爲最合適的路徑應該使用的是*.action
|——隨後將進入到開發包的配置界面;
|——那麼此時Struts 2.x的開發環境就搭建完成了。

3 開發第一個Struts 2.x程序(編寫ECHO程序)

1、項目建立完成之後會自動在src目錄之中建立一個struts.xml文件,這個文件就是struts 2.x的核心配置文件。同時也會在web.xml文件中增加如下配置:

<filter>
    <filter-name>struts2</filter-name>
    <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>struts2</filter-name>
    <url-pattern>*.action</url-pattern>
  </filter-mapping>

Struts 2.x的所有的處理操作都由過濾器完成的。
2、本次將完成一個ECHO程序,即:由用戶在文本框中輸入一段文字,而後在文字前增加“ECHO:”的信息返回到頁面上進行顯示。
範例:開發EchoAction
(1)所有的Struts 2.x應用程序都要求繼承自ActionSupport父類。

package org.lks.action;

import com.opensymphony.xwork2.ActionSupport;

@SuppressWarnings("serial")
public class EchoAction extends ActionSupport {
	private String msg;

	public String getMsg() {
		return msg;
	}

	public void setMsg(String msg) {
		this.msg = msg;
	}
	
	@Override
	public String execute() throws Exception {  //執行操作
		this.msg = "ECHO: " + this.msg;
		return "echo.page";  //返回路徑的映射的key
	}
}

那麼現在控制器就完成了。
3、所有的程序都一定要在struts.xml文件裏面進行配置。
範例:修改struts.xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN" "http://struts.apache.org/dtds/struts-2.1.dtd">
<struts>
	<!-- 所有的Struts 2.x程序都要求設置命名空間 -->
	<package name="root" namespace="/" extends="struts-default"></package>
</struts>    

在此程序之中<package>主要定義頁面的可執行路徑,如果設置爲/(namespace="/"),那麼就表示映射到根路徑下,但是在配置文件裏面有一些是需要父配置文件給予的支持(例如:自動賦值、數據轉換等),所以使用extends繼承一個父的配置文件(extends=“struts-default”)。每一個<package>下具有多個<action>的設置。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN" "http://struts.apache.org/dtds/struts-2.1.dtd">
<struts>
	<!-- 所有的Struts 2.x程序都要求設置命名空間 -->
	<package name="root" namespace="/" extends="struts-default">
		<!-- 配置程序所需要的Action的名稱以及對應的類型 -->
		<action name="EchoAction" class="org.lks.action.EchoAction">
			<!-- 定義跳轉路徑 -->
			<result name="echo.page">echo.jsp</result>
		</action>
	</package>
</struts>    

那麼此時Struts 2.x的Action配置完成。
4、在根路徑下開發echo.jsp

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<html>
  <head>
    <base href="<%=basePath%>">
    
    <title>Echo Action</title>
  </head>
  
  <body>
  	<h1><s:property value="msg"/></h1>
    <s:form action="EchoAction.action" method="post">
    	<s:textfield key="msg" label="請輸入信息:"/>
    	<s:submit value="send"/>
    </s:form>
  </body>
</html>

此時的echo.jsp負責內容的輸入以及內容的顯示。
5、在所有標準的MVC設計模式之中,不應該通過路徑直接訪問一個jsp頁面,所有的jsp頁面必須經過Servlet進行跳轉後顯示,所以在這一點上Struts 2.x強烈奉行着這一特徵,所以要想正常訪問頁面,必須要首先訪問Action,而後通過Action跳轉到一個jsp頁面。

如果此時使用了Struts 2.x標籤,那麼裏面的屬性跳轉後會與標籤的相關組件進行自動匹配。但是從實際的開發來看,這樣的自動匹配意義不大。

通過以上的開發可以發現,整個Struts 2.x與Struts 1.x相比優秀在以下部分:
(1)避免了ActionForm的強制定義問題;
(2)在配置文件之中定義簡單;
(3)在處理過程之中,所有的請求是先提交到過濾器,而後根據struts.xml文件的配置進行相關Action的查找,調用execute()方法後再跳轉回指定的jsp頁面。整體的處理流程要比Struts 1.x容易;
(4)支持的標籤更加簡化,但是Struts 1.x的標籤嚴格來講不如Struts 1.x乾淨。

4 跳轉類型

之前已經完成了一個最爲基礎的Struts 2.x程序,從整個結構來講比較清晰比較簡單,除了頁面裏面的代碼有些陌生之外(不建議使用)。

在Struts 2.x裏面如果要進行跳轉的話,那麼實際上在Action之中只要返回一個字符串,那麼此字符串與配置的路徑進行吻合後就可以跳轉。

public String execute() throws Exception {  //執行操作
	this.msg = "ECHO: " + this.msg;
	return "echo.page";  //返回路徑的映射的key
}
<result name="echo.page">echo.jsp</result>

所以默認使用的是服務器端跳轉操作,這樣纔可以將頁面中使用標籤定義的文本框的名字與Action中的屬性關聯上。但是對於在Struts 2.x裏面的跳轉模式常用的有以下三種:
(1)type="dispatcher",默認的跳轉形式,表示使用服務器端跳轉;

<result name="echo.page" type="dispatcher">echo.jsp</result>

(2)type=redirect,採用客戶端跳轉的形式,跳轉後的頁面路徑改變;

<result name="echo.page" type="redirect">echo.jsp</result>

(3)type=redirect-action,跳轉到另外一個Action中。

<result name="echo.page" type="redirect-action">echo.jsp</result>

如果現在由一個Action要跳轉到另外一個Action,例如:刪除數據之後需要重新列表,所以刪除操作是在一個Action要跳轉到其他的Action進行數據的顯示列表,這種情況下就使用redirect-action

5 過濾器

在整個Struts 2.x裏面所有的請求處理都是由過濾器完成的,這一點和之前的Struts 1.x是完全不同的,但是現在的過濾器有一個問題,發現所有的頁面必須經過Action而後跳轉JSP頁面後,JSP頁面纔可以正常顯示,因爲只要你使用了Struts 2.x標籤,那麼JSP頁面就一定要與某一個Action關聯在一起。

但是是不是必須要進行關聯呢?並不是,如果想要解決關聯緊密的問題,那麼首先來觀察當前使用的過濾器:

<filter>
  <filter-name>struts2</filter-name>
  <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>

首先必須要清楚,如果要進行Struts 2.x的開發,那麼官方推薦使用的過濾器StrutsPrepareAndExecuteFilter,但是嚴格來講在org.apache.struts2.dispatcher.ng.filter包中有三個可以使用的過濾器。
(1)StrutsPrepareAndExecuteFilter:指的是在頁面執行之前以及頁面執行的時候都要操作此過濾器;
(2)StrutsPrepareFilter:在頁面執行之前(此時頁面還沒有生成)執行過濾器;
(3)StrutsExecuteFilter:在頁面執行完畢(此時頁面代碼已經生成了)執行過濾器。
但是經過解釋可以發現這些過濾器都必須要一個前提:依然要通過Action跳轉到JSP頁面纔可以。只要你的JSP頁面中存在有Struts 2.x標籤,那麼就不可避免的一定要與Action關聯,那麼如果說現在不想關聯呢?那麼可以更換一個過濾器:org.apache.struts2.dispatcher.FilterDispatcher
範例:使用其他過濾器

<filter>
  <filter-name>struts2</filter-name>
  <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>
<filter-mapping>
  <filter-name>struts2</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

此時,在直接訪問具備有Struts 2.x標籤的頁面就不會出現錯誤了,但是這種做法現在在新的版本之中已經被禁止了。只能夠使用StrutsPrepareAndExecuteFilter過濾器,那麼是不是就不能夠直接執行JSP呢?依然可以直接執行JSP,但是這個時候的JSP頁面要求不能夠出現任何與Struts 2.x有關的標籤操作;
範例:定義一個msg.jsp頁面

<%@ page language="java" pageEncoding="UTF-8"%>

<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<html>
<head>
	<base href="<%=basePath%>">

	<title>Struts 2.x</title>
</head>
  
<body>
	<form action="EchoAction.action" method="post">
		Info:<input type="text" name="msg" id="msg">
		<input type="submit" value="submit">
	</form>
</body>
</html>

如果此時參數名稱不一致,那麼將無法實現自動賦值,但是也不會像Struts 1.x那樣出錯。

如果在以後的Struts 2.x開發還是使用其它框架開發,都強烈不建議使用各個框架自己的標籤,習慣的做法依然是由Action傳遞request屬性到JSP頁面,而JSP頁面,而JSP頁面使用JSTL輸出。

6 跳轉配置

在Struts 2.x裏面所有的跳轉都是由Action進行的,而且只要想進行跳轉,只需要在相應的方法裏面返回指定的字符串即可,而這些字符串都會在struts.xml文件裏面出現相應的<result>節點。
範例:觀察已有代碼

public String execute() throws Exception {  //執行操作
	this.msg = "ECHO: " + this.msg;
	return "echo.page";  //返回路徑的映射的key
}
<result name="echo.page" type="dispatcher">echo.jsp</result>

<result>節點裏面保存的是要跳轉的路徑,但是對於這個路徑一定要記住它是與包匹配的:
(1)包的定義:<package name="root" namespace="/" extends="struts-default">
|————namespace表示的是整個可以訪問程序的公共路徑,有了這個路徑之後在此包中配置的一切頁面,如果沒有明確聲明都是在此路徑下的;
(2)result定義:<result name="echo.page" type="dispatcher">echo.jsp</result>
|————組合:namespace/result,所以最終的這個跳轉路徑:/echo.jsp
很多時候寫這種相對路徑可能有些人並不習慣,所以可以使用絕對路徑:

<result name="echo.page" type="dispatcher">/pages/demo/echo.jsp</result>

但是對於跳轉結果的返回內容,實際上在ActionSupport類裏面也有幾個常量支持,這幾個常量都有其特殊的使用環境,首先來觀察常量(都在com.opensymphony.xwork2.Action接口裏定義):
(1)成功操作:public static final String SUCCESS,“success”;
(2)錯誤操作:public static final String ERROR,“error”;
(3)重新登錄:public static final String LOGIN,“login”;
(4)服務器驗證未通過:public static final String INPUT,“input”;
(5)不做任何操作:public static final String NONE,“none”;
通過代碼結構可以發現ActionSupport是Action接口的子類,所以現在EchoAction的繼承關係:

如果在開發過程之中也可以直接使用這些常量來作爲跳轉的key。

@Override
	public String execute() throws Exception {  //執行操作
		this.msg = "ECHO: " + this.msg;
		return Action.SUCCESS;  //返回路徑的映射的key
	}
<result name="success">echo.jsp</result>

這些常量在自己的開發之中不是必須編寫的,只是留給標準使用的。

7 取得JSP內置對象

寫了Action之後發現,這個Struts 2.x的Action與Struts 1.x不同,Struts 1.x裏面的方法至少都帶有HttpServletRequest、HttpServletResponse這樣的接口實例對象。

在Struts 2.x爲了方便的解決這些內置對象的操作,專門提供有一個org.apache.struts2.ServletActionContext的類,這個類裏面可以方便的取出常用內置對象:
(1)取得pageContext:public static javax.servlet.jsp.PageContext getPageContext()
(2)取得request:public static javax.servlet.http.HttpServletRequest getRequest()
(3)取得response:public static javax.servlet.http.HttpServletResponse getResponse()
(4)取得ServletContext:public static javax.servlet.ServletContext getServletContext()
HttpSession對象可以通過HttpServletRequest接口裏面定義的getSession()方法取得。
範例:觀察內置對象的信息取得

public String execute() throws Exception {  //執行操作
	HttpServletRequest request = ServletActionContext.getRequest();
	HttpServletResponse response = ServletActionContext.getResponse();
	ServletContext servletContext = ServletActionContext.getServletContext();
	HttpSession session = request.getSession();
	System.out.println(servletContext.getRealPath("/"));
	System.out.println(request.getContextPath());
	System.out.println(response.getContentType());
	System.out.println(session.getId());
	this.msg = "ECHO: " + this.msg;
	return "echo.page";  //返回路徑的映射的key
}

日後如果要由Action傳遞request屬性或者是在session中保存登錄信息的話,這些操作就可以使用了。

8 多人開發

在所有的項目開發之中,一定是多人團隊集中開發,但是現在如果使用框架開發會出現一個問題,所有的Action以及相關的路徑都要求在struts.xml文件裏面配置,如果所有的人都去修改一個文件,那麼會很混亂,有可能會一直衝突,那麼在Struts 2.x裏面爲了解決這樣的問題,可以由用戶定義多個配置文件,並且在一個struts.xml文件裏面合併。

首先在項目裏面可以複製struts.xml文件爲struts-lks.xml。在這裏面編寫所需要的內容,但是隨後需要在綜合的struts.xml文件裏面導入這些小的配置信息。

<include file="struts-lks.xml"></include>

如果你有多個文件,那麼就重複編寫<include>元素即可。

9 亂碼解決

如果你現在項目之中的編碼都統一使用的是UTF-8編碼,那麼根本就沒有必要去解決亂碼,如果你採用的是其他編碼形式,例如:整個項目都使用的是“GBK”,那麼就必須解決亂碼。

如果要想進行亂碼的解決實際上是進行的是Struts 2.x的屬性配置,既然是屬性配置,那麼就要在src目錄下建立一個struts.properties的文件。

範例:建立struts.properties文件

struts.i18n.encoding=UTF-8
struts.locale=zh_CN

只要寫上了此配置,那麼Struts就可以使用任意的編碼進行處理了。

但是此時有一個問題千萬要記住:Struts 2.x利用過濾器完成所有處理,所以在Struts 2.x裏面要想再使用過濾器解決亂碼,或者是解決用戶登錄操作,那麼就不這麼方便了。

10 配置資源文件

從任何的項目開發來講,都一定會存在有*.properties文件,這個·文件的結構使用了key=value的形式,同時在這種文件上還可以輕鬆實現國際化操作的需要,所以只要是項目,都建議至少定義一個Messages.properties文件保存所有的提示信息。
範例:在src下定義Messages.properties文件(資源文件的命名同類名稱命名標準一致)

info=Hello, {0}, {1}, {2}, {3}, {4}, {5}!

那麼現在需要在struts.properties文件裏面定義資源文件。
範例:修改struts.properties文件

struts.custom.i18n.resources=Messages,Pages

隨後在Action裏面進行資源文件的讀取。
範例:讀取資源文件

//未填充滿輸出:Hello, hello, world, {2}, {3}, {4}, {5}!
System.out.println(super.getText("info", new String[]{"hello", "world"}));

//填充滿輸出:Hello, hello, world, 黃海燕, 大, 大, 笨蛋!
System.out.println(super.getText("info", new String[]{"hello", "world","黃海燕", "大", "大", "笨蛋"}));

儘可能保證有多少的佔位符,就填寫多少個內容。
在整個Struts 2.x開發之中,以上形式的資源讀取是最爲常見的,但是在整個Struts 2.x規劃的時候定義了三種級別的資源文件類型:
(1)第一種:全局資源文件類型,推薦的使用形式(需要在struts.properties中註冊名稱);
(2)第二種:包級別資源文件,只能夠在一個包下使用(package.properties);
(3)第三種:指定Action的資源文件,爲一個Action服務(名稱與Action相同)。
範例:定義包級別的資源文件——org/lks/action/package.properties

info=Hello, {0}, {1}, {2}, {3}, {4}!

在讀取資源文件的時候如果多個級別的資源文件都出現了,那麼會按照由小到大的範圍讀取(先讀取單個Action,如果沒有再讀取package.properties,如果還沒有則讀取全局資源)。
範例:定義EchoAction專門使用的資源文件——EchoAction.properties(EchoAction.java)

info=Hello, {0}, {1}!

雖然有三種資源文件的類型,但是如果真使用,別太麻煩了,還是定義全局資源文件,如果是多個,使用,分隔,但是請一定記住,在struts.properties之中定義全局資源文件的時候,不許加後綴,它默認找到*.properties文件。

11 結合VO輸入

從2005開始,VO的開發模式已經深入人心,所有的開發框架必定都要支持VO轉換,並且在Struts 2.x裏面也支持了VO轉換,並且可以多級設置。
範例:定義兩個VO類同時設置關係

package org.lks.vo;

public class Employee {
	private Long eid;
	private String ename;
	private String ejob;
	private Department edepartment;

	public Employee() {
	}

	public Long getEid() {
		return eid;
	}

	public void setEid(Long eid) {
		this.eid = eid;
	}

	public String getEname() {
		return ename;
	}

	public void setEname(String ename) {
		this.ename = ename;
	}

	public String getEjob() {
		return ejob;
	}

	public void setEjob(String ejob) {
		this.ejob = ejob;
	}

	public Department getEdepartment() {
		return edepartment;
	}

	public void setEdepartment(Department edepartment) {
		this.edepartment = edepartment;
	}
}

package org.lks.vo;

public class Department {
	private Long did;
	private String dname;
	private String dlocation;
	
	public Department() {
	}

	public Long getDid() {
		return did;
	}

	public void setDid(Long did) {
		this.did = did;
	}

	public String getDname() {
		return dname;
	}

	public void setDname(String dname) {
		this.dname = dname;
	}

	public String getDlocation() {
		return dlocation;
	}

	public void setDlocation(String dlocation) {
		this.dlocation = dlocation;
	}
}

範例:建立一個EmployeeAction的程序類,在這個程序類裏面準備好要接收的數據

package org.lks.action;

import org.lks.vo.Employee;

import com.opensymphony.xwork2.ActionSupport;

@SuppressWarnings("serial")
public class EmployeeAction extends ActionSupport {
	private Employee employee = new Employee();

	public EmployeeAction(){	
	}
	
	public Employee getEmployee() {
		return employee;
	}
	
	@Override
	public String execute() throws Exception {  //執行操作
		return "employee.insert";  //返回路徑的映射的key
	}
}

範例:在struts.xml文件裏面配置EmployeeAction

<action name="EmployeeAction" class="org.lks.action.EmployeeAction">
			<!-- 定義跳轉路徑 -->
			<result name="employee.insert">employee_insert.jsp</result>
		</action>

後面的任務就是要進行表單的編寫,但是在編寫表單的時候請要考慮到名稱問題。
範例:定義employee_insert.jsp頁面

<%@ page language="java" pageEncoding="UTF-8"%>

<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<html>
<head>
	<base href="<%=basePath%>">

	<title>Struts 2.x</title>
</head>
  
<body>
	<form action="EmployeeAction.action" method="post">
		僱員編號: <input type="text" name="employee.eid" value="31613131"><br>
		僱員姓名: <input type="text" name="employee.ename" value="hhy"><br>
		僱員職位: <input type="text" name="employee.ejob" value="軟件設計師"><br>
		部門編號: <input type="text" name="employee.edepartment.did" value="1001"><br>
		部門名稱: <input type="text" name="employee.edepartment.dname" value="軟件部"><br>
		部門地址: <input type="text" name="employee.edepartment.dlocation" value="深圳"><br>
		<input type="submit" value="submit">
	</form>
</body>
</html>

你現在發現所有的操作都可以向指定的類型進行轉換,這一點的確是很方便。這種轉換嚴格來講並不安全,因爲它必須要求前臺用戶輸入正確格式的數據纔可以操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章