首页 > 编程 > JSP > 正文

Template和JSP技术

2024-09-05 00:19:16
字体:
来源:转载
供稿:网友

最大的网站源码资源下载站,

(本文发于java emag第一期)
一、起源与现状:
关于template和jsp的起源还要追述到的远古年代,那个时候的人们用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被用于前端的显示。这个就是的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 世界里。目前比较流行的模板技术有:xstl,velocity,jdynamite,tapestry等。另外因为jsp技术毕竟是目前标准,相当的系统还是利用jsp来完成页面显示逻辑部分,在sun公司的jstl外,各个第三方组织也纷纷推出了自己的taglib,一个代表是struts tablib。
二、 模板技术分析:
模板技术从本质上来讲,它是一个占位符动态替换技术。一个完整的模板技术需要四个元素:0. 模板语言,1. 包含模板语言的模板文件,2. 拥有动态数据的数据对象,3. 模板引擎。以下就具体讨论这四个元素。(在讨论过程中,我只列举了几个不同特点技术,其它技术或有雷同就不重复了)
1.       模板语言:
模板语言包括:变量标识和表达式语句。根据表达式的控制力不同,可以分为强控制力模板语言和弱控制力模板语言。而根据模板语言与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规范
兼容语言
 
2.       模板文件:
模板文件指包含了模板语言的文本文件。
模板文件由于其模板语言的兼容性导致不同结果。与html兼容性的模板文件只是一个资源文件,其具有良好的复用性和维护性。例如jdynamite的模板文件不但可以在不同的项目中复用,甚至可以和php程序的模板文件互用。而如velocity的非兼容模板文件,由于其事实上是一个脚本程序,复用性和可维护性大大降低。
3.       拥有动态数据的数据对象:
模板文件包含的是静态内容,那么其所需的动态数据就需要另外提供。根据提供数据方式的不同可以分为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。
4.       模板引擎:
模板引擎的工作分为三步:
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技术分析
       1. jsp技术:
       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);
    }
  }
}
 
       2. taglib技术:
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代码中插入业务逻辑是如此的容易。
上一篇:JSP与JavaBean详谈

下一篇:JSP问答

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表