用 Reflection 将 beann 属性提取为值 | <nobr>第 2 页(共3 页)</nobr> |
开发人员通常使用 Java Reflection 改进自定义标签代码。 在这一小节中,我们将用 Reflection 改写 MapEntryTag
,使它可以使用所有范围内的所有 bean 属性定义 map 中的项。 例如,假设有像这样的一个 bean:
public class Test { String test="Jenny"; public String getTest() { return test; } public void setTest(String string) { test = string; } }
改写后的标签可以将 bean 属性作为 map 中的项使用,像下面这样:
<jsp:usebean id="bean" class="trivera.tags.map.Test"></jsp:usebean> <map:mapdefine id="employee2"> <map:mapentry property="test" name="bean" id="firstName"></map:mapentry> <map:mapentry value="33" type="java.lang.Integer" id="age"></map:mapentry> <map:mapentry value="22.22" type="java.lang.Float" id="salary"></map:mapentry> <map:mapentry name="properties" id="properties"></map:mapentry> </map:mapdefine>
注意 firstName
项现在是用 bean 的 test 属性定义的。
为了做到这一点,我们需要在自定义标签中加入 Relection,像这样(参看代码中的注释以了解改变的过程):
public class MapEntryTag extends TagSupport {... /* All of these have corresponding getters and setter method */ String type = "java.lang.String"; //Holder for the type attribute String id; //Holder for the id attribute String value; //Holder for value attribute String name; //Holder for name attribute String property; //Holder for property attribute String scope; //Holder for scope attribute public int doEndTag() throws JspException { MapDefineTag mapDef = (MapDefineTag) this.getParent(); Object objectValue = null; /* Check to see if the value property is set, if it is then this is a simple entry */ if (value !=null){ if (type.equals("java.lang.String")) { objectValue = value; } else if (type.equals("java.lang.Integer")) { Integer intValue = Integer.valueOf(value); objectValue = intValue; } else if (type.equals("java.lang.Float")) { Float floatValue = Float.valueOf(value); objectValue = floatValue; } /* If it is not a simple entry, then use reflection to get the property from the bean */ }else { Object bean =null; if (scope == null){ bean = pageContext.findAttribute(name); }else if("page".equalsIgnoreCase(scope)){ bean = pageContext.getAttribute(name); }else if("request".equalsIgnoreCase(scope)){ bean = pageContext.getRequest().getAttribute(name); }else if("session".equalsIgnoreCase(scope)){ bean = pageContext.getSession().getAttribute(name); }else if("application".equalsIgnoreCase(scope)){ bean = pageContext.getServletContext().getAttribute(name); } /* If the property attribute is null, then just use the bean as the entry*/ if (property==null){ objectValue = bean; mapDef.getMap().put(id,bean); /* If the property attribute is set, then use reflection to read the property */ }else { try{ String propertyMethod = "get" + property.substring(0,1).toUpperCase() + property.substring(1, property.length()); Method prop = bean.getClass() .getMethod(propertyMethod,new Class[]{}); objectValue = prop.invoke(bean, new Object[]{}); }catch(Exception e){ throw new RuntimeException(e); } } } mapDef.getMap().put(id,objectValue); return EVAL_PAGE; }
看起来仅仅是为了实现一些很多标签都要有的功能就需要做大量的工作。幸运的是,有一个库使这种开发变得容易了。在下一小节分析这个库 —— Struts —— 是如何发挥作用的。
用 Struts 简化自定义标签开发 | <nobr>第 3 页(共3 页)</nobr> |
对 Struts 的深入讨论超出了本教程的范围(有关这个框架的更多信息请参阅 参考资料)。不过,如果熟悉这个框架,那么这些知识会对开发自定义标签有帮助。
也可以不使用 Reflection,而使用 Struts 的 RequestUtils
实现在上一小节看到的结果,如下所示:
public class MapEntryTag extends TagSupport {... private String type = "java.lang.String"; ... public int doEndTag() throws JspException { MapDefineTag mapDef = (MapDefineTag) this.getParent(); Object objectValue = null; if (value !=null){ if (type.equals("java.lang.String")) { objectValue = value; } else if (type.equals("java.lang.Integer")) { Integer intValue = Integer.valueOf(value); objectValue = intValue; } else if (type.equals("java.lang.Float")) { Float floatValue = Float.valueOf(value); objectValue = floatValue; } }else { /** THIS USED TO BE 30 LINES OF CODE */ objectValue = RequestUtils.lookup(pageContext, name, property, scope); } mapDef.getMap().put(id,objectValue); return EVAL_PAGE; }
可以看到,objectValue = RequestUtils.lookup(pageContext, name, property, scope);
这一行代替了使用 Reflection 的代码中的 30 行! Struts 自带了许多实用程序,使自定义标签的开发更容易了。
介绍 | <nobr>第 1 页(共3 页)</nobr> |
可以编写标签处理程序对其 正文内容 进行操作。记住,标签的正文内容是 JSP 页中出现在自定义标签的开始和结束标签之间的数据。操纵其正文的标签称为 正文标签。编写正文标签处理程序比简单标签处理程序要复杂。
注意: 记住,简单标签也可以有正文。惟一的不同是简单标签不能读取或者操纵其正文。
要编写正文标签处理程序,必须实现 BodyTag
接口。BodyTag
实现了 Tag
实现的所有方法 (详见 第 1 步:创建一个实现了 Tag 接口的标签处理程序 ),而且还实现了另外两个处理正文内容的方法:
void setBodyContent(BodyContent b) |
bodyContent 属性的 Setter 方法。 |
void doInitBody() |
准备对正文进行判断。每次调用标签时,在获得新的 BodyContent 并通过 setBodyContent() 对其进行设置之后调用一次。 如果没有请求正文内容就不调用,因为 doStartTag() 返回 EVAL_BODY_BUFFERED 。 |
就像 Tag
接口有 TagSupport 类一样,BodyTag
接口有 BodyTagSupport
类。 因此,正文标签处理程序只需要覆盖它们要使用的方法。BodyTagSupport
类继承了 TagSupport 并实现了 BodyTag
接口。这使得编写正文标签处理程序更容易了。BodyTagSupport
定义了 get
/setBodyContent()
和一个 protected bodyContent
实例变量。
BodyTagSupport
类重新定义了 doStartTag()
生存周期方法以返回 EVAL_BODY_BUFFERED
。 通过返回 EVAL_BODY_BUFFERED
,doStartTag()
请求创建一个新的缓冲区 —— 即一个 BodyContent
。
BodyContent
是一个包含运行时正文判断结果的缓冲区。 BodyContent
继承了 JspWriter
并作为标签正文的隐式 out
。因此,JSP 容器创建 BodyContent
的一个实例,并且在处理标签的正文内容时,它写入这个实例而不是根 JspWriter
中。 因此,在标签中使用隐式对象 out
时,实际上使用的是 BodyContent
对象而非 JspWriter
(JspWriter
是页的隐式 out
)。
可以从 BodyContent
得到判断后的正文,它是一个 String
。BodyContent
是在运行时由容器调用页的 pageContext
的 pushBody()
和 popBody()
方法创建的 (只有在 doStartTag()
返回 EVAL_BODY_BUFFERED
时才调用pushBody()
)。 因此,BodyContent
是在 JspWriter
和 BodyContent
的一个嵌套结构中的。 (外面的 out
可以是另一个 BodyContent
对象,因为 BodyContent
是一个 JspWriter
。) 通过 setBodyContent()
方法将 BodyContent
提供给正文标签处理程序。 向正文标签处理程序传递一个 BodyContent
实例 (通过 setBodyContent()
) 并可以决定如何处理它。 可以对它做进行一步处理、放弃它、将它发送给浏览器等。
已经介绍了足够的背景知识了,该分析代码了!在下面几小节中我们将分析一个简单正文标签的例子。
例子:map 标签 | <nobr>第 2 页(共3 页)</nobr> |
自定义标签的开发人员可以决定如何处理标签的正文。 例如,可以编写一个执行 SQL 语句的标签,标签的正文是要执行的 SQL 语句。
回到 hashmap 主题,我们将编写一个像下面这样解析字符串的标签:
{ firstName=Jennifer, lastName=Wirth, age=25 }
实际上,这个标签将读取这种字符串并将它转换为一个 Map
(java.util.Map
)。我们将调用新标签 map
。下面是使用新标签的例子:
<%@taglib uri="map" prefix="map"%> <map:map id="employee"> { firstName=Jennifer, lastName=Jones, age=25 } </map:map>
The employee map is <%=employee%>
上面的代码创建一个名为 employee
的 map
,它有三项:firstName
、lastName
和 age
。
实现标签处理程序
MapParseTag
是一个 map
标签的标签处理程序。它根据传递给正文的字符串定义一个 map。在 doAfterBody()
方法中,MapParseTag
用 body.getString()
将正文内容抓取为字符串。然后用 body.clearBody()
清除正文内容。
public class MapParseTag extends BodyTagSupport { private String id; private Map map; public int doStartTag() throws JspException { map=new FastTreeMap(); return EVAL_BODY_BUFFERED; } public int doAfterBody() throws JspException { /* Grab the body content */ BodyContent body = this.getBodyContent(); /* Get the body content as a String */ String content = body.getString(); /* Clear the body */ body.clearBody(); /* Parse the map */ int start = content.indexOf("{"); int end = content.lastIndexOf("}"); content = content.substring(start+1, end); /* Parse the entries in the map */ StringTokenizer token = new StringTokenizer(content,"=;, \t\r\n"); while(token.hasMoreTokens()){ String key = token.nextToken(); String value = token.nextToken(); map.put(key,value); } this.pageContext.setAttribute(id,map); return SKIP_BODY; }
多简单!我们已经讨论了对其正文进行处理的标签,现在要详细讨论实现了执行流程的标签了。