最近在用 Struts 2 练习着做一个 Toy Project 。为了使试图层代码保持整洁,需要在 JSP 页面中使用自定义标签。自定义 JSP 标签的方法在这篇教程 中已经说得很清楚了,但是由于这样自定义的标签只支持 EL 表达式而不支持 OGNL,所以在使用了 Struts 2 的项目中发挥不出太多用处,特别是当需要将 Action 中的值传给标签作为其属性时。当然,有一些办法 使得可以用 JSTL 访问到 Action 的值,但为了保持编码的统一风格,不在页面上同时出现两种表达式语言,我还是决定使自定义标签支持 OGNL 。
使自定义 JSP 标签支持 OGNL 的方法也很简单,只需要做三件事情就可以办到:
创建一个 Component 类,继承自 org.apache.struts2.components.Component ,这个类封装了标签的逻辑;
创建一个 Tag 类,继承自 org.apache.struts2.views.jsp.ComponentTagSupport ,这个类提供 JSP 支持并且向 Component 类传递参数;
创建一个 tld 文件,放在 Web 项目的 WEB-INF 或其子目录下,定义了自定义标签的格式。
自定义 Component 类
也许你会产生疑问,为什么 Struts 2 要把标签的逻辑分离出来,封装在一个 Component 类中而不直接写在 Tag 类中呢?这是因为 Struts 2 支持多种试图,例如 JSP,Freemarker,Velocity。不同视图的标签的自定义方法是不同的,为了提高代码的复用性,Struts 2 把标签的逻辑分离了出来。
以万能的 HelloWorld 为例子,写一个简单的 Component:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com . psjay . tag . component ;
import java.io.IOException ;
import java.io.Writer ;
import org.apache.struts2.components.Component ;
import com.opensymphony.xwork2.util.ValueStack ;
public class HelloWorldComponent extends Component {
private String name ;
public HelloWorldComponent ( ValueStack stack ) {
super ( stack );
}
@Override
public boolean start ( Writer writer ) {
try {
writer . write ( name );
} catch ( IOException e ) {
e . printStackTrace ();
}
return true ;
}
@Override
public boolean end ( Writer writer , String body ) {
try {
writer . write ( "tag end" );
} catch ( IOException e ) {
e . printStackTrace ();
}
return super . end ( writer , body );
}
// setter and getter
public String getName () {
return name ;
}
public void setName ( String name ) {
this . name = name ;
}
}
这个类的关键就是 start 和 end 方法。这两个方法分别在标签的开始和结束处被调用,而 start 的返回值决定了标签体是否输出。这个类有一个 name 属性,它对应着从页面上通过标签属性传回来的值,当然,要按照国际管理给这个属性加上 setter 和 getter。请注意这个类的构造函数,它有一个 ValueStack 参数,说明了在这个类中其实是可以访问到 Action 的 ValueStack 的。调用 getStack() 方法就返回 Action 的 ValueStack,然后你就可以通过 ValueStack 的 findValue 方法来提供 ONGL 支持了。
自定义 Tag 类
完成了逻辑部分的 Component ,就需要开始创建 Tag 类了。接着上面的例子,创建一个 HelloWorldTag:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com . psjay . tag ;
import javax.servlet.http.HttpServletRequest ;
import javax.servlet.http.HttpServletResponse ;
import org.apache.struts2.components.Component ;
import org.apache.struts2.views.jsp.ComponentTagSupport ;
import com.psjay.tag.component.HelloWorldComponent ;
import com.opensymphony.xwork2.util.ValueStack ;
public class HelloWorldTag extends ComponentTagSupport {
private static final long serialVersionUID = 7767836903901043944L ;
private String name ;
@Override
public Component getBean ( ValueStack stack , HttpServletRequest req ,
HttpServletResponse res ) {
return new HelloWorldComponent ( stack );
}
@Override
protected void populateParams () {
HelloWorldComponent component = ( HelloWorldComponent ) getComponent ();
component . setName ( name );
}
public String getName () {
return name ;
}
public void setName ( String name ) {
this . name = name ;
}
}
同样的,需要为这个类声明相应的属性和对应的 setter 和 getter。然后重写 getBean 方法,返回一个新创建的 HelloWorldComponent。populateParams 方法就是为 component 传递参数的方法了,这个例子只是简单的将 Tag 的 name 属性直接赋值给了 Component 的 name 属性。
定义 tld
tld 描述了标签的语法,具体的格式本文不赘述,本文开头部分链接到的教程里已经有了详细的说明,这里只给出代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<taglib version= "2.0" 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 web-jsptaglibrary_2_0.xsd" >
<tlib-version> 1.0</tlib-version>
<short-name> mytaglib</short-name>
<uri> /mytaglib</uri>
<tag>
<name> HelloWorld</name>
<tag-class> com.psjay.tag.HelloWorldTag</tag-class>
<body-content> JSP</body-content>
<attribute>
<name> name</name>
<required> true</required>
<rtexprvalue> false</rtexprvalue>
</attribute>
</tag>
</taglib>
将这个 tld 文件放在 Web 目录下的 WEB-INF 或其子目录下即可。
使用自定义的标签
到这里,就已经完成了定义标签的所有工作,使用方法也很简单:
1
2
3
4
< %@taglib prefix="my" uri="/mytaglib" %>
<my:HelloWorld name= "psjay" >
this is the tag body
</my:HelloWorld>
使用浏览器打开这个 JSP 页面,页面就会输出
psjay this is the tag body tag end