Struts2のOGNL式について
概要
Struts2ではJSPやvalidation.xmlで簡単なプログラム言語としてOGNL式が記述できる。OGNL式は他にもstruts.xmlのresultにも記述できたり、JSPではJakartaEEのEL式、スクリプトレットと組み合わせて使用できる。
OGNL式、スクリプトレット、EL式の違い、組み合わせ、OGNL式のstrutsタグ以外での使用について述べる。
調査環境
- Ubuntu 22.04.1 LTS
- openjdk version "11.0.12" 2021-07-20 LTS
- struts2 6.1.0-SNAPSHOT
- ognl 3.3.4
- Tomcat 9.0.64
OGNL式とは
OGNL式はコンテキストに格納された値の取得・設定、格納されたオブジェクトのメソッド呼び出しなどを行う式言語である。
コンテキストはrootと呼ばれるオブジェクトと、変数を保持するキーバリューストアで構成される。
Struts2ではrootはスタックになっており複数のオブジェクトを格納できる。
Struts2ではValueStackを通してrootにActionクラスのインスタンスなど、キーバリューストアにHTTPセッション、ページコンテキストなどを格納している。
OGNL式にはOgnl.getValue
(以下、getValue)とOgnl.setValue
(以下、setValue)の2つのメソッドがあり、それぞれコンテキスト内の値の取得、設定を行うのだが、Javaと異なりgetValueでも#a=1
のような記述で値の設定も同時にできる。その場合、取得する値は最後に設定した値になる。
Struts2では<s:set>タグでsetValueを使用しており、それ以外(<s:property>タグなど)はほとんどgetValueを使用する。
OGNL式の演算子
記述式 | 名称 | 説明 | Javaの疑似コード | |
---|---|---|---|---|
e1, e2, … | カンマ演算子 | 指定した式を順に評価したのち最後の値を (getValueの場合)返却 (setValueの場合)設定 |
e1; return e2; |
|
e1 = e2 | 代入演算子 | e1にe2の値を設定する。 | e1 = e2 | |
e1 ? e2 : e3 | 条件(三項)演算子 | e1の結果がtrueの場合e2、そうでない場合e3を (getValueの場合)返却 (setValueの場合)設定 |
e1 ? e2 : e3 | |
e1 || e2, e1 or e2 | 論理和演算子 | e1がtrueの場合e1、そうでない場合e2を返却。e1がtrueの場合はe2は評価されない。 | e1 || e2 | |
e1 && e2, e1 and e2 | 論理積演算子 | e1がfalseの場合e1、そうでない場合e2を返却。e1がfalseの場合はe2は評価されない。 | e1 && e2 | |
e1 | e2, e1 bor e2 | ビット論理和 | e1とe2のビット論理和を返却。e1とe2は整数として解釈される | e1 | e2 | |
e1 ^ e2, e1 xor e2 | ビット排他的論理和 | e1とe2のビット排他的論理和を返却。e1とe2は整数として解釈される | e1 ^ e2 | |
e1 & e2, e1 band e2 | ビット論理積 | e1とe2のビット論理積を返却。e1とe2は整数として解釈される | e1 & e2 | |
e1 == e2, e1 eq e2 | 等価 | 以下の方法で等価かどうか判断する。 ・いずれかの値がnullの場合、両方がnullであれば等価 ・e1、e2が同じオブジェクトの場合、またはe1.equals(e2)の結果がtrueであれば等価 ・e1、e2が数値の場合、倍精度浮動小数点数として値が等しければ等価 |
||
e1 != e2, e1 neq e2 | 不等価 | |||
e1 < e2, e1 lt e2 | 小なり | e1、e2がComparableを実装しているオブジェクトの場合、compareTo()で比較する。そうでない場合、数値として比較する。 | e1.compareTo(e2) < 0 e1 < e2 |
|
e1 <= e2, e1 lte e2 | 以下 | e1.compareTo(e2) <= 0 e1 <= e2 |
||
e1 > e2, e1 gt e2 | 大なり | e1.compareTo(e2) > 0 e1 > e2 |
||
e1 >= e2, e1 gte e2 | 以上 | e1.compareTo(e2) >= 0 e1 >= e2 |
||
e1 in e2 | IN演算子 | e2にe1が含まれているかどうかをテストする。 ※e2はコレクションとして解釈される。 ※e2がMapの場合はKey、ValueのうちValueと比較する。 ※このテストはコレクションを反復処理するため効率的ではない。 |
e2.contains(e1) | |
e1 not in e2 | NOT IN演算子 | !e2.contains(e1) | ||
e1 << e2, e1 shl e2 | 左シフト | e1とe2を整数としてシフト演算を行う。 | e1 << e2 | |
e1 >> e2, e1 shr e2 | 符号維持右シフト | e1 >> e2 | ||
e1 >>> e2, e1 ushr e2 | ゼロ埋め右シフト | e1 >>> e2 | ||
e1 + e2 | 加算 | e1、e2が数値の場合、数値として加算する。そうでない場合、文字列として連結する。 | e1 + e2 | |
e1 - e2 | 減算 | e1、e2を数値として減算する。 | e1 - e2 | |
e1 * e2 | 乗算 | e1、e2を数値として乗算する。 | e1 * e2 | |
e1 / e2 | 徐算 | e1、e2を数値として除算する。 | e1 / e2 | |
e1 % e2 | 剰余 | e1、e2を数値として剰余を計算する。 | e1 % e2 | |
+ e | 単項プラス | なにもせずeを返す。 | + e | |
- e | 単項符号反転 | eを数値として解釈し符号を判定した値を返す。 | - e | |
! e, not e | 論理否定 | eをブール値として解釈し論理否定した値を返す。 | ! e | |
~ e | ビット否定 | eを整数として解釈しビット否定した値を返す。 | ~ e | |
e instanceof class | instanceof演算子 | eがclassに指定したクラスのインスタンスかどうか判定する。class引数にはJavaクラスの完全修飾名(FQCN)を指定する必要がある。OGNL式では[]はインデックスの意味になるためjava.lang.String[]のような配列の指定はできない。 | e instanceof クラス名 | |
e.method(args) | メソッド呼び出し | コンテキストに格納されているeのメソッドを呼び出す。Struts2では※1の制約がある。 | e.method(args); | |
e.property | プロパティ | コンテキストに格納されているeのプロパティにアクセスする。 名前が一致するメンバ変数(大小文字の区別なし)以外にも以下のプリフィックスを持つgetter、setterが検索される。 getter:get、is、has setter:set |
取得時: e.getProperty(); 設定時: e.setProperty(value); |
|
e1[ e2 ] | インデックス | 配列またはリストe1のe2番目の要素や、オブジェクトe1のプロパティe2にアクセスする。 JavaBeans Indexed Properties、OGNL Object Indexed Propertiesのアクセスにも使用できる。 |
配列:e1[e2]; List:e1.get(e2); Map:e1.get(e2); Object:e1.e2(); |
|
e1.{ e2 } | 投影 | 各要素にe2のロジックを適用した結果をArrayListで返す。要素には#thisでアクセスする。※2 | Stream.of(e1).map(e2); | |
e1.{? e2 } | 選択 | 要素の絞り込みを行う。以下の選択方法がある。※2 ? - 選択ロジックe2にマッチした全要素 ^ - 選択ロジックe2にマッチした最初の要素 $ - 選択ロジックe2にマッチした最後の要素 |
?: Stream.of(e1).filter(e2) .toArray(); ^: Stream.of(e1).filter(e2) .findfirst(); $: Stream.of(e1).filter(e2) .reduce((first, second) -> second).orElse(null); |
|
e1.(e2) | 部分式の評価 | 括弧内の式e2(カンマ区切りで複数指定する)はすべてドットの前のe1を対象に評価される。最後に評価した値を返却する。括弧内でも#thisはe1にはならない | e1.e2その1(); e1.e2その2(); ... |
|
e1(e2) | 式の評価 | lambda式を実行したり、コンテキストに格納されたオブジェクトのメソッドe1を呼び出す。e2はその引数 | ||
constant | 定数 | 文字列リテラル(String型) | \"A\"、'abc'、'ABC' '\\\''(シングルクォート) '\\\\'(バックスラッシュ) |
"A"、"abc"、"ABC"、"'"、"\\" |
文字リテラル(char型) | 'A'、'+' ※シングルクォートで半角1文字をくくる | 'A'、'+' | ||
数値リテラル | int:123 long:123l、123L float:1.2f、1.2F double:1.2d、1.2D BigDecimal:1.2b、1.2B BigInteger:123h、123H ※「huge」の頭文字 |
int:123 long:123l、123L float:1.2f、1.2F double:1.2d、1.2D BigDecimal:new BigDecimal("1.2") BigInteger:new BigInteger("123") |
||
ブールリテラル | true、false | true、false | ||
nullリテラル | null | null | ||
( e ) | 括弧付き式 | 2つの利用方法がある ・数学の括弧と同じく、式の評価順序を制御する。 ・メソッド引数でカンマ演算子を記述するために使用する。 |
||
method(args) | メソッド呼び出し | rootのメソッドを呼び出す。※1 ※3 | ||
property | プロパティ参照 | rootのプロパティにアクセスする。※3 名前が一致するメンバ変数(大小文字の区別なし)以外にも以下のプリフィックスを持つgetter、setterが検索される。 getter:get、is、has setter:set |
||
[ e ] | インデックス参照 | rootのプロパティeにアクセスする。※3 | ||
{ e, ... } | List生成 | 指定した要素を持つArrayListを生成する | new ArrayList() {{ add(e); ... }}; | |
#variable | コンテキスト変数参照 | コンテキストに格納されたオブジェクトにアクセスする | ||
@class@method(args) | staticメソッド参照 | staticメソッドを呼び出す。 Struts2ではSecurityMemberAccessクラスによりStaticメソッド参照はできないように制限されている |
System.currentTimeMillis() | |
@class@field | staticフィールド参照 | staticフィールドにアクセスする。 値の設定はできない。 Struts2ではstruts.xmlに以下を記述すれば無効にできる。既定では有効 <const name="struts.ognl.allowStaticFieldAccess" value="false"/> |
Math.PI | |
new class(args) | コンストラクタ呼び出し | classのインスタンスを生成する。Struts2では※1の制約がある | new class(args) | |
new array-component-class[] { e, ... } | 配列生成 | array-component-class型の配列を生成する | new array-component-class() {{ add(e); ... }}; | |
#{ e1 : e2, ... } | Map生成 | 指定したエントリーを持つLinkedHashMapを生成する | new LinkedHashMap() {{ put(e1, e2); ... }}; | |
#@classname@{ e1 : e2, ... } | Map生成(サブクラス指定) | 指定したエントリーを持つclassname型のMapを生成する。 classnameにMap以外の型を指定するとClassCastExceptionでエラーになる |
new classname() {{ put(e1, e2); ... }}; | |
:[ e ] | Lambdba式定義 | Lambda式を定義する。 有効にするにはstruts.xmlに以下を記述する必要がある。本番環境での使用は要注意 <constant name="struts.ognl.enableEvalExpression" value="true"/> |
※1 OGNLでは標準ではpublicメソッド以外はアクセスできないように制限されており、Struts2ではさらに特定のパッケージとクラスのインスタンス生成、メソッド呼び出しができないように拡張されている。
対象のパッケージとクラス
<constant name="struts.excludedClasses"
value="
java.lang.Object,
java.lang.Runtime,
java.lang.System,
java.lang.Class,
java.lang.ClassLoader,
java.lang.Shutdown,
java.lang.ProcessBuilder,
java.lang.Thread,
sun.misc.Unsafe,
com.opensymphony.xwork2.ActionContext"/>
<constant name="struts.excludedPackageNames"
value="
ognl.,
java.io.,
java.net.,
java.nio.,
javax.,
freemarker.core.,
freemarker.template.,
freemarker.ext.jsp.,
freemarker.ext.rhino.,
sun.misc.,
sun.reflect.,
javassist.,
org.apache.velocity.,
org.objectweb.asm.,
org.springframework.context.,
com.opensymphony.xwork2.inject.,
com.opensymphony.xwork2.ognl.,
com.opensymphony.xwork2.security.,
com.opensymphony.xwork2.util.,
org.apache.tomcat.,
org.apache.catalina.core.,
org.wildfly.extension.undertow.deployment."/>
以下の設定で使用可能になる。※非推奨
<constant name="struts.excludedClasses" value="" />
<constant name="struts.excludedPackageNames" value="" />
また、以下の方法でpublicでないメンバーにアクセスできる。
OgnlRuntime.getMethodValue(context, root, "getter名")
OgnlRuntime.setMethodValue(context, root, "setter名", "newValue");
※2 投影、選択、IN演算子、NOT IN演算子では対象のオブジェクトをコレクションとみなして、保持する要素を一つ一つ順番に処理する。対象のオブジェクトはコレクションでなくてもよく、対象のオブジェクトに応じて以下のとおり処理される。
- 配列、Collection: 要素を順番に処理する
- Iterator、Enumeration: 自身を列挙処理する
- Map: 値のCollectionを順番に処理する。キーは対象としない
- Number(Integer、Longなど): 0~自身の数値-1の配列(0,1,2,...自身の数値-1)とみなして数値を順番に処理する
- その他のオブジェクト: 自身を唯一の要素とするコレクションとして処理する
※3 Struts2ではrootはスタックであり、スタック内のオブジェクトのうち該当するメソッドまたはプロパティを持つ最初のものを呼び出す、または取得する。
記述例
カンマ演算子
<s:set var="result" value="#a = 1, #b = #a * 2, #b * #b"/> ※resultには最後の値が設定される
<s:property value="#result"/> →4
<s:property value="#a"/> →1
<s:property value="#b"/> →2
<s:property value="#result, #a, #b"/> →2 ※最後の値のみ表示される
代入演算子
<s:set var="result" value="#a = 'hello'"/> ※同時に2つの変数に代入
<s:property value="result"/> →hello
<s:property value="a"/> →hello
<s:property value="test = 5"/> →5 ※代入と表示を同時に行う
<s:property value="test"/> →5
条件(三項)演算子
<s:set var="result" value="#a == 1 ? 'one' : 'two?'"/>
<s:property value="#result"/> a=1の場合、"one"、#a≠1の場合、"two?"
論理和演算子
<s:if test="#a > 3 || #a < 0"> または <s:if test="#a > 3 or #a < 0">
<s:property value="#a"/> #a>3、または#a<0の場合は表示される
</s:if>
論理積演算子
<s:if test="#a < 3 && #a > 0"> または <s:if test="#a < 3 and #a > 0">
<s:property value="#a"/> #a<3、かつ#a>0の場合に表示される
</s:if>
論理演算
<s:set var="val1" value="9"/> 1001
<s:property value="#val1 | 12"/> 1001 | 1100 = 1101 → 13
<s:property value="#val1 ^ 12"/> 1001 ^ 1100 = 0101 → 5
<s:property value="#val1 & 12"/> 1001 & 1100 = 1000 → 8
IN演算子
<s:if test="'foo' in {'foo','bar'}">
リストに存在する →'foo'がリストに存在するので表示される
</s:if>
NOT IN演算子
<s:if test="'foo' not in {'foo','bar'}">
リストに存在する →'foo'がリストに存在するので表示されない
</s:if>
ビット演算
<s:set var="val1" value="-9"/> 1111 1111 1111 1111 1111 1111 1111 0111
<s:property value="#val1 << 2"/> 1111 1111 1111 1111 1111 1111 1101 1100 → -36
<s:property value="#val1 >> 2"/> 1111 1111 1111 1111 1111 1111 1111 1101 → -3
<s:property value="#val1 >>> 2"/> 0011 1111 1111 1111 1111 1111 1111 1101 → 1073741821
instanceof演算子
<s:property value="'abc' instanceof java.lang.String"/> 'abc'は文字列なのでtrue
<s:property value="top instanceof com.opensymphony.xwork2.ActionSupport"/> Struts2ではtopはコンテキストの先頭のオブジェクトであり、それがActionクラスのインスタンスであればtrue
メソッド呼び出し(e.method(args))
<s:property value="#action.getText('hello.message')"/> 実行中のActionのgetTextメソッドを呼び出す
<s:property value="#session.get('nonce')"/> sessionのgetメソッドを呼び出す。※sessionへのアクセスは後述のインデックスのほうが簡単
<s:property value="#action.getProp1()"/> プロパティもgetterの呼び出しで取得できるが後述のプロパティを使用するのが適切
プロパティ(e.property)
<s:property value="#action.prop1"/> 実行中のActionのprop1プロパティを表示する
<s:property value="#action.method1"/> 引数のないメソッドであればプロパティのようにアクセス可能 public String method1() { 略 }
<s:property value="#session.value1"/> application、session、request、parameters、attrのようにMapインターフェースを実装しているものはその値をプロパティのように取得できる
インデックス(e1[ e2 ])
<s:property value="#array[0]"/> arrayが配列またはリストの場合、0番目の要素取得。arrayがMapの場合、キー=0の値を取得
<s:property value="#array['length']"/> arrayが配列の場合、要素数を取得。arrayがMapの場合、キー="length"の値を取得
<s:property value="#array['len' + 'gth']"/> 文字列を結合させても同じ結果になる。
<s:property value="#application['org.apache.catalina.jsp_classpath']"/> application、session、request、parameters、attrはMapインターフェースを実装しているのでインデックスでアクセス可能
<s:property value="#session['nonce']"/>
<s:property value="#request['struts.request_uri']"/>
<s:property value="#parameters['page']"/>
<s:property value="#attr['com.opensymphony.xwork2.ActionContext.name']"/>
<s:property value="#action['method1']"/> 引数のないメソッドであればメソッドも呼び出せる
<!-- JavaBeans Indexed Properties
public String[] getLanguages() { return languages; }
public void setLanguages(String[] lngs) { languages = lngs; }
public String getLanguages(int index) { return languages[index]; }
public void setLanguages(int index, String lang) { languages[index] = lang; }
※配列はString型以外でもよい
-->
<s:property value="#action.languages[5]"/> Actionクラスに上記の定義がある場合、プロパティに数字のインデックスでアクセスできる
<!-- OGNL Object Indexed Properties
public String getStatus(String index) { return index + " status: good" ; }
public void setStatus(String index, String value) { 省略 }
※インデックス、戻り値はString型以外でもよい
-->
<s:property value="#action.status['player1']"/> Actionクラスに上記の定義がある場合、プロパティに文字列のインデックスでアクセスできる
投影
<s:set var="list" value="{'eye','mouth','nose'}" />
<s:property value="#list.{#this + ':' + length}"/> [eye:3, mouth:5, nose:4]。{}中では#this.lengthの#thisは省略可能
<!-- 集計もやろうと思えばできる -->
<s:set var="nums" value="{1,2,3,4,5,6,7,8,9,10}"/>
<s:set var="total" value="0"/>
<s:property value="#nums.{#total = #total + #this}"/>
<s:property value="#total"/> →55
<!-- 数値から配列を生成 -->
<s:property value="5f.{#this}"/> [0.0, 1.0, 2.0, 3.0, 4.0]
選択
<s:set var="list" value="{'10',null,30,'40',50,true}" />
<s:property value="#list.{? #this instanceof java.lang.String}"/> [10, 40]。文字列の要素のみ抽出
<s:property value="5f.{#this * #this}.{? #this > 8}" /> [9.0, 16.0]。0.0~4.0の値を2乗し、結果が8より大きいものを配列で返却する
<s:set var="cities" value="{'Tokyo','Aomori','Sendai','Yamaguchi'}" />
<s:property value="#cities.{^ #this.indexOf('a') > 0}"/> ['Sendai']。aが含まれる最初の要素
<s:property value="#cities.{$ #this.indexOf('o') > 0}"/> ['Aomori']。oが含まれる最後の要素
部分式の評価
<!--
public class Parent {
public void ensureLoaded() {
if (this.name==null) this.name="Oyadesu";
}
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
public class HelloAction extends ActionSupport {
private Parent parent;
public Parent getParent() { return parent; }
}
-->
<s:property value="#action.parent.(ensureLoaded(), name)"/> 括弧内の処理はいずれもparentに対して評価される。この場合action.parent.nameがnullであれば"Oyadesu"が設定されて表示される
式の評価
<s:property value="#fact(5)"/> Lambda式を呼び出す
<s:property value="'#this*#this'(9)"/> 括弧の前に文字列で式を書けば括弧内の値を引数として式を評価した結果が得られる。左記の結果は81
<!--
private String expr = "#this*#this";
public String getExpr() { return this.expr; }
-->
<s:property value="(expr)(9)"/> 上記のプロパティがある場合、プロパティを括弧で囲めば次の括弧内の値を引数として式を評価できる。左記の結果は81
<s:property value="expr(9)"/> この書き方ではメソッド呼び出しと判断されてNoSuchMethodExceptionになる。
定数
<!-- 文字列リテラル(String型) -->
<s:set var="string1" value="\"A\""/> "A"
<s:set var="string2" value="'abc'"/> "abc"
<s:set var="string3" value="'ABC'"/> "ABC"
<s:set var="string4" value="'\\\''"/> "'"(シングルクォート)
<s:set var="string5" value="'\\\\'"/> "\"(バックスラッシュ)
<!-- 文字リテラル(char型) -->
<s:set var="char1" value="'A'"/> 'A'
<!-- 数値リテラル -->
<s:set var="int1" value="123"/> 123
<s:set var="long1" value="3000000000l"/> 3000000000
<s:set var="long2" value="3000000000L"/> 3000000000
<s:set var="float1" value="3.141592654f"/> 3.1415927
<s:set var="float2" value="3.141592654F"/> 3.1415927
<s:set var="double1" value="3.141592654d"/> 3.141592654
<s:set var="double2" value="3.141592654D"/> 3.141592654
<s:set var="bigdecimal1" value="1.2345678901234567891b"/> 1.2345678901234567891
<s:set var="bigdecimal2" value="1.2345678901234567891B"/> 1.2345678901234567891
<s:set var="biginteger1" value="100000000000000000000h"/> 100000000000000000000
<s:set var="biginteger2" value="100000000000000000000H"/> 100000000000000000000
<!-- ブールリテラル -->
<s:set var="bool1" value="true"/> true ※Trueは不可
<s:set var="bool2" value="false"/> false ※Falseは不可
<!-- nullリテラル -->
<s:set var="null1" value="null"/> null ※Nullは不可
括弧付き式
<s:property value="(#a + 5) * 3"/> 数学の括弧と同じく演算の順序を制御する
<s:property value="method(ensureLoaded(), name)"/> methodが引数を1つだけ取るメソッドの場合、これはNoSuchMethodExceptionになる
<s:property value="method((ensureLoaded(), name))"/> メソッドの引数にカンマ演算子を使用したい場合に括弧付き式を使う
メソッド呼び出しmethod(args)
<s:property value="getText('hello.message')"/> rootのgetTextメソッドを呼び出す
<s:property value="describe()"/> Struts2ではdescribeメソッドでrootに格納されているオブジェクトのプロパティ一覧を表示できる。ただし表示されるのはpublicなもののみ
プロパティ参照(property)
<s:property value="prop1"/> rootのprop1プロパティを表示する
<s:property value="method1"/> 引数のないメソッドであればプロパティのようにアクセス可能 public String method1() { 略 }
インデックス参照([ e ])
<s:property value="['prop1']"/> rootのプロパティprop1を返す
<s:property value="['method1']"/> 引数のないメソッドであればメソッドも呼び出せる
<s:property value="[prop1]"/> シングルクォートを付け忘れると、まずprop1プロパティの値を取得し、その値に合致するプロパティを探すので注意
<!-- 以下はStruts2固有 -->
<s:property value="[0]"/> rootのスタックの0番目の要素以降を返す。[org.demo.actions.HelloAction@7ea3f73f, com.opensymphony.xwork2.DefaultTextProvider@1ac707ca]
<s:property value="[1]"/> rootのスタックの1番目の要素以降を返す。[com.opensymphony.xwork2.DefaultTextProvider@1ac707ca]
List生成
<s:select label="label" name="name" list="{'name1','name2','name3'}" value="%{'name2'}" />
コンテキスト変数参照
<s:property value="#root"/> コンテキストのrootにアクセスする
<s:property value="#this"/> コンテキストのcurrentObjectにアクセスする。currentObjectにはメソッド呼び出しやプロパティ参照ではレシーバー、投影や選択ではリストの各要素、Lambda式で引数が設定される。定数などではrootが設定される
<!-- 以下のようにrootやthisに値を設定することもできるが次のOGNL式の実行時には元に戻るのであまり意味がない -->
<s:property value="#root=5"/> 5と表示される
<s:property value="#root"/> [org.demo.actions.HelloAction@29d89f4e, com.opensymphony.xwork2.DefaultTextProvider@1ac707ca]のようなrootの内容が表示される
<s:set var="x" value="'abc'"/>
<s:property value="#x"/> <s:set>タグでセットした変数にアクセスする
<!-- 以下はStruts2固有 -->
<s:property value="#application"/> アプリケーションスコープ
<s:property value="#session"/> セッションスコープ
<s:property value="#request"/> リクエストスコープ
<s:property value="#parameters"/> リクエストパラメータ
<s:property value="#attr"/> page、request、session、applicationの順にスコープを検索し最初に見つかったものを返す
<s:property value="#action"/> 実行中のActionのインスタンス
<!-- Struts2ではOgnlValueStackのおかげで#を付けなくてもコンテキストにアクセスできる -->
<s:property value="application"/> "#application"を指定したのと同じ。ただし#を付けたほうが処理の効率はよい
staticメソッド参照
ognl.Ognl.getValue("@java.lang.System@currentTimeMillis()", null); →1680439988745など
<s:property value="@java.lang.System@currentTimeMillis()"/> ※properyタグではstaticメソッドの呼び出しは不可
staticフィールド参照
<s:property value="@java.lang.Math@PI"/> →3.141592653589793
<s:property value="@vs@MAX_LENGTH"/> Struts2では@vs、@vsN(Nは1以上の数値でスタック内の位置)と記述することでValueStackに格納されているオブジェクトのstaticフィールドにアクセスできる
<s:property value="@vs@class"/> classと記述することでclassオブジェクトが取得できる
<s:property value="@java.lang.System@out.println('Hello World!!')"/> catalina.outに"Hello World!!"が出力される。画面には出力されない。
※上記の実行には※1に記載したstruts.xmlの設定が必要
コンストラクタ呼び出し
<s:set var="myAction" value="new org.demo.actions.HelloAction()" />
<s:property value="new java.lang.StringBuilder('Current Date:' + new java.util.Date())"/>
※上記の実行にはいずれも※1に記載したstruts.xmlの設定が必要
配列生成
<s:set var="myArray" value="new java.lang.String[] {1, 2, 3}" /> Stringの配列{"1","2","3"}
<s:set var="myArray" value="new java.math.BigDecimal[] {1, 2, 3}" /> BigDecimalの配列{1B, 2B, 3B}
<s:set var="myArray" value="new int[5]" /> 要素数が5個ですべて0に初期化されたintの配列
Map生成
<s:select label="label" name="name" list="#{'03':'Tokyo', '011':'Hokkaido'}" />
Map生成(サブクラス指定)
<s:select label="label" name="name" list="#@java.util.TreeMap@{'03':'Tokyo', '011':'Hokkaido'}" />
Lambda式定義
<s:property value="#fib =:[#this==0 ? 0 : #this==1 ? 1 : #fib(#this-2)+#fib(#this-1)], #fib(11)" /> →89
<s:property value="#fact =:[#this[0]<=1? 1: #this[0]*#fact({#this[0]-1, #this[1]+1})+#this[1]], #fact({3, 2})" /> 引数を複数設定したい場合は配列として渡す。左記の結果は17
<s:property value="#fib = \"#this==0 ? 0 : #this==1 ? 1 : #fib(#this-2)+#fib(#this-1)\", #fib(11)" /> 文字列で式を記述することもできる
JSPにおけるOGNL式、スクリプトレット、EL式の解析タイミング
JPSではOGNL式、スクリプトレット、EL式が記述可能だが、それぞれ解析のタイミングが異なる。
スクリプトレット
スクリプトレットはJSPの初回呼び出し時にJasperコンパイラによってJavaクラスにコンパイルされる。そしてJavaクラスのメソッド、コードの一部として実装される。
いつ解析する? JSPの初回呼び出し時
誰が解析する? org.apache.jasper.compiler.Compiler
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ page import="java.lang.*"%>
<%!
// declaration tag
public String method(String arg) {
return arg + "だ!";
}
private String val;
public String getVal() {
return this.val;
}
public void setVal(String val) {
this.val = val;
}
%>
<%
// scriptlet tag
pageContext.setAttribute("self", this);
%>
<html>
<body>
<!-- Expression tag -->
<%= this.getClass().getName() %>
<!-- 後略 -->
public final class hello_jsp extends HttpJspBase implements JspSourceDependent, JspSourceImports {
// ↓JSPに記載した内容がそのまま実装される
public String method(String arg) {
return arg + "だ!";
}
private String val;
public String getVal() {
return this.val;
}
public void setVal(String val) {
this.val = val;
}
// ↑JSPに記載した内容がそのまま実装される
public void _jspService(final HttpServletRequest request, final HttpServletResponse response)
throws IOException, ServletException {
try {
response.setContentType("text/html; charset=UTF-8");
pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;
out.write("\n");
out.write("\n");
out.write(" \n");
out.write("\n");
out.write("\n");
out.write('\n');
pageContext.setAttribute("self", this); // ←JSPに記載した内容がそのまま実装される
out.write("\n");
out.write("<html>\n");
out.write("<body>\n");
out.print( this.getClass().getName() ); // ←JSPに記載した内容が出力される
// (後略)
EL式
EL式はorg.apache.jasper.runtime.PageContextImplクラスのproprietaryEvaluateメソッドの呼び出しに置き換えられる。このメソッドは最初のリクエスト時に式の構文解析を行ってAST(抽象構文木)をキャッシュする。実行はリクエスト毎に行われる。
いつ解析する? EL式の初回呼び出し時
誰が解析する? org.apache.el.parser.ELParser
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ page import="java.lang.*"%>
<html>
<body>
<jsp:useBean id="h" class="org.demo.tags.Hoge" >
<jsp:setProperty name="h" property="val" value="10"/>
</jsp:useBean>
${"el expression"}<br/>
${h.method('a')}<br/>
${h.val}
<!-- 後略 -->
public final class hello_jsp extends HttpJspBase implements JspSourceDependent, JspSourceImports {
public void _jspService(final HttpServletRequest request, final HttpServletResponse response)
throws IOException, ServletException {
try {
// 中略
out.write((String) PageContextImpl.proprietaryEvaluate("${\"el expression\"}", String.class, (PageContext)_jspx_page_context, null));
out.write("<br/>\n");
out.write((String) PageContextImpl.proprietaryEvaluate("${h.method('a')}", String.class, (PageContext)_jspx_page_context, null));
out.write("<br/>\n");
out.write((String) PageContextImpl.proprietaryEvaluate("${h.val}", String.class, (PageContext)_jspx_page_context, null));
package org.demo.tags;
public class Hoge {
private String val;
public String getVal() {
return this.val;
}
public void setVal(String val) {
this.val = val;
}
public String method(String arg) {
return "method arg=" + arg;
}
}
OGNL式
OGNL式はstrutsタグの属性に指定するのでJSPからはstrutsタグの呼び出しにしか見えない。strutsタグでは属性によってOGNL式として解析したり文字列として使用したりする。それはタグの実装に依存する。通常、解析結果はキャッシュされる。
いつ解析する? OGNL式の初回呼び出し時
誰が解析する? ognl.OgnlParser
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ page import="java.lang.*"%>
<html>
<body>
<s:set var="result" value="h.val"/> <!-- setタグのvalueはOGNL式として扱われる -->
<s:property value="#result"/> <!-- propertyタグのvalueはOGNL式として扱われる -->
<!-- 後略 -->
public final class hello_jsp extends HttpJspBase implements JspSourceDependent, JspSourceImports {
public void _jspService(final HttpServletRequest request, final HttpServletResponse response)
throws IOException, ServletException {
try {
// 中略
if (_jspx_meth_s_005fset_005f0(_jspx_page_context)) // setタグ
return;
out.write('\n');
if (_jspx_meth_s_005fproperty_005f0(_jspx_page_context)) // propertyタグ
return;
out.write("<br/>\n");
// 中略
}
}
private boolean _jspx_meth_s_005fset_005f0(PageContext _jspx_page_context) throws Throwable {
PageContext pageContext = _jspx_page_context;
JspWriter out = _jspx_page_context.getOut();
// s:set ←strutsのsetタグを生成している
SetTag _jspx_th_s_005fset_005f0 = (SetTag) _005fjspx_005ftagPool_005fs_005fset_0026_005fvar_005fvalue_005fnobody.get(SetTag.class);
boolean _jspx_th_s_005fset_005f0_reused = false;
try {
_jspx_th_s_005fset_005f0.setPageContext(_jspx_page_context);
_jspx_th_s_005fset_005f0.setParent(null);
// /WEB-INF/content/hello.jsp(11,0) name = var type = String reqTime = false required = true fragment = false deferredValue = false expectedTypeName = null deferredMethod = false methodSignature = null
_jspx_th_s_005fset_005f0.setVar("result");
// /WEB-INF/content/hello.jsp(11,0) name = value type = String reqTime = false required = false fragment = false deferredValue = false expectedTypeName = null deferredMethod = false methodSignature = null
_jspx_th_s_005fset_005f0.setValue("h.val");
int _jspx_eval_s_005fset_005f0 = _jspx_th_s_005fset_005f0.doStartTag(); // 式の解析はstrutsタグに任せている
if (_jspx_th_s_005fset_005f0.doEndTag() == Tag.SKIP_PAGE) {
return true;
}
_005fjspx_005ftagPool_005fs_005fset_0026_005fvar_005fvalue_005fnobody.reuse(_jspx_th_s_005fset_005f0);
_jspx_th_s_005fset_005f0_reused = true;
} finally {
JspRuntimeLibrary.releaseTag(_jspx_th_s_005fset_005f0, _jsp_getInstanceManager(), _jspx_th_s_005fset_005f0_reused);
}
return false;
}
private boolean _jspx_meth_s_005fproperty_005f0(PageContext _jspx_page_context) throws Throwable {
PageContext pageContext = _jspx_page_context;
JspWriter out = _jspx_page_context.getOut();
// s:property ←strutsのpropertyタグを生成している
PropertyTag _jspx_th_s_005fproperty_005f0 = (PropertyTag) _005fjspx_005ftagPool_005fs_005fproperty_0026_005fvalue_005fnobody.get(PropertyTag.class);
boolean _jspx_th_s_005fproperty_005f0_reused = false;
try {
_jspx_th_s_005fproperty_005f0.setPageContext(_jspx_page_context);
_jspx_th_s_005fproperty_005f0.setParent(null);
// /WEB-INF/content/hello.jsp(12,0) name = value type = String reqTime = false required = false fragment = false deferredValue = false expectedTypeName = null deferredMethod = false methodSignature = null
_jspx_th_s_005fproperty_005f0.setValue("#result");
int _jspx_eval_s_005fproperty_005f0 = _jspx_th_s_005fproperty_005f0.doStartTag(); // 式の解析はstrutsタグに任せている
if (_jspx_th_s_005fproperty_005f0.doEndTag() == Tag.SKIP_PAGE) {
return true;
}
_005fjspx_005ftagPool_005fs_005fproperty_0026_005fvalue_005fnobody.reuse(_jspx_th_s_005fproperty_005f0);
_jspx_th_s_005fproperty_005f0_reused = true;
} finally {
JspRuntimeLibrary.releaseTag(_jspx_th_s_005fproperty_005f0, _jsp_getInstanceManager(), _jspx_th_s_005fproperty_005f0_reused);
}
return false;
}
public class Set extends ContextBean {
protected String scope;
protected String value;
protected boolean trimBody = true;
public Set(ValueStack stack) {
super(stack);
}
public boolean end(Writer writer, String body) {
ValueStack stack = getStack();
Object o;
if (value == null) {
if (body == null) {
o = findValue("top");
} else {
o = body;
}
} else {
// 先のJSPの場合、valueは"h.val"
o = findValue(value); // ←getStack().findValueを呼び出す。getStack()の戻り値がOgnlValueStackのためOGNL式として解析される。
}
public class Property extends Component {
// (中略)
public boolean start(Writer writer) {
boolean result = super.start(writer);
String actualValue = null;
if (value == null) {
value = "top";
}
else {
value = stripExpressionIfAltSyntax(value);
}
// exception: don't call findString(), since we don't want the
// expression parsed in this one case. it really
// doesn't make sense, in fact.
actualValue = (String) getStack().findValue(value, String.class, throwExceptionOnELFailure);
// ↑getStack()の戻り値がOgnlValueStackのためOGNL式として解析される。
// 先のJSPの場合、valueは"#result"
package org.demo.actions;
import org.demo.tags.Hoge;
import com.opensymphony.xwork2.ActionSupport;
public class HelloAction extends ActionSupport {
private Hoge hoge;
public Hoge getH() {
return this.hoge;
}
public void setH(Hoge h) {
this.hoge = h;
}
public String execute() throws Exception {
this.hoge = new Hoge();
this.hoge.setVal("hogehoge");
return SUCCESS;
}
}
OGNL式、スクリプトレット、EL式の組み合わせ
tldファイルで属性のrtexprvalue=trueと指定されていれば属性にスクリプトレット、EL式が指定可能だが、struts2のタグには該当するものがない。そのためOGNL式、スクリプトレット、EL式の合わせ技は限定される。
<attribute>
<rtexprvalue>true</rtexprvalue>
setタグのボディにスクリプトレット、EL式を指定することは可能。
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ page import="java.lang.*"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<body>
<jsp:useBean id="h" class="org.demo.tags.Hoge" >
<jsp:setProperty name="h" property="val" value="${h.method('abc')}"/>
</jsp:useBean>
<s:set var="result">${h.val}</s:set>
<s:property value="#result"/><br/>
</body>
</html>
しかしこれは無理。${h.val}が"%{h.method('abc')}"に置き換わり、setタグでOGNL式として解析されるかと思ったがそのまま文字列として出力される。
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ page import="java.lang.*"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<body>
<jsp:useBean id="h" class="org.demo.tags.Hoge" >
<jsp:setProperty name="h" property="val" value="%{h.method('abc')}"/>
</jsp:useBean>
<s:set var="result">${h.val}</s:set>
<s:property value="#result"/><br/>
</body>
</html>
遅延評価EL式、動的属性でのOGNL式の使用
遅延評価EL式(遅延評価メソッド式、遅延評価バリュー式)
遅延評価EL式では#{...}の書き方で式を記述するのでOGNL式を記述するとすれば以下のようになると思う。
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ page deferredSyntaxAllowedAsLiteral="false" %>
<%@ taglib uri="http://www.test.com/tags/mytags" prefix="my" %>
<html>
<body>
<my:mytag method="#{%{h.method}}" value="#{%{h.val}}" /> <!-- 遅延評価式の#{}の中にOGNL式%{}を指定する -->
</body>
</html>
しかしEL式では%は剰余を表す演算子のためこの式は無効となる。そもそもEL式の仕様を見てもOGNL式が使用できるという記述はない。どうしてもOGNL式を使う必要がある場合は以下のように書けばエラーにならない。
<my:mytag method="%{h.method('abc')}" value="#{1%{h.val}}" />
遅延評価メソッド式は「#」を「%」に変更しメソッド呼び出しに必要な引数を記述する。遅延評価バリュー式のほうは"#{1%{h.val}}"のようにして剰余の演算子として正しい式に変更する。そしてカスタムタグのdoStartTagメソッドなどで上記の式をOGNL式として評価するようにする。
動的属性
JSPで動的属性にOGNL式を記述すればその内容がそのまま文字列でカスタムタグに渡させるので、カスタムタグの実装次第ではOGNL式が記述できる。
<my:mytag attr1="%{h.val}" attr2="%{h.method('abc')}" />
public class MyTagHandler extends TagSupport implements DynamicAttributes {
private Map<String, String> dynamicAttributes = new HashMap<>();
@Override
public void setDynamicAttribute(String uri, String localName, Object value) throws JspException {
// ここでvalue="%{h.val}"などがセットされてくる。それを文字列のまま保持
dynamicAttributes.put(localName, value.toString());
}
public int doStartTag() throws JspException {
JspWriter out = pageContext.getOut();// returns the instance of JspWriter
ELContext elContext = pageContext.getELContext();
try {
// 動的属性をOGNL式として解析する。
for (String key : dynamicAttributes.keySet()) {
out.print(key + ":" + TextParseUtil.translateVariables(this.dynamicAttributes.get(key), ActionContext.getContext().getValueStack()));
out.print("<br/>");
}
} catch (Exception e) {
e.printStackTrace();
}
return SKIP_BODY;
}
遅延評価EL式、動的属性のその他のコード
package org.demo.tags;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.DynamicAttributes;
import javax.servlet.jsp.tagext.TagSupport;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.util.TextParseUtil;
import javax.el.ELContext;
import javax.el.MethodExpression;
import javax.el.ValueExpression;
public class MyTagHandler extends TagSupport implements DynamicAttributes {
private String message;
private MethodExpression method;
private ValueExpression value;
private Map<String, String> dynamicAttributes;
public void setMessage(String message) {
this.message = message;
}
public void setMethod(MethodExpression method) {
this.method = method;
}
public void setValue(ValueExpression value) {
this.value = value;
}
@Override
public void setDynamicAttribute(String uri, String localName, Object value) throws JspException {
dynamicAttributes.put(localName, value.toString());
}
public int doStartTag() throws JspException {
JspWriter out = pageContext.getOut();// returns the instance of JspWriter
ELContext elContext = pageContext.getELContext();
try {
// 通常の遅延評価EL式の使い方
// if (!method.isLiteralText()) {
// out.print(method.invoke(pageContext.getELContext(), new Object[] {message}));
// out.print("<br/>");
// }
// out.print(value.getValue(elContext));
// 遅延評価EL式をOGNL式として解析する
out.print(TextParseUtil.translateVariables(this.method.getExpressionString(), ActionContext.getContext().getValueStack()));
out.print("<br/>");
out.print(TextParseUtil.translateVariables(this.value.getExpressionString(), ActionContext.getContext().getValueStack()));
out.print("<br/>");
// 動的属性をOGNL式として解析する
for (String key : dynamicAttributes.keySet()) {
out.print(key + ":" + TextParseUtil.translateVariables(this.dynamicAttributes.get(key), ActionContext.getContext().getValueStack()));
out.print("<br/>");
}
} catch (Exception e) {
e.printStackTrace();
}
return SKIP_BODY;
}
}
package org.demo.actions;
import org.demo.tags.Hoge;
import com.opensymphony.xwork2.ActionSupport;
public class HelloAction extends ActionSupport {
private Hoge h;
public Hoge getH() {
return this.h;
}
public void setH(Hoge h) {
this.h = h;
}
public String execute() throws Exception {
return SUCCESS;
}
}
package org.demo.tags;
public class Hoge {
private String val;
public String getVal() {
return this.val;
}
public void setVal(String val) {
this.val = val;
}
public String method(String arg) {
return "method arg=" + arg;
}
}
<?xml version="1.0" ?>
<taglib 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/javaee/web-jsptaglibrary_2_1.xsd"
version="2.1">
<tlib-version>1.0</tlib-version>
<jsp-version>2.1</jsp-version>
<short-name>mytags</short-name>
<tag>
<name>mytag</name>
<tag-class>org.demo.tags.MyTagHandler</tag-class>
<body-content>empty</body-content>
<attribute><!-- EL式が有効な文字列属性 -->
<name>message</name>
<required>false</required>
<type>java.lang.String</type>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute><!-- 遅延評価メソッド式 -->
<name>method</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<deferred-method>
<method-signature>
java.lang.String method(java.lang.String)
</method-signature>
</deferred-method>
</attribute>
<attribute><!-- 遅延評価バリュー式 -->
<name>value</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<deferred-value>
<type>java.lang.String</type>
</deferred-value>
</attribute>
<dynamic-attributes>true</dynamic-attributes>
</tag>
JSPに直接OGNL式を記述する
需要があるかわからないが、以下のようにJSPに直接OGNL式を記述することもできる。
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ taglib uri="http://www.test.com/tags/mytags" prefix="my" %>
<html>
<body>
<s:set var="val1">値その1</s:set>
直接OGNL式を書く:${my:translateVariables("%{val1}", my:getActionContext().getValueStack())}
</body>
</html>
上記を実現するために以下のカスタムELファンクションの定義が必要
<?xml version="1.0" ?>
<taglib 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/javaee/web-jsptaglibrary_2_1.xsd"
version="2.1">
<tlib-version>1.0</tlib-version>
<jsp-version>2.1</jsp-version>
<short-name>myTag</short-name>
<function>
<name>translateVariables</name>
<function-class>com.opensymphony.xwork2.util.TextParseUtil</function-class>
<function-signature>java.lang.String translateVariables(java.lang.String, com.opensymphony.xwork2.util.ValueStack)</function-signature>
</function>
<function>
<name>getActionContext</name>
<function-class>com.opensymphony.xwork2.ActionContext</function-class>
<function-signature>com.opensymphony.xwork2.ActionContext getContext()</function-signature>
</function>
</taglib>
struts.xmlにOGNL式を記述する
struts.xmlでは以下にOGNL式が記述できる
- result
- constant
- interceptor
result
Resultのlocation(Resultタグのボディ部分)はparse属性を意図的にfalseに設定しなければ${...}または%{...}形式でOGNL式が記述できる。
<action name="hello" class="org.demo.actions.HelloAction">
<result name="success">/WEB-INF/content/%{h.page}.jsp</result>
</action>
これ以外にもActionChainResultのnamespace、actionNameや、HttpHeaderResultのheaders、errorなどもOGNL式が記述できる。
<action name="hello" class="org.demo.actions.HelloAction">
<result name="success" type="chain">
<param name="namespace">%{h.namespace}</param>
<param name="actionName">%{h.actionName}</param>
</result>
</action>
constant
struts.xmlのconstantにはほとんどOGNL式は記述できないが、1つだけ記述できるものを見つけた。
以下のようにstruts.ui.staticContentPathは「/」で開始すればそのあとに%{...}の形式でOGNL式が記述できる。
<constant name="struts.ui.staticContentPath" value="/%{h.val}"/>
この値はhtmlなどの静的コンテンツを返却するStaticContentLoaderクラスとformなどのUIタグで使用しているが、StaticContentLoaderではOGNL式として認識されないのでUIタグのバグでOGNL式として誤って解析されているかもしれない。
上記にOGNL式を記述するとJSPにheadタグなどを記載した際にインポートされるjavascriptやcssのパスの「/static」の部分を動的に変更できる。例では「/test/static」に変更している。正直あまり意味がない。
<script type="text/javascript" src="/test/static/utils.js"></script>
<script type="text/javascript" src='/test/static/domTT.js'></script>
<link rel="stylesheet" type="text/css" href="/test/static/domTT.css"/>
interceptor
以下のinterceptorのパラメータにOGNL式が指定できる。
AliasInterceptor
OGNL式を指定できるというかaliasesパラメータはMapを指定しなければいけないのでOGNL式を書く必要がある。MapのKeyとValueもOGNL式として評価される。
<action name="hello" class="org.demo.actions.HelloAction">
<interceptor-ref name="alias"/>
<param name="aliases">#{'name1':'alias1', 'h.val':'h.val2'}</param>
<result name="success">/WEB-INF/content/hello.jsp</result>
</action>
StaticParametersInterceptor
parseパラメータにtrueを設定した場合、paramのnameはそのままOGNL式と評価され、paramの値も${...}または%{...}の形式でOGNL式が記述できる。
<action name="hello" class="org.demo.actions.HelloAction">
<interceptor-ref name="staticParams">
<param name="parse">true</param>
</interceptor-ref>
<param name="h.val">${h.val2}</param>
<result name="success">/WEB-INF/content/hello.jsp</result>
</action>