Custom Action
(史帝芬, idealist@gcn.net.tw, 2002/11/11, JSP 1.1, JDK 1.4.0)
1. Abstract
Custom action又稱為custom tag,JSP制定這項規格的用意在於提供一個可使java code和html儘可能分開的機制,避免在網頁中因為混雜了java code和html,而使網頁難以撰寫和維護。
Custom action其實只是一種特殊的bean,這個特殊的bean有個正式的名稱,稱為tag handler class,如果要實作tag handler class一定要繼承TagSupport或BodyTagSupport這兩個類別。繼承這兩個類別的tag的差別在於繼承TagSupport的tag沒有body,繼承BodyTagSupport的tag有body。

圖1 TagSupport和BodyTagSupport的繼承圖
2. Simple custom action

圖2 繼承TagSupport的tag
現在我們寫一個最簡單的custom action,它只會根據傳入的參數傳回西元或民國的時間,這個custom action沒有body所以只要繼承TabSupport就好。使用這個custom action的JSP寫法如下…
<%@ page contentType="text/html; charset=BIG5" %>
<html>
<head>
<title>Now Tag Example</title>
</head>
<body>
<H1>Now Tag Example</H1>
<%@ taglib uri="/WEB-INF/tlds/now.tld" prefix="idealist" %>
現在時間: <idealist:now calendar="民國" />
</body>
</html>

圖3 Now Tag執行結果
在JSP中引入的now.tld是custom action的定義檔,透過這個定義檔JSP container可以順利的找到相關的custom action類別檔。定義檔除了定義類別檔外,也定義了版本編號、prefix所用名稱(shortname)、tag名稱、還有attribute…因為這個custom action是繼承TagSupport,所以bodycontent設為empty。如下所示…
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib
PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"
"http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
<taglib>
<tlibversion>1.0</tlibversion>
<jspversion>1.1</jspversion>
<shortname>idealist</shortname>
<info>
A tag library from Steven Shi,
http://www.idealist.idv.tw/
</info>
<tag>
<name>now</name>
<tagclass>tw.idv.idealist.ctl.Now</tagclass>
<bodycontent>empty</bodycontent>
<attribute>
<name>calendar</name>
<required>true</required>
</attribute>
</tag>
</taglib>
沒有body的tag類別繼承的是TagSupport,TagSupport提供的method雖然相當多,但是對程式員來說,比較重要的只有doStartTag和doEndTag。如圖2所示,tag一開始執行就會載入attribute的值,然後就會執行doStartTag,在這裡程式員可以做一些初始化的動作,當JSP container遇到結尾標籤時,會呼叫doEndTag,大多數tag handler都會改寫doEndTag來進行實際的工作。
doStartTag和doEndTag都要傳回值,傳回的值如下表所示…因為這個custom action沒有body,所以doStartTag一定是傳回SKIP_BODY,表示不用處理body。
|
Method |
傳回值 |
|
doStartTag |
SKIP_BODY EVAL_BODY_BUFFERED |
|
doEndTag |
EVAL_PAGE SKIP_PAGE |
以下為tag handler class程式碼…
package tw.idv.idealist.ctl;
import javax.servlet.jsp.tagext.TagSupport;
import javax.servlet.jsp.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.Calendar;
import java.text.DecimalFormat;
public class Now extends TagSupport {
private String m_strC = null;
public int doStartTag() throws JspException {
return SKIP_BODY;
}
public int doEndTag() throws JspException {
HttpServletResponse response = (HttpServletResponse) pageContext.getResponse();
DecimalFormat df2 = new DecimalFormat("00");
Calendar c = Calendar.getInstance();
int nYear = 0;
if (m_strC.equals("民國"))
nYear = 1911;
else
m_strC = "西元";
try {
JspWriter out = pageContext.getOut();
out.print(m_strC + (c.get(Calendar.YEAR)-nYear) + "/" + df2.format(c.get(Calendar.MONTH)+1) + "/" + df2.format(c.get(Calendar.DAY_OF_MONTH)) + " " + df2.format(c.get(Calendar.HOUR_OF_DAY)) + ":" + df2.format(c.get(Calendar.MINUTE)) + ":" + df2.format(c.get(Calendar.SECOND)));
}
catch (IOException e) {}
return EVAL_PAGE;
}
public void setCalendar(String strC) {
m_strC = strC;
}
}
3. Custom action with body

圖4 繼承BodyTagSupport的Tag
第二個範例我們將寫一個會將html特殊符號編碼的cuatom action,讓寫在body中的文字可以以html的原始碼輸出,因為這個custom action會用到body,所以要繼承BodyTagSupport,底下我們先看JSP怎麼寫,還有它的輸出…
<%@ page contentType="text/html; charset=BIG5" %>
<html>
<head>
<title>
encodeHTMLExample
</title>
</head>
<body>
<H1>encodeHTML Tag Example</H1>
<%@ taglib uri="/WEB-INF/tlds/encodeHTML.tld" prefix="idealist" %>
底下的文字是經過編碼的…
<idealist:encodeHTML>
<html>
<body>
這是測試…
<body>
</html>
</idealist:encodeHTML>
</body>
</html>

圖5 encodeHTML Tag執行結果
底下為設定檔,特別注意bodycontent不再是empty而是JSP,因為這個custom action的body是需要填東西的。
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib
PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"
"http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
<taglib>
<tlibversion>1.0</tlibversion>
<jspversion>1.1</jspversion>
<shortname>idealist</shortname>
<info>
A tag library from Steven Shi,
http://www.idealist.idv.tw/
</info>
<tag>
<name>encodeHTML</name>
<tagclass>tw.idv.idealist.ctl.encodeHTML</tagclass>
<bodycontent>JSP</bodycontent>
</tag>
</taglib>
繼承了BodyTagSupport的custom action提供了比較多的method,如圖4,比繼承TagSupport的cuatom action多了setBodyContent、doInitBody、doAfterBody,由名稱不難了解其功用都在於控制Body。底下為各method的傳回值,要特別注意的是,如果程式要處理body,則在doStartTag中要傳回EVAL_BODY_BUFFERED,還有doAfterBody因為只處理一次body,不重複處理,所以傳回SKIP_BODY。
|
Method |
傳回值 |
|
doStartTag |
SKIP_BODY EVAL_BODY_BUFFERED |
|
doAfterBody |
SKIP_BODY EVAL_BODY_AGAIN |
|
doEndTag |
EVAL_PAGE SKIP_PAGE |
以下為tag handler class程式碼…
package tw.idv.idealist.ctl;
import javax.servlet.jsp.tagext.*;
import javax.servlet.jsp.*;
import java.io.*;
public class encodeHTML extends BodyTagSupport {
public int doAfterBody() throws JspException {
BodyContent bc = getBodyContent();
JspWriter out = getPreviousOut();
try {
out.write(toHTMLString(bc.getString()));
} catch (IOException e) { }
return SKIP_BODY;
}
private String toHTMLString(String strT) {
String[] strSpecial = { "&", "<", ">", "\"" };
String[] strMark = { "&", "<", ">", """ };
StringBuffer bstrT = new StringBuffer(strT);
int nStart;
for(int i=0; i<strSpecial.length; i++) {
while ((nStart = bstrT.indexOf(strSpecial[i])) != -1) {
bstrT = bstrT.replace(nStart, nStart+1, strMark[i]);
}
}
return bstrT.toString();
}
}
4. Looping custom action
第三個範例我們將寫一個迴圈的custom action,迴圈的custom action和一般的custom action的差別只在於doAfterBody這個method傳回值不同,如果要繼續繞迴圈,就傳回EVAL_BODY_AGAIN,否則傳回SKIP_BODY。
底下為JSP…
<%@ page contentType="text/html; charset=BIG5" %>
<%@ taglib uri="/web-inf/tlds/loop.tld" prefix="idealist" %>
<html>
<head>
<title>
LoopExample
</title>
</head>
<body>
<idealist:loop reps="10">
Current value
</idealist:loop>
</body>
</html>

圖6 Loop Tag執行結果
底下為設定檔,需特別注意有個屬性reps…
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib
PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"
"http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
<taglib>
<tlibversion>1.0</tlibversion>
<jspversion>1.1</jspversion>
<shortname>idealist</shortname>
<info>
A tag library from Steven Shi,
http://www.idealist.idv.tw/
</info>
<tag>
<name>loop</name>
<tagclass>tw.idv.idealist.ctl.LoopTag</tagclass>
<bodycontent>JSP</bodycontent>
<attribute>
<name>reps</name>
<required>true</required>
</attribute>
</tag>
</taglib>
底下為t ag handler class程式碼…
package tw.idv.idealist.ctl;
import javax.servlet.jsp.tagext.*;
import javax.servlet.jsp.*;
import java.io.*;
public class LoopTag extends BodyTagSupport {
private int reps;
public void setReps(String repeats) {
try {
reps = Integer.parseInt(repeats);
}
catch(NumberFormatException e) {
reps = -1;
}
}
public int doAfterBody() {
if (reps-- >= 1) {
BodyContent body = getBodyContent();
try {
JspWriter out = body.getEnclosingWriter();
out.println(body.getString() + ": " + reps + "<br>");
body.clearBody();
} catch (IOException e) {
System.out.println("Error in LoopTag: " + e);
}
return EVAL_BODY_AGAIN;
}
else {
return SKIP_BODY;
}
}
}
5. Nested custom action
第四個範例我們將寫一個巢狀的custom action,巢狀的custom action和一般的custom action並沒有什麼差別,需注意的只有findAncestorWithClass這個method,這個method是內層的tag用來尋找外層的tag用的,它會由內而外找,直到找到為止,如果到了最外層仍然找不到,這個method將傳回null。
底下為JSP…
<%@ page contentType="text/html; charset=BIG5" %>
<html>
<head>
<title>If Tag Example</title>
</head>
<body>
<H1>If Tag Example</H1>
<%@ taglib uri="/WEB-INF/tlds/if.tld" prefix="idealist" %>
<idealist:if>
<idealist:condition>true</idealist:condition>
<idealist:then>show then</idealist:then>
<idealist:else>show else</idealist:else>
</idealist:if>
</body>
</html>

圖7 If tag執行結果
底下為設定檔…
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib
PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"
"http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
<taglib>
<tlibversion>1.0</tlibversion>
<jspversion>1.1</jspversion>
<shortname>idealist</shortname>
<info>
A tag library from Steven Shi,
http://www.idealist.idv.tw/
</info>
<tag>
<name>if</name>
<tagclass>tw.idv.idealist.ctl.IfTag</tagclass>
<bodycontent>JSP</bodycontent>
</tag>
<tag>
<name>condition</name>
<tagclass>tw.idv.idealist.ctl.IfConditionTag</tagclass>
<bodycontent>JSP</bodycontent>
</tag>
<tag>
<name>then</name>
<tagclass>tw.idv.idealist.ctl.IfThenTag</tagclass>
<bodycontent>JSP</bodycontent>
</tag>
<tag>
<name>else</name>
<tagclass>tw.idv.idealist.ctl.IfElseTag</tagclass>
<bodycontent>JSP</bodycontent>
</tag>
</taglib>
底下為t ag handler class程式碼…
package tw.idv.idealist.ctl;
import javax.servlet.jsp.tagext.*;
import javax.servlet.jsp.*;
import javax.servlet.*;
import java.io.*;
public class IfTag extends TagSupport {
private boolean condition;
private boolean hasCondition = false;
public void setCondition(boolean condition) {
this.condition = condition;
hasCondition = true;
}
public boolean getCondition() {
return condition;
}
public void setHasCondition(boolean flag) {
this.hasCondition = flag;
}
public boolean hasCondition() {
return hasCondition;
}
public int doStartTag() {
return EVAL_BODY_INCLUDE;
}
}
package tw.idv.idealist.ctl;
import javax.servlet.jsp.tagext.*;
import javax.servlet.jsp.*;
import javax.servlet.*;
import java.io.*;
public class IfConditionTag extends BodyTagSupport {
public int doStartTag() throws JspTagException {
IfTag parent = (IfTag) findAncestorWithClass(this, IfTag.class);
if (parent == null) {
throw new JspTagException("condition not inside if");
}
return (EVAL_BODY_BUFFERED);
}
public int doAfterBody() {
IfTag parent = (IfTag) findAncestorWithClass(this, IfTag.class);
String bodyString = getBodyContent().getString();
if (bodyString.trim().equals("true")) {
parent.setCondition(true);
}
else {
parent.setCondition(false);
}
return (SKIP_BODY);
}
}
package tw.idv.idealist.ctl;
import javax.servlet.jsp.tagext.*;
import javax.servlet.jsp.*;
import javax.servlet.*;
import java.io.*;
public class IfThenTag extends BodyTagSupport {
public int doStartTag() throws JspTagException {
IfTag parent = (IfTag) findAncestorWithClass(this, IfTag.class);
if (parent ==null) {
throw new JspTagException("then not inside if");
} else if (!parent.hasCondition()) {
String warning = "condition tag must come before then tag";
throw new JspTagException(warning);
}
return EVAL_BODY_BUFFERED;
}
public int doAfterBody() {
IfTag parent = (IfTag) findAncestorWithClass(this, IfTag.class);
if (parent.getCondition()) {
try {
BodyContent body = getBodyContent();
JspWriter out = body.getEnclosingWriter();
out.print(body.getString());
}
catch (IOException ioe) {
System.out.println("Error in IfThenTag: " + ioe);
}
}
return SKIP_BODY;
}
}
package tw.idv.idealist.ctl;
import javax.servlet.jsp.tagext.*;
import javax.servlet.jsp.tagext.BodyTag.*;
import javax.servlet.jsp.*;
import javax.servlet.*;
import java.io.*;
public class IfElseTag extends BodyTagSupport {
public int doStartTag() throws JspTagException {
IfTag parent = (IfTag) findAncestorWithClass(this, IfTag.class);
if (parent == null) {
throw new JspTagException("else not inside if");
} else if (!parent.hasCondition()) {
String warning = "condition tag must come before else tag";
throw new JspTagException(warning);
}
return EVAL_BODY_BUFFERED;
}
public int doAfterBody() {
IfTag parent = (IfTag) findAncestorWithClass(this, IfTag.class);
if (!parent.getCondition()) {
try {
BodyContent body = getBodyContent();
JspWriter out = body.getEnclosingWriter();
out.print(body.getString());
}
catch (IOException ioe) {
System.out.println("Error in IfElseTag: " + ioe);
}
}
return SKIP_BODY;
}
}