JSP标签在tomcat中的运行原理
在网上找了很多关于jsp标签的文章,但大多数都是只是告诉你怎么去使用,怎么去编写一个jsp标签。几乎没有可以从源头上告诉你怎么去理解原理,它在tomcat中又是怎么样的呢?所以我花一点时间整理一编关于jsp标签原理的文章,如有什么不周祥之处还望各位大侠谅解。
首先我们需要大致了解开发自定义标签所涉及到的接口与类的层次结构(其中SimpleTag接口与SimpleTagSupport类是JSP2.0中新引入的)。
(一) JSP自定义标签的定义步骤:
1 创建标记处理类
2 创建TLD文件
3 在jsp页面通
过指令引入标签库
1、处理标签的类必须扩展javax.servlet.jsp.TagSupport 或 BodyTagSupport。先来讨论TagSupport
2、TagSupport类的主要属性:
A.parent属性:代表嵌套了当前标签的上层标签的处理类;
B.pageContex属性:代表Web应用中的javax.servlet.jsp.PageContext对象;
3、JSP容器在调用doStartTag或者doEndTag方法前,会先调用setPageContext和setParent方法,设置pageContext和parent。因此在标签处理类中可以直接访问pageContext变量;
4、在TagSupport的构造方法中不能访问pageContext成员变量,因为此时JSP容器还没有调用setPageContext方法对pageContext进行初始化。
先写一个扩展javax.servlet.jsp.TagSupport的标签类MyTag :
package com.common.tag; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.tagext.TagSupport; public class MyTag extends TagSupport { int i=1; String first; String date; JspWriter jw; public int doStartTag() throws JspException { jw=super.pageContext.getOut(); try { jw.print("this is first field :"+getFirst() +"<br/>"); jw.print("this is first date :"+getDate() +"<br/>"); } catch (IOException e) { e.printStackTrace(); } return TagSupport.EVAL_BODY_INCLUDE; } public int doAfterBody() throws JspException { try { jw.println(i++ + " :我想知道这里doafterbody都做了些什么了呢<br/>"); } catch (IOException e) { e.printStackTrace(); } if(5==i)return TagSupport.SKIP_BODY; return TagSupport.EVAL_BODY_AGAIN; } public int doEndTag() throws JspException { try { jw.print("最后的结束标签了,doendtag"); } catch (IOException e) { e.printStackTrace(); } return TagSupport.EVAL_PAGE; } public String getFirst() { return first; } public void setFirst(String first) { this.first = first; } public String getDate() { SimpleDateFormat sdf=new SimpleDateFormat(date); return sdf.format(new Date()); } public void setDate(String date) { this.date = date; } }
我们以Tomcat 6.0.20服务器为例,来看看最简单的myTag.jsp是怎么运行的。
我们仅以myTag.jsp中的 <my:mytag/>标签的解析为例进行分析,看容器是怎样把这个自定义标签解析成HTML输出的。
<%@ taglib prefix="my" uri="/my-tags" %> <html> <head> <base href="<%=basePath%>"> <title>My JSP 'index.jsp' starting page</title> </head> <body> <br> <my:mytag date="yyyy-MM-dd HH-mm-ss" first=" {这是我第一个属性}"> 中间体执行吧。 </my:mytag> </body> </html>
先附上我执行的结果:
this is first field : {这是我第一个属性}
this is first date :2013-03-10 01-28-02
中间体执行吧。 1 :我想知道这里doafterbody都做了些什么了呢
中间体执行吧。 2 :我想知道这里doafterbody都做了些什么了呢
中间体执行吧。 3 :我想知道这里doafterbody都做了些什么了呢
中间体执行吧。 4 :我想知道这里doafterbody都做了些什么了呢
最后的结束标签了,doendtag
让我们来看看Tomcat都做了什么。转到Tomcat的\work\Standalone\localhost\_目录下,可以找到如下的myTag_jsp.java,这个文件就是Tomcat解析myTag.jsp时生成的源文件:
public final class myTag_jsp extends org.apache.jasper.runtime.HttpJspBase implements org.apache.jasper.runtime.JspSourceDependent { private static final JspFactory _jspxFactory = JspFactory.getDefaultFactory(); private static java.util.List _jspx_dependants; static { _jspx_dependants = new java.util.ArrayList(1); _jspx_dependants.add("/WEB-INF/myTag.tld"); } //为所有的定制标签定义处理器池类的引用 private org.apache.jasper.runtime.TagHandlerPool _005fjspx_005ftagPool_005fmy_005fmytag_0026_005ffirst_005fdate; private javax.el.ExpressionFactory _el_expressionfactory; private org.apache.AnnotationProcessor _jsp_annotationprocessor; .................. ............. //为处理器池类赋值 public void _jspInit() { _005fjspx_005ftagPool_005fmy_005fmytag_0026_005ffirst_005fdate = org.apache.jasper.runtime.TagHandlerPool.getTagHandlerPool(getServletConfig()); _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory(); _jsp_annotationprocessor = (org.apache.AnnotationProcessor) getServletConfig().getServletContext().getAttribute(org.apache.AnnotationProcessor.class.getName()); } public void _jspService(HttpServletRequest request, HttpServletResponse response) throws java.io.IOException, ServletException { PageContext pageContext = null; HttpSession session = null; ServletContext application = null; ServletConfig config = null; JspWriter out = null; Object page = this; JspWriter _jspx_out = null; PageContext _jspx_page_context = null; 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("\r\n"); out.write("\r\n"); String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; ................ ....................... out.write(" <body>\r\n"); out.write(" This is my JSP page. <br>\r\n"); out.write(" \r\n"); out.write(" "); if (_jspx_meth_my_005fmytag_005f0(_jspx_page_context)) //标签的处理,请往下看方法的实现。 return; out.write("\r\n"); out.write(" \r\n"); out.write(" \r\n"); out.write(" </body>\r\n"); out.write("</html>\r\n"); } catch (Throwable t) { if (!(t instanceof SkipPageException)){ out = _jspx_out; if (out != null && out.getBufferSize() != 0) try { out.clearBuffer(); } catch (java.io.IOException e) {} if (_jspx_page_context != null) _jspx_page_context.handlePageException(t); } } finally { _jspxFactory.releasePageContext(_jspx_page_context); } } //真正处理标签<my:myTag >方法 private boolean _jspx_meth_my_005fmytag_005f0(PageContext _jspx_page_context) throws Throwable { PageContext pageContext = _jspx_page_context; JspWriter out = _jspx_page_context.getOut(); // my:mytag com.common.tag.MyTag _jspx_th_my_005fmytag_005f0 = (com.common.tag.MyTag) _005fjspx_005ftagPool_005fmy_005fmytag_0026_005ffirst_005fdate.get(com.common.tag.MyTag.class); //JSP容器在调用doStartTag或者doEndTag方法前,会先调用setPageContext和setParent方法,设置pageContext和parent,而且也调用setFiled()方法设置标签的相关属性; _jspx_th_my_005fmytag_005f0.setPageContext(_jspx_page_context); _jspx_th_my_005fmytag_005f0.setParent(null); // /index.jsp(19,4) name = date type = java.lang.String reqTime = false required = false fragment = false deferredValue = false expectedTypeName = null deferredMethod = false methodSignature = null _jspx_th_my_005fmytag_005f0.setDate("yyyy-MM-dd HH-mm-ss"); // /index.jsp(19,4) name = first type = java.lang.String reqTime = false required = false fragment = false deferredValue = false expectedTypeName = null deferredMethod = false methodSignature = null _jspx_th_my_005fmytag_005f0.setFirst(" {这是我第一个属性}"); int _jspx_eval_my_005fmytag_005f0 = _jspx_th_my_005fmytag_005f0.doStartTag(); // JSP容器 处理 //到自 定义标签的起始标志,就会调用doStartTag()方法 if (_jspx_eval_my_005fmytag_005f0 != javax.servlet.jsp.tagext.Tag.SKIP_BODY) { //如果doStartTag()方 // 法返回值不是Tag.SKIP_BODY就执行标签体 do { out.write("\r\n"); out.write(" \r\n"); out.write(" 中间体执行吧。\r\n"); out.write(" \r\n"); out.write(" "); int evalDoAfterBody = _jspx_th_my_005fmytag_005f0.doAfterBody(); //看到这里你应该知道 //tomcat是怎么处理doAfterBody(); 了吧。 if (evalDoAfterBody != javax.servlet.jsp.tagext.BodyTag.EVAL_BODY_AGAIN) //如果doAfterBody()返 //回值是EVAL_BODY_AGAIN,则再次执行方法体和doAfterBody()方法, // 直到doAfterBody()返 //回值为SKIP_BODY才跳出循环 break; } while (true); } //do{}while(true)无论doAfterBody()返回值如何都会执行方法体一次 if (_jspx_th_my_005fmytag_005f0.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE) { _005fjspx_005ftagPool_005fmy_005fmytag_0026_005ffirst_005fdate.reuse(_jspx_th_my_005fmytag_005f0); return true; } _005fjspx_005ftagPool_005fmy_005fmytag_0026_005ffirst_005fdate.reuse(_jspx_th_my_005fmytag_005f0); return false; } }
生成的myTag_jsp.java继承于org.apache. jasper.runtime.HttpJspBase。研究这个文件为我们了解定制标签的运行机理提供了途径。
从上面可以看出,Tomcat在解析一个JSP页面时,首先为每一个定制标签定义并实例化了一个TagHandlerPool对象。页面的处理方法覆盖 父类的_ jspService()方法,_jspService方法首先初始化环境,为内置对象赋值。由于myTag.jsp页面整体由一 个<html:html/>标签包裹,Tomcat对每一个标签都产生一个私有方法加以实现。<html:html/>标签的处 理方法是_jspx_meth_html_html_0()。这个方法的命名规范大家也可以从这里看出,就是“_jspx_meth + 标签的前缀 + 标签名 + 该标签在JSP页面同类标签中出现的序号”。其它标签都被包含在该标签中,所以其它标签在_jspx_meth_html_html_0()方法中进行解 析。
在_jspx_meth_html_html_0()方法中,首先从_jspx_tagPool_html_html_locale池中得到一个 org.apache.struts.taglib.html.HtmlTag的实例,然后设置这个tag实例的页面上下文及上级标签,由于 html:html标签是页面的最顶层标签,所以它的parent是null。然后对该标签的内容进行解析。HTML代码直接输出,下面主要看 看<html:html></html:html>标签之间包含的<my:mybodyTag count="5" >标签的解析。
对<my:mytag>标签的解析类似于html:html,Tomcat也将其放入一个 单独的方法_jspx_meth_my_005fmytag_005f0()中进行。
在myTag.jsp中定义了一个<my:mytag>标签,并设置了一个属性: <my:mytag date="yyyy-MM-dd HH-mm-ss" first=" {这是我第一个属性}">。Tomcat在解析时,调用MyTag对象的date和first属性设置方法setDate()和setFirst(),将该属性置 入。然后调用MyTag的doStartTag()和doEndTag()方法,完成解析。如果doEndTag()方法的返回值为 javax.servlet.jsp.tagext.Tag. SKIP_PAGE,表明已经完成解析,返回true,Tomcat将立即停止剩余页面代码的执行,并返回。如果doEndTag()方法的返回值为TagSupport.EVAL_PAGE ,表明还没完成解析,返回false,继续解析这个标签后面的内容。并在最后把该MyTag的实例放回池 中。
标签类对象实例的池化
为了提高运行效率,Tomcat对所有的定制标签类进行了池化,池化工作由org.apache.jasper. runtime.TagHandlerPool类完成。TagHandlerPool类主要有两个方法,代码如下:
TagHandlerPool.java
public class TagHandlerPool {
private static final int MAX_POOL_SIZE = 5;
private Tag[] handlers;
public synchronized Tag get(Class handlerClass) throws JspException {……}
public synchronized void reuse(Tag handler) {……}
}
TagHandlerPool简单地实现了对标签类的池化,其中MAX_POOL_SIZE是池的初始大小,handlers是一个Tag的数组,存储标 签类的实例。get(Class handlerClass)得到一个指定标签类的实例,如果池中没有可用实例,则新实例化一个。reuse(Tag handler)把handler对象放回池中。
至此,我们对JSP在容器中的运行过程已经了然于胸 了。虽然每种JSP容器的解析结果会有差异,但其中的原理都雷同。对于编写JSP应用,我们并不需要干涉容器中的运行过程,但如果你对整个底层的运行机制 比较熟悉,就能对JSP/Servlet技术有更深的认识。
二 我们再来讨论标签类继承BodyTagSupport
我们仅以myBodyTag.jsp中的 <my:mybody/>标签的解析为例进行分析,看容器是怎样把这个自定义标签解析成HTML输出的。
<%@ taglib prefix="my" uri="/my-tags" %> <html> <head> <base href="<%=basePath%>"> <title>My JSP 'index.jsp' starting page</title> </head> <body> <br/> <my:mybodyTag count="5" > 中间体执行吧。<br/> </my:mybodyTag> </body> </html>
先附上执行后的结果:
---dostartTag---
---setBodyContent---
---doinitBody---
0 :---doAfterBody---
1 :---doAfterBody---
2 :---doAfterBody---
3 :---doAfterBody---
4 :---doAfterBody---
---doEndTag---
中间体执行吧。
中间体执行吧。
中间体执行吧。
中间体执行吧。
中间体执行吧。
中间体执行
让我们来看看Tomcat都做了什么。转到Tomcat的\work\Standalone\localhost\_目录下,可以找到如下的myBodyTag_jsp.java,这个文件就是Tomcat解析myBodyTag.jsp时生成的源文件:
public final class myBodyTag_jsp extends org.apache.jasper.runtime.HttpJspBase implements org.apache.jasper.runtime.JspSourceDependent { private static final JspFactory _jspxFactory = JspFactory.getDefaultFactory(); private static java.util.List _jspx_dependants; static { _jspx_dependants = new java.util.ArrayList(1); _jspx_dependants.add("/WEB-INF/myTag.tld"); //加载标签描述文件 } //为所有的定制标签定义处理器池类的引用 private org.apache.jasper.runtime.TagHandlerPool _005fjspx_005ftagPool_005fmy_005fmybodyTag_0026_005fcount; private javax.el.ExpressionFactory _el_expressionfactory; private org.apache.AnnotationProcessor _jsp_annotationprocessor; public Object getDependants() { return _jspx_dependants; } public void _jspInit() { _005fjspx_005ftagPool_005fmy_005fmybodyTag_0026_005fcount = org.apache.jasper.runtime.TagHandlerPool.getTagHandlerPool(getServletConfig());//给处理池类赋值 _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory(); _jsp_annotationprocessor = (org.apache.AnnotationProcessor) getServletConfig().getServletContext().getAttribute(org.apache.AnnotationProcessor.class.getName()); } public void _jspDestroy() { _005fjspx_005ftagPool_005fmy_005fmybodyTag_0026_005fcount.release(); } public void _jspService(HttpServletRequest request, HttpServletResponse response) throws java.io.IOException, ServletException { PageContext pageContext = null; HttpSession session = null; ServletContext application = null; ServletConfig config = null; JspWriter out = null; Object page = this; JspWriter _jspx_out = null; PageContext _jspx_page_context = null; 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("\r\n"); out.write("\r\n"); String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; out.write("\r\n"); out.write("\r\n"); out.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\r\n"); out.write("<html>\r\n"); out.write(" <head>\r\n"); out.write(" <base href=\""); out.print(basePath); out.write("\">\r\n"); out.write(" \r\n"); out.write(" <title>My JSP 'index.jsp' starting page</title>\r\n"); out.write(" </head>\r\n"); out.write(" \r\n"); out.write(" <body>\r\n"); out.write(" <br/> \r\n"); out.write(" "); if (_jspx_meth_my_005fmybodyTag_005f0(_jspx_page_context)) return; //标签被处理的方法 out.write("\r\n"); out.write(" \r\n"); out.write(" \r\n"); out.write(" </body>\r\n"); out.write("</html>\r\n"); } catch (Throwable t) { if (!(t instanceof SkipPageException)){ out = _jspx_out; if (out != null && out.getBufferSize() != 0) try { out.clearBuffer(); } catch (java.io.IOException e) {} if (_jspx_page_context != null) _jspx_page_context.handlePageException(t); } } finally { _jspxFactory.releasePageContext(_jspx_page_context); } } private boolean _jspx_meth_my_005fmybodyTag_005f0(PageContext _jspx_page_context) throws Throwable { PageContext pageContext = _jspx_page_context; JspWriter out = _jspx_page_context.getOut(); // my:mybodyTag com.common.tag.MyBodyTag _jspx_th_my_005fmybodyTag_005f0 = (com.common.tag.MyBodyTag) _005fjspx_005ftagPool_005fmy_005fmybodyTag_0026_005fcount.get(com.common.tag.MyBodyTag.class); _jspx_th_my_005fmybodyTag_005f0.setPageContext(_jspx_page_context); _jspx_th_my_005fmybodyTag_005f0.setParent(null); // /myBodyTag.jsp(18,4) name = count type = java.lang.String reqTime = false required = false fragment = false deferredValue = false expectedTypeName = null deferredMethod = false methodSignature = null _jspx_th_my_005fmybodyTag_005f0.setCount(5); int _jspx_eval_my_005fmybodyTag_005f0 = _jspx_th_my_005fmybodyTag_005f0.doStartTag(); //执行.doStartTag() if (_jspx_eval_my_005fmybodyTag_005f0 != javax.servlet.jsp.tagext.Tag.SKIP_BODY) { //继承BodyTagSupport处继承TagSupport不同之处就是以下标为棕色的代码了。 if (_jspx_eval_my_005fmybodyTag_005f0 != javax.servlet.jsp.tagext.Tag.EVAL_BODY_INCLUDE) { out = _jspx_page_context.pushBody(); //如果dostartTag()返回BodyTagSupport.EVAL_BODY_BUFFERED; 注意了此时的out 引用已经被改赋值为BodyContent对象了。它会把它会通过out.print()设置标签体的内容缓存起来, 直到super.bodyContent.writeOut(getPreviousOut())执行到再输出标签体的内容 _jspx_th_my_005fmybodyTag_005f0.setBodyContent((javax.servlet.jsp.tagext.BodyContent) out); //setBodyContent()方法用于设置标签体内容,如果在此之前要作一些初始化工作, //则在doInitBody()方法中完成 _jspx_th_my_005fmybodyTag_005f0.doInitBody(); } do { out.write("\r\n"); out.write(" \r\n"); out.write(" 中间体执行吧。<br/>\r\n"); out.write(" \r\n"); out.write(" "); int evalDoAfterBody = _jspx_th_my_005fmybodyTag_005f0.doAfterBody(); //这里在讲继承TagSupport处理类时是一样的,请看前面 if (evalDoAfterBody != javax.servlet.jsp.tagext.BodyTag.EVAL_BODY_AGAIN) break; } while (true); if (_jspx_eval_my_005fmybodyTag_005f0 != javax.servlet.jsp.tagext.Tag.EVAL_BODY_INCLUDE) { out = _jspx_page_context.popBody(); } } if (_jspx_th_my_005fmybodyTag_005f0.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE) { //这里执行了.doEndTag()方法//如果 此方法中没有super.bodyContent.writeOut(getPreviousOut());这一句时, //则标签体内容没有输出! _005fjspx_005ftagPool_005fmy_005fmybodyTag_0026_005fcount.reuse(_jspx_th_my_005fmybodyTag_005f0); return true; } _005fjspx_005ftagPool_005fmy_005fmybodyTag_0026_005fcount.reuse(_jspx_th_my_005fmybodyTag_005f0); return false; } }
最后总结一下BodyTagSupport执行流程:
他们执行顺序如下:
doStartTag()—>doInitBody()-->setBodyContent()-->doAfterBody()-->doEndTag()
doStartTag()方法可返回EVAL_BODY_INCLUDE或SKIP_BODY,还有EVAL_BODY_BUFFERED;
如果返回EVAL_BODY_INCLUDE则继续执行; 如果返回EVAL_BODY_BUFFERED; 注意了此时的out 引用已经被改赋值为BodyContent对象了。它会把它会通过out.print()设置标签体的内容缓存起来,
直到super.bodyContent.writeOut(getPreviousOut())执行到再输出标签体的内容
如果返回SKIP_BODY则接下来的setBodyContent(), doInitBody(),doAfterBody()三个方法不会被执行,
而直接执行doEndTag()方法。
setBodyContent()方法用于设置标签体内容,如果在此之前要作一些初始化工作,则在doInitBody()方法中完成。
标签体内容执行完后,会调用doAfterBody()方法,此方法可返回EVAL_BODY_TAG, SKIP_BODY,
EVAL_PAGE或SKIP_PAGE。
如果返回EVAL_BODY_TAG则会再次设置标签体内容,直到返回SKIP_BODY;
如果返回EVAL_PAGE则标签体执行完后会继续执行JSP页面中接下来的部分;
如果返回SKIP_PAGE,则JSP页面的后续内容将不再执行。
附上MyBodyTag类:
package com.common.tag; import java.io.IOException; import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.tagext.BodyContent; import javax.servlet.jsp.tagext.BodyTagSupport; public class MyBodyTag extends BodyTagSupport { int count; int iteraterCount; JspWriter jw; BodyContent bc; public int doStartTag() throws JspException { try { jw=super.pageContext.getOut(); jw.print("---dostartTag---<br/>"); } catch (IOException e) { e.printStackTrace(); } return BodyTagSupport.EVAL_BODY_BUFFERED; } public void doInitBody() throws JspException { try { jw.print("---doinitBody---<br/><br/>"); } catch (IOException e) { e.printStackTrace(); } } public void setBodyContent(BodyContent b) { try { jw.print("---setBodyContent---<br/>"); } catch (IOException e) { e.printStackTrace(); } super.setBodyContent(b); } public int doAfterBody() throws JspException { if(iteraterCount<count){ try { jw.print(iteraterCount++ +" :---doAfterBody---<br/><br/>"); } catch (IOException e) { e.printStackTrace(); } return BodyTagSupport.EVAL_BODY_AGAIN; }else { return BodyTagSupport.SKIP_BODY; } } public int doEndTag() throws JspException {//如果 此方法没有,则标签体内容没有输出! try { jw.print("---doEndTag---<br/>"); if(super.bodyContent!=null){ super.bodyContent.writeOut(getPreviousOut()); } } catch (IOException e) { e.printStackTrace(); } iteraterCount=0; return BodyTagSupport.EVAL_PAGE; } public int getCount() { return count; } public void setCount(int count) { this.count = count; } }
附上myTag.TLD:
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"> <description><![CDATA["To make it easier to access dynamic data; the Apache Struts framework includes a library of custom tags. The tags interact with the framework's validation and internationalization features; to ensure that input is correct and output is localized. The Struts Tags can be used with JSP FreeMarker or Velocity."]]></description> <display-name>"my Tags"</display-name> <tlib-version>1.0</tlib-version> <short-name>mytag</short-name> <uri>/my-tags</uri> <tag> <description><![CDATA[Execute an action from within a view]]></description> <name>mytag</name> <tag-class>com.common.tag.MyTag</tag-class> <body-content>JSP</body-content> <attribute> <description><![CDATA[Whether the result of this action (probably a view) should be executed/rendered]]></description> <name>first</name> <required>false</required> <rtexprvalue>false</rtexprvalue> </attribute> <attribute> <description><![CDATA[Whether the writer should be flush upon end of action component tag, default to true]]></description> <name>date</name> <required>false</required> <rtexprvalue>false</rtexprvalue> </attribute> </tag> <tag> <name>mybodyTag</name> <tag-class>com.common.tag.MyBodyTag</tag-class> <body-content>JSP</body-content> <attribute> <name>count</name> <required>false</required> <rtexprvalue>false</rtexprvalue> </attribute> </tag> </taglib>
推荐阅读