Struts2标签原理分析

0x00 标签使用

1.需求

Struts2标签库提供了主题、模板支持,极大地简化了视图页面的编写。而且struts2的主题、模板都提供了很好的扩展性,实现了更好的代码复用。Struts2允许在页面中使用自定义组件,这完全能满足项目中页面显示复杂,多变的需求。
使用struts2的标签的jsp页面,需要头声明:<%@ taglib uri ="/struts-tags" prefix ="s" %>
用struts2开发的人员使用标签,会加速开发进程。

2.样例使用

首先,建立一个ok.jsp的文件,里面使用和标签实现一个链接的编写。
ok.jsp页面如下:

1
2
3
4
5
6
7
8
9
10
11
12
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="s" uri="/struts-tags" %>

<html>
<head><title>Simple jsp page</title></head>
<body>
ok~~!

<s:url var="url" action="hello" includeParams="all"/>
<s:a href="%{#url}" >Source</s:a>
</body>
</html>

<s:url>标签生成了一个变量名为url的链接字符串。然后使用<s:a>标签通过OGNL语句%{#url}获取该链接。
效果如下:

0x01 Struts2标签工作原理

1.配置

Struts2-core核心jar包里面定义了各个标签的属性以及处理类等内容。文件名为:META-INF/struts-tags.tld
在里面可以找到标签的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<tag>
<description><![CDATA[This tag is used to create a URL]]></description>
<name>url</name>
<tag-class>org.apache.struts2.views.jsp.URLTag</tag-class>
<body-content>JSP</body-content>
<attribute>
<description><![CDATA[The action to generate the URL for, if not using value]]></description>
<name>action</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<attribute>
<description><![CDATA[The anchor for this URL]]></description>
<name>anchor</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
以下省略
........
</tag>

其中包含了对标签的定义,<tag-class>org.apache.struts2.views.jsp.URLTag</tag-class>说明了标签的具体实现类为org.apache.struts2.views.jsp.URLTag
另外定义了标签的各个属性。比如action,anchor等~
现在来查看下org.apache.struts2.views.jsp.URLTag类,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class URLTag extends ContextBeanTag {

private static final long serialVersionUID = 1722460444125206226L;

protected String includeParams;
protected String scheme;
protected String value;
protected String action;
protected String namespace;
protected String method;
protected String encode;
protected String includeContext;
protected String escapeAmp;
protected String portletMode;
protected String windowState;
protected String portletUrlType;
protected String anchor;
protected String forceAddSchemeHostAndPort;

public Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res) {
return new URL(stack, req, res);
}

看到该类继承了ContextBeanTag类,里面含有许多公用的属性,而URLTag定义了自己特有的属性,action,value,escapeAmp等,与配置文件相呼应。这里用到了Struts2程序中的一种组件模式, public Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res)。这里返回一个URL类的组件。而该URL组件实际上执行了自身标签特有的工作。

2.工作流程

标签的实现原理,实际上是继承了http servlet中可扩展的BodyTagSupport类。然后会依次执行以下方法:

1
2
3
4
5
doStartTag()
setBodyContent()
doInitBody()
doAfterBody()
doEndTag()

根据上面分析,可以知道URL标签是依靠URLTag类实现的。我们找到了它继承的超类ComponentTagSupport组件,有doStartTag方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 public int doStartTag() throws JspException {
ValueStack stack = getStack();
component = getBean(stack, (HttpServletRequest) pageContext.getRequest(), (HttpServletResponse) pageContext.getResponse());
Container container = (Container) stack.getContext().get(ActionContext.CONTAINER);
container.inject(component);

populateParams();//对component组件赋值
boolean evalBody = component.start(pageContext.getOut());

if (evalBody) {
return component.usesBody() ? EVAL_BODY_BUFFERED : EVAL_BODY_INCLUDE;
} else {
return SKIP_BODY;
}
}

这里主要做了两件事:
(1)通过getBean()方法获取具体的组件Bean,这里是URL组件。然后将组件插入XWork容器中进行维护。
(2)对获取的URL组件中属性赋值。 紧接着执行component.start(pageContext.getOut());在这里看是执行URL组件的start方法。

1
2
3
4
5
public boolean start(Writer writer) {
boolean result = super.start(writer);
urlRenderer.beforeRenderUrl(urlProvider);
return result;
}

对于具体的组件,会有自己特定自定义执行的过程,URL组件是执行渲染URL前的准备工作。 最后执行doEndTag()方法:

1
2
3
4
5
public int doEndTag() throws JspException {
component.end(pageContext.getOut(), getBody());
component = null;
return EVAL_PAGE;
}

也是主要执行组件的自定义方法end(),在这个方法中,真正有对标签的渲染内容。

1
2
3
4
 public boolean end(Writer writer, String body) {
urlRenderer.renderUrl(writer, urlProvider);//实现对URL标签的渲染。
return super.end(writer, body);
}

关键流程就是以上说明的方式,接着就在doStartTag方法开始对标签流程进行单步调试。

0x02 工作流程调试

在StartTag()方法中下断点,可以清楚的看到取出URL组件并对其属性赋值的过程。
然后在准备工作做完之后,我们跳过中间过程,并且直接进入到URL的end()方法中实现渲染的过程。 urlRenderer.renderUrl(writer, urlProvider);
该方法就开始对<s:url var="url" action="hello" includeParams="all"/>这句标签语句进行url链接生成。具体实现可以追踪到urlComponent.determineActionURL方法:
然后进入DefaultUrlHelper类,使用buildUrl方法,生成链接。生成的URL方法如下: 生成了链接URL字符串后,将生成的result放入page上下文和request Servlet上下文中。 然后我们就顺利的将处理好的URL放入request中,然后供标签调用。同样进入到了doStartTag()流程。 最后进入到了Anchor组件的end()方法。并使用FreemarkerTemplateEngine渲染引擎的renderTemplate(),通过定义的模板(默认.ftl文件)页面的渲染操作。

文章作者: angelwhu
文章链接: https://www.angelwhu.com/paper/2015/07/24/principle-analysis-of-struts2-tag/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 angelwhu_blog