- 说明:阅读此文章需要有:strut2, ognl, freemarker, 自定义标签的基础。采用自然语言来描述struts2标签的底层实现过程。
- 视图层页面:
页面: movies-edit.jsp >>>>>>
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<%@taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<!-- 简单样式表 -->
<style type="text/css">
div{
background-color: #FFEBCD;
margin-left: 5px;
margin-bottom: 20px;
}
div table {
margin-left: 20px
}
div a {
margin-left: 20px
}
</style>
<title> 编辑 ID 为 <s:property value="id"/> 的影片 </title>
</head>
<body>
<div>
<s:form method="post" action="%{#request.contextPath}/movies/%{id}">
<!-- 添加 _method 请求参数,参数值为 put 用于模拟 PUT 操作 -->
<s:hidden name="_method" value="put" />
<table>
<!-- s:textfield标签是如何从值栈中获取到数据的?
此过程在struts2标签解析过程中进行:
struts.tag.altSyntax=true(是否开启表达式语法),数据类型转换器:XWorkConverter
Object value = ognlUtil.getValue(expr, context, root, asType);
expr="name",CompoundRootAccessor和Ognl从CompoundRoot栈中匹配到元素Movie的成员属性name取到值getName();
(2).如何把取到的数据,输出到浏览器?参见struts2标签底层实现_概要
-->
<s:textfield name="id" label="影片 ID" disabled="true"/>
<s:textfield name="name" label="片名"/>
<s:textfield name="price" label="价格" />
<tr>
<td colspan="2">
<s:submit value="修改"/>
</td>
</tr>
</table>
</s:form>
<a href="<%=request.getContextPath() %>/movies"> 返回首页 </a>
</div>
</body>
</html>
3.标签底层实现过程简述
>>>>>>>>>>> Struts2 标签(属于自定义标签范畴)底层实现原理或过程简述
前言:首先来说下自定义标签的两种方式(打开javaEE1.6文档或Tomcat服务器安装目录下的doc文档找到包:javax.servlet.jsp.tagext)
接口层级,标签顶层接口为JspTag,直接子接口有两个:SimpleTag(JSP2.0后出现的)和Tag(原来老式的)
1.javax.servlet.jsp.tagext.JspTag
1.1 javax.servlet.jsp.tagext.SimpleTag
1.2 javax.servlet.jsp.tagext.Tag
1.2.1 javax.servlet.jsp.tagext.IterationTag
1.2.2 javax.servlet.jsp.tagext.BodyTag
1.实现SimpleTag接口
这种方式感觉简便一些
2.实现Tag接口
这种方式感觉繁琐一些
说明:(1) struts-tags.tld这种标签文件定义自己去看,这里就不简述!
这里主要是讲struts2标签底层原理或流程是如何进行。
(2) 下面的例子属于第两种方式(实现Tag接口,这个例子中Struts2作了适当扩展)
环境:tomcat + eclipse + struts2(Ognl,Freemarker等) ,视图层文件:movies_edit.jsp
此外还有一个字符流分析文件:struts2标签底层实现过程_jspWriterImple字符流中的数据.txt
一、Tomcat中的jasper引擎将movies_edit.jsp文件解析成:movies_002dedit_jsp.java
准备工作>>>>>>
1.值栈中存储Movie数据(name="拈花为何不一笑"),读者自己完成(相当于一个helloword级别的action)。
public class Movie{
private String name;
public void setName(String name){this.name=name;}
public String getName(){return this.name;}
}
自定义标签实现流程讲解>>>>>
2.以movies_edit.jsp中如下标签进行演示,后面称此标签为"演示标签"
<s:textfield name="name" label="片名"/>
二、Tomcate会调用movies_002dedit_jsp.java(当然会生成字节码文件,在这里我们不需要关注)进行以下业务处理:
1.TextFieldTag textFieldTag = TagHandlerPool.get(org.apache.struts2.views.jsp.ui.TextFieldTag.class);
2.ComponentTagSupport.java >>>>>>
//此方法进行一些准备工作
textFieldTag.doStartTag();
3.TagSupport.java>>>>>>
//设置页面上下文和演示标签的父标签(Form)
TextFieldTag.setPageContext(_jspx_page_context);
TextFieldTag.setParent((javax.servlet.jsp.tagext.Tag) _jspx_th_s_005fform_005f0);
4.AbstractUITag.java>>>>>>
//给演示标签设置属性attribute(name,lable,disabled)值
TextFieldTag.setName("id");
TextFieldTag.setLabel("影片 ID");
TextFieldTag.setDisabled("true");
5.ComponentTagSupport.java >>>>>>
//主要业务处理(通过Ognl从值栈中取数据,参数填充,将参数parameters中的数据插值到freemarker模板中,JspWriterImpl字符流将处理过的freemarker模板数据输出到浏览器)
TextFieldTag.doEndTag();
5.1 UIBean.java>>>>>>
填充参数:evaluateParams();
addParameter("name", findString(name));
addParameter("label", findString(label));
addParameter("required", findValue(required, Boolean.class));
addParameter("onclick", findString(onclick));
addParameter("cssClass", findString(cssClass));
addParameter("cssStyle", findString(cssStyle));
addParameter("nameValue", findValue(expr, valueClazz)); //重点
......
5.2 调用Freemarker:将参数中的数据插值到freemarker模板中然后输出 >>>>>>
Freemarker准备工作 >>>>>>>>>>>>
找到Freemarker模板: /template/xhtml/text.ftl
(5.2.1) TemplateEngineManager:
//Struts2 IOC容器创建org.apache.struts2.components.template.FreemarkerTemplateEngine
TemplateEngine create();
(5.2.2) TemplateRenderingContext:
TemplateRenderingContext构造造器初始化
{
this.template = template;
this.writer = writer;
this.stack = stack;
this.parameters = params; //此参数params就是evaluateParams()方法填充的,查看5.1 具体填充了哪些数据至Component中的Map parameters
this.tag = tag; //UIBean tag = TextField; class TextField extends UIBean 而 class UIBean extends Component(拥有protected Map parameters;)
}
//初始化时几个重要参数说明,如下:
(1)writer:
jspWriterImpl
(2)parameters:
{templateDir=template, theme=xhtml, dynamicAttributes={}, name=name, label=片名, nameValue=拈花为何不一笑,
id=1_name, escapedId=1_name,
form={templateDir=template, theme=xhtml, dynamicAttributes={}, action=/struts2_restful/movies/1,
namespace=/struts2_restful/movies, id=1, method=post, tagNames=[_method, id, name]}
}
(3)tag:
TextField
Freemarker核心工作 >>>>>>>>>>>>
(5.2.3)FreemarkerTemplateEngine.renderTemplate(templateRenderingContext);
(i).创建config: freemarker.template.Configuration config = freemarkerManager.getConfiguration(servletContext);
(ii)获取模板: freemarker.template.Template template = config.getTemplate("/template/xhtml/text.ftl");
(iii)建立模板模型:freemarkerManager.buildTemplateModel(stack, action, servletContext, request, response, wrapper)
populateContext(model, stack, action, request, response);
Map<String,TagLibrary> tagLibraries: {s=org.apache.struts2.views.DefaultTagLibrary@1a325f0}
(iv)将Map数据插值到模板中并通过Writer字符流输出:template.process(model, writer);
参数说明:
model(ScopesHashModel)此对象包含以下属性:
A): map= {Parameters=freemarker.ext.servlet.HttpRequestParametersHashModel@abb9ce,
tag=org.apache.struts2.components.TextField@1730d54, //从此对象中获取parameters
Request=freemarker.ext.servlet.HttpRequestHashModel@674bf6, JspTaglibs=null,
struts=org.apache.struts2.util.StrutsUtil@17f3921,
response=org.apache.catalina.connector.ResponseFacade@969e08,
s=org.apache.struts2.views.freemarker.tags.StrutsModels@195b5ad,
themeProperties={parent=simple},
ognl=org.apache.struts2.views.jsp.ui.OgnlTool@182e8c2,
session=null,
Application=null,
request=org.apache.struts2.dispatcher.StrutsRequestWrapper@8f46a6, action=com.bs.action.MoviesController@4e3801,
base=/struts2_restful,
stack=com.opensymphony.xwork2.ognl.OgnlValueStack@dd9e27}
B):objectWrapper
org.apache.struts2.views.freemarker.StrutsBeanWrapper@69f31d
C):ServletContext
D):stack=OgnlValueStack
E):request
F):此过程中用到的Freemarker模板:
(f1) /template/xhtml/txt.ftl
<#include "/${parameters.templateDir}/${parameters.theme}/controlheader.ftl" />
<#include "/${parameters.templateDir}/simple/text.ftl" />
<#include "/${parameters.templateDir}/xhtml/controlfooter.ftl" />
(f2) controlheader.ftl
<#include "/${parameters.templateDir}/xhtml/controlheader-core.ftl" />
<td
<#if parameters.align??>
align="${parameters.align?html}"<#t/>
</#if>
(f3)/template/simple/text.ftl(这个是主要的模板,用于构建
<s:textfield name="name" label="片名"/> 对应的html标签,最后输出到浏览器)
构建成这样:================此为演示标签被解析后构建的样貌============================
<input type="text" name= name="${parameters.name?default("")?html}"<#rt/>
<#if parameters.nameValue??>
<!--ftl使用struts2标签<s:property value="xxx" /> -->
value="<@s.property value="parameters.nameValue"/>"<#rt/>
</#if>
<#if parameters.readonly?default(false)>
readonly="readonly"<#rt/>
</#if>
....
....
/> <!--input 标签结束-->
其中还有javascript event等...
===>重点:这个参数parameters(Map数组结构)是从哪来的?它里面放的是什么数据?
来源于:tag=org.apache.struts2.components.TextField@1730d54
分析:UIBean tag = textField;
类: class TextField extends UIBean{...} 而 类class UIBean extends Component{protected Map parameters; ...}
//从这些继承关系可以得出TextField拥有parameters属性。
//===>而此属性parameters数据是哪里来的?是UIBean(TextField)的 evaluateParams()方法填充的
//查看5.1 具体填充了哪些数据
//===>Freemarker插值过程简述,例如:${parameters.name}会进行以下取值过程
1).首先会在TextField(继承父辈Component的方法)中调用下面方法拿到parameters
public Map getParameters() {
return parameters;
}
2).然后调用:parameters.get("name")取值
//tips: 这个取值过程跟Ognl很相似...