PSJay Blog

#FIXME, seriously

在 Struts 2 中自定义支持 OGNL 的标签

| Comments

最近在用 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

Comments