Struts2入門教程

http://www.chengxuyuans.com 程序員之家 php

1、準備工做及實例 html

1.解壓struts-2.1.6-all.zip java

apps目錄:struts2自帶的例子程序
docs目錄:官方文檔。 python

lib 目錄:存放全部jar文件。 程序員

Src 目錄:源文%件存放地 web

2.六個基本包 正則表達式

struts2-core-2.1.6.jar 開發的核心類庫
freemarker-2.3.13.jar struts2的UI標籤的模板使用freemarker編寫
commons-logging-1.0.4.jar 日誌包
ognl-2.6.11.jar
對象圖導航語言,經過它來讀寫對象屬性
xwork-2.1.2.jar xwork類庫,struts2在其上進行構建
commons-fileupload-1.2.1.jar
文件上傳組件,2.1.6版本後必須加入此jar包 數據庫

特別須要說明的是目前strust2的最新版本是struts-2.1.6,它做爲2.1.X的正式版。特別要注意導入commons-fileupload-1.2.1.jar包,在此jar包中包含了RequestContext類,若是不導入該jar包將會報異常。 express

3.初識struts2配置文件 apache

(1).web.xml文件

主要完成對StrutsPrepareAndExecuteFilter的配置(在之前的版本中是對FilterDispatcher配置,新版本一樣支持用FilterDispatcher配置),它的實質是一個過濾器,它負責初始化整個Struts框架而且處理全部的請求。這個過濾器能夠包括一些初始化參數,有的參數指定了要加載哪些額外的xml配置文件,還有的會影響struts框架的行爲。除了StrutsPrepareAndExecuteFilter外,Struts還提供了一個ActionContexCleanUp類,它的主要任務是當有其它一些過濾器要訪問一個初始化好了的struts框架的時候,負責處理一些特殊的清除任務。

(2).struts.xml文件

框架的核心配置文件就是這個默認的struts.xml文件,在這個默認的配置文件裏面咱們能夠根據須要再包括其它一些配置文件。在一般的應用開發中,咱們可能想爲每一個不一樣的模塊單獨配置一個struts.xml文件,這樣也利於管理和維護。這也是咱們要配置的主要文件。

(3).struts.properties(參default.properties

在Struts框架使用了不少屬性,咱們能夠經過改變這些屬性來知足咱們的需求。要改變這些屬性,只需在struts.properties文件中指定屬性的keyvalue便可。屬性文件能夠放在任何一個包含在classpath中的路徑上,可是一般咱們都把它放在/WEB-INF/classes目錄下面。咱們能夠在struts-default.properties文件中找到一個屬性的列表。

(4struts-default.xml

此文件是struts2框架默認加載的配置文件,它定義了struts2一些核心bean和攔截器,它會自動包含(included)struts.xml文件中(實質是經過<package  extends="struts-default">),併爲咱們提供了一些標準的配置。咱們能夠在struts2-core.jar中找到這個文件。

(5)其它配置文件

velocity.propertiesstruts-default.vmstruts-plugin.xml

4.MyEclipse提示xml信息

當咱們在編寫struts.xml時,發現eclipse並不會給出幫助提示,那是由於MyEclipse默認並不支持struts2,因此咱們須要手工導入dtd以支持提示。步驟:[window][preferences][MyEclipse][Files and Editors][XML][xml Catelog]而後在右邊點add添加:locationdtd文件所在的位置(struts-2.0.dtd文件struts2-core-2.1.6.jar中能夠獲得),KeyType選擇URIKeystruts-2.0.dtd文件中文檔聲明的內容(http://struts.apache.org/dtds/struts-2.0.dtd),在struts.xml文件中也有此key值。

5.如何使用alt+/提示

在MyEclipse6.5,默認的提示爲Ctrl+Space,而它會與咱們的輸入法切換衝突,使提示失效。找到key,先取消Content Assist命令的綁定,再用「alt+/」來綁定。

6.實例

步驟一,新建myStruts2項目,並導入struts2的六個基本jar包。
步驟二,創建LoginAction文件,主要代碼以下:
package com.asm;

import com.opensymphony.xwork2.Action;

public class LoginAction implements Action {

      private String username;

      private String password;

...省略get/set方法

      public String execute() throws Exception {

            if (username.equals("struts2")) {

                  return "loginSuccess";

            } else {

                  return "loginFailure";

            }

      }

}
說明:實現了Action接口,主要是爲了保證execute的正肯定義,其實咱們也能夠不實現此接口,只要能保證execute方法書寫的正確書寫(方法名,返回值)。

步驟三,在struts.xml文件中註冊LoginAction。此配置文件要放在src目錄下,實質就是成爲classpath環境變量下的文件。主要代碼以下:
<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE struts PUBLIC

    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"

    "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

      <package name="myFirst" namespace="/" extends="struts-default">

            <action name="login" class="com.asm.LoginAction">

                  <result name="loginSuccess">/success.jsp</result>

                  <result name="loginFailure">/failure.jsp</result>

            </action>

      </package>

</struts>
說明package後面會有詳細說明。action元素中的name屬性值指定了此action所指定的請求路徑爲「login.action」。後面login.jsp中的<form action=...>屬性值就會參照此name屬性。

步驟4、提供jsp頁面
login.jsp主要代碼
<body>

            <form action="<%=request.getContextPath()%>/login.action" method="get">

            戶名:<input type="text" name="username"><br>

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

            <input type="submit" value="login">

            </form>
</body>
failure.jsp主要代碼

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>

<%@ taglib uri="/struts-tags" prefix="s" %>
<html>

      <body>

            登陸失敗,錯誤的用戶名:<s:property value="username"/><br>

            <a href="<%=request.getContextPath()%>/login.jsp">返回</a>

      </body>

</html>
說明:使用了標籤庫,在struts2中使用標籤庫很是簡單,只須要像上面那樣導入標籤庫即可以使用全部的struts2的全部標籤

success.jsp主要代碼

<body>      登陸成功!</body>

步驟5、配置web.xml。完成核心監聽器註冊。內容以下:
<?xml version="1.0" encoding="UTF-8"?>

<web-app id="WebApp_9" 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">

      <filter>

            <filter-name>struts2</filter-name>

            <!-- <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>

            -->

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

            </filter-class>

      </filter>

 

      <filter-mapping>

            <filter-name>struts2</filter-name>

            <url-pattern>/*</url-pattern>

      </filter-mapping>

</web-app>

說明:註釋掉的部分爲之前2.1.4版本中用的核心filter類。StrutsPrepareAndExecuteFilter類的init方法將會讀取類路徑下默認的配置文件struts.xml,並以javabean形式存放在內存中,之後struts2對用戶的每次請求將使用內存中數據,而不是重讀struts.xml文件。

步驟6、發佈測試。
簡要分析執行流程
當輸入login.jsp訪問jsp頁面填寫完相關信息並提交給login.action時,它會首先被在web.xml中配置的過濾器監聽到,過濾器會去查找strust.xml文件,並結合namespace查找名爲loginaction,查找到此action便交給其處理,LoginAction內部會執行execute方法,並返回結果resultresult也是參照的struts.xmlaction下的result配置)。 關於表單傳參,主要是參照的action中的方法名,而非屬性名。

7.開啓struts2自帶的開發模式常量

在之前的開發中,當修改一些配置時老是不能及時地更新到服務器,咱們總會從新部署或重啓來更新改變的內容,在struts2中能夠經過一個常量來達到此目的。即在struts.xml中的<struts>元素下增長以下內容:<constant name="struts.configuration.xml.reload" value="true" /> 這樣配置後,當配置文件修改保存時就會及時更新到服務器中。其它一些常量:

<!-- 指定WEB應用的編碼集,至關於調用HttpServletRequest.setCharacterEncoding方法,若是使用了velocity或freemarker,它也用於指定輸出的編碼格式 -->

      <constant name="struts.i18n.encoding" value="UTF-8" />

      <!-- 指定請求後綴爲.action,指定多個請求後綴用逗號分隔 -->

      <constant name="struts.action.extension" value="action" />

      <!--設置瀏覽器是否緩存靜態內容,建議:開發階段關閉,運行時開啓  -->

      <constant name="struts.serve.static.browserCache" value="false" />

      <!--當struts.xml配置文件修改後,系統是否從新加載該文件,開發階段打開此功能  -->

      <constant name="struts.configuration.xml.reload" value="true" />

      <!--  開發提示:出錯時打印更詳細的信息-->

      <constant name="struts.devMode" value="true" />

<!-- 指定請求的後綴能夠是.do或.action -->

      <constant name="struts.action.extension" value="do,action" />

注意:在struts2.1.6版本中存在一個bug:即配置了struts.i18n.encoding常量也不能解決中文亂碼問題,緣由是此版本在獲取請求參數後才調用了setCharacterEncoding()方法進行編碼設置。解決此bug的方法是配置一個filter,並在doFilter方法中增長以下代碼:request.setCharacterEncoding. 2.1.8版本中解決了此問題及2.1.6中存在的其它bug,建議新項目使用2.1.8版本。

8.vo傳參模式

Copy上面的myStruts2項目,更名爲myStruts2Vo項目。做以下修改:在LoginAction中有兩個字段:username,password。把此兩個屬性重構到com.asm.vo.User類中,而後在LoginAction中提供User對象及相應的get/set方法。如今須要注意的是在login.jsp中會有以下的修改:

戶名:<input type="text" name="user.username"><br>

密碼:<input type="password" name="user.password"><br>

關鍵就是改掉name屬性值。其它基本無變更。 後話:假如此此User對象並不能和Model層的相應對象徹底對應,咱們還應藉助此User對象在Action中構建出Model層的相應對象,這樣,在exectue方法中便能經過構建的Model對象做爲參數與Model層交互。

9.ModerDriven傳參模式(不建議採用)

Copy上面的myStruts2Vo項目,更名爲myStruts2Model項目。重點是修改LoginAction,修改後的主要內容以下:
package com.asm;

import com.asm.vo.User;

import com.opensymphony.xwork2.Action;

import com.opensymphony.xwork2.ModelDriven;

public class LoginAction implements Action, ModelDriven<User> {

      private User user = new User();

      public String execute() throws Exception {

            if (user.getUsername().equals("struts2")) {

                  return "loginSuccess";

            } else {

                  return "loginFailure";

            }

      }

      public User getModel() {

            return user;

      }

}

說明:它實現了ModelDriven接口,並使用了泛性機制(必須),所以要求jdk1.5以上。
如今須要注意的是在login.jspname屬性值爲User中兩個字段,和第一個實例同樣。說明:此方式通常不會使用,在此略做了解。

10.爲何要使用struts2代替struts1.x

(1)struts2execute方法中的參數不會依賴於servletAPI,實現了也servlet解耦,是一種無侵入式的設計。

(2)struts2提供了攔截器,利用攔截器能夠進行AOP編程,實現權限攔截等功能。

(3)struts2提供了類型轉換器,咱們能夠很容易地對請求參數轉換成須要的類型。

(4)提供了同種表現層技術支持,如JSPfreeMarkervelocity

(5)能夠對指定的方法進行校驗,能夠輕鬆地實現表單校驗功能

(6)提供了全局範圍、包範圍和action範圍的國際化資源文件管理實現。

2、struts.xml配置及例程

1.配置文件的優先級

在struts2中一些配置(好比常量)能夠同時在struts-default.xml(只讀性),strtus-plguin.xml(只讀性),struts.xmlstruts.propertiesweb.xml文件中配置,它們的優先級逐步升高,便是說後面的配置會覆蓋掉前面相同的配置。

2.配置形式

下面以對struts.i18n.encoding=UTF-8的配置爲例進行說明:

在struts.xml配置形式以下:

      <constant name="struts.i18n.encoding" value="gbk"></constant>

在struts.properties的配置形式以下:

struts.i18n.encoding=UTF-8

在web.xml中配置以下:
<filter>

<filter-name>struts2</filter-name>    

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

      </filter-class>

      <init-param>

            <param-name>struts.i18n.encoding</param-name>

            <param-value>UTF-8</param-value>

      </init-param>

</filter>
說明:官方聲稱配置了此常量能夠解決中文亂碼問題,但實事上並不能達到目的,在前面的三個項目中,若是咱們在表單中輸入中文,其結果是會出現亂碼。解決此問題參看[一.7的注意]。這是struts2.1.6中的一bug,它的下一版2.1.8已解決此問題。

3.package配置相關

屬性名

是否必須

說明

Name

Package的惟一標識,不容許同名

Extends

指定要繼承的包

Namespace

指定名稱空間

Abstract

聲明包爲抽象否

下面咱們創建struts2package項目來進行package相關測試:
 SHAPE \* MERGEFORMAT

            <action name="test1" class="com.asm.Test1Action">

                  <result name="success">/forward/test1.jsp</result>

            </action>

</package>

<package name="tt2" namespace="/test2" extends="struts-default">

            <action name="test2" class="com.asm.Test2Action">

                  <result name="success">/forward/test2.jsp</result>

            </action>

</package>


<a href="<%=path%>/test2/test2.action">test2</a><br>

struts.xml中的內容:

連接地址

<a href="<%=path%>/test1/test1.action">test1</a><br>


連接地址


說明:在上面的配置文件中所用到的Test1ActionTest2Action這兩個Action都只是繼承了com.opensymphony.xwork2.ActionSupport類,而ActionSupport默認返回的就是「success,因此當點擊上面的連接分別轉到了forward目錄下的test1.jsptest2.jsp。下面重點來看這個package元素的namespace屬性及actionname屬性,它們共同定義了action所映射到的實質文件。上圖展現了連接地址和action的對應關係,因此當咱們要想訪問一個action所關聯到的jsp文件時,應該用namespace+actionname 關於它的內容測試能夠參考struts2package項目。
補充:一般狀況下,action元素的name是屬性值是不能出現「/」的,因此但願經過actionname屬性來實現多級映射,須要在sturts.xml中增長以下屬性: 

<constant name="struts.enable.SlashesInActionNames" value="true"/> 這樣配置後就能夠再actionname元素中使用「/」了。好比:
<package name="tt3" extends="struts-default">

            <action name="test3/test3" class="com.asm.Test3Action">

                  <result name="success">/forward/test3.jsp</result>

            </action>

</package>

而後輸入<a href="<%=path%>/test3/test3.action">test3</a><br>連接地址就能夠訪問了

強調:namespace默認值「」,即不配置namespace屬性。它的意思是:若是action不能進行完整路徑匹配,則會來此namespace下進行匹配,好比:.../test/test/test.action,若是參照namespaceactionname不能找到也之徹底對應的action,它會再到依次追溯到上級目錄中查找,便是說它會以…/test/test.action這樣的路徑來對應namespaceactionname進行查找。若是返回到最終的目錄仍找不到,它就會到namespace="/"對應的包下查找名爲testaction,若是仍找不到,它就會去默認的namespace下查找名爲testaction,若是找到則執行此action。另外,namespace也能夠配置成namespace="/"。它表明配置爲項目的根。 總結action的名稱探索順序:徹底對應、逐步追溯到上級目錄查找、"/"下查找、默認namespace下查找。

爲何要提出namespace,主要是避免多人共同開發項目出現名字衝突。若是不使用namespace,多我的所寫的action中可能出現重名的現象,這樣當項目合併時就會出現衝突。而有了namespace能夠在項目開發時由項目經理給每個人分不一樣的namespace,這樣每一個開發人員只須要保證本身所寫的action不一樣名便可。
namespace引起的連接問題
:當咱們爲action配置了namespace時,訪問此action的形式總會是以下形式:.../webappname/xxx/yyy/ActionName.action 而當此action成功執行跳轉到某個jsp頁面時,如想在此jsp頁面寫連接,必定要寫絕對路徑,由於相對路徑是相對.../webappname/xxx/yyy/,而若是之後咱們修改了actionnamespace時,相對路徑又要變,因此連接不能寫成相對路徑。 如下介紹絕對路徑的寫法:一般用myeclipse開發時創建一個jsp文件,默認總會有以下內容:

<%

String path = request.getContextPath();

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

%>
咱們寫絕對路徑能夠參此內容。還能夠參<head>下的<base href="<%=basePath%>"> 來完成絕對路徑的書寫。

4.分工合做include:指定多個配置文件

好比讓jack來單獨開發一個action,在jack.xml中的配置文件爲:
<struts>

      <package name="jack" namespace="/jack" extends="struts-default">

            <action name="test4" class="com.asm.Test4Action">

                  <result name="success">/forward/test4.jsp</result>

            </action>

      </package> 

</struts>

而後在struts.xml文件中增長以下內容:<include file="jack.xml"></include> 它實質就是把jack.xml中的<package>及其內容寫進struts.xml中的<struts>根元素下。

連接:<a href="<%=path %>/jack/test4.action">test4</a> 這樣即可以訪問到了forward目錄下的test4.jsp了。

5.tomcat認證訪問

接上例:namespce的做用除了在前面提到的避免協同開發名字衝突外,還爲認證提供一個條件。好比jack開發的東西所關聯到的頁面須要權限才能被訪問。因爲多爲tomcat中的內容,下面只列出步驟。
步驟一,tomcat的conf目錄下tomcat-users.xml內容以下:
<?xml version='1.0' encoding='utf-8'?>

<tomcat-users>

  <role rolename="manager"/>

  <role rolename="admin"/>

  <user username="jack" password="jack" roles="admin,manager"/>

  <user username="tom" password="tom" roles="manager"/>

</tomcat-users>
步驟二,在web.xml中增長以下內容:
<security-constraint>

            <web-resource-collection>

                  <web-resource-name>jack</web-resource-name>

                  <url-pattern>/jack/*</url-pattern>

                  <http-method>POST</http-method>

                  <http-method>GET</http-method>

            </web-resource-collection>

            <auth-constraint>

                  <role-name>admin</role-name>

            </auth-constraint>

      </security-constraint>

 

      <security-role>

            <role-name>admin</role-name>

      </security-role>

 

      <login-config>

            <auth-method>BASIC</auth-method>

            <realm-name>input authentication message</realm-name>

      </login-config>

這樣配置完成後,當咱們訪問.../jack中的任何內容都會要求輸入密碼認證信息,認證時輸入tomcat-users.xml配置的admin權限的用戶名和密碼便可訪問(這裏就只有jack用戶名能夠訪問)

6.初識攔截器

攔截器能在action被調用以前和被調用以後執行一些代碼Struts2框架的大部分核心功能都是經過攔截器來實現的,如防止重複提交、類型轉換、對象封裝、校驗、文件上傳、頁面預裝載等等,都是在攔截器的幫助下實現的。每個攔截器都是獨立裝載的(pluggable),咱們能夠根據實際的須要爲每個action配置它所須要的攔截器。

myStruts2項目下,從新對配置文件做以下修改:
<package name="myFirst" namespace="/" extends="struts-default">

<interceptors>

<interceptor name="timer"

                        class="com.opensymphony.xwork2.interceptor.TimerInterceptor" />


     <interceptor name="params"                                                                                                                      class="com.opensymphony.xwork2.interceptor.ParametersInterceptor" />

      </interceptors>

 

            <action name="login" class="com.asm.LoginAction">

                  <interceptor-ref name="timer"></interceptor-ref>

                  <interceptor-ref name="params"></interceptor-ref>

                  <result name="loginSuccess">/success.jsp</result>

                  <result name="loginFailure">/failure.jsp</result>

            </action>

      </package>

首先在package中定義了兩個攔截器,而後在login action中引用了這兩個攔截器,須要說明的是這裏使用的攔截器都是系統自帶的攔截器。其實在extends所繼承的struts-default中就包含了不少攔截器,也包括咱們這裏所用的攔截器,但若是在此action中不使用params攔截器,將會報空指針錯,由於params攔截器的做用是傳遞表單參數,若是不使用此攔截器就不能在action中獲得表單參數,因此引用時會報空指針錯。雖然extends繼承的strust-default自帶有params攔截器,可是當咱們本身引用了攔截器時,繼承struts-default將不會再爲咱們分配默認的攔截器(有點相似構造器),可是咱們仍然能夠經過<interceptor-ref name="defaultStack"/>來繼續使用struts-defalut的攔截器。補充:因爲上面的package繼承於struts-default,而咱們這裏所用到的timerparams都是在struts-defalut中定義過,因此即便咱們在<interceptors>中沒有定義過這兩個攔截器,也能夠直接在action中引用。

使用<interceptor-stack>組合多個攔截器:好比咱們想把上面的paramstimer這兩個攔截器組合:

      <interceptor-stack name="timer_param">

                        <interceptor-ref name="timer" />

                        <interceptor-ref name="params" />

      </interceptor-stack>

而後再在action引用<interceptor-ref name="timer_param"/>」,效果和分別引用兩個是同樣的。其實咱們使用strtus-default中的<interceptor-ref name="defaultStack"/>也是使用interceptor-stack方式。

 

7.Action中的method屬性

在struts1.x中咱們知道經過繼承DispatchAction能夠實現把多個Action進行統一操做,在struts2中實現action的統一操做也很簡單。咱們以crud操做爲例,把crud集中到一個Action中。
步驟1、創建CRUDAction,內容以下:
package com.asm;

import com.opensymphony.xwork2.ActionSupport;

public class CRUDAction extends ActionSupport {

      public String add() {

            return "success";

      }

      public Stringdel() {

            return "success";

      }

      public String update() {

            return "success";

      }

      public String query() {

            return "success";

      }

}

步驟2、配置此Action,爲了清晰明瞭,專爲此Action,創建一個配置文件crud.xml,主要內容以下:
<struts>

      <package name="crud" extends="struts-default" namespace="/crud">

            <action name="add" class="com.asm.CRUDAction" method="add">

                  <result name="success">/crud/addSuccess.jsp</result>

            </action>

            <action name="del" class="com.asm.CRUDAction" method="del">

                  <result name="success">/crud/delSuccess.jsp</result>

            </action>

            <action name="update" class="com.asm.CRUDAction" method="update">

                  <result name="success">/crud/updateSuccess.jsp</result>

            </action>

            <action name="query" class="com.asm.CRUDAction" method="query">

                  <result name="success">/crud/querySuccess.jsp</result>

            </action>

      </package>

</struts>

分析:上面的method方法的值來源於CRUDAction中方法的名字,這樣當咱們訪問上面的每個Action時,它實質是和method指定的方法關聯上。

步驟3、把crud.xml配置文件併入struts.xml中,只需增長以下代碼:
<include file="crud.xml"></include>

步驟4、編寫相應的jsp頁面,在此略去crud文件夾下的四個跳轉jsp頁面(addSuccess.jsp等),重點是crud.jsp頁面。內容以下:
<html>

<%

      String path=request.getContextPath();

%>

      <body>

            <a href="<%=path %>/crud/add.action">添加數據</a><br>

            <a href="<%=path %>/crud/del.action">刪除數據</a><br>

            <a href="<%=path %>/crud/query.action">查詢數據</a><br>

            <a href="<%=path %>/crud/update.action">修改數據</a><br>

      </body>

</html>
步驟5、發佈測試。
補充擴展,動態調用DMI不使用method實現統一.咱們在crud.xml中增長以下內容:
<action name="op" class="com.asm.CRUDAction">

                  <result name="success">/crud/op.jsp</result>

</action>

而後再在crud.jsp中定義以下連接:

<a href="<%=path %>/crud/op!add.action">添加數據</a><br>

<a href="<%=path %>/crud/op!del.action">刪除數據</a><br>

<a href="<%=path %>/crud/op!query.action">查詢數據</a><br>

<a href="<%=path %>/crud/op!update.action">修改數據</a><br>

注意查看上面的連接地址,它們都是針對op action,而後再加地上「!+CRUDAction中相應的方法名」,最後再寫上.action便可以訪問到統一頁面op.jsp。這樣作雖然能減小頁面,可是因爲它們實質用到的是同一個Action,因此這就意味着咱們要使用的攔截器相同,相同的跳轉result。實際中這種方式不多使用,在此略做了解。若是不想使用動態方法調用,咱們能夠經過常量來關閉,即在struts.xml中增長以下配置:
<constant name="struts.enable.DynamicMethodInvocation" value="false"/>

擴展2在CRUDAction中使用do。舉例:咱們在CRUDAction中增長一個新的方法,內容以下:

      public String doMain(){

            return "success";

      }

而後再在在crud.xml中增長以下內容:

<action name="main" class="com.asm.CRUDAction" method="main">

                  <result name="success">/crud/main.jsp</result>

</action>
注意:配置中method屬性值是doMain中去掉doM小寫。而後再在crud.jsp中增長以下連接:
<a href="<%=path %>/crud/main.action">main頁面</a><br>

隨後即可以訪問到.../crud/main.jsp頁面了。

8.使用ForwardAction實現頁面屏蔽。

咱們在jsp頁面之間寫連接總會是.../xxx.jsp,而若是咱們想屏蔽掉具體的jsp,只須要所jsp頁面配置成一個ForwardAction便可實現。示例以下:在根目錄下有一個index.jsp主頁,咱們strtus.xml中做以下配置:

<package name="def" extends="struts-default">        

            <action name="forward">

                  <result >/index.jsp</result>

            </action>

</package>

說明:若是沒有爲action指定class,默認就是ActionSupport類,若是沒有爲action指定method屬性,則默認執行execute方法,若是沒有指定resultname屬性,默認值爲success。知道了這些再結合ActionSupport的源碼就不難理解實現轉發的原理了。
隨後再在前面7點擴展中用到的op.jsp中增長以下代碼:
<a href="<%=request.getContextPath()%>/forward.action">forward</a>

最後再測試訪問op.jsp,在op.jsp中頁面中直接點連接即可以跳到index.jsp,觀察地址欄發現此時跳到index頁面是進行的服務器跳轉,若是咱們在上面的配置中的result增長type屬性變成<result type="redirect">/index.jsp</result>,實現的跳轉就是客戶端跳轉。 補充:像這種forward形式的action實質是執行的ActionSupport 這個Action。所以配置它的result能夠參看此類的api文檔,好比它經常使用的result name有:successlogininput等。

8.使用default-Action配置統一訪問

default-action-ref,當訪問沒有找到對應的action,默認就會調用default-action-ref指定的action.一樣在上面的package中增長以下內容:

<default-action-ref name="error"></default-action-ref>

            <action name="error">

                  <result>/other/error.jsp</result>

            </action>

上面一段內容就是說當咱們訪問的action不能被找到時便指向名爲erroraction中去,接着咱們在下面配置了這個error Action。可是要注意,一個package內只配置一個<default-action-ref>,若是配置多個,就沒法預測結果了. 此時咱們只要輸入.../myStruts2/luanFangWen.action這樣的形式,它就會去訪問這個默認的<default-action-ref>,一般咱們會爲它配置一個錯誤頁面,以提示用戶訪問的頁面不存在。 在web開發中,咱們還能夠把這個默認的action訪問配置成主頁,這樣當用戶訪問一些不存在的action時,總會跳到主頁上去。

經過此配置,只要是訪問一個不存在的action便會轉向到.../other目錄下的error.jsp頁面。可是若是訪問是其它的不存在資源則還是報tomcat所標識的404錯誤,咱們能夠在web.xml中做以下配置:
<error-page>

            <error-code>404</error-code>

            <location>/other/404error.jsp</location>

</error-page>

這樣配置後,訪問錯誤頁面將跳到.../other/404error.jsp頁面中去。補充說明:若是咱們用ie訪問時,若是選中了[工具][IE選項][高級][瀏覽][顯示友好的http錯誤信息],則配置的錯誤頁面將失效,由於找不到資源時會報HTTP404錯誤,而ie截取到此錯誤進行了它自身的友好處理,因此咱們設置<error-page>就失效。

小結Action

在struts2中一個普通的java類只要有public String execute()這樣的方法均可以配置成一個Action,另外咱們能夠實現Action接口來使java類成爲一個Action,但一般的作法是繼承ActionSupport類,這也是之後的項目中慣用的方法,也是推薦的首選方法。 與struts1.x不一樣的是:在struts2中每個Action被請求訪問時都會new出這個Action對象,因此Action自己不存在線程安全的問題。

9.使用通配符

創建struts2wildcard項目,此實例基本仿照前面前面第7的實例改寫而成。爲了使用通配符,只須要改寫配置文件便可。此實例未使用通配時的配置文件以下:

<action name="addUser" class="com.asm.UserAction" method="addUser">

                  <result name="success">/user/addUser.jsp</result>
</action>

<action name="delUser" class="com.asm.UserAction" method="delUser">

                  <result name="success">/user/delUser.jsp</result>

</action>

<action name="queryUser" class="com.asm.UserAction" method="queryUser">

                  <result name="success">/user/queryUser.jsp</result>

</action>

<action name="updateUser" class="com.asm.UserAction" method="updateUser">

               <result name="success">/user/updateUser.jsp</result>

</action>

咱們註釋掉上面的配置,使用通配符只需以下內容便可達到相同的效果:
<action name="*User" class="com.asm.UserAction" method="{1}User">

                  <result name="success">/user/{1}User.jsp</result>

</action>

原理:當有.../addUser.action請求時,若是不能在當前應用中找到徹底相同的addUser名字的Action時,通配符配置這時就起做用了,按通配原則,它便和上面的name="*User"相配成功,這裏不難明瞭*此時代指的內容是add,再來看method偏偏是引用第一個*的內容,因此它的method此時的完整名爲addUser,它恰好和com.asmUserAction中的addUser方法相對,因此它會去addUser方法,再來看下面的result配置所指代的頁面,它也用到了{1},因此它的完整頁面是/addUser.jsp。其實若是咱們有良好的編程命名習慣,全部的Action咱們都只須要進行一次配置。舉例:規定全部的Action類都用XXXAction來命名,類中全部的CRUD方法都用add/del/update/queryJsp頁面也用add/del/update/query_XXX.jsp這樣的形式。即配置文件能夠寫成以下形式:
<action name="*_*" class="com.asm.{2}Action" method="{1}">

                  <result name="success">.../{1}_{2}.jsp</result>

</action>

Name中第一個*表明CRUD操做的名字,第二個*表明類的名字。因此訪問連接地址舉例以下:

.../del_User.action將訪問到User類的del方法,成功後跳到del_User.jsp頁面。補充說明{0}是表明name中全部的*組合。

10.使用0配置:ZERO Annotation




11.Result配置詳解

說明:在前面的許多案例中咱們所用到的Action基本都繼承自ActionSupport這個類,而在這個類中咱們定義了五個字段:SUCCESSNONEERRORINPUTLOGING。咱們能夠直接返回這些字段值,這些字段值實質是被定義成:String SUCCESS=」success」這樣的形式,因此咱們只要在Result元素中用它們的小寫便可。
<result>標準完整形式以下:

<result name="success" type="dispatcher">

            <param name="location">/default.jsp</param>

</result>

若是咱們都採用默認的形式,最終能夠簡寫成:<result>/default.jsp</result>

探討type類型

Type類型值

做用說明

對應類

chain

用來處理Action

com.opensymphony.xwork2.ActionChainResult

dispatcher

用來轉向頁面,一般處理JSP

org.apache.struts2.dispatcher.ServletDispatcherResult

redirect

重定向到一個URL

org.apache.struts2.dispatcher.ServletRedirectResult

redirectAction

重定向到一個Action

org.apache.struts2.dispatcher.ServletActionRedirectResult

plainText

顯示源文件內容,如文件源碼

org.apache.struts2.dispatcher.PlainTextResult

freemarker

處理FreeMarker模板

org.apache.struts2.views.freemarker.FreemarkerResult

httpheader

控制特殊http行爲的結果類型

org.apache.struts2.dispatcher.HttpHeaderResult

stream

 

向瀏覽器發送InputSream對象,一般用來處理文件下載,還可用於返回AJAX數據。

 

org.apache.struts2.dispatcher.StreamResult

 

velocity

處理Velocity模板

org.apache.struts2.dispatcher.VelocityResult

xslt   

  處理XML/XLST模板

 org.apache.struts2.views.xslt.XSLTResult

以上對type類型做簡要的說明,下面來看實例:當一個Action處理後要返回的Result是另外一個Action時,做如何配置,關鍵就是配置type類型。下面創建struts2result項目說明
步驟一:創建兩個Action:TestActionTest2Action
步驟二:web.xml配置省略。struts.xml主要配置內容以下:
<struts>

      <package name="resultTest" extends="struts-default">

            <action name="test" class="com.asm.TestAction">

                  <result name="success" type="chain">

                        <param name="actionName">test2</param>

                  </result>

            </action>

 

            <action name="test2" class="com.asm.Test2Action">

                  <result name="success">/test2Suc.jsp</result>

            </action>  

      </package>

</struts>
說明:在名爲「test」的action中,咱們配置result元素的type類型值爲chain,意爲將繼續把Action傳遞到下一個名爲test2Action中去,在test2.action中會把頁面轉向到test2Suc.jsp中去。在type類型爲chain時,它的param4個值可配,除了這裏用到的name=」actionName」外(必須配置,不然報錯),還有name=namespace|method|skipActions。其中namespace指定要轉向到action的名字空間,因爲此處是轉到Action位於同一個namespace下,而namesapace的默認值the current namespace,因此能夠省略不寫(須要說明的是若是要跳到別的名稱空間的action中去,除了使用namespace指定外,還能夠用:/要跳去action所在名稱空間的值/要跳去的actionname)Method用於指定轉向到一個目標action所調用的方法,默認是調用下一個actionexecute方法,因此此處仍能夠省略。SkipActions是一個可選的屬性,通常不用。具體能夠參看chain所對應類的api幫助。
在本實例中,咱們還在TestAction中設定一個username字段,並在execute方法執行爲它賦了值,並在test2Suc.jsp中引用了此值。其實這種作法在web開發中仍是頗有用處,好比能夠代替隱藏域。須要注意的是之因此在action的傳遞中能把設定的這個值保存下去,主要是由於轉向都是服務器跳轉。若是咱們跳轉時採起了客戶端跳轉,好比在test2 actionresult中指定type類型爲redirect,要想傳遞參數能夠在result指向的jsp頁面中附加參數便可,咱們能夠在test2 actionresult中寫成:
<result name="success" type="redirect">
/test2Suc.jsp?username=${username}
</result>   隨後在test2Suc.jsp頁面中引用時會出現三個問題:1.EL表達式引用失效,(EL表達式應該使用${param.username}形式)。咱們也可使用<%=request.getParameter("username")%>獲取參數值。  2.因爲在前面的TestAction中設定的值爲中文,而附加到這裏的uri請求的參數後面時會出現亂碼問題。(可使用URI編碼再解碼解決此問題)3.值棧取值失效:由於每一次request共享同一個值棧,因此服務器端的forward跳轉也是能共享同一值棧得。可是着當test action執行後把請求交由test2 action時,test2 action採起的是redirect重定向到test2Suc.jsp頁面,這時其實就是重發的一次request,因此在test action保存的值棧內容所有失效。這也就是爲何咱們要附加參數的緣由。而參數是保存在actionContext中,因此採用了#的方式來取出值。圖示說明:

 SHAPE \* MERGEFORMAT

<package name="resultTest" extends="struts-default">

      <action name="test" class="com.asm.TestAction">

            <result name="success" type="chain">

                  <param name="actionName">test2</param>

            </result>

      </action>

      <action name="test2" class="com.asm.Test2Action">

<result name="success" type="redirect">               /test2Suc.jsp?username=${username}
   
</result>

      </action>
</package>

test2.action

同一個
context map

result tyep:
chain

參配置

參配置

test2Suc.jsp

建立新的
context map

重新的context map中取值


result tyep:
redirect



步驟三,編寫連接頁面index.jsp。發佈測試:

全局result

若是咱們全部的action均有可能跳到相同的頁面,則不防使用全局result。爲了方便引用咱們專門創建一個package來存放公共的result。在會用到個全局的跳轉時,只須要把繼承自這個公共的package便可。

創建公共包,代碼以下:

<package name="pubResult" extends="struts-default" abstract="true">

            <global-results>

                  <result name="error">/error.jsp</result>

            </global-results>

</package>

因爲它下面沒的action配置,因此咱們能夠像默認的struts-default包同樣,聲明abstract=true,這樣聲明表示此packgage下不會有action,它通常是用來讓別的package繼承。隨後再在要用到全局result中引用這個公共的package。代碼以下:

<package name="testGlobal" extends="pubResult" >

      <action name="error" class="com.asm.ErrorAction"></action>

      <action name="error2" class="com.asm.Error2Action"></action>

</package>這樣操做至關於把全局的result加到了此package下的全部action中去。

動態Result:瞭解

步驟一:創建DynaAction,主要代碼以下:
package com.asm;

public class DynaAction extends ActionSupport {

      private String username;

      private String nextAction;

 

      public String execute() throws Exception {

            if (username.equals("admin")) {

                  nextAction = "admin";

            } else if (username.equals("user")) {

                  nextAction = "user";

            } else {

                  nextAction = ERROR;

            }

            return SUCCESS;

      }

      ...省略get/set方法 

}

步驟2、創建jsp頁面dyna.jsp,主要是爲了向DynaAction中傳遞username參數。

步驟3、相關配置以下:

<package name="dynaTest" extends="pubResult">

            <action name="dyna" class="com.asm.DynaAction">

                  <result name="success" type="chain">${nextAction}</result>

            </action>

 

            <action name="admin" >

                  <result>/admin.jsp</result>

            </action>

 

            <action name="user">

                  <result>/user.jsp</result>

            </action>

</package>

分析:當dyna.jsp把參數傳遞到DynaAction中去時,若是傳遞的值爲admin,咱們便設定了nextAction的值admin,在配置文件中咱們經過${nextAction}(用在struts配置文件中的ognl,其實nextAction的值是存在值棧中,咱們經過${}這樣的形式取出。在此只做瞭解)來獲取值便爲admin,隨後再繼續把請求傳遞到下一個Action中去(此時也即admin.action),爲了方便咱們設定了兩個ForwardAction:admin.actionuser.action這樣即可以跳到指定的jsp頁面。 原理:dyna.action執行後會繼續把請求傳遞給下一個Action,而下一個Action的究竟是哪個Action,是經過DynaAction中動態指定的,好比這裏是根據傳遞的username的值指定。

12.異常處理

步驟1、創建struts2exception項目下,在該項目下創建登陸頁面login.jsp。主要代碼以下:
<form action="<%=request.getContextPath() %>/login.action">

            username:<input type="username" name="username"><br>

            <input type="submit" value="login">

</form>    

步驟2、創建LoginAction,代碼以下:
package com.asm;

public class LoginAction extends ActionSupport {

      private String username;

      public String execute() throws Exception {

            if (username.equals("exception")) {

                  throw new ClassNotFoundException("類未被找到");

            } else if (username.equals("global")) {

                  throw new Exception("全局異常");

            } else {

                  return SUCCESS;

            }

      }
      ...省力get/set方法

}

步驟3、struts.xml配置文件以下:

<struts>

      <package name="ex" extends="def">

            <action name="login" class="com.asm.LoginAction">

                  <exception-mapping result="myException"

                        exception="java.lang.ClassNotFoundException">

                  </exception-mapping>

                  <result name="myException">/exception.jsp</result>

                  <result name="success">/main.jsp</result>

            </action>

      </package>

 

      <package name="def" extends="struts-default" abstract="true">

            <global-results>

                  <result name="myGlobal">/globalException.jsp</result>

            </global-results>

 

            <global-exception-mappings>

                  <exception-mapping result="myGlobal"

                        exception="java.lang.Exception">

                  </exception-mapping>

            </global-exception-mappings>

      </package>

</struts>

分析:異常處理機制較爲簡單,因此在此只略做說明。當登陸時輸入「exception」時,在LoginAction中會拋出會一個ClassNotFoundException異常,此異常咱們採起的局部異常處理的方式,若是登陸時輸入「global,則會拋出Exception異常,此異常咱們採起的是全局異常的處理方式,在ex包中咱們繼承了全局異常所在的包。提示:<exception-mapping>中的result屬性的值來源於<result>元素中的name屬性的值。從實例能夠看出,咱們通常把這種全局性的異常放在一個抽象包中供其實包繼承。

3、在Action獲取Scope對象

引言:在前面的Action操做中,關鍵就是Action中的exectue方法,可是此方法並無requestsessionapplication等對象做爲參數,天然就不能利用這些對象來操做。下面咱們創建struts2scope項目,並用四種方式來獲取這些對象:

方式1、與Servlet解耦合的非IOC方式

獲取的scope對象與容器無關,經過ActionContext獲取。
LoginAction代碼以下

package com.asm;

public class LoginAction extends ActionSupport {

      private String username;

      ActionContext context;

      Map request;

      Map session;

      Map application;

      public String execute() throws Exception {

            context=ActionContext.getContext();

            request=(Map) context.get("request");

            session=context.getSession();

            application=context.getApplication();

           

            request.put("req", "requst屬性");

            session.put("ses", "sesion屬性");

            application.put("app", "application屬性");

            return SUCCESS;

      }

      ...省略username的get/set方法

}

struts.xml配置以下:

<struts>

      <package name="scope" extends="struts-default">

            <action name="login" class="com.asm.LoginAction">

                  <result>/loginSuc.jsp</result>

</action>
</package>          

</struts>

login.jsp內容以下:

<form action="<%=request.getContextPath() %>/login.action">

            用戶名:<input type="text" name="username"><br>

            <input type="submit" value="login">

</form>

loginSuc.jsp的主要內容以下:
${requestScope.req}

${sessionScope.ses}

${applicationScope.app}
<h4>如下使用scope.getAttribute的形式來接受</h4>

            request:   <%=request.getAttribute("req") %><br>

            session:   <%=session.getAttribute("ses") %><br>

            application:<%=application.getAttribute("app") %><br>

分析:經過ActionContext的getContext靜態方法獲得ActionContext對象,而後ActionContext對象調用get方法來獲取一個存儲在request範圍中的對象。咱們使用el或經過request.getAttribute這樣的方式都可以獲取對象值,這說明了這些Map request對象實際是存儲在request範圍內的對象。

方式2、與Servlet解耦合的IOC方式

咱們創建Login2Action,主要代碼以下
package com.asm;

public class Login2Action extends ActionSupport implements RequestAware,SessionAware,ApplicationAware {

      private String username;

      Map request;

      Map session;

      Map application;

      public String execute() throws Exception {

            request.put("req", "requst屬性");

            session.put("ses", "sesion屬性");

            application.put("app", "application屬性");

            return SUCCESS;

      }

      public void setRequest(Map<String, Object> request) {

            this.request=request;

      }

      public void setSession(Map<String, Object> session) {

            this.session=session;

      }

      public void setApplication(Map<String, Object> application) {

            this.application=application;

      }

      ...省略username的get/set方法   

}
註冊此Actionnamelogin2,隨後修改登陸提交爲.../login2.action。即可以發佈測試。說明:此方法其實和方式一很類似,只是在方式一中咱們須要手動的爲Map request賦值,可是在方式二中它是經過實現接口,在重寫接口中的方法中完成對Map requset的賦值,因此稱之IOC方式。藉助此例,略談下依賴注入與控制反轉:所謂依賴注入就是一個對象本身自己的初始化是依賴其它對象。好比這裏Map request這些對象會依賴struts2來給其初始化,稱爲依賴注入,而依賴注入的就表示,這些對象的控制權再也不由此類自己掌握,而是交給了別的對象,便是控制權反轉了。 強調:方式二是開發中主要用的方式,應重點掌握

方式3、與Servlet耦合的非IOC方式

創建Login3Action,代碼以下:
package com.asm;
public class Login3Action extends ActionSupport {

      private String username;

      HttpServletRequest request;

      HttpSession session;

      ServletContext application;

      public String execute() throws Exception {

            request = ServletActionContext.getRequest();

            session = request.getSession();

            application = ServletActionContext.getServletContext();

 

            request.setAttribute("req", "requst屬性");

            session.setAttribute("ses", "sesion屬性");

            application.setAttribute("app", "application屬性");

            return SUCCESS;

      }

      ...省略username的get/set方法。

}
此方法獲取的純粹的Scope對象,它與容器相關,這些Scope對象操做更強。一樣只須要註冊此Action並修改登陸頁面即可進行測試。

方式4、與Servlet耦合的IOC方式

創建Login4Action,代碼以下:

package com.asm;
public class Login4Action extends ActionSupport implements ServletRequestAware,ServletContextAware{

      private String username;

      ActionContext context;

      HttpServletRequest request;

      HttpSession session;

      ServletContext application;

      public String execute() throws Exception {

            context=ActionContext.getContext();

            session=request.getSession();

            request.setAttribute("req", "requst屬性");

            session.setAttribute("ses", "sesion屬性");

            application.setAttribute("app", "application屬性");

            return SUCCESS;

      }

     

      public void setServletRequest(HttpServletRequest request) {

            System.out.println("測試:"+request);

            this.request=request;

      }

      public void setServletContext(ServletContext application) {

            System.out.println("測試:"+application);

            this.application=application;

      }

      ...省略username的get/set方法

}

一樣只須要註冊此Action並修改登陸頁面即可發佈測試

4、OGNLValueStack(VS)

1.值棧入門

下面咱們創建struts2ognl項目來練習ognl的使用。
步驟1、搭建strust2的開發環境
步驟2、創建LoginAction,主要代碼以下:
package com.asm;

public class LoginAction extends ActionSupport{

      private User user;

      public String execute() throws Exception {     

                  return SUCCESS;

      }

      ...省略user的get/set方法 

}

步驟3、配置此Action,struts.xml的主要內容以下:
<struts>

      <constant name="struts.devMode" value="true"></constant>

      <package name="ognl" extends="struts-default">

            <action name="login" class="com.asm.LoginAction">

                  <result>/loginSuc.jsp</result>

            </action>

      </package>

</struts>
步驟4、編寫login.jsp頁面,主要代碼以下:
<body>

      <form action="<%=request.getContextPath()%>/login.action"  method="get">

            用戶名:<input type="text" name="user.username"><br>

            密  碼:<input type="password" name="user.password"><br>

            <input type="submit" value="login">

      </form>

</body>

步驟5、編寫loginSuc.jsp頁面,主要代碼以下:
<body>

            調試:<s:debug></s:debug>

            獲取值棧中的username屬性:<s:property value="user.username"/> <br>
</body>

步驟6、發佈測試及說明
當咱們輸入用戶名併成功跳到logSuc.jsp頁面後,會獲得登陸時輸入的用戶名信息。下面簡要說明這一過程:
1.login.jsp登陸提交登陸信息給login.action

(2.struts2監聽到客戶端的login.action請求,按配置文件要求,把此請求交給LoginAction處理。這表示會去new LoginAction(), 當struts2  new出此Action對象後會把這個對象放在context map中,只是這個Action很是特殊,因此放在值棧中,而放在值棧中的對象是能夠直接引用的,放在其它context map中的對象引用時會要求加#。
3.new LoginAction時,表示它也會初始化此類中的對象,好比這裏會去初始化User對象,可是要注意的是若是咱們在用戶名和密碼什麼都不輸,再來用debug來看值棧中的user是,發現它仍會new此對象,由於儘管咱們沒用輸入值,可是後臺的set方法仍是要被調用,因此會new出此對象,可是若是咱們直接輸入.../struts2ognl/login.action時咱們會發現跳轉到loginSuc.jsp頁面時,用debug來看值棧中此User user,發現它的值爲null。第二點要注意的是咱們必須爲User類提供默認的構造方法,不然將會出現以下錯誤: java.lang.InstantiationException: com.asm.vo.User
總結:1.Action會在請求時被建立,且會把建立的對象放到值棧中。
2.Action中的對象字段只有在須要時纔會以new 的形式初始化,並且這些對象字段必須提供默認的構造方法。
3.ValueStack對象貫穿整個Action的生命週期(每一個Action類的對象實例會擁有一個ValueStack對象)。當Struts 2接收到一個.action的請求後,會先創建Action類的對象實例,但並不會調用Action方法,而是先將Action類的相應屬性放到ValueStack對象的頂層節點(vs對象至關於一個棧)。

補充:值棧(根)對象也能夠直接使用EL表達式訪問,好比這裏能夠直接經過${user.username}來獲取username的值,咱們知道el表達式只能訪問四種scope範圍內的對象,那爲何這裏能訪問到值棧對象呢?緣由是struts2HttpServletRequet進行了一次封裝,封裝的代碼主要是重寫了getAttribute方法,簡述重寫此方法的核心代碼:首先在原始的HttpServletRequest對象中查找el表達式中的屬性,若是找不到再經過ActionContext獲取值棧對象,進而再從值棧對象中查找el表達式中要訪問的屬性。

2.OGNL入門

下面咱們在com.asm.vo.User類中增長一個字段private Address addres;,並提供此字段的get/set方法,隨後再在login.jsp中增長以下代碼:
城  市:<input type="text" name="user.addres.city"><br>
而後再在loginSuc.jsp中增長以下代碼:

獲取城市屬性:<s:property value="user.addres.city"/><br>
而後測試,會獲得登陸時輸入的城市信息(中文會有亂碼)。下面藉助此例談ognl的定義:在這個例子中,咱們的LoginAction中有一個User對象,而在User對象中又有一個Address對象,這些對象之間依靠這種類的字段進行關聯,或者說是依靠字段屬性進行導航,這也就是OGNL的定義:Object Graph Navigation Language:對象圖導航圖語言,它是創建在值棧技術之上的一種全新語言。
補充用%{}能夠取出存在值堆棧中的Action對象,直接調用它的方法.咱們在loginSuc.jsp中增長以下內容調用LoginAction中的get方法:
調用值棧對象中的方法:<s:property value="%{get()}"/>
LoginACtion中增長的get方法以下:
public String get(){

            return "這是User中的get方法";

}

3.普通方法訪問

首先在User中增長一個成員方法,代碼以下:
public String get(){

            return "這是User中的get方法";

}
在LoginAction中也有相似的get方法,隨後再在loginSuc.jsp中增長以下代碼:
調用值棧對象中的普通方法(2):<s:property value="user.username.length()"/><br>

調用值棧對象中的普通方法(1):<s:property value="user.get()"/><br>

調用LoginAction中的普通方法:<s:property value="get()"/><br>
最後測試,發現這些方法均可以訪問到。

4.靜態方法訪問

在LoginAction中增長以下方法:
public static String getSta() {

            return "這是LoginAction中的靜態方法";

}

而後在loginSuc.jsp中增長以下代碼:

調用Action中的靜態方法:<s:property value="@com.asm.LoginAction@getSta()"/><br>

調用LoginAction中的靜態方_方式(2):<s:property value="@vs @getSta()"/><br>

說明:咱們在方式二中用到@vs ,只有那些值棧中的對象才能夠這樣寫。
而後訪問,發現訪問不到,由於在struts2.1.6的版本中,struts.ognl.allowStaticMethodAccess的默認值爲false,咱們只需在struts.xml中增長以下內容:
<constant name="struts.ognl.allowStaticMethodAccess" value="true"/>
再來訪問時即可以訪問到。

5.默認類Math的訪問

在loginSuc.jsp中增長以下代碼:
調用Math類中的靜態方法:<s:property value="@java.lang.Math@min(1,2)"/><br>

調用Math類中的靜態方法_方式(2):<s:property value="@@min(1,2)"/><br>

調用Math類中的字段:<s:property value="@@PI"/><br>

說明:由於是默認的類,因此能夠省略類名

6.調用普通類的構造方法

創建一個新的類:Student,在此省略代碼。
而後在loginSuc.jsp中增長以下代碼:
調用普通類中的構造方法  :
<s:property value="new com.asm.vo.Student('jack','20','85.5')"/><br>

調用普通類中的構造方法並訪問其字段 :
<s:property value="new com.asm.vo.Student('jack','20','85.5').name"/>

說明:第一種是隻new出對象,顯示的時候實際上是調用對象的toString方法。

7.集合對象初步

首先在LoginAction中增長以下字段並提供相應的get/set方法:
private List myList = new ArrayList();

private Set mySet = new HashSet();

private Map myMap = new HashMap();
而後再在execute方法中初始化這些集合對象,代碼以下:
myList.add("list1");

myList.add("list2");

myList.add("list3");

myList.add("list4");

 

mySet.add("set1");

mySet.add("set3");

mySet.add("set1");

mySet.add("set2");

 

myMap.put("m1", "map1");

myMap.put("m3", "map3");

myMap.put("m2", "map2");
最後在loginSuc.jsp中增長以下代碼:
獲取List:<s:property value="myList"/><br>

獲取List中的第一個元素:<s:property value="myList[0]"/><br>

獲取Set:<s:property value="mySet"/><br>

獲取Set中的第一個元素(set無序,不能取到):<s:property value="mySet[0]"/><br>

獲取Map:<s:property value="myMap"/><br>

獲取Map中的key=m1的元素的值:<br>

方式一:<s:property value="myMap.m1"/>

方式二:<s:property value="myMap['m1']"/><br><hr>

獲取List的大小:
<s:property value="myList.size"/>|<s:property value="myList.size()"/><br>

獲取Map中全部鍵:<s:property value="myMap.keys"/><br>

獲取Map中全部值:<s:property value="myMap.values"/><br>
最後測試,這些東西很少做解釋。

8.集合對象進階

首先在LoginAction中增長以下字段並提供相應的get/set方法:
private List studentList = new ArrayList();
而後再在execute中爲其初始化賦值,代碼以下:
studentList.add(new Student("jack", 20,86.0f));

studentList.add(new Student("lily", 22,96.5f));

studentList.add(new Student("tom", 23,56.5f));

最後在loginSuc.jsp中增長以下代碼:
獲取List中的Student對象:<s:property value="studentList"/><br>

利用投影獲取List中的name屬性:<s:property value="studentList.{name}"/><br>

利用投影獲取List中的age屬性:<s:property value="studentList.{age}"/><br>

利用投影獲取List中的第一個對象的name屬性:<s:property value="studentList.[0]{name}"/>   或者<s:property value="studentList.{name}[0]"/><br>

利用選擇獲取List中grade>60的student信息:

<s:property value="studentList.{?#this.grade>60}"/><br>

利用選擇獲取List中grade>60的student名字信息:

<s:property value="studentList.{?#this.grade>60}.{name}"/><br>

利用選擇獲取List中grade>60的第一個student名字信息:

<s:property value="studentList.{?#this.grade>60}.{name}[0]"/><br>

利用選擇獲取List中grade>60的第一個student名字信息(鏈表):

<s:property value="studentList.{^#this.grade>60}.{name}"/><br>

利用選擇獲取List中grade>60的最後一個student名字信息(鏈表):

<s:property value="studentList.{$#this.grade>60}.{name}"/><br>

說明:這裏重點是說明?#的使用,結合此例來看,studentList中有許多Stutdent對象,咱們能夠用條件來限制取哪些對象,這些條件必須以?#開始,而且條件要用{}括起。而this是指在判斷studentList中的對象是否符合條件的當前對象。?#是指取出符合條件的全部Student對象,而^#是指取出符合條件的第一個對象,$#是指取出符合條件的最後一個對象。

9.N語法top語法

咱們在loginSuc.jsp中增長以下下代碼:
N語法[0]:<s:property value="[0]"/><br>

N語法[1]:<s:property value="[1]"/><br>
N語法[0].top:<s:property value="[0].top"/><br>

N語法[1].top:<s:property value="[1].top"/><br>
N語法top:<s:property value="top"/><br>

N語法取值:<s:property value="[0].user.username"/><br>

N語法取值:<s:property value="top.user.username"/><br>
說明:規定棧頂的對象爲[0],而咱們只使用[0]的意思是從值棧中第一個對象取,一直取至棧底。N的意思是從值棧中的第N個對象開始,取到棧底爲止。若是要想訪問某個對象,須要使用[N].top,它的意思是取出符合N語法的棧頂對象,好比在這裏,[0]會取出兩個對象,而[0].top是取出這兩個對象的棧頂對象。純top能夠簡潔地取出值棧中的棧頂對象。
爲何要提出N語法,當咱們經過chain鏈訪問時,值棧中可能有兩個以上的Action對象,若是這些對象中存在相同的屬性,N便能正確區分他們。一般,這些Action對象的入棧順序是:先訪問先入棧。

從上面的N語法取值實例中,咱們知道[N]top語法的一個重要做用就是能經過它們引用值棧對象中的屬性。結合前面的五種[N]top語法實例,不難理解這裏的取值實例。
補充:在此實例中,咱們用<s:debug>調試會發現,值棧中還有一個DefaultTextProvider對象(由於此Action繼承自ActionSupport),它的做用是獲取資源文件中的內容(其實本質是ActionSupport重寫了getText()方法),這也就是在國際化問題中咱們能直接調用它的getText()方法的緣由。

10.獲取Stack Context中的信息

咱們知道,除了能夠從值棧中獲取信息,還能夠從Stack Context中獲取信息,只是要加上#,下面咱們經過scope對象來演示。首先是在LoginAction中增長以下字段:
Map myRequest;
Map mySession;
隨後再用前面提到的「在Action中獲取Scope對象」的方式二來完成這些對象的初始化。即實現RequestAware和SessionAware接口。而後再在execute方法中增長以下內容:
myRequest.put("req", "Req屬性");

mySession.put("ses", "Ses屬性");
最後在loginSuc.jsp中增長以下代碼:
獲取Request屬性:<s:property value="#request.req"/><br>

獲取Session屬性:<s:property value="#session.ses"/><br>

獲取parameters屬性:<s:property value="#parameters.mes"/>
說明:咱們獲取這些對象都用了#,由於這些對象都是存在通常的Context Map中,而不是存在值棧中。別最後一個信息的獲取是由於咱們在login.jsp中增長了以下代碼:
<input type="hidden" name="mes" value="the message is transfer by hidden">
關於這些scope的更多信息能夠參看下表:

名稱

做用

例子

parameters

包含當前HTTP請求參數的Map

#parameters.id[0]做用至關於request.getParameter("id")

request

包含當前HttpServletRequest的屬性(attribute)Map

#request.userName至關於request.getAttribute("userName")

session

包含當前HttpSession的屬性(attribute)的Map

#session.userName至關於session.getAttribute("userName")

application

包含當前應用的ServletContext的屬性(attribute)的Map

#application.userName至關於application.getAttribute("userName")

Attr

用於按request > session > application順序訪問其屬性

#application.userName至關於application.getAttribute("userName")

11.總結$ # %的區別

$用於i18n和struts配置文件

#取得ActionContext的值

%將原來的文本串解析爲ognl,對於原本就是ognl的文本不起做用。形式:%{要解析的文本串}

12.總結OGNL[重點]

OGNL是Object Graphic Navigation Language(對象圖導航語言)的縮寫,它是一個開源項目。Struts2使用OGNL做爲默認的表達式語言。

相對於EL表達式,它提供了平時咱們須要的一些功能,如:支持對象方法調用,支持各種靜態方法調用和值訪問,支持操做集合對象。OGNL有一個上下文的概念,這個上下文件實質就是一個Map結構,它實現了java.utils.Map接口,在struts2中上下文的實現爲ActionContext,下面是上下文的結構示意圖:

 SHAPE \* MERGEFORMAT

ValueStack(值棧,它是根對象)

Parameters

request

session

application

attr




當struts2接受一個請求時,會迅速建立ActionContextValueStackaction。而後把action存放進ValueStack,因此action的實例變量能夠接受OGNL訪問。

訪問上下文中的對象須要使用#號標註命名空間,如#application#session。另外OGNL會設定一個根對象,在struts2中根對象就是ValueStack值棧對象,若是要訪問根對象中對象的屬性,則能夠省略#命名空間,直接訪問該對象的屬性便可。在struts2中,根對象的實現類爲OgnlValueStack,該對象不是咱們想象的只存放單個值,而是存放一組對象,在OgnlValueStack類裏有一個List類型的變量,就是使用這個List變量來存放一組對象。在root變量(List類型)中處於第一位的對象叫棧頂對象,一般咱們在Ognl表達式裏直接寫上屬性的名稱便可訪問root變量裏對象的屬性,搜索順序是從棧頂對象開始尋找,若是棧頂對象不存在該屬性,就會從第二個對象尋找,若是沒有找到就從第三個對象尋找,依次往下尋找。 注意struts2中 ,OGNL表達式須要配合struts的標籤纔可使用。

 

5、攔截器

在前面咱們已經初步使用過攔截器,下面繼續細細探討。

1.概述strust2中的攔截器

攔截器是Struts2框架的核心,它主要完成解析請求參數、將請求參數賦值給Action屬性、執行數據校驗、文件上傳等工做。Struts2設計的靈巧性,攔截器起了關鍵性的做用,當須要擴展Struts2功能時,只須要提供對應攔截器,並將它配置在Struts2容器中便可;若是不須要該功能時,也只須要取消該攔截器的配置便可。  
Struts2內建了大量的攔截器,這些攔截器以name-class對的形式配置在struts-default. xml文件中,其中name是攔截器的名字,就是之後咱們使用該攔截器的惟一標識;class則指定了該攔截器的實現類,若是咱們定義的package繼承了Struts2的默認struts-default包,則能夠自由使用它下面定義的攔截器,不然必須本身定義這些攔截器。

2.自定義攔截器

自定義攔截器須要特別注意的是不要忘記引入struts2默認的攔截器。爲了實現某些操做,咱們能夠自定義攔截器,自定義攔截器有三種方式定義。分別爲實現Interceptor接口,繼承抽象類AbstractInterceptor,繼承MethodFilterInteceptor類。

方式一,實現Interceptor接口。

準備工做,新建struts2interceptor項目。構建一個登陸環境:當咱們點登陸連接時,便成功登陸(爲了方便,這裏不進行驗證)。即在link.jsp頁面中寫以下連接:<a href="<%=request.getContextPath()%>/login.action">登陸</a> 而後,咱們點擊此連接即可以登陸。login.actionstrutst.xml中的的配置以下:
<package name="interceptor"   extends="struts-default">
    <action name="login" class="com.asm.LoginAction">

                  <result name="success">/success.jsp</result>

</action>
</package>
com.asm.LoginAction爲了簡單,com.asm.LoginAction老是返回SUCCESS;這樣請求這個Action總會返回到.../success.jsp頁面。
編寫攔截器:MyInterceptor類,內容以下:
package com.asm;

import com.opensymphony.xwork2.ActionInvocation;

import com.opensymphony.xwork2.interceptor.Interceptor;

public class MyInterceptor implements Interceptor {

      public void destroy() {

      }

      public void init() {

      }

      public String intercept(ActionInvocation invocation) throws Exception {

            System.out.println("開始攔截");

            String result = invocation.invoke();

            System.out.println("結束攔截");

            return result;

      }

}
爲了使用此攔截器,咱們必須將此攔截器進行註冊,隨後再在要使用此攔截器的Action中引用。即首先在<package>中註冊,內容以下:
<interceptors>

      <interceptor name="myIpt" class="com.asm.MyInterceptor"></interceptor>

</interceptors>

註冊完成後,若是咱們要在login.action中使用此攔截器,只須要在<action>中增長以下內容:
<interceptor-ref name="myIpt"></interceptor-ref>

這樣便成功爲LoginAction配置了咱們自定義的攔截器MyInterceptor,下面只需發佈測試。

實例流程分析:當咱們爲LoginAction配置了攔截器時,而且有客戶端請求此Action時,會首先被此攔截器攔住,而後執行System.out.println("開始攔截"),隨後咱們調用invocation.invoke()方法,它會把請求繼續傳遞給下一個攔截器,下一個攔截器也會繼續執行相應代碼後再調用invoke方法繼續傳遞,直到請求到達最後一個攔截器,它會把請求傳遞給Action,好比,咱們這裏只用到了一個攔截器,當它執行完成後,會把請求直接轉交到LoginAction處理,LoginAction處理完成後,它會返回結果給MyInterceptor攔截器。

方式2、繼承AbstractInterceptor抽象類

建立攔截器類MyAbstractInterceptor:主要代碼以下:
package com.asm;
import com.opensymphony.xwork2.ActionInvocation;

import com.opensymphony.xwork2.interceptor.AbstractInterceptor;

public class MyAbstractInterceptor extends AbstractInterceptor {

      public String intercept(ActionInvocation invocation) throws Exception {

            System.out.println("Abs開始攔截");

            String result = invocation.invoke();

            System.out.println("Abs結束攔截");

            return result;

      }

}

而後註冊此攔截器,在<interceptors>元素進行進行配置,內容以下:
<interceptor name="myAbs" class="com.asm.MyAbstractInterceptor"></interceptor>

隨後再在LoginAction中引用此攔截器,即在<action name="login" ...>配置以下內容:
<interceptor-ref name="myAbs"></interceptor-ref>

最後發佈測試。

方式3、繼承MethodFilterInteceptor

建立攔截器類MyMethodFilterInterceptor,主要代碼以下:
package com.asm;

import com.opensymphony.xwork2.ActionInvocation;

import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;

public class MyMethodFilterInterceptor extends MethodFilterInterceptor{

      protected String doIntercept(ActionInvocation invocation) throws Exception {

            System.out.println("method開始攔截");

            String result=invocation.invoke();

            System.out.println("method結束攔截");

            return result;

      }

}

而後註冊此攔截器,在<interceptors>元素進行進行配置,內容以下:
<interceptor name="myMet" class="com.asm.MyMethodFilterInterceptor">
</interceptor>

隨後再在LoginAction中引用此攔截器,即在<action name="login" ...>配置以下內容:
<interceptor-ref name="myMet"></interceptor-ref>

最後發佈測試。

分析:當配置到此,實質便爲LoginAction配置了三個攔截器,當咱們點擊登陸時會在控制檯打印出以下語句:
開始攔截

Abs開始攔截

method開始攔截

--先執行攔截器,再執行此Action

method結束攔截

Abs結束攔截

結束攔截
其實當咱們點擊登陸時,原本是要訪問LoginAction,最後會把LoginAction的執行結果傳遞給訪問者。可是當咱們配置了攔截器時,當咱們去訪問Action時,會首先被攔截,隨後攔截器執行一些操做後纔會繼續把請求傳遞下去。下面做圖說明攔截流程:
 SHAPE \* MERGEFORMAT

MyInterceptor攔截器

MyAbstractInterceptor攔截器

MethodAction攔截器

當咱們訪問Login.action時,會首先被第一個攔截器攔截

首先會執行打印語句,隨後繼續把請求傳遞給一下個攔截器

因爲它是最後一個攔截器,它會把請求轉交到LoginAction處理。

LoginAction處理完成後會返回一個結果(多爲jsp頁面)給上一個攔截器


執行後續操做,返回處理結果給客戶端

客戶端


結合現實理解:好比咱們要去某樓層找某人(LoginAction )取一個資源(LoginAction 處理後返回的結果,這裏表現爲success.jsp ),(1 進樓層時會被大門保安攔截檢查(第一個攔截器:MyInterceptor 攔截器),(2 檢查經過後再進電梯時會被電梯保安員檢查(第二個攔截器:MyAbstractInterceptor 攔截器),(3 檢查經過後再上到某樓層會被樓層保安員檢查(第三個攔截器:MethodAction攔截器


),(4檢查經過後會找到某個(LoginAction,並與某個交談(LoginAction處理),隨後某我的和咱們會帶着請求的資源出去,(5出去時,會依次被樓層,電梯,大門保安員檢查,最終檢查經過。某我的把資源給咱們(實質就是返回請求資源給客戶端)。 其實攔截器的執行流程和過濾器差很少,因此咱們不防用過濾器的眼光來看這些攔截器。
注意:咱們在爲LoginAction配置攔截器時,都沒使用默認的攔截器,是緣由這裏的測試能夠不用,可是之後在咱們使用自定義的攔截器是,必定要加上默認的攔截器,不然會致使許多不可預知的結果。
補充:從上面的圖並結合代碼,咱們能夠看出攔截器的核心過程應該是ActionInvocation對這些攔截器回調處理,下面咱們創建com.asm.interceptor.simulation來模擬這一過程,具體的代碼參源文件,在此略去。在此咱們做圖分析ActionInvocation的實現過程:
 SHAPE \* MERGEFORMAT

攔截器一

攔截器二

攔截器N

Inteceptor方法



invoke方法

Inteceptor方法

Inteceptor方法

Action


補充2 :上面分別使用了三種方式來建立自定義的攔截器,第一種方式是最原始的實現方式,第二種方式的好處是咱們能夠沒必要重寫全部的方法,較經常使用。第三種方式進行了擴展,下面將會展現它的擴展性能。


3.使用來MethodFilterInterceptor靈活攔截

步驟1、創建MethodAction,代碼以下:
package com.asm;

import com.opensymphony.xwork2.ActionSupport;

public class MethodAction extends ActionSupport{

      public String m1(){

            return SUCCESS;

      }

      public String m2(){

            return SUCCESS;

      }

      public String m3(){

            return SUCCESS;

      }

}

步驟2、註冊此Action,併爲此Action配置攔截器。配置內容以下:
<action name="*_*" class="com.asm.MethodAction" method="{2}">

            <result name="success">/{2}Suc.jsp</result>

            <interceptor-ref name="myMet">

            </interceptor-ref>

</action>

咱們爲此Action配置了前面寫的MyMethodFilterInterceptor攔截器,並在link.jsp中增長以下連接:
<a href="<%=request.getContextPath()%>/Method_m1.action">m1</a><br>

<a href="<%=request.getContextPath()%>/Method_m2.action">m2</a><br>

<a href="<%=request.getContextPath()%>/Method_m3.action">m3</a><br>

當點m1時會訪問到m1Suc.jsp頁面, m2m3會分別訪問到m2Suc.jspm3Suc.jsp頁面。如今假如咱們想訪問m2m3時不被攔截,咱們只需修改MyMethodFilterInterceptor註冊:修改內容爲:
<interceptor name="myMet"     class="com.asm.MyMethodFilterInterceptor">

                        <param name="excludeMethods">m2,m3</param>
</interceptor>
它的做用和增長<param name="includeMethods">m1</param>等價。上面是指定m2,m3方法調用時不被攔截,這裏是指定只攔截m1。除了這種在註冊攔截器時指定攔截外,還能夠在引用攔截器時指定,即以下形式:
<interceptor-ref name="myMet">

                        <param name="excludeMethods">m2,m3</param>

                        <param name="includeMethods">m1</param>
</interceptor-ref>
上面的兩處<param>配置是等價的,可是若是〈param〉配置衝突,誰起做用?即若是咱們對m1配置了excludeMethods同時又配置了includeMethods時,誰起做用,咱們能夠進行這些衝突的驗證。如下是驗證結果:
引用配置(在Action引用攔截器時配置)時,以includeMethods的配置爲準。一旦咱們爲攔截器使用了<param>配置,而對m1這樣的方法不配置任何,就不會被攔截。可是若是不使用<param>,它們所有都要被攔截。
註冊配置時(在註冊攔截器時配置),狀況和「引用配置」徹底同樣。
引用配置和註冊配置衝突時,以引用配置爲準。

4.使用默認的execAndWait攔截器

當咱們進行數據庫查詢等相關的操做時,若是服務器負荷太重可能不能及時把數據查詢出來,進而會在狀態攔顯示「正在打開...」,但卻一直轉不到相關的頁面,這將給客戶端帶來不便,甚於不少人會所以不肯使用網站的全部服務。對此咱們能夠在客戶提交時,立刻轉到一個頁面,並在該頁面顯示「您的請求已提交,服務器正在查詢,請等待...」的內容,這樣客戶將不會陷於無賴的等待中。 對於此要求,struts2能夠輕鬆幫咱們完成。下面新建struts2wait項目演示此實例。
創建LoginAction,代碼以下:
package com.asm;
public class LoginAction extends ActionSupport {

      public String execute() throws Exception {

            Thread.sleep(5000);

            return SUCCESS;

      }

}

說明:爲了模擬服務器負荷太重,查詢時間要很長。咱們在使用了線程休眠的方式。

隨後配置此Action,配置的主要內容以下:

<action name="login" class="com.asm.LoginAction">

                  <interceptor-ref name="defaultStack"></interceptor-ref>

                  <interceptor-ref name="execAndWait"></interceptor-ref>

                  <result name="wait">/wait.jsp</result>

                  <result name="success">/success.jsp</result>
</action>

注意:在配置前咱們先是使用了默認的攔截器,再此強調在咱們爲Action配置攔截器時,應該老是配上默認的攔截器。隨後咱們使用了execAndWait攔截器,如須要配置此攔截器,此攔截器必定要配置在最後,不然會出現一些難預知的結果。若是使用此攔截器,咱們一般還會配置waitresult結果集,由於「On the initial request or any subsequent requests (before the action has completed), the wait result will be returned. The wait result is responsible for issuing a subsequent request back to the action, giving the effect of a self-updating progress meter」,大概意思就是當咱們請求的Action在未執行完,就是未返回結果時,會首先把wait result返回,而在wait result所指定的頁面中一般會再次發送請求給原始的Action。因此wait.jsp的主要內容以下:
<head>

      <meta http-equiv="refresh" content="1;login.action">

</head>

      <body>            查詢請求已提交,正在查詢數據,請等待...    </body>

在此頁面中,咱們指定了每隔1秒便發送請求到login.action中去。這樣,客戶端即可以及時獲取查詢結果。結合此實例,咱們簡要分析流程:當咱們發出請求到此Login.Action中去時,首先會被exeAndWait攔截器攔截到,這樣它便跳轉到wait.jsp頁面,在wait.jsp頁面中每隔1秒咱們會繼續發送此Action的請求,當再次請求到達LoginAction時,若是它已經返回,則會跳到此Action返回的頁面,若是LoginAction未返回,則繼續停留在wait.jsp中,再隔1秒又再次發送請求到LoginAction中去。

其實若是服務器能很快查詢出結果,咱們則不須要用到wait.jsp頁面,咱們只需在<interceptor-ref name="execAndWait"></interceptor-ref>中增長以下一段配置:
<param name="delay">6000</param> 這樣便延遲請求到達wait.jsp頁面,這樣當請求到達時它會在LoginAction中執行6秒時間再到wait.jsp,而6LoginAction足以執行完並返回結果,因此當攔截器

執行時首先檢查到此Action已經返回結果。則攔截器會直接用此返回頁面,若是此時發現LoginAction並未執行完,它便會把wait resutl指定的頁面返回。須要說明的是,一般咱們設定的延遲最多一秒,這裏爲了演示,設置的很長。圖示此攔截器原理:
 SHAPE \* MERGEFORMAT

Wait Result

檢查Action是否返回結果

是否配置了延遲

延遲時間結束


配置延遲

返回的結果

未返回

返回其中一個

 
關於此攔截器的詳細的配置及文檔說明能夠參看ExecuteAndWaitInterceptor類的api 信息。


5. TokenInterceptor防止表單重複提交。

因爲某些緣由,用戶在進行相似表單提交的操做後,覺得表單未被提交,會進行屢次的重複提交。爲了不用戶屢次提交給服務器帶來負荷。咱們會對錶單提交這樣的操做進行一些處理,以告訴用戶不要重複提交。下面咱們創建struts2token項目,使用struts2的token攔截器來實現此案例。
步驟一,編寫login.jsp頁面,內容以下:
<%@ page language="java" pageEncoding="UTF-8"%>

<%@ taglib uri="/struts-tags" prefix="s" %>

<html>

      <body>

            <form action="<%=request.getContextPath()%>/login.action" >

                  姓名:<input type="text" name="username"><br>

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

                  <input type="submit" value="登陸">

                  <s:token></s:token>

            </form>

      </body>

</html>
說明,此登陸頁面中的關鍵技術就是使用了標籤庫中的<s:token></s:token>標籤,它的做用就是在用戶訪問此頁面時會生成一個sessionId,在提交時會服務器會據此驗證表單是否已提交。「To set a token in your form, you should use the token tag. This tag is required and must be used in the forms that submit to actions protected by this interceptor」,這句話的大概意思就是咱們必需要在提交的表單中使用這個token tag,這樣提交到的Action便能配置TokenInterceptor攔截器驗證表單是否重複提交。
步驟二,編寫LoginAction,主要代碼以下:
package com.asm;
public class LoginAction extends ActionSupport {

      public String execute() throws Exception {

            System.out.println("---->執行execute方法...");

            return SUCCESS;

      }

}
步驟三,struts.xml主要配置內容以下:
<struts>

      <package name="tokenTest" extends="struts-default">

            <action name="login" class="com.asm.LoginAction">

                  <result name="success">/success.jsp</result>

                  <result name="invalid.token">/subError.jsp</result>

                  <interceptor-ref name="token"></interceptor-ref>

                  <interceptor-ref name="defaultStack"></interceptor-ref>

            </action>

      </package>

</struts>

說明:在此Action下,咱們配置了token攔截器,另注意到在此Action下咱們還配置了一個「invalid.token」result,由於「This interceptor uses a fairly primitive technique for when an invalid token is found: it returns the result invalid.token, which can be mapped in your action configuration」。它的大概意思就是:提交時服務器若是根據token標籤產生的sessionId判斷出表單已提交,它則返回invalid.token指向的視圖。好比這裏,若是重複提交則會轉到.../subError.jsp中去。另不要忘記了引入默認的攔截器棧。補充:關於token攔截器更多細節能夠訪問org.apache.struts2.interceptor.TokenInterceptor類的api說明。
步驟四,編寫配置中所用到jsp頁面,這些頁面編寫簡單,在此省去。
步驟5、發佈測試,請注意訪問login.jsp頁面時,查看源文件時會發現增長了兩個隱藏域信息。
步驟6、更換攔截器:咱們還可使用tokenSession攔截器,它的功能比上面的加強,它能保證持有相同sessionId的併發請求等待第一個完成以後才能被提交處理,可是它返回的是action執行後的result.接着上例,咱們只須要在配置中做以下修改:把上面的token攔截器改爲<interceptor-ref name="tokenSession"></interceptor-ref> 便可。隨後即可以測試,測試時會發現若是咱們重複提交,它老是返回到上一次的success.jsp頁面,可是它並非通過LoginAction中的execute處理後返回(咱們System.out.print語句在重複提交時並未打印出來),而是此攔截器判斷出是重複後直接返回上一次提交轉向的頁面。

6.使用攔截器實現權限驗證

爲了說明此問題,咱們創建struts2auth項目,流程圖以下:
 SHAPE \* MERGEFORMAT

login.jsp

.../WEB-INF/note.jsp配置了note.action,並配置了攔截器

未登陸

登陸

攔截器:判斷是否登陸

login直接登陸到main.jsp


簡短說明:當咱們訪問main.jsp頁面,並試圖經過此頁面中的連接地址:note.action來訪問到.../WEB-INF/note.jsp頁面時,因爲訪問的note.action配置了攔截器,因此會被攔截,若是攔截器判斷登陸則能夠訪問,不然會跳到登陸頁面。若是咱們從登陸頁面直接到main.jsp頁面,再來訪問note.action時,一樣被攔截可是因爲登陸過,因此能夠訪問到此action對應的內容。由這裏的分析能夠看出關鍵點就登陸成功時給出標誌提供給攔截器判斷是否成功登陸。
步驟一,搭建好相關的開發環境,並準備好登陸頁面login.jsp,代碼以下:
<form action="<%=request.getContextPath()%>/login.action" method="post">

            姓名:<input type="text" name="username"><br>

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

            <input type="submit" value="登陸">

</form>
步驟二,創建相應的Action:LoginAction。代碼以下:
package com.asm;

public class LoginAction extends ActionSupport {

      private String username;

      Map session;

      public String execute() throws Exception {

            if(username.equals("admin")){

                  session = ActionContext.getContext().getSession();

                  session.put("loginSign", "loginSuccess");

                  return SUCCESS;

            }else{

                  return LOGIN;

            }

}
...省略username的get/set方法

}

說明:咱們這裏是設定了只有登陸用戶名爲admin時,此Action才設置登陸標誌。另這裏獲取Session對象採起的是「與Servlet解耦合的非IOC方式」。
步驟三,編寫攔截器類,代碼以下:
package com.asm.interceptor;

public class AuthInterceptor extends AbstractInterceptor {

      public String intercept(ActionInvocation invocation) throws Exception {

            Map session = invocation.getInvocationContext().getSession();

            // session=ActionContext.getContext().getSession();

            if (session.get("loginSign") == null) {

                  return "login";

            } else {

                  String result = invocation.invoke();

                  return result;

            }

      }

}

步驟四,配置此Action相關,主要配置內容以下:
<struts>

      <package name="tokenTest" extends="struts-default">

            <interceptors>

                  <interceptor name="auth"

                        class="com.asm.interceptor.AuthInterceptor">

                  </interceptor>

                  <interceptor-stack name="authStack">

                        <interceptor-ref name="auth"></interceptor-ref>

                        <interceptor-ref name="defaultStack"></interceptor-ref>

                  </interceptor-stack>

            </interceptors>

            <action name="login" class="com.asm.LoginAction">

                  <result name="success">/main.jsp</result>

                  <result name="login">/login.jsp</result>

            </action>

 

            <action name="note">

                  <result>/WEB-INF/note.jsp</result>

                  <result name="login">/login.jsp</result>

                  <interceptor-ref name="authStack"></interceptor-ref>

            </action>

      </package>

</struts>

說明:結合前面的一些代碼來看,當咱們爲note.action配置了前面寫所的AuthInterceptor攔截器時,若是咱們要訪問note.action,攔截器會首先判斷是否登陸,若是登陸則繼續把請求傳遞下去,若是沒有登陸則會返回到登陸頁面。
步驟5、編寫相關的其它jsp頁面,而後發佈測試。此實例應重點是進一步掌握攔截器的配置使用。做爲「實現資源權限訪問」,此實例不具參考價值。

7.攔截器中的註解

AnnotationWorkflowInterceptor:Invokes any annotated methods on the action。意思是此攔截器能夠調用在Action中任何有註解的方法。下面咱們來演示它的使用,具體步驟以下:
步驟一,創建struts2annotationInt項目,並創建LoginAction類,代碼以下:
package com.asm;
...省略導入的包

public class LoginAction extends ActionSupport {

      private String username;

      @Before

      public String myBefore() {

            System.out.println("調用myBefore方法");

            return LOGIN;

      }

      @After

      public void myAfter() throws InterruptedException {

            Thread.sleep(5000);

            System.out.println("----調用myAfter方法");

      }

      @BeforeResult

      public void myBeforeResult() {

            System.out.println("----調用myBeforeResult方法");

      }

      public String execute() throws Exception {

            System.out.println("調用execute方法");

            return SUCCESS;

      }

      public String getUsername() {

            return username;

      }

      public void setUsername(String username) {

            System.out.println("---調用set方法" + username);

            this.username = username;

      }

}

說明:要想使用方法成爲被攔截器監視的註解方法,只需在方法關加上@...這樣的形式並導入相關的類便可。

步驟二,編寫相關的jsp及配置該Action,主要配置內容以下:
<struts>

      <package name="ano" extends="struts-default">

            <interceptors>

                  <interceptor name="anno"                        class="com.opensymphony.xwork2.interceptor.annotations.AnnotationWorkflowInterceptor">

                  </interceptor>

                  <interceptor-stack name="annoStack">

                        <interceptor-ref name="anno"></interceptor-ref>

                        <interceptor-ref name="defaultStack"></interceptor-ref>

                  </interceptor-stack>

            </interceptors>

            <action name="login" class="com.asm.LoginAction">

                  <result name="success">/success.jsp</result>

                  <result name="login">/login.jsp</result>

                  <interceptor-ref name="annoStack"></interceptor-ref>

            </action>

      </package>

</struts>

結合配置說明:當咱們爲LoginAction配置了AnnotationWorkflowInterceptor攔截器時,LoginAction中的全部註解方法才真正生效。下面重點是來討論這些方法的執行順序及做用。

加@Before註解的方法:will be invoked before the action method. If the returned value is not null, it is returned as the action result code。意思是在action的execute方法執行以前被調用,可是此方法若是返回不爲空的話,它的返回結果將是真正的返回結果,好比這裏咱們return LOGIN,這樣不管以什麼用戶名登陸,它總會返回到login result(這裏爲login.jsp頁面) 。可是從執前結果來看,在返回前仍執行了標記爲@BeforeResult的方法:will be invoked after the action method but before the result execution。意思是在返回結果集前調用此方法。下面咱們把public String myBefore()方法中的return LOGIN註釋掉,並讓修改此方法的返回類型爲void。隨後登陸測試(注意要從新部署當前項目),能夠發現執行結果以下:
調用myBefore方法

---調用set方法

調用execute方法

----調用myBeforeResult方法
----調用myAfter方法

從執行的順序來看,標記爲@After的方法最後執行,而且能夠發現:它會延時5秒執行,可是在延時執行時,瀏覽器並無成功跳到success.jsp頁面,而是在5秒後,控制檯打印出myArter方法中的內容同步跳轉到success.jsp頁面。@After :will be invoked after the action method and result execution。意爲在execute方法執行而且返回結果後此方法被調用。可是從測試來看,標記爲@After的方法是會影響到結果的返回(延時返回)。 強調:注意方法的執行順序,相關的內容能夠參看AnnotationWorkflowInterceptor類的api文檔。

8.使用PreResultListener實現回調

在進行本實例前請前複習:五.2自定義攔截器。由於PreResultListener對象通常是綁定在攔截器上使用。

下面咱們新建struts2PreResultListener項目進行測試。

步驟一,創建類,實現PreResultListener接口,主要代碼以下:

package com.asm;

import com.opensymphony.xwork2.ActionInvocation;

import com.opensymphony.xwork2.interceptor.PreResultListener;

public class MyPreResultListener implements PreResultListener {

      public void beforeResult(ActionInvocation invocation, String res) {

            // System.out.println(invocation.getAction());

            // System.out.println(invocation.getResultCode());

            /**回調Action中的方法:

             * LoginAction lg = (LoginAction) invocation.getAction(); try {

             * lg.execute(); } catch (Exception e) { e.printStackTrace(); }

             */

            System.out.println("檢驗到PreResultListener被執行");

      }

}

步驟二,copy前面在自定義攔截器中用到的三個攔截器,並綁定MyPreResultListener對象,首先是在MyInterceptor類中,咱們只須要修改intercept方法便可,代碼以下:

public String intercept(ActionInvocation invocation) throws Exception {

            invocation.addPreResultListener(new MyPreResultListener());

            System.out.println("開始攔截");

            String result = invocation.invoke();

            System.out.println("結束攔截");

            return result;

}

隨後在MyMethodFilterInterceptor類中做相似修改。爲了區別,咱們在MyAbstractInterceptor類中不綁定MyPreResultListener對象。

步驟三,編寫struts.xml文件,主要配置內容以下:
<struts>

      <package name="interceptor" extends="struts-default">

            <interceptors>

                  <interceptor name="myIpt" class="com.asm.MyInterceptor">

                  </interceptor>

                  <interceptor name="myAbs"

                        class="com.asm.MyAbstractInterceptor">

                  </interceptor>

                  <interceptor name="myMet"

                        class="com.asm.MyMethodFilterInterceptor">

                  </interceptor>

            </interceptors>

 

            <action name="login" class="com.asm.LoginAction">

                  <interceptor-ref name="myIpt"></interceptor-ref>

                  <interceptor-ref name="myAbs"></interceptor-ref>

                  <interceptor-ref name="myMet"></interceptor-ref>

                  <result name="success">/success.jsp</result>

            </action>                    

      </package>

</struts>

步驟四,編寫相應的jsp頁面,發佈測試。

說明:此實例的只是簡要地演示了PreResultListener的使用,因此相對簡單。對於其它相關操做,咱們能夠從MyPreResultListener類註釋掉的內容中找到一此端倪。強調:從執行結果來看,PreResultListener對象會在返回結果前執行,請注意結合攔截器執行的順序來看。此實例目前做爲了解。

 

6、使用標籤

1.基礎表單標籤

準備工做:創建struts2tag項目,搭建好struts2的開發環境。在html咱們經常使用的基礎表單標籤主要有文本域、密碼域、提交、重置四種。它們在strust2中能夠經過標籤來生成。下面創建login.jsp頁面,與這四種標籤相關的內容以下:

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

<%@ taglib uri="/struts-tags" prefix="s" %>

<html>

  <body>

      <s:form action="login" method="post" namespace="/my">

            <s:textfield label="用戶名" name="user.username" required="true" requiredposition="right"/>

<s:password label="密碼" name="user.password" required="true" /> 
<s:reset value="重置" align="left"/>
<s:submit value="註冊" align="left"/>

      </s:form>

  </body>

</html>

說明:label中的內容是顯示在表單前的提示內容,required設爲true,表示此表單項爲必填內容。

2.單選按鈕和複選框:

<s:radio list="#{1:'男',0:'女'}"  value="1"  label="性別" name="user.sex"/>
<s:checkboxlist list="#{1:'足球',2:'排球',3:'藍球',4:'網球'}" name="user.love" label="愛好"/>

3.三種方式實現下拉列表

<s:bean id="p" name="com.asm.NativePlaceFormAction"></s:bean>

<s:bean name="com.asm.NativePlaceMapFormAction" id="pMap"></s:bean>

<s:bean name="com.asm.NativePlaceProFormAction" id="pp"></s:bean>

<s:select list="#p.place" label="籍貫" name="user.place"/>

<s:select list="#pMap.place" label="籍貫2" name="user.place"/>

<s:select list="#pp.place" listKey="pId" listValue="pName" label="籍貫3" name="user.place" headerKey="-1" headerValue="---省---" emptyOption="true"/>

說明:三種方式實現下拉列表分別對應了三個java類,這三個類的內容爲:
NativePlaceFormAction主要代碼爲:
package com.asm;

public class NativePlaceFormAction extends ActionSupport {

      private List<String> place;  

      public NativePlaceFormAction(){

            place=new ArrayList<String>();

            place.add("山東省");

            place.add("山西省");

            place.add("河南省");

            place.add("河北省");

            place.add("四川省");

            place.add("雲南省");

      }

      ...省略place的get/set方法

}

NativePlaceMapFormAction主要代碼爲:
package com.asm;

public class NativePlaceMapFormAction extends ActionSupport {

      private Map<Integer, String> place;

 

      public NativePlaceMapFormAction() {

            place = new HashMap<Integer, String>();

            place.put(1, "山東省");

            place.put(2, "山西省");

            place.put(3, "河南省");

            place.put(4, "河北省");

            place.put(5, "四川省");

            place.put(6, "雲南省");

      }
      ...省略place的get/set方法

}
NativePlaceProFormAction主要代碼爲:
package com.asm;
public class NativePlaceProFormAction extends ActionSupport {

private List<Object> place;  

      public NativePlaceProFormAction(){

            place=new ArrayList<Object>();

            new Province(1,"山東省","濟南");

            place.add(new Province(1,"山東省","濟南"));

            place.add(new Province(2,"山西省","太原"));

            place.add(new Province(3,"河南省","鄭洲"));

            place.add(new Province(4,"河北","石家莊"));

            place.add(new Province(5,"四川","成都"));

            place.add(new Province(6,"雲南","昆明"));

      }

      ...省略place的get/set方法

}

說明:此三種實現效果同樣,可是在它們提交時傳遞給服務器的參數不一樣,具體能夠參看login.jsp頁面的源碼。另外,這三種實現其實都依賴了<s:bean>設定的對象,若是咱們不但願依賴<s:bean>來設定,能夠經過配置action來實現:下面咱們以NativePlaceFormAction說明:首先在struts.xml中配置此action,配置內容以下:
<action name="npf" class="com.asm.NativePlaceFormAction">

                  <result>/login2.jsp</result>

</action>
隨後,咱們在login.jsp中增長以下內容:
      <a href="<%=request.getContextPath()%>/my/npf.action">另外一個註冊頁面</a>
其中login2.jsp中的關鍵內容爲:
            <s:select list="place" label="籍貫" name="user.place"/>

咱們能夠發現:在login2.jsp中填寫list的值時並無用ognl表達式,由於咱們經過npf.action來訪問時,Action已經被寫入到了值棧中,因此咱們能夠直接引用。 後面所用到的實例,咱們都會把這樣的類作成Action,這樣若是咱們想經過這種方式訪問便只須要在struts.xml中配置便可

4.二級聯動

<s:bean name="com.asm.TwoSelectAction" id="ts"></s:bean>

<s:doubleselect

                        list="#ts.place"                   

           listKey="pId" listValue="pName"
name="user.place"

                       

                        doubleList="#ts.citys[top]"

                        doubleListKey="cId"     doubleListValue="cName"

                        doubleName="user.city"

                        label="籍貫4(二級聯動)">

</s:doubleselect>

它所依賴的TwoSelectAction類的主要代碼以下:
package com.asm;

public class TwoSelectAction extends ActionSupport {

      private List<Province> place;

      private Map<Province,List<City>> citys;

      ...省略place 、citys中get/set方法

      public TwoSelectAction(){

            place= new ArrayList<Province>();

            citys=new HashMap<Province,List<City>> ();

           

            Province p1=new Province(1,"山東省","濟南");

            Province p2=new Province(2,"山西省","太原");

            Province p3=new Province(3,"河南省","鄭洲");

            Province p4=new Province(4,"河北","石家莊");

            Province p5=new Province(5,"四川","成都");

            Province p6=new Province(6,"雲南","昆明");

            place.add(p1);

            place.add(p2);

            place.add(p3);

            place.add(p4);

            place.add(p5);

            place.add(p6);

           

            //山東省的市:

            City c1=new City(1,"濟南");

            City c2=new City(2,"招遠市");

            City c3=new City(2,"壽光市");

            List p1City=new ArrayList();

            p1City.add(c1);

            p1City.add(c2);

            p1City.add(c3);

           

            //山西省的市:

            City c4=new City(4,"太原市");

            City c5=new City(5,"大同市");

            City c6=new City(6,"晉中市");

            List p2City=new ArrayList();

            p2City.add(c4);

            p2City.add(c5);

            p2City.add(c6);

           

            //河南省的市:

            City c7=new City(7,"鄭州市");

            City c8=new City(8,"衛輝市");

            City c9=new City(8,"信陽市");

            List p3City=new ArrayList();

            p3City.add(c7);

            p3City.add(c8);

            p3City.add(c9);

           

            //河北省的市:

            City c10=new City(10,"石家莊");

            City c11=new City(11,"晉州市");

            City c12=new City(12,"鹿泉市");

            List p4City=new ArrayList();

            p4City.add(c10);

            p4City.add(c11);

            p4City.add(c12);

           

           

            //四川省的市:

            City c13=new City(13,"成都");

            City c14=new City(14,"南充");

            City c15=new City(15,"綿陽");

            List p5City=new ArrayList();

            p5City.add(c13);

            p5City.add(c14);

            p5City.add(c15);

           

            //雲南省的市:

            City c16=new City(16,"昆明市");

            City c17=new City(17,"安寧市");

            City c18=new City(18,"曲靖市");

            List p6City=new ArrayList();

            p6City.add(c16);

            p6City.add(c17);

            p6City.add(c18);

           

            citys.put(p1,p1City );

            citys.put(p2,p2City );

            citys.put(p3,p3City );

            citys.put(p4,p4City );

            citys.put(p5,p5City );

            citys.put(p6,p6City );

      }

}

簡要分析:此實例有些繁瑣,主要思想:咱們的place對象主要爲一級列表服務,只要理解了前面的下拉列表,這裏不難理解一級列表。而二級列表中咱們使用#ts.citys[top]取出的一個List對象,這樣也正是下拉列表所要求的對象類型(List,Map),而top是很是關鍵的,它明確指出咱們取出的是棧頂的對象,這樣就能根據一級列表的值來動態生成這個List對象。

5.其它表單標籤

<s:select name="singer"  list="{}" label="歌星" headerKey="0" headerValue="--歌手名單--" emptyOption="true">

                  <s:optgroup list="#{1:'任賢齊',2:'劉德華',3:'周杰倫'}" label="男歌手"/>

                  <s:optgroup list="#{1:'蕭亞軒',2:'蔡依林',3:'she'}" label="女歌手"/>

</s:select>

           

<s:combobox label="來源調查" list="{'朋友介紹','電視廣告','網絡廣告'}" name="from" />

           

            <s:updownselect

                        list="{'java','C#','VC','php','vb','vc','python'}"

                        moveDownLabel="下移一位"

                        moveUpLabel="上移一位"

                        selectAllLabel="所有選中"

                        label="您經常使用編程語言排名"

                        />

           

            <s:optiontransferselect

                        leftTitle="選擇喜歡作的事:"

                        list="{'聽歌','看電影','編程','玩遊戲','chat'}"

                        name="love"

                        headerKey="0"

                        headerValue="喜歡作的事"

                        emptyOption="true"

                       

                        rightTitle="選擇討厭作的事:"

                        doubleList="{'跳舞','唱歌','打籃球','旅遊','shopping'}"

                        doubleName="hate"

                        doubleHeaderKey="0"

                        doubleHeaderValue="不喜歡的事"

                        doubleEmptyOption="true"

                       

                        label="我的興趣說明"

                       

                        leftUpLabel="上移"

                        leftDownLabel="下移"

                        rightUpLabel="上移"

                        rightDownLabel="下移"

                        addToLeftLabel="<—添加"

                        addToRightLabel="添加—>"

                        addAllToLeftLabel="<—添加(All)"

                        addAllToRightLabel="添加(All)—>"

                        selectAllLabel="全選"

                        />

<s:checkbox label="接受服務條款" value="false" name="user.accept"/>

有了前面的標籤學習,這些標籤很容易理解,只需結合顯示效果和查看源碼來加深它們的理解。可是特別要注意的是<s:checkbox>標籤與</s:checkboxlist>的區別。

補充:使用struts2生成的表單標籤會在標籤內嵌套一些特殊的格式,在使用了struts2生成的標籤所在網頁內查看源代碼能夠發現多了一些如<tr><td>這樣的格式化代碼。若是不想struts2增長這些多餘的格式化代碼,能夠在struts.xml中配置以下內容:

<!-- struts2生成的表單標籤使用默認的主題,即不附加格式化標籤 -->

<constant name="struts.ui.theme" value="simple"/>

6.其它經常使用標籤的使用(代碼參名爲「補充」的文件夾下的tag.jsp)

(1)<s:set>標籤

此標籤主要用於設置一些屬性值。

Scope:指定變量被設置的範圍,該屬性能夠接受application、session、request、page或Action。若是沒有設置該屬性,則默認放置在OGNL Context中,咱們能夠經過#號來引用。

Value:賦給變量的值,若是沒有設置該屬性,則將ValueStack棧頂的值賦給變量。

Id/name/var:屬性的引用名稱,id/name均過期,建議用var來取代他們。

(2)<s:property>

Default:可選屬性,若是須要輸出的屬性值爲null,則顯示屬性指定的值

Escape:可選屬性,指定是否格式化html代碼。

Value:可選屬性,指定須要輸出的屬性值,若是沒有指定該屬性,則默認輸出ValueStack棧頂的值

Id:可選屬性,指定該元素的標識

(3)<s:Iterator>

Value:可選屬性,指定迭代的集合,若是沒有指定該屬性,則使用ValueStack棧頂的集合

Id:可選屬性,指定集合裏元素的id(已被標記爲過期)

Status:可選屬性,該屬性指定迭代時當前迭代對象的一個實例,並把此實例放在ognl的上下文中,咱們能夠經過#號來引用這個實例。該實例包含以下幾下方法:

Int  getCount:返回當前迭代了幾個元素。

Int  getIndex:返回當前被迭代的元素的索引

Boolean  isEven:返回當前被迭代的元素的索引是不是偶數

Boolean  isOdd:返回當前被迭代的元素的索引是不是奇數

Boolean  isFirst:返回當前被迭代的元素是不是第一個元素

Boolean  isLast:返回當前被迭代的元素是不是最後一個元素

說明:由於iterator會把每次迭代的實例放在值棧的棧頂,而<s:property>默認訪問的是值棧的棧頂元素。因此以下代碼可行:

<s:set var="list" value="{'第一個','第二個','第三個'}"/>

<!-- iterator迭代的特色:會把迭代的對象放到值棧的棧頂 -->

<s:iterator value="#list">

      <s:property/>

</s:iterator>

若是想用status來實現一些功能,可參下面的代碼:

<br/>-------------------奇數紅色,偶數藍色---------------<br/>

<s:iterator value="#list" status="i">

      <font color='<s:if test="#i.even">blue</s:if><s:else>red</s:else>' >

      <s:property/>

      </font><br/>

</s:iterator>

(4)url標籤

      <br/><br/>-----------使用url---------------<br/>

      <s:set var="age" value="25" scope="request"/>

      <s:url action="asm" namespace="/" >

            <s:param name="age" value="#request.age"></s:param>

      </s:url>   

說明:它會根據actionnamespace並附加上下文路徑構建一個連接。

<br/><!-- value的值中必定要用單引號引發,這樣才表示它的值是一個字串 -->

      <s:set var="bdUrl" value="'http://www.baidu.com'" />

      <s:url value="#bdUrl" /> <br/>

      <s:url value="%{#bdUrl}" /> 

說明:因爲url標籤的value屬性默認不支持ognl,因此咱們要使用%{}來表示{}中的#bdUrl是一個ognl表達式。

7、國際化

儘管國際化不是重點內容,可是也有必要了解它的使用。在struts2中國際化有三種級別:分別是針對某個Action的action級別,針對package的package級別,針對webapp的webapp級別。下面咱們創建struts2i18n項目來演示國際化在struts2中的使用。

1.action級別下的國際化

步驟1、首先是創建login.jspLoginAction,因爲它們常用,在此省去它們的代碼。
步驟2、創建資源文件,因爲LoginActioncom.asm包中,因此咱們應在com.asm包下咱們創建兩個資源文件:一個是中文LoginAction_zh_CN.properties、一個是英文LoginAction_en_US.properties。注意它們的名字相對固定,前面與Action的名字相同,後面是語言和國家代碼。
英文資源文件內容以下:
login_page=login page

login_username=userName

login_password=password

login_sex=sex

login_male=male

login_female=female

login_submit=login

login_reset=reset

login_suc=Welcome {0}

中文資源文件,須要特別注意:咱們應使用Myeclipse自帶的MyEclipse properties Editer編輯器來打開此資源文件,並在properties視圖下進行編輯,這樣它會把中文進行編碼(咱們切換到source視圖下能夠看到經編碼後的中文)。 這一步很是重要,不然會出現亂碼。
步驟三,修改login.jsp中的內容:
<%@ page language="java" pageEncoding="utf-8"%>

<%@ taglib uri="/struts-tags" prefix="s"%>

<html>

      <body>

      <s:text name="login_page"/><br>

      <s:label key="login_username"/>

            <s:form action="/login.action" method="post">        

            <!--

                  <s:textfield label="用戶名" name="username" required="true" />

             -->

             <s:textfield label="%{getText('login_username')}" name="username" />

                   

             <!--

                  <s:password label="密碼" name="password" required="true"/>

             -->

                   <s:password key="login_password" name="password" />

                   

             <!--

                  <s:radio list="#{1:'男',2:'女'}" value="1" label="性別" name="sex" />

             -->

             <s:radio list="#{1:getText('login_male'),2:getText('login_female')}" value="1" label="%{getText('login_sex')}" name="sex" />

            <s:submit  key="login_submit" /> <s:reset key="login_reset"/>

            </s:form>

      </body>

</html>

說明:對資源文件的引用,咱們採起了兩種方式:有的是經過在label中使用%{getText('資源文件中的key')}這樣的形式,有的是經過key=資源文件中的key這種形式。須要注意在radio標籤中list對資源文件的引用。另外須要注意:  

<s:text name="login_page"/><br>

<s:label key="login_username"/>

它們的區別:前面是純文本,後者是一個塊。咱們能夠經過查看login.jsp的源碼來證實。
步驟4、當咱們直接訪問login.jsp時會報錯,由於在login.jsp中用到了資源文件,而資源文件又依賴於LoginAction,因此咱們只能經過此Action來跳到login.jsp。可是使用包範圍、全局範圍的資源文件時,能夠直接訪問login.jsp文件實現國際化。操做步驟以下:

首先在LoginAction中增長一個方法:

      public String doGoLogin() {

            return LOGIN;

      }

隨後再在struts.xml中配置以下內容:
<package name="i18n" extends="struts-default" namespace="/">

            <action name="login" class="com.asm.LoginAction">

                  <result name="success">success.jsp</result>

                  <result name="login">login.jsp</result>              

            </action>

</package>

接着再編寫一個link.jsp頁面,內容以下:
<a href="<%=request.getContextPath() %>/login!goLogin.action">登陸</a>

直接訪問Action中的方法 格式:doX(大寫)xxx ---- ActionName!x(小寫)xxx.action  注意此方法和前面二.7中相關方法的區別。 咱們經過此Action跳轉到login.jsp這樣便能成功訪問到login.jsp頁面。
步驟5、在success.jsp中使用資源文件,主要內容以下:
<s:text name="login_suc">

                  <s:param value="%{username}"></s:param>

</s:text>

說明:在前面的資源文件中,咱們配置了login_suc=Welcome {0},其中{0}表示佔位參數,這裏咱們使用<s:param>來給此參數賦值。
步驟6、測試:在ieinternet選項中改變語言實現國際化的訪問。

2.配置package的資源文件

一樣在創建com.asm包下創建兩個資源文件(package級別的資源文件名必須以package開頭):取名爲:package_zh_CN.properties,它的內容爲:pack=pack屬性值 package_en_US.properties,它的內容爲:pack=packageAttributeValue

而後再在login.jsp頁面中增長以下內容:
<h4>測試包資源文件</h4>

<s:text name="pack"></s:text>

這樣便完成了package級別的資源文件配置,最後發佈測試。

3.app級別的資源文件

在src目錄下創建兩個資源文件,取名爲myapp_en_US.properties,它的內容爲:
app=appAttributeValue 和myapp_zh_CN.properties,它的內容爲:

而後還須要在strust.xml中增長以下配置:
<constant name="struts.custom.i18n.resources" value="myapp"></constant>

注意:name是固定值,而value來自於這個資源文件的基名。

最後在login.jsp中增長以下內容:

<h4>測試app級別資源文件</h4>

<s:text name="app"></s:text>

這樣便完成了app級別的資源文件配置,隨後發佈測試。

說明:action級的資源文件優先級別最高,app最低。Pack級別的資源文件可做用於同一個包,app級別的資源文件可做用於當前項目。

補充:在jsp頁面中直接訪問某個資源文件,struts2爲咱們提供了i18n標籤,使用此標籤咱們能夠在類路徑下直接從某個資源文件中獲取國際化數據,而無需任何配置:

      <s:i18n name="XXX"> --xxx爲類路徑下資源文件的基名

            <s:text name="">

                  <s:param></s:param>

            </s:text>

      </s:i18n>

而若是要訪問的資源文件在類路徑的某個包下(如actionpackage級別的資源文件),能夠這樣訪問:

<s:i18n name="com/asm/資源文件基名">--com.asm爲包名

4.使用資源文件的原理

咱們創建ReadResourceFileTest類,代碼以下:

package com.asm;

import java.util.Locale;

import java.util.ResourceBundle;

public class ReadResourceFileTest {

      public static void main(String[] args) {

            ResourceBundle rb=ResourceBundle.getBundle("com.asm.LoginAction", Locale.US);

            System.out.println(rb.getString("login_suc"));

      }

}

補充:在Action類(必須繼承自ActionSupport)中獲取資源文件的值的方法,可使用以下代碼:

String value = this.getText("資源文件的鍵名");  

//獲取資源文件的對應的值。若是想給資源文件中的佔位符賦值,可使用getText的重載方法。

ActionContext.getContext().put("XXX",value);//存放在request範圍,供jsp獲取此值

5.選擇使用資源文件

其實在咱們成功訪問到login.jsp頁面後,只要在地址欄中增長參數request_locale=en_US即可以正確切換到登陸頁面爲英文。固然咱們能夠再連接根據此參數寫這個資源文件的連接。固然咱們也可藉助一個新Action來實現,操做步驟以下:在login.jsp中增長以下代碼:
<a href="change.action?request_locale=zh_CN">

            <s:text name="chinese"></s:text>

</a>
<a href="change.action?request_locale=en_US">

            <s:text name="english"></s:text>

</a>
change.action對應的配置爲:
<action name="change" class="com.asm.ChangeLangAction">

                  <result>/login.jsp</result>

</action>
ChangeLangAction的主要代碼以下:
package com.asm;

public class ChangeLangAction extends ActionSupport {

      public String execute() throws Exception {

            return SUCCESS;

      }

}

以上是第一種方法,特別要注意,因爲使用了不一樣Action,因此要資源文件這時只有pack級別和app級別的才起做用,因此這時還應把action級別的資源文件內容增長到app級別的資源文件中去。下面使用第二種方法,原理基本和上面同樣,只需在此ChangeLangAction中增長一個新的字段String lang及相應的get/set方法,再增長一個新的方法changeLang,代碼以下:
public String changeLang() throws Exception {

            Locale locale = null;

            System.out.println(lang);

            if (lang.equals("zh")) {

                  // 顯示中文

                  locale = Locale.CHINA;

                  System.out.println("======" + lang+locale);

            } else {

                  // 顯示英文

                  locale = Locale.US;

            }

            ActionContext.getContext().setLocale(locale);                                                ServletActionContext.getRequest().getSession().setAttribute("WW_TRANS_I18N_LOCALE", locale);

            return SUCCESS;

      }

配置內容爲:
      <action name="cl" class="com.asm.ChangeLangAction" method="changeLang">

                  <result>/login.jsp</result>

      </action>
login.jsp中對應的連接爲:

<a href="cl.action?lang=zh">

            <s:text name="chinese"></s:text>

</a>
<a href="cl.action?lang=en">

            <s:text name="english"></s:text>

</a>
這樣操做後,當咱們成功訪問到login.jsp後,即可以點擊連接來隨意切換訪問英文或中文頁面。

8、驗證機制

注意:要想實現校驗,action必須繼承自ActionSupport類。

1.基於手工編碼的校驗

咱們創建struts2validate項目 ,其中reg.jsp頁面主要代碼以下:

<body>

      <s:head/>

      <h3>註冊頁面</h3>

      <s:form method="post" action="reg" >

            <s:bean name="com.asm.AgeAction" id="aa"></s:bean>

            <s:textfield name="user.username" label="用戶名"/>

            <s:property value="errors.user.username"/>

            <s:password name="user.password" label="密碼"/>

            <s:password name="user.password2" label="確認密碼"/>

            <s:select list="#aa.ageMap" name="user.age" label="年齡" headerValue="填寫真實年齡"  headerKey="0"/>

            <s:reset value="重置" align="left" />

            <s:submit value="註冊" align="left"/>

      </s:form>

</body>
說明:<s:head/>能夠用來對驗證信息進行一些美化效果處理,另在此頁面中咱們用到了一個AgeAction用來動態生成「年齡」表單項,在前面的表單標籤中已用過相似的作法。AgeAction的代碼以下:

package com.asm;

public class AgeAction extends ActionSupport {

      private Map<Integer, String> ageMap;

      public AgeAction() {

            ageMap = new HashMap();

            for (int i = 1; i <= 120; i++) {

                  ageMap.put(new Integer(i), i + "");

            }

      }

      ...省略ageMap的get/set方法     

}

Reg action的配置以下:

      <package name="validate" extends="struts-default">         

                  <action name="reg" class="com.asm.RegAndLoginAction" method="reg">

                        <result name="success">/regSuc.jsp</result>

                        <result name="login">/reg.jsp</result>

</action>
</package>          

根據配置,咱們來看它的對應Action: RegAndLoginAction,代碼以下:

package com.asm;

public class RegAndLoginAction extends ActionSupport {

      private User user;

      public String reg() throws Exception {

            if (user.getUsername() == null || user.getUsername().equals("")) {

                  this.addFieldError("user.username", "用戶名不能爲空");

            } else if (!Pattern.matches("^[a-zA-Z][a-zA-Z0-9_]{3,14}$", user.getUsername())) {

                  this.addFieldError("user.username", "用戶名只能是以字母開頭,後面能夠跟字母、數字或下滑線,長度只能是4-15位");

            } else if (user.getPassword() == null || user.getPassword().equals("")) {

                  this.addFieldError("user.password", "密碼不能爲空");

            } else if (!user.getPassword().equals(user.getPassword2())) {

                  this.addFieldError("user.password2", "兩次輸入的密碼不一致,請從新輸入");

            } else if (user.getAge() < 16) {

                  this.addFieldError("user.age", "未滿16歲,不能註冊");

            }

 

            if (this.hasFieldErrors()) {

                  return LOGIN;

            }

            System.out.println("reg success....");

            return SUCCESS;

      }    

      ...省略user的get/set方法

}
說明:當reg.jsp提交給此Action對應的reg方法處理時,它會調用addFieldError把錯誤信息加到FiledError中去,關於這點,咱們能夠在前臺reg.jsp頁面中用<s:debug>調試時,能夠看到值棧中的此Action對象中的fieldErrors對應着咱們添加的錯誤信息,所以這點也就爲咱們取出驗證信息提供一個參考,便是說咱們能夠取出此驗證信息,對它進行美化處理,而不是按struts2默認來顯示。 後面,咱們接着對登陸頁面用login方法進行了相似的驗證(在此省略),因此此action取名爲regAndLoginAction.
補充:當咱們把login.jsp頁面的驗證寫完後,能夠發現reg和login這兩個方法顯示至關的繁瑣,對此咱們能夠專門把驗證分別放在validateReg和validateLogin方法中去。咱們新建一個Action來演示,新的RegAndLogin2Action主要代碼以下:
package com.asm;

public class RegAndLogin2Action extends ActionSupport {

      private User user;

 

      @Override

      public void validate() {

      System.out.println("校驗的統一出口,對全部方法進行校驗:這裏能夠放一些公共的驗證");

      }

 

      public void validateReg() {

            ...省略,對reg方法進行驗證

      }

 

      public void validateLogin() {

            ...省略,對login方法進行驗證

      }

 

      public String reg() throws Exception {         

            System.out.println("reg success....");

            return SUCCESS;

      }

 

      public String login() throws Exception {       

            System.out.println("login success....");

            return SUCCESS;

      }
      ...省略user的get/set方法

}

說明:當reg.jsp提交給此Action對應的reg方法處理時,它會首先調用此reg方法專屬的驗證方法valiadteReg(注意取名規則:validate+方法名<首字母大寫>),此方法驗證完成後,會調用validate方法,此方法完成後纔會調用reg方法。所以通常狀況下,咱們會把一些公共的驗證放在validate方法中,而這些全部的驗證方法也只進行驗證處理,並把錯誤信息封裝到fieldError字段中(或者其它字段)。reg這些真正執行的方法只進行一些其它處理(好比把註冊信息寫進數據庫)。測試時須要修改把前面的配置註釋掉,寫上下面的配置:
<action name="login" class="com.asm.RegAndLogin2Action"     method="login">

                  <result name="success">/logSuc.jsp</result>                

                  <result name="input">/login.jsp</result>

</action>        

<action name="reg" class="com.asm.RegAndLogin2Action" method="reg">

            <result name="success">/regSuc.jsp</result>

            <result name="input">/reg.jsp</result>

</action>
說明:配置中有一個input result的配置,由於帶有validate的方法進行驗證時,若是驗證失敗,會返回input所對應的result結果集

簡析校驗流程:

(1)類型轉換器請求參數執行類型轉換,並把轉換後的值賦給action中屬性。

(2)若是在執行類型轉換過程當中出現異常,系統會將異常信息保存到ActionContextconversionError攔截器將異常信息添加到fieldErrors裏,無論類型轉換是否出現異常都會進入第(3)步。

(3)系統經過反射技術調用action中的validateXxx()方法

(4)再調用action中的validate()方法

(5)通過上面4步,若是系統中的fieldErrors存在錯誤信息(即存放錯誤信息的集合size大於0,系統自動將請求轉發至名爲input的視圖。若是系統中的fieldErrors沒有任何錯誤信息,系統將執行action中的處理方法。

注意:通過以上過程的分析,能夠知道若是類型轉換失敗,也會到input視圖。

2.基於XML配置形式的校驗

新建struts2validateXML項目,在此項目中,基本的代碼和上面的struts2validate項目類似,只是在上一個項目中咱們在Action的具體方法中進行了驗證處理,如今先修改RegAndLoginAction的代碼以下:
package com.asm;

public class RegAndLoginAction extends ActionSupport {

      private User user;

 

      public String reg() throws Exception {         

            System.out.println("reg success....");

            return SUCCESS;

      }

 

      public String login() throws Exception {       

            System.out.println("login success....");

            return SUCCESS;

      }

      ...省略user的get/set方法

}

下面咱們在action所在的包下創建一個對此Action進行校驗的xml文件,文件名爲:RegAndLoginAction-validation.xml,取名原則就是actionClassName-validation.xml 。它會對此Action中的全部方法進行校驗。主要代碼以下:
<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE validators PUBLIC

            "-//OpenSymphony Group//XWork Validator1.0.3//EN"

            "http://www.opensymphony.com/xwork/xwork-validator-1.0.3.dtd">

<validators>

      <field name="user.username">

            <field-validator type="requiredstring">

                  <message>用戶名不能爲空</message>

            </field-validator>

            <field-validator type="regex">

                  <param name="expression">^[a-zA-Z][a-zA-Z0-9_]{3,14}$</param>

<message>
用戶名只能是以字母開頭,後面能夠跟字母、數字或下滑線,長度只能是4-15位

</message>

            </field-validator>

      </field>

     

      <field name="user.password">

            <field-validator type="requiredstring">

                  <message>密碼不能爲空</message>

            </field-validator>           

      </field>

</validators>

進行此配置,至關於在RegAndLoginAciton中增長用validate ()方法進行驗證。若是咱們想對某個方法進行驗證,配置文件應取名爲actionClassName-ActionName-validation.xml,好比咱們對reg方法進行驗證,在前面用到validateReg方法,這裏只需增長RegAndLoginAction-reg-validation.xml配置文件便可,它的做用和validateReg方法相同,在此省略此配置文件內容。
關於驗證的配置文件中用到的驗證類型能夠參看文檔或者叄看壓縮包中的配置參照文件,下面對校驗器類型進行簡單說明:

Required-必須校驗器:要求field的值不能爲null

Requiredstring-必須字串校驗器:不能爲null,用長度大於0,默認狀況下會對字串去先後空格

int[longshortdouble]:整數值[long型、短整形、double]型值必須在指定範圍。參數min指定最小值,參數max指定最大值

date-日期校驗器:日期校驗類型,符合日期格式,用可使用min/max來指定日期範圍

expression-OGNL表達式校驗器:expression參數指定ognl表達式,該邏輯表達式基於值棧進行求值,返回true時校驗經過,不然不經過,該校驗器不可用在字段校驗器風格的配置中

fieldexpression-字段ognl表達式校驗器:要求field知足一個ognl表達式,expression參數指定ognl表達式,該邏輯表達式基於值棧進行求值,返回true校驗經過,不然不經過

email-郵件地址校驗器:非空用爲合法的郵件地址

url-網址校驗器:非空用爲合法的url地址

visitor-複合屬性校驗器:它指定一個校驗文件用於校驗複合屬性中的屬性

conversion-轉換校驗器:指定在類型轉換失敗時,提示的錯誤信息

stringlength-字符器長度校驗器:要求字段必須在指定的範圍內,不然校驗失敗。minLength參數指定最小長度,maxLength參數指定最大長度。Trim參數指定校驗field以前是否去除字串先後的空格

regex-正則表達式校驗器:校驗字段是否與expression參數指定的正則表達式匹配。caseSensitive參數指定進行匹配時是否區分大小寫,默認爲true,即區分大小寫。

補充:基於xml校驗的一些特色

當爲某個Action提供了ActionClassName-validation.xmlActionClassName-ActionName-validation.xml兩種規則的校驗文件時,系統會按下面的順序尋找校驗文件:(1ActionClassName-validation.xml (2)ActionClassName-ActionName-validation.xml

系統尋找到第一個校驗文件時還會繼續搜索後面的校驗文件,當探索到全部校驗文件時,會把校驗文件裏的全部校驗規則彙總,而後所有應用於action方法的校驗。若是兩個校驗文件中指定的校驗規則衝突,則會只使用後面文件中的校驗規則。

當action繼承了另外一個action,父類action的校驗文件會先被搜索到。

假定UserAction繼承BaseAction:

<action name="user" class="com.asm.UserAction" method="execute">訪問上面的action,系統會先搜索父類的校驗文件:BaseAction-validation.xml,BaseAction-user-validation.xml,接着搜索子類的校驗文件:UserAction-validation.xmlUserAction-user-validation.xml.應用於上面action校驗規則爲四個文件的總和。

9、文件上傳下載(瞭解)

首先創建struts2UpDownLoad項目,搭建好struts2基本的開發環境。

1.  上傳實例

2.  步驟一:upload.jsp代碼以下:
<s:form action="upload" method="post" enctype="multipart/form-data">

            <s:file name="file" label="上傳的頭像(格式:jpg,gif,bmp)"></s:file>

            <s:submit value="上傳"/>       <s:reset value="取消"/>

      </s:form>
注意:在form標籤中咱們用到了enctype實體,這是上傳時必須用到得。
步驟二,創建struts.xml。對upload.action的配置以下:
<action name="upload" class="com.asm.UploadAction">

                  <param name="savePath">img</param>

                  <result>/upSuc.jsp</result>

                  <result name="input">upload.jsp</result>

                  <interceptor-ref name="defaultStack">

                        <param name="fileUpload.maximumSize">1024*1024</param>

<param name="fileUpload.allowedTypes">
image/bmp,image/pjpeg,image/gif

</param>

                  </interceptor-ref>

</action>  

在這裏惟一須要說明的是<interceptor-ref>下的參數問題,在之前若是要爲某個特定的攔截器傳遞參數須要在<interceptor>下配置pararm參數,在此處咱們用.形式來配置fileUpload攔截器的參數。這用作便可以保證默認的攔截器棧起做用,也能夠向fileUpload攔截器傳遞參數。第一個參數是限制上傳圖片的大小(除了能夠這樣限制圖片大小,也能夠配置一個常量的方法來限制上傳文件的大小,配置的內容爲<constant name="struts.multipart.maxSize" value="文件大小"/>),第二個參數是限制上傳圖片的格式只能爲bmp,pjpeg,gif關於這些參數能夠參看fileupload攔截器對應類的api文檔。

另還需注意:在action下的「<param name="savePath">img</param>」代碼能夠爲UploadActionsavePath字段傳遞值,這樣做的好處是在項目正式發佈後,咱們能夠經過修改struts.xml中的配置來靈活給savePath賦值。而若是直接在java源代碼中初始化savePath的值,在項目運行後就不能簡單修改。這種用法主要是爲了給客戶提供一個靈活可配的特定初始化方式。
步驟3、編寫UploadAction,主要代碼以下:
package com.asm;

public class UploadAction extends ActionSupport {

      private String savePath;

      private File file;

      private String fileFileName;

      private String fileContentType;

 

      public String execute() throws Exception {

            String path=ServletActionContext.getServletContext().getRealPath(savePath);

            String savaFileName=path+"\\"+fileFileName;

            //System.out.println(savaFileName);

           

            BufferedInputStream bis=null;

            BufferedOutputStream bos=null;

            try{

            bis=new BufferedInputStream(new FileInputStream(file));

            bos=new BufferedOutputStream(new FileOutputStream(savaFileName));

           

            byte []buf=new byte[(int) file.length()];

            int len=0;

            while((len=bis.read(buf))!=-1){

                  bos.write(buf,0,len);

            }}catch(Exception e){

                  e.printStackTrace();

            }finally{

                  if(bis!=null)

                        bis.close();

                  if(bos!=null)

                        bos.close();

            }

            return SUCCESS;

      }

      ...省略以上四個字段的get/set方法

}

說明:其實上傳的難點就是在此action的處理上。首先是從配置文件中讀取文件的保存路徑,而後聯合fileFileName(命名規則是上傳的文件對應的字段名+FileName,若是要獲得上傳文件的類型,固定的寫法應是上傳的文件對應的字段名+ContentType,好比這裏應爲fileContentType)來肯定完整的保存路徑,並最終爲建立BufferedOutputStream做準備。BufferedInputStream是經過前臺upload.jsp頁面傳遞的file構建。特別要注意處理流,若是書寫不當可能會使上傳文件循環寫入,使得硬盤容量不夠。還要注意對流的關閉問題。 補充:關於文件的操做可使用commons-io.jar包的FileUtils類下的copyFile方法來進行文件的拷貝,好比這裏調用copyFile方法(file,要保存的目錄)

上傳成功後,upSuc.jsp的主要內容以下:
<body>

<h4>上傳成功,如下是你剛上傳的圖片:</h4>
<img src="<%=request.getContextPath() %>/<s:property value="savePath+'/'+fileFileName"/>">   <br>

      保存路徑爲:<s:property value="savePath+'/'+fileFileName"/>

 </body>

說明:當上傳成功後,會顯示上傳的圖片。

擴展實例:若是想上傳多個文件,能夠在Action中使用File[] files來接受上傳的文件(jsp中對應的上傳文件的參數均應爲files)。對應的上傳文件的名字,使用String[] fileFileName。而後循環files數組保存File文件對象。

2.下載實例

下載頁面的doload.jsp的主要代碼以下:
<a href="download.action?downloadName=img/a.bmp">下載圖片</a><br>

<a href="download.action?downloadName=img/music.rar">下載千千靜聽</a><br>
對應的download action配置以下:

<action name="download" class="com.asm.DownloadAction">

                  <result name="success" type="stream">

<param name="inputName">targetFile</param>
               <param name="contentType">
              image/bmp,application/x-zip-compressed
            </param>                  

                  </result>

            </action>
說明:type類型指明告終果集爲流類型,而且爲流類型結果集配置了參數,inputName指定流的來源,這裏來源爲targetFile,因此在下面的Action中有getTargetFile方法, contentType指明下載時的文件類型。

DownloadAction的主要代碼以下:
package com.asm;

public class DownloadAction extends ActionSupport {

      private String downloadName;

      public String execute() throws Exception {

            return SUCCESS;

      }

 

public InputStream getTargetFile(){

      return ServletActionContext.getServletContext().getResourceAsStream(downloadName);

      }

 

      public void setDownloadName(String downloadName) {

            this.downloadName = downloadName;

      }

}
說明:下載實例在此略做了解,具體能夠藉助apache組織提供的上傳下載開源項目理解。

10、類型轉換

創建struts2conversion項目,並搭建好struts2的基本開發環境

1.基於Action的直接屬性轉換

創建t.jsp頁面,內容以下:
<s:form action="phone" method="post">

            <s:textfield name="thePhone" label="電話"/>

            <s:submit value="提交"/>

            <s:reset value="重置"/>

</s:form>

此action對應的配置以下:

<action name="phone" class="com.asm.action.PhoneAction">

            <result name="success">tSuc.jsp</result>

            <result name="input">/t.jsp</result>

</action>

對應的PhoneAction類的代碼以下:
package com.asm.action;

public class PhoneAction extends ActionSupport {

      private Telephone thePhone;

      public String execute() throws Exception {

            return SUCCESS;

      }

      ...省略thePhone的get/set方法

}

說明,若是直接這樣執行將會出錯,由於前臺t.jsp傳的String默認是不能被轉成這裏的Phone對象,因此咱們必須使用類型轉換,並且咱們配置了input result就是告訴咱們若是類型轉換失敗,將會停在t.jsp頁面,並且會報錯。下面接着看怎麼類型轉換。在這裏咱們要把010-123456這樣的電話換成:區號:010 電話:123456這樣的形式時。具體的操做步驟以下:
建立類型轉換類TelephoneConversion,代碼以下:
package com.asm.conversion;

public class TelephoneConversion extends StrutsTypeConverter {

public Object convertFromString(Map context, String[] values, Class toClass) {

            System.out.println("執行字串到Telephone對象的轉換");

            Telephone tp=new Telephone();

            String [] tel=values[0].split("-");

            tp.setSecNum(tel[0]);

            tp.setPhone(tel[1]);

            return tp;

      }

 

      public String convertToString(Map context, Object obj) {

            System.out.println("執行Telephone對象到字串的轉換");

            Telephone tp=(Telephone) obj;

            return "區號:"+tp.getSecNum()+"\t電話:"+tp.getPhone();

      }

}
說明:類型轉換類必須實現TypeConverter接口,而這裏的StrutsTypeConverter類即是TypeConverter接口實現類DefaultTypeConverter的子類。此類中有兩個方法,一個方法實現把字串轉成其它對象,一個方法實現把其它對象轉成字串。在convertFromString方法中,咱們實現把客戶端傳遞的字串轉成Telephone對象,這樣就能讓PhoneActionsetThePhone方法得以正確執行。然後面的方法是爲了咱們要取值時進行的處理,好比在tSuc.jsp中咱們要獲得此值,須要把Telephone對象轉換字串。其實若是沒有convertToString方法,只要咱們重寫Telephone的toString方法也能達到目的。
寫完類類型轉換類後,咱們還應告知struts2,因此咱們還需創建一個properties文件。咱們在PhoneAction的同包下創建PhoneAction-conversion.properties文件,它的主要代碼以下:
thePhone=com.asm.conversion.TelephoneConversion
說明:這句話的意思是說咱們要把PhoneAction(經過properties文件名能夠知道要轉換的是此Action)下的thePhone使用TelephoneConversion進行轉換。其實咱們也能夠配置全局的properties文件說明,好比咱們在src目錄下創建xwork-conversion.properties文件(名字固定),它的內容以下:
com.asm.vo.Telephone=com.asm.conversion.TelephoneConversion

說明:它的意思是隻要遇到Telephone對象,就要用後面的轉換器來實行轉換。

2.基於Action的間接屬性vo轉換

t2.jsp主要內容以下:
<s:form action="up" method="post">

            <s:textfield name="user.thePhone" label="電話"/>

            <s:submit value="提交"/>

            <s:reset value="重置"/>

</s:form>
咱們創建UserPhoneAction類,它的主要代碼以下:
package com.asm.action;

public class UserPhoneAction extends ActionSupport {

      private User user;

      public String execute() throws Exception {

            return SUCCESS;

      }
      ...省略user的get/set方法

}

User類的代碼以下:
package com.asm.vo;

public class User {

      private Telephone thePhone;

      ...省略thePhone的get/set方法。

}

說明:經過這兩個類及t2.jsp頁面,咱們知道,前臺傳遞的thePhone對象不時直接傳遞,而是採用了vo模式,因此當咱們配置類型轉換時,要特別注意。由於前面咱們使用了全局的類型轉換,因此這裏不會出錯,可是當咱們去掉前面的全局轉換時,配置類型轉換的properties文件就應在User類對應的包下配置User-conversion.properties文件,它的主要內容以下:

thePhone=com.asm.conversion.TelephoneConversion
說明及總結:類型轉換的配置文件若是不採用全局的配置時,咱們就應以要轉換的類型的直接持有類爲基準:好比,這裏的thePhone的直接持有類爲User對象,因此咱們就應以User爲基準寫properties文件名。

11、註解配置

在此先略去註解配置的實例,具體能夠參看官方提供的文檔。其實在熟悉struts及相關的一些內容後,再來看文檔是比較容易理解得。只是要注意使用註解Annotition時:(1)要多導入一個jar包:struts2-convention-plugin-2.1.6.jar。(2)須要在web.xml中增長以下內容:

<filter>

            <filter-name>struts2</filter-name> 

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

            </filter-class>
                  <!-- 增長了如下內容 -->

            <init-param>

                  <param-name>actionPackages</param-name>

                  <param-value>com.struts2.action</param-value>

            </init-param>

      </filter>

12、總結
本教程對struts2的基本知識進行了一些說明,關於struts2的更多詳細內容應參看struts2的官方文檔及提供的app實例。
下面對struts2的基本執行流程做一簡要說明,此流程說明能夠結合官方提供的struts2結構圖來看:

n  客戶端提交一個(HttpServletRequest)請求,如上文在瀏覽器中輸入
http://localhost: 8080/appName/...就是提交一個(HttpServletRequest)請求。

n  請求被提交到一系列(主要是3層)的過濾器(Filter),如(ActionContextCleanUp、其餘過濾器(SiteMesh等)、 FilterDispatcher)。注意:這裏是有順序的,先ActionContext CleanUp,再其餘過濾器(Othter FiltersSiteMesh等),最後到FilterDispatcher

n  FilterDispatcher是控制器的核心,就是MVCStruts 2中實現控制層(Controller)的核心。(有點struts1.xActionServlet的感受)

n  FilterDispatcher詢問ActionMapper是否須要調用某個Action來處理這個(HttpServlet Request)請求,若是ActionMapper決定須要調用某個ActionFilterDispatcher則把請求的處理交給ActionProxy

n  ActionProxy經過Configuration Managerstruts.xml)詢問框架的配置文件,找到須要調用的Action類。例如,用戶註冊示例將找到UserReg類。

n  ActionProxy建立一個ActionInvocation實例,同時ActionInvocation經過代理模式調用Action。但在調用以前,ActionInvocation會根據配置加載Action相關的全部Interceptor(攔截器)。 關於ActionInvocation的執行過程咱們在5、2自定義攔截器最後的補充中已經進行了較詳細說明。

n  一旦Action執行完畢,ActionInvocation負責根據struts.xml中的配置找到對應的返回結果result


http://www.chengxuyuans.com 程序員之家

相關文章
相關標籤/搜索