(从csdn的blog上同步过来)
(本文发于java emag第一期)
关于
Template
和
JSP
的起源还要追述到
Web
开发的远古年代,那个时候的人们用
CGI
来开发
web
应用,在一个
CGI
程序中写
HTML
标签。
在这之后世界开始朝不同的方向发展:
sun
公司提供了类似于
CGI
的
servlet
解决方案,但是无论是
CGI
还是
servlet
都面对同一个问题:在程序里写
html
标签,无论如何都不是一个明智的解决方案。于是
sun
公司
于1999年推出了JSP技术。
而在另一个世界里,以
PHP
和
ASP
为代表的
scriptlet
页面脚本技术开始广泛应用。
不过即便如此,问题并没有结束,新的问题出现了:业务和
HTML
标签的混合,这个问题不仅导致页面结构的混乱,同时也使代码本身难以维护。
于是来自起源于
70
年代后期的
MVC
模式被引入开发。
MVC
的三个角色:
Model
——包含除
UI
的数据和行为的所有数据和行为。
View
是表示
UI
中模型的显示。任何信息的变化都由
MVC
中的第三个成员来处理——控制器。
在之后的应用中,出现了技术的第一次飞跃:前端的显示逻辑和后端的业务逻辑分离,
COM
组件或
EJB
或
CORBA
用于处理业务逻辑,
ASP
、
JSP
以及
PHP
被用于前端的显示。这个就是
Web
开发的
Model 1
阶段(页面控制器模式)。
不过这个开发模式有很多问题:
1.
页面中必须写入
Scriptlet
调用组件以获得所必需的数据。
2.
处理显示逻辑上
Scriptlet
代码和
HTML
代码混合交错。
3.
调试困难。
JSP
被编译成
servlet
,页面上的调试信息不足以定位错误。
这一切都是因为在
Model 1
中并没有分离视图和控制器。完全分离视图和控制器就成了必须。这就是
Model 2
。它把
Model 1
中未解决的问题——分离对组件(业务逻辑)的调用工作,把这部分工作移植到了控制器。现在似乎完美了,不过等等,原来的控制器从页面中分离后,页面所需的数据怎么获得,谁来处理页面显示逻辑?两个办法:
1.
继续利用
asp
,
php
或者
jsp
等机制,不过由于它们是运行在
web
环境下的,他们所要显示的数据(后端逻辑产生的结果)就需要通过控制器放入
request
流中;
2.
使用新手法——模板技术,使用独立的模板技术由于脱离的了
web
环境,会给开发测试带来相当的便利。至于页面所需数据传入一个
POJO
就行而不是
request
对象。
模板技术最先开始于
PHP
的世界,出现了
PHPLIB Template
和FastTemplate这两位英雄。不久模板技术就被引入到java web开发世界里。目前比较流行的模板技术有:XSTL,Velocity,
JDynamiTe
,Tapestry等。另外因为JSP技术毕竟是目前标准,相当的系统还是利用JSP来完成页面显示逻辑部分,在Sun公司的JSTL外,各个第三方组织也纷纷推出了自己的Taglib,一个代表是struts tablib。
模板技术从本质上来讲,它是一个占位符动态替换技术。一个完整的模板技术需要四个元素:
0.
模板语言,
1.
包含模板语言的模板文件,
2.
拥有动态数据的数据对象,
3.
模板引擎。以下就具体讨论这四个元素。(在讨论过程中,我只列举了几个不同特点技术,其它技术或有雷同就不重复了)
模板语言包括:变量标识和表达式语句。根据表达式的控制力不同,可以分为强控制力模板语言和弱控制力模板语言。而根据模板语言与
HTML
的兼容性不同,又可以分为兼容性模板语言和非兼容性模板语言。
模板语言要处理三个要点:
1.
标量标记。把变量标识插入
html
的方法很多。其中一种是使用类似
html
的标签;另一种是使用特殊标识,如
Velocity
或者
JDynamiTe
;第三种是扩展
html
标签,如
tapestry
。采用何种方式有着很多考虑,一个比较常见的考虑是“所见即所得”的要求。
2.
条件控制。这是一个很棘手的问题。一个简单的例子是某物流陪送系统中,物品数低于一定值的要高亮显示。不过对于一个具体复杂显示逻辑的情况,条件控制似乎不可避免。当你把类似于
<IF condition=”$count <
=
40”><then><span class=”highlight”>count </span></then></IF>
引入,就象我们当初在
ASP
和
PHP
中所做得一样,我们将不得不再一次面对
scriptlet
嵌入网页所遇到的问题。我相信你和我一样并不认为这是一个好得的编写方式。实际上并非所有的模板技术都使用条件控制,很多已有的应用如
PHP
上中的以及我曾见过一个基于
ASP.NET
的应用,当然还有
Java
的
JDynamiTe
。这样网页上没有任何逻辑,不过这样做的代价是把高亮显示的选择控制移交给编程代码。你必需做个选择。也许你也象我一样既不想在网页中使用条件控制,也不想在代码中写
html
标记,但是这个显示逻辑是无可逃避的(如果你不想被你的老板抄鱿鱼的话),一个可行的方法是用
CSS
,在编程代码中决定采用哪个
css
样式。特别是
CSS2
技术,其
selector
机制,可以根据
html
类型甚至是
element
的
attributes
来
apply
不同的样式。
3.
迭代(循环)。在网页上显示一个数据表单是一个很基本的要求,使用集合标签将不可避免,不过幸运的是,它通常很简单,而且够用。特别值得一提的是
PHP
的模板技术和
JDynamiTe
技术利用
html
的注释标签很简单的实现了它,又保持了“所见既所得”的特性。
下面是一些技术的比较:
Velocity
|
变量定义:用
$
标志
表达式语句:以
#
开始
强控制语言:变量赋值:
#set $this = "Velocity"
外部引用:
#include ( $1 )
条件控制:
#if …. #end
非兼容语言
|
JDynamiTe
变量定义:用
{}
包装
表达式语句:写在注释格式(
<!--
à
)中
弱控制语言
兼容语言
|
XSLT
变量定义:
xml
标签
表达式:
xsl
标签
强控制语言:外部引用:
import
,
include
条件控制:
if
,
choose…when…otherwise
非兼容语言
|
Tapestry
采用
component
的形式开发。
变量定义(组件定义):在
html
标签中加上
jwcid
表达式语句:
ognl
规范
兼容语言
|
模板文件指包含了模板语言的文本文件。
模板文件由于其模板语言的兼容性导致不同结果。与
HTML
兼容性的模板文件只是一个资源文件,其具有良好的复用性和维护性。例如
JDynamiTe
的模板文件不但可以在不同的项目中复用,甚至可以和
PHP
程序的模板文件互用。而如
velocity
的非兼容模板文件,由于其事实上是一个脚本程序,复用性和可维护性大大降低。
模板文件包含的是静态内容,那么其所需的动态数据就需要另外提供。根据提供数据方式的不同可以分为
3
种:
1.
Map
:利用
key/value
来定位。这个是最常见的技术。如
velocity
的
VelocityContext
就是包含了
map
对象。
Example.vm
:
Hello from $name in the $project project.
Example.java
:
VelocityContext context = new VelocityContext();
context.put("name", "Velocity");
context.put("project", "Jakarta");
|
2.
DOM
:直接操作
DOM
数据对象,如
XSLT
利用
XPath
技术。
3.
POJO
:直接利用反射取得
DTO
对象,利用
JavaBean
机制取得数据。如
Tapestry
。
模板引擎的工作分为三步:
1.
取得模板文件并确认其中的模板语言符合规范。
比如
velocity
,确定
#if
有对应得
#end
等。
Xml
+
xslt
的模型中,
xml
文件标签是否完整等。在完成这些工作后,模板引擎通常会把模板文件解析成一颗节点树(包含模板文件的静态内容节点和模板引擎所定义的特殊节点)。
2.
取得数据对象。
该数据对象一般通过程序传递引用实现。现有的大量框架在程序底层完成,处理方式也各自不同,有两种技术分别为推技术和拉技术。推技术:
controller
调用
set
方法把动态数据注入,模板引擎通过
get
方法获得,典型代表:
Struts
;拉技术:模板引擎根据配置信息,找到与
view
对应的
model
,调用
model
的
get
方法取得数据,典型代表:
Tapestry
。
3.
合并模板文件(静态内容)和数据对象(动态内容),并生成最终页面。
合并的机制一般如下,模板引擎遍历这颗节点树的每一个节点,并
render
该节点,遇到静态内容节点按正常输入,遇到特殊节点就从数据对象中去得对应值,并执行其表达式语句(如果有的话)。
以下详细说明:
Velocity
|
Template template = Velocity.getTemplate("test.wm");
Context context = new VelocityContext();
context.put("foo", "bar");
context.put("customer", new Customer());
template.merge(context, writer);
当调用
Velocity.getTemplate
方法时,将调用
ResourceManger
的对应方法。
ResourceManger
先查看该模板文件是否在
cache
中,如果没有就去获取,生成
resource
对象并调用
process()
方法,确定该模板是否有效,如果有效,则在内存中生成一个
Node
树。
当调用
template.merge()
时,遍历这颗
Node
树,并调用每个
Node
的
render
方法。对于模板中的变量和对象
Node
,还将调用
execute()
方法,从
context
中取得
value
。
注:
ResourceManger
在
runtime\resource
包下,
Node
在
runtime\parser\node
包下
|
Tapestry
|
Tapestry
比较麻烦,先介绍一下
http
请求的处理过程。
当
httprequest
请求到达时。该请求被
ApplicationServlet
捕获,随后
ApplicationServlet
通过
getEngine
取到对应的
Engine
,通过该
engine
的
getService
拿到对应的
service
,调用其
service
方法执行
http
请求。
每个
service
通过
RequestCycle
对象的
getPage
方法取得
Page
对象,并将其设置为该
Cycle
对象的
active Page
。之后
service
调用
renderResponse
方法执行输出。
renderResponse
调用
page
的
getResponseWriter(output)
取得
writer
对象,并把它传给
cycle.renderPage(writer)
方法,该方法调用
page
的
renderPage
方法。
Page
执行
renderPage
时,首先判断是否有
listener
的请求,如果有则处理
listener
请求;然后调用
BaseComponentTemplateLoader
的
process
方法把模板文件载入并形成一个
component
节点树,依次执行节点的
renderComponent
方法。
每个
component
对象将通过
ongl
的机制取得对象属性。并把该值写入输入流。
例如:
insert component
protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle) {
if (cycle.isRewinding())
return;
Object value = getValue();
if (value == null)
return;
String insert = null;
Format format = getFormat();
if (format == null) {
insert = value.toString();
}
else{
try{
insert = format.format(value);
}
catch (Exception ex) {
throw new ApplicationRuntimeException(
Tapestry.format("Insert.unable-to-format",value),this, getFormatBinding().getLocation(), ex);
}
}
String styleClass = getStyleClass();
if (styleClass != null) {
writer.begin("span");
writer.attribute("class", styleClass);
renderInformalParameters(writer, cycle);
}
if (getRaw())
writer.printRaw(insert);
else
writer.print(insert);
if (styleClass != null)
writer.end(); // <span>
}
getValue
为取得
insert
的
value
属性。
|
技术分析
技术:
JSP
,一个伪装后的
servlet
。
web server
会对任何一个
jsp
都生成一个对应
jsp
类,打开这个类,就会发现,
jsp
提供的是一个代码生成机制,把
jsp
文件中所有的
scriptlet
原封不动的
copy
的到生成的
jsp
类中,同时调用
println
把所有的
html
标签输出。
Test.jsp
:
<html>
<head><title>jsp test</title></head>
<body>
<table width="226" border="0" cellspacing="0" cellpadding="0">
<tr><td><font face="Arial" size="2" color="#000066">
<b class="headlinebold">The jsp test file</b>
</tr></td> </font>
</table>
<body>
</html>
|
Test_jsp.java:
package org.apache.jsp;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import org.apache.jasper.runtime.*;
public class Test _jsp extends HttpJspBase {
private static java.util.Vector _jspx_includes;
public java.util.List getIncludes() {
return _jspx_includes;
}
public void _jspService(HttpServletRequest request, HttpServletResponse response)
throws java.io.IOException, ServletException {
JspFactory _jspxFactory = null;
javax.servlet.jsp.PageContext pageContext = null;
HttpSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
Object page = this;
JspWriter _jspx_out = null;
try {
_jspxFactory = JspFactory.getDefaultFactory();
response.setContentType("text/html;charset=ISO-8859-1");
pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 8192, true);
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;
out.write("<html>\r\n");
out.write("<head><title>jsp test</title></head> \r\n");
out.write("<body>\r\n");
out.write("<table width=\"226\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\r\n ");
out.write("<tr><td><font face=\"Arial \" size=\"2\" color=\"#000066\"> \r\n\t ");
out.write("<b class=\"headlinebold\">The jsp test file");
out.write("</b>\r\n\t ");
out.write("</tr></td></font>\r\n\t ");
out.write("</table>\r\n");
out.write("<body>\r\n");
out.write("</html>");
} catch (Throwable t) {
out = _jspx_out;
if (out != null && out.getBufferSize() != 0)
out.clearBuffer();
if (pageContext != null) pageContext.handlePageException(t);
} finally {
if (_jspxFactory != null) _jspxFactory.releasePageContext(pageContext);
}
}
}
|
技术:
Taglib
作为
jsp
之上的辅助技术,其工作本质依托与
jsp
技术,也是自定义标签翻译成
java
代码,不过这次和
jsp
略有不同,它还要经过几个过程。
先来看一下,实现一个
tag
的
2
个要点:
1.
提供属性的set方法,此后这个属性就可以在jsp页面设置。以jstl标签为例 c:out value=""/,这个value就是jsp数据到tag之间的入口。所以tag里面必须有一个setValue方法,具体的属性可以不叫value。例如setValue(String data){this.data = data;}。这个“value”的名称是在tld里定义的。取什么名字都可以,只需tag里提供相应的set方法即可。
2.
处理 doStartTag 或 doEndTag 。这两个方法是 TagSupport提供的。还是以c:out value=""/为例,当jsp解析这个标签的时候,在“<”处触发 doStartTag 事件,在“>”时触发 doEndTag 事件。通常在 doStartTag 里进行逻辑操作,在 doEndTag 里控制输出。
在处理tag的时候:
0.
从tagPool中取得对应tag。
1.
为该tag设置页面上下文。
2.
为该tag设置其父tag,如果没有就为null。
3.
调用setter方法传入标签属性值tag,如果该标签没有属性,此步跳过。
4.
调用doStartTag方法,取的返回值。
5.
如果该标签有body,根据doStartTag返回值确定是否pop该标签内容。如果要pop其body,则:setBodyContent(),在之后,doInitBody()。如果该标签没有body,此步跳过。
6.
调用doEndTag()以确定是否跳过页面剩下部分。
7.
最后把tag类返还给tagPool。
tag
类为:
package my.customtags;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.TagSupport;
public class Hidden extends TagSupport{
String name;
public Hidden(){ name = ""; }
public void setName(String name){ this.name = name; }
public void release(){ value = null; }
public int doStartTag(){
return EVAL_BODY_INCLUDE
;}
public int doEndTag() throws JspTagException{
try{ pageContext.getOut().write(", you are welcome"); }
catch(IOException ex){ throw new JspTagException("Error!"); }
return EVAL_PAGE;
}
}
Jsp
页面:
<my:hidden name="testname"/>
生成的jsp代码:
my.customtags.Hidden _jspx_th_my_hidden_11 = (my.customtags.Hidden) _jspx_tagPool_my_hidden_name.get(my.customtags.Hidden.class);
_jspx_th_my_hidden_11.setPageContext(pageContext);
_jspx_th_my_hidden_11.setParent(null);
_jspx_th_my_hidden_11.setName("testname");
int _jspx_eval_my_hidden_11 = _jspx_th_my_hidden_11.doStartTag();
if (_jspx_th_my_hidden_11.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE)
return true;
_jspx_tagPool_my_hidden_name.reuse(_jspx_th_my_hidden_11);
return false;
|
Taglib
技术提供两个机制,Body和non-Body导致了taglib的出现了两个分支:Display Tag和Control Tag, 前者在java code中嵌入了html标签,相当与一个web component,而后者则是另一种模板脚本。
1.
技术学习难易度
模板技术。使用模板技术,第一点就是必须学习模板语言,尤其是强控制的模板语言。于是模板语言本身的友好性变的尤为重要。以下依据友好性,表现力以及复用性三点为主基点比较了一下几种模板技术。
Velocity
:
Turbine
项目(
http://jakarta.apache.org/Turbine
)采用了
velocity
技术。
1.
友好性不够。理由:
强控制类型,出现页面显示控制代码和
html
混合。与
Html
的不兼容,无法所见即所得。遇到大的
HTML
页面,从一个
“
#if
”找到对应的
“
#end
”也是很痛苦的一件事情。
2.
表现力强。理由:强控制语言。
3.
复用性弱。理由:模板脚本和页面代码混合。
|
XSLT
Cocoon
项目(
http://cocoon.apache.org/
)采用
XML + XSLT
的方法。
CSDN
社区也是采用此方案。
1.
内容和显示风格分离,这点
XSLT
做的最好。
2.
速度慢。理由:
XSLT
的使用
XPath
,由于是要解析
DOM
树,当
XML
文件大时,速度很慢。
3.
友好性不够。理由:由于没有
HTML
文件,根本看不到页面结构、显示风格和内容。
XSL
语法比较难以掌握,由于没有“所见即所得”编辑工具,学习成本高。
4.
表现力强。理由:强控制语言。
5.
复用性弱。理由:
xsl
标签和
html
标签混合。
|
JDynamiTe
1.
表现力中等。理由:弱控制语言。
2.
友好性强。理由:所见即所得的效果。在模板件中的
ignore block
在编辑条件下可展示页面效果,而在运行中不会被输出。
3.
复用性强。理由:利用
html
标签。
|
Tapestry
1.
友好性中等。理由:整个
Tapestry
页面文件都是
HTML
元素。但是由于
component
会重写
html
标签,其显示的样子是否正确,将不预测。
2.
表现力强。理由:强控制语言。
3.
复用性强。理由:扩展了
HTML
元素的定义。
|
在
JSP
中大量的使用
TagLib
,能够使得
JSP
的页面结构良好,更符合
XML
格式,而且能够重用一些页面元素。但
TagLib
的编译之后的代码庞大而杂乱。
TabLib
很不灵活,能完成的事情很有限。
TabLib
代码本身的可重用性受到
TagSupport
定义的限制,不是很好。
另外是,我不得不承认的一件事是,
TagLib
的编写本身不是一件愉快的事情,事实我个人很反对这种开发方式。
2.
技术使用难易度
模板技术:模板技术本身脱离了
Web
环境,可以在不启动
Web server
得情况下进行开发和测试,一旦出错详细的信息易于错误的定位。由于模板引擎的控制,页面中将只处理显示逻辑(尽管其可能很复杂)
JSP
技术:工作在
Web
环境下,开发测试一定要运行
web server
。此外,一些
TagLib
能够产生新的标签,页面的最终布局也必须在
web
环境下才可以确定。测试时出错信息不明确,特别是
TagLib
得存在,极不容易定位。由于其本质是程序,很容易在其中写入业务逻辑,甚至于数据库连接代码,造成解耦的不彻底。
3.
总结
模板技术更加专注于页面的显示逻辑,有效帮助开发人员分离视图和控制器。在学习,开发和测试都更加容易。
JSP
技术本身是一个早期的技术,本身并没有提出足够的方式来分离视图和控制器。相反,我认为其本身是鼓励开发人员不做解耦,因为在
JSP
代码中插入业务逻辑是如此的容易。