Tapestry1)概述:Tapestry 是一个全面web application 框架,是使用JAVA 写的。Tapestry 不是一个application server,Tapestry 是一个使用在application server中的框架。Tapestry 不是一个application,Tapestry 是一个用来创建web application 的框架。Tapestry 不是JSP 的一种使用方式,Tapestry 和JSP 只能够选择一种。Tapestry不是一个脚本环境,Tapestry使用一种组件对象模式(component objectmodel),这并不是一种简单的脚本,而是用于生成高动态性高互交性的web页面。Tapestry基于Java Servlet API version 2.2,兼容于JDK 1.2以上版本,Tapestry通过变换多样的组件模式,将一个web application分离为一个联合组件。每一个组件都拥有其特殊的责任用于显示web页面或者响应HTML请求。2)Tapestry工作原理Tapestry应用程序由几个页面组成,这些页面都是由独立的,可重复使用,可配置的组件组成。下面是用于描述Tapestry应用程序的基本术语:1,页面(Page):应用程序由一堆命名唯一的页面组成,每个页面有一个模板和若干组件;2,模板(Template):一个用于页面(或一个组件)的HTML模板。Tapestry中,一个模板包括基本的HTML markup,以及一些用于标记组件的特殊属性的标签。3,组件(Component):用于Tapestry页面的可重复使用的对象。当一个页面表现时,或者页面中的一个链接被触发时,组件产生相应的HTML代码。多个组件也可以用来构成一个新的组件。4,参数(Parameter):组件拥有一些参数,用于组件属性与页面属性之间的连接。组件通常读取自己的参数,但是一些组件(与HTML forms相关)能够更新自己的参数,并且更新与参数绑定的页面属性。3)Tapestry与MVCTapestry组件扮演着控制器Controller的角色,是模式层(Model)中pure-domain objects和包含有组件的HTML模板之间的媒介。大多数情况下,这种方式应用于页面(页面也是Tapestry组件),但是在某些情况中,一个组件拥有自己的模板,包含着更多的组件,并且支持与使用者的互交。页面通过配置一系列属性表达式(Property expressions)连接模式层和表现层。属性表达式使用另外一种开源框架OGNL(Object Graph NavigationLanguage)。OGNL的开源工程(project)独立于Tapestry,但是在Tapestry中起很重要的作用。OGNL主要的目的在于读取和更新对象的Java Bean属性。4)Tapestry classesTapestry框架由400多个类和接口组成,但是构建一个Tapestry应用程序仅需要少数几个类,接口和方法。
1,两个关键接口:IComponent和IPage这两个接口分别用于定义Tapestry组件和页面。所有的Tapestry代码都是继承于接口,而不是继承于实现。所以IComponect一向被用于传递参数或者返回值,而不是使用其实现AbstractComponent。AbstractComponent类是一个基础类,用来实现组件。AbstractComponect是一个抽象类,定义却不实现renderComponect()方法。它的子类实现这个方法,使用JAVA代码生成所有HTML。BaseComponect继承AbstractComponent,增加初始化逻辑以便定位和读取一个模板。所以大多数自定义组件继承BaseComponent。在Tapestry中,页面作为一个特殊的组件,Ipage继承Icomponent和BasePage继承BaseComponent。所以当要创建新页面的时候,继承BasePage。2,三个很有用的接口:IRequestCycle,IMarkupWriter,和IEngine大多数Tapestry页面和组件直接使用这三个接口的引用。IRequestCycle:一个request cycle储存着当前请求的信息。它跟踪有关的活动页面,用于响应response。在特殊事件中,它通常用来访问Servlet API 对象(HttpServletRequest, HttpSession, HttpServletResponse)。IMarkupWriter:一个markup复写器用来生成HTML输出,当一个页面收到一个响应(response)的时候。它的运作很像java.io.PrintWriter,但是它包含了其它有用的方法,以生成markup输出(包括XML-style元素和属性)。IEngine:这是引擎是一个重要对象,用于处理Tapestry应用程序挂起。最初,这个引擎用来维护服务器端的状态。但是它也可以作为一个处理Tapestry内部子系统的网关。5)关于morkup和domain object。1,mockup:page mockups是静态HTML页面,用于表现这些动态页面在应用程序运行时的样子。也就是指在HTML模板中,将会在应用程序运行时被Tapestry组件替换掉的那部分旧HTML代码。Tapestry组件是动态的,当对HTML模板做美工时,markup的存在将会提供很大的方便。这样,Tapestry程序员可以完全与美工人员各负其责。2,domain object:应用程序的运行,最终取决于整个团队中JAVA部分的构架师和程序员。在大多数应用程序中,怎样连接用户接口和domain objects成为一个问题。Domain objects是中间层对象,是应用层,在整个应用程序中,它们是全局对象,将数据保存到数据库,或者实现你的特殊业务。通常,我们涉及到这些问题:这些对象中储存着什么信息,怎样将不同的对象关联在一起,以及它们怎样读取数据,或着将数据储存到数据库。
servlet作为控制器,收到请求。定位并更新domain objects,读取或更新数据库数据。控制器servlet选择一个表现层(JSP)表现响应。表现层绘制domain objects并最终将响应页面发送客户端。6)页面结构:在Tapestry应用程序中,一个页面(page)由一个HTML模块,一个页面规范(page specification),和一个JAVA页面类(page class)构成。每个Tapestry页面有一个特殊的唯一的名称。页面名称被用来定位页面规范和HTML模板。页面规范的一部分用来实例化JAVA类,这部分称为页面类(page class),包括指定应用程序中的一些特殊属性和方法。表现(rendering)页面的第一步是实例化页面。Tapestry框架读取页面规范和HTML模板并生成页面实例。一个Tapestry页面不是一个单一的对象。页面对象是树对象的根对象,这些对象包括页面模板中的组件,HTML模板中的内容,以及一些用来连接分散区域的对象。最简单的页面:1 一个HTML模板;2 一个页面规范;该规范使用XML,必须声明:<?xml version="1.0"?><!DOCTYPE page-specification PUBLIC"-//Apache Software Foundation//Tapestry Specification3.0//EN""http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd"><page-specification class="hangman1.Home"/>3 一个页面类;该类必须继承BasePage类。Public hangman1 extends BasePage {}只要HTML模板中使用Tapestry,就必须声明页面规范和页面类,即便页面规范和页面类都没有任何属性或方法,变量。7)关于属性标签:a) jwcid属性:(Java Web Component ID)在模板中用来指定组件。b) <span>标签:HTML<span>标签是一个用来包容text和elements的容器,其本身并不能显示任何内容,仅仅是作为一个stylesheet协助对页面显示的控制。c) @记号:用来标明一个隐式组件。8)监听方法(Listener method):监听方法是普通的实例方法,其签名为:public void method(IRequestCycle cycle)这个方法必须是public,return void,并且只有一个类型为IRequestCycle的参数。所有的页面和组件从AbstractComponent基础类中继承一个listeners参数。listeners参数包含一个嵌套参数,以便类中每一个监听方法得以执行。这里有一个接口:IActionListener,以及一些JAVA的反射机制,用来连接组件和页面的监听方法。一个类中可以有多个监听方法,每一个监听方法必须有一个不重复的名字,从父类继承的监听方法同样可以通过listeners参数使用。9)Visit对象Visit对象是一个应用程序空间,用来储存应用程序逻辑和数据。这个对象能被应用程序中所有的页面和组件访问,并且包含WEB应用程序中某一个客户端的特殊信息。一个单一Visit对象实例被应用程序中所有的页面共享。该对象类似HttpSession在典型servlet应用程序中扮演的角色。实际上,Visit对象最终作为一个HttpSession属性被储存。为了在应用程序中使用一些通用数据,Tapestry认可Visit对象。Tapestry并不知道也不关心Visit对象的类型。在框架中也没有定义特殊的Visit类,每一个应用程序自己定义Visit类。页面方法访问Visit对象时并不会指定具体的类型:public Object getVisit();注意强制转换类型:Visit visit = (Visit)getVisit();Visit对象是框架自动生成的,在第一次运行时被引用。你必须配置Tapestry提供实例化,一旦Visit对象生成,就将会持久化储存在HttpSession中。10) PageRenderListener接口这个接口用来通知页面实例,当页面第一次运行时,应该首先执行pageBeginRender()方法。这个方法的签名为:public void pageBeginRender(PageEvent event)如:11)属性指定机制(specified properties)属性指定就是由Tapestry自动生成典型的JavaBean属性。在代码中,你定义抽象方法用来读取和更新属性,你必须只定义需要使用的方法。Tapestry自己会生成一个子类来实现你的方法。你甚至不用声明变量,只需要在页面规范中指明类型即可。如:<page-specification class="examples.Login"><property-specification name="message"type="java.lang.String"/><property-specification name="userName"type="java.lang.String"/><property-specification name="password"type="java.lang.String"/></page-specification>Tapestry会自动创建一个子类来继承Login类,并实现以JavaBean方式实现变量。这样做有三个好处:第一:减轻程序员负担;第二:Tapestry可以确保自动重置属性,当请求失效过期的时候。第三:属性可以被定义为persistent。这种机制与EJB的container-managed persistence(CMP)一样。12) 组件的分类:按照组件的使用方式:a) 隐式组件:组件类型和其结构直接在HTML模板中申明的组件。通常,Tapestry已经定义好的组件都是以隐式组件的方式使用。b) 显示组件:其组件类型和结构储存在页面规范中。通常,自定义的组件都是以显示组件的方式使用。我个人倾向于将组件按照其工作方式分为三类:1)容器组件:指组件中可以包含其它组件的组件。目前接触到的容器组件有:Shell,Body,Conditional和Form四种。其中最简单的是:Shell和Body,它们仅仅是用在HTML模板的开头,用来声明<html>,<head>和<title>元素和CSS规范。最复杂的是Form容器组件,这个组件可以包容若干组件。而这些被包容的组件有根据它们原理上的不同,分为面向元素组件和面向任务组件两大类。而Conditional组件根据使用情况,分为两种,较Form简单。2)普通组件:指容器组件以外的Tapestry定义组件,可以独立于容器组件单独运行的组件。3)自定义组件:上面这种分类纯粹是我自己的理解,并不是绝对。实际上Tapestry的组件逻辑非常复杂,再加上OGNL表达式和属性指定机制。甚至使得写注释都变得很不容易。所以在阅读别人写的Tapestry代码的时候,难免有雾里看花,尤抱琵琶半遮面的感觉。我的看法是,假如有看不懂的代码,暂时死记硬背先。因为Tapestry是一个功能强大的框架,其组件的可重复使用(reusable)能力非常强。通常例子程序的页面类中的某一个方法,就已经能够解决与此方法相关的一系列问题。13) 普通组件:a) DirectLink组件:用来生成一个从应用程序中反馈回来的特殊类型。这个组件是Tapestry中两种主要互交产生方式之一,另外一种是user-submitted forms。DirectLink组件表现为一个HTML<a>元素,用来提供一个URL,当用户点击时,触发页面中一个特定的监听方法。如:<a href="#" jwcid="@DirectLink"listener="ognl:listeners.start"><img src="images/start.png" width="250"height="23"border="0" alt="Start"/></a>实际运行原理为:
组件容器通过调用RenderComponent()方法,将组件表现为Java代码。而renderBody()方法是DirectLink组件从基础类AbstractComponent中继承的。DirectLink组件通过renderComponent()方法来调用renderBody()方法,来表现组件内容(被DirectLink的<a>和</a>标签包围的静态<img>标签)。DirectLink组件有几个参数以及一个请求(listener),这个监听参数用来找到监听方法,并且一旦用户点击链接访问WEB浏览器,监听方法就会执行。b)Image组件Tapestry标准组件,用于插入<img>标签,通过image参数生成标签src的属性。标签alt用来显示图片名称。如:<IMG jwcid="@Image"alt="ognl:visit.game.incorrectGuessesLeft"image="ognl:digitImage"height="36"src="images/Chalkboard_3x8.png"width="36" border="0"/>这里需要介绍一下Asset:Asset被用访问静态文件如images和stylesheets。Image组件的image参数必须是asset object而不是String,并通过getAsset()方法作为一个object返回。如:public IAsset getDigitImage(){Visit visit = (Visit)getVisit();int guessesLeft =visit.getGame().getIncorrectGuessesLeft();return getAsset("digit" + guessesLeft);}Asset对象执行Iasset接口,getAsset()方法从AbstractComponent基础类中继承,能够访问在页面规范中标示为<context-asset>的元素。如:<context-asset name="digit0"path="images/Chalkboard_1x7.png"/><context-asset name="digit1"path="images/Chalkboard_1x8.png"/><context-asset name="digit2"path="images/Chalkboard_2x7.png"/><context-asset name="digit3"path="images/Chalkboard_2x8.png"/><context-asset name="digit4"path="images/Chalkboard_3x7.png"/><context-asset name="digit5"path="images/Chalkboard_3x8.png"/>使用Asset来定义页面规范:c) Foreach组件Foreach是一个循环组件,它遍历source参数,并在表现其内容前更新value参数。这是Tapestry组件参数的至关重要特性:将一个属性与一个组件参数绑定,组件不仅读取被绑定的属性,而且更新属性。Foreach组件使用<span>标签,当其表现(render)时,并不直接生成任何HTML代码。它仅仅是将内容(Text)和包含的组件重复表现。d)Insert组件这个组件很简单,使用起来很像JSP中的out.print()。只需要指定value参数即可。10)Form组件现在讨论在Tapestry里最能激动人心,也最能让人头晕的组件:Form。Form在HTML里与在Tapestry里有很大的联系,但是Tapestry里Form组件逻辑和运行方式远比在HTML中复杂。对于HTML中Form的运行原理,我就不多说了,仅仅列张表,以方便与Tapestry对比。
Tapestry引进一个全新的概念:task-oriented component面向任务组件。Tapestry将原始的Form称为element-oriented component面向元素组件。但是Tapestry为了衔接HTML,并没有完全抛弃面向元素组件。下面是HTML与Tapestry相互对应的列表:Form组件与一个监听方法绑定。Tapestry为Form中的每一个组件提供一个ID,如同原来的name参数,用来确定各种参数值。通过OGNL来读取和更新属性,所以程序员只需要关心OGNL。Form组件的内部运行结构为:
name=”service”,name=”sp”和name=”Form0”是Tapestry自动添加的。一旦form被submitted,Tapestry必须完成一些与非Tapestry应用程序一样的工作。? 确定被询问(query)参数的名字;? 提取被询问参数的值;? 完成所有类型转换(如:string转换为integer);? 将转换后的值赋给当前页面的属性;Tapestry使用一种不同的方式来连接被询问参数的名称和当前组件。当Form再次表现时,这些名称和关系会被重新获得,这种方式称为重新解析(rewiindphase)。当form的submission进行时,重新解析允许Form遍历其内部所有的组件。通过这种方式,在初始化表现时,form都将会以同一种顺序访问各个控制组件,并且获得与初始化完全相同的元素ID。重新解析仅仅发生在Form以及其body里面,而不是整个页面。如果将与form不相关的组件放在Form里面,如果在重新解析时它们产生HTML输出,将会被丢弃。重新解析时,组件将会从Form组件那里获得自己的元素ID,以便能够正确地表现。每个组件也可以决定,是否让重新解析发生。通过元素ID,组件能够从引入的请求中提取当前被询问参数,并将值赋给页面property。1,面向元素组件:
每一个面向元素组件都拥有相似的目的和使用方法。这些组件用来编辑属性。当一个Form组件被表现,它读取property,并用这个值构造HTML元素和属性以回复响应。当Form被submitted,相同的组件读取被询问参数的值并用这个值更新相同的property。a) TextField组件
这个组件很简单,唯一值得注意的就是TextField组件将HTML中的Text和Password两个元素合二为一,通过参数hidden来控制,hidden参数默认为false,当声明它为true时,如同HTML中的Password元素。b) Checkbox组件当Form被submitted的时候,一个参数会被询问以检查check box是否被checked。Tapestry Checkbox组件用来编辑一个boolean页面属性。这个属性被绑定于组件的selected参数。当组件被表现的时候,这个属性会被读取。如果其值为true,那么组件会包括一个被标定为selected的<input>元素。如:<input type="checkbox" jwcid="@Checkbox"selected="ognl:accepted"> I accept the terms andconditions.c) Radio and RadioGroup组件这两个组件一起工作,RadioGroup为每一个Redio提供一个唯一的ID,即Redio的value参数。RadioGroup跟踪当前被选中的属性值,并且只有一个Redio能够被选中。需要注意的是,value参数可以是任意类型:一个integer,一个string甚至一个对象。当Form被submitted的时候,RadioGroup和Radio组件返回一个被selected的ID。Radio组件将被selected的value参数传给RadioGroup组件,RadioGroup组件更新绑定与selected参数绑定的页面属性。如:
d) Select and Option组件在HTML中,<select>和<option>是一起使用的,以实现下拉菜单和多选。每一个option有一个label用于在菜单中显示内容(string),一个value用于传递值(当submited时),还有一个selected参数用于标明是否被选中。Selectand Option组件运作与Radio and RadioGroup组件相似,如:
e) Submit and ImageSubmit组件Submit组件是一个标准的submit按钮,而ImageSubmit组件是一个可点击的image。如:<input type="submit" jwcid="@Submit"listener="ognl:listeners.moveUp"label="Move Up"/><input type="submit" jwcid="@Submit"listener="ognl:listeners.moveDown"label="Move Down"/>监听方法用于处理submitted后的工作,而label用于按钮的显示。关于失效链接(stale link):当form的动态部分用来表现持久化属性的时候,web浏览器和服务器有可能不同步,就造成了一个固有的问题。当form被submitted的时候,Tapestry会发觉引入的form与储存在服务器中的状态不匹配,这就是失效连接。当Tapestry发觉失效连接,就会抛出StaleLinkException。在TapestryInAction的ToDo例子里面,当我们执行以下操作的时候,就会造成失效连接:1, 启动应用程序;2, 来到ToDo页面;3, 点击Add Item按钮;4, 按下浏览器的后退按钮;5, 点击Update按钮。对于失效连接,是Tapestry特有的没有办法避免的问题。所以我们在处理这种情况的时候要非常小心。至于为什么产生失效连接,这与Form组件运行机制有关,通常发生在Form组件使用DirectLink组件循环的时候。Tapestry为Form及包含在其中的所有组件自动分配一个唯一的元素ID。当submitted的时候,Tapestry将form里面动态的数据输入到服务器中的持久化属性。然后,点击浏览器上的后退按钮,浏览器回到被submitted之前的状态,如果这个时候,再次发生submitted,Tapestry会发现,form里面的动态数据和服务器中的持久化属性不匹配,因为服务器中的持久化属性已经被改动。在以前的servlet应用程序里面不会发生这种情况,因为在后退之后再submitted,HttpSession保存的状态是后退之前放在缓存中的状态,所以不会造成失效连接。而Tapestry是动态加载组件,并且为每个组件指定唯一的不可重复的ID。为了解决失效连接问题,Tapestry提供了另外一个组件listEdit来取代Form+DirectLink组合来完成循环遍利功能。2, 面向任务组件a) PropertySelection组件为了容易便捷地创建下拉菜单,对应于select-option组件,PropertySelection功能更加强大。<select jwcid="priority@PropertySelection"value="ognl:item.priority"model="ognl:priorityModel"/>参数model用来指定PropertySelection的具体运作,所以有两种方式完成model参数,第一种方式:构造一个类实现IpropertySelectionModel接口,为model参数构造一个类。需要注意以下几个必须的方法:? getOptionCount()返回option的数量。? getLabel(int index)返回显示在下拉菜单中的string。? getOption(int index)返回的对象作为参数value的值,需要注意的是,这里的返回类型为Object,也就是说,不仅仅可以是String或原始类型,也可以是任意的Object,或者是Enum(Tapestry自己构造的一种Integer类型,并且认为使用这种类型比使用Integer更好。其典型用法为:)。? getValue(int index)返回选项option值。? Object translateValue(String value)将option值转换为属性值。第二种方式:声明属于IpropertySelectionModel接口的方法。1) 这种方式一旦构造,就是永恒的。构造只发生在第一次需要时,并储存数据以便响应以后的请求。2) 这种构造通过适当地局部使用ResourceBundle。3) 从页面返回现场属性(locale property),现场应该是可见的。在这段代码中,还使用了Tapestry的框架类EnumPropertySelectionModel,实现接口IpropertySelectionModel,并连接Enums。第一参数是一组Enum值,用于下拉菜单中显示的内容,第二个参数是一个ResourceBundle包含我们将要使用的局部化labels,ResourceBundle的keys是Enum实例的名字。(ResourceBundle是JDK提供的一个工具类。一个ResourceBundle是一个包含string keys和value的容器,用以读取文件中对应的数据。)b) Hidden组件:没有示例,看了半天不知所谓,不晓得怎么用。通过组件规范说明大概结构为:1,value参数,object类型,可in或out,必须被声明。当HTML响应请求时,读取value属性,当submitted时,写出value值。2,id参数,object类型,只能in,可不必声明。3,listener方法。只能in,可不必声明。4,encode参数,boolean型,只能in,可不必声明,默认为true。当为true时,自动将value类型object转换为string。c) ListEdit组件:作为From+DirectLink组合的替代品,可以避免失效连接。它的运行原理是,将ListEdit组件中包容的所有组件的ID作为Map集合保存起来,同时实现循环遍历。因此在循环的过程中,它实际上保存了每次循环的状态,因此不论浏览器怎样后退,ListEdit都能够找到该页面与服务器持久化属性对应的状态,从而防止不匹配现象发生。ListEdit组件的使用方法类似DirectLink。ListEdit必须通过ListEditMap容器使用,在使用前,需要初始化该容器。例如:
1,pageBeginRender()方法用来初始化页面;2,每一个item被添加到ListEditMap,但是要确保key/value同时被保存。3,ListEdit组件在设置ListEditMap的key property之后调用监听方法,通过map的getValue()方法返回与先前储存在pageBeginRender()方法中相应的对象。如果当前key没有被储存进map,那么返回null。--------这样做,可以使用户在点击浏览器后退按钮之后再点击submit,不会发生失效连接。4, ListEdit组件储存了很多form中的item Ids,页面类必须使用这些ID来确定item属性以便实例化ToDoItem4。所有这些是通过ListEditMap的监听方法synchronizieItem()实现的。d) Upload组件和DatePicker组件Upload组件用来上传文件,DatePicker组件用来显示时间。我对这两个组件的看法是,它们的使用方法单一,而且很少被使用,可以直接照搬例子。11)Conditional组件当一个页面中,任意组件被执行,却没有跳转到其它页面时,Conditional组件就会评估condition参数(一个OGNL表达式),要么执行其body,要么跳过。如:有一点需要注意的时,condition参数通过OGNL返回的未必是一个boolean值。这与我们平常习惯的判断方式不同。例如在上面这个例子里,只要message非空或非null,就会马上执行Insert组件输出message。所以condition参数通过OGNL返回的甚至可能是一个任意类型的对象。当然,按照JAVA的观点,String属性也是对象。只是当返回对象时,Conditional组件的逻辑,显得更复杂。另外,Conditioal组件可以作为Validation的容器,显示错误信息。12)验证机制(validation)比如说,我们要求一个TextField输入的内容必须为String,或者要求password的输入必须高于6位等,这是就要用到验证机制。验证机制通过FieldLabel和ValidField两个组件并配搭Conditional组件。一个FieldLabel搭配一个ValidField。每一个FieldLabel通过FieldLable的field参数连接到ValidField。FieldLable可以调整自己以反映field参数。如果field中有错误,FieldLabel能够表现出来,另外FieldLabel获得field用户表现名称以便显示field(ValidField也有一个相同功能的参数displayName)。这样确保field label和用于错误信息的field匹配。Validator是负责将用户输入的信息由string转换为任意类型,并且执行验证检查。Validator本身并不是组件,它们实现Ivalidator接口。Validator被ValidField组件使用,实现转换和验证功能。每一个ValidField组件有一个validator参数,用来绑定validator对象。Validator对象是共享的,它们并不特别属于某一个ValidField。多个ValidField能够共享同一个validator实例。所有validator都包含一个boolean类型的required属性。当required属性被设为true时,不允许field的输入为空。Tapestry提供一系列的Ivalidator接口实现:StringValidator用来确认string输入,以及最少输入字符数目;NumberValidator用来确认numeric输入,以及最大和最小输入值;DateValidator用来确认Date输入。Validation的最后一个部分是(验证代理)validation delegate:一个对象,用来跟踪form 中field的错误信息,并且将错误信息分派给每一个field。1,使用验证代理。验证代理有两个功能,第一,它追踪一个form中的每一个错误ValidField。当form被点击的时候,每一个ValidField传递用户提供的string到对应field的validator对象。Validator对象可能会将string转换为一个object类,如:Long或者Double,或者仍然让它保持为String。Validator对象会提供任何验证,如:将转换后的值填充到一个指定队列。如果转换后的值没有通过验证,validator回报告错误并返回至ValidField。Validator报告这些错误是通过ValidationException。ValidField捕捉到异常,并使用验证代理来记录出错的ValidField元素ID,异常信息以及用户输入的错误信息。验证代理的第二个功能,是不连续能力。代理(delegate)的职责是装饰(decorating)field和label的出错信息。在表现额外的HTML之前,在表现FieldLabel之后,在表现ValidField之前或之后,验证代理都有可能触发,甚至ValidField的<input>元素写入额外的属性。逻辑关系如下:方法writeLabelPrefix()和writeLabelSuffix()允许验证代理将label装饰为另外的HTML。Validator的renderValidatorContribution()方法主要用来向客户端写入JavaScript脚本。验证代理的方法(主要是:writeAttributes())用来当field出现错误时,装饰<input>元素(被ValidField表现)。Validator对象和验证代理对象,是Form和ValidField组件必须的。它们通过组件的参数,将组件联系在一起(Form组件的delegate参数和ValidField组件的Validator参数)。这些对象必须在使用前实例化和构造,可以通过在页面类使用JAVA代码中或者在页面规范中使用:helper beans来实现。下面是两种最简单的构造实例化:这里的helper bean等同于上面的那个Java代码段。这个<bean>元素指定了三件事:? 你想要实例化的JAVA类。? 构造helper bean的所有properties.? Bean的生命周期(lifecycle)默认情况下,一个Bean的生命周期是request,也就是一旦的request2,错误显示Conditioal组件可以作为Validation的容器,显示错误信息。代理(delegate)的hasErrors属性,默认状态下为false,但是当验证代理发现页面中存在错误的时候,会将这个属性设为true。如:其中,监听方法总是询问代理的hasErrors属性。如果该属性为false,则代表输入验证为安全,并进行下一步。如果为true为真,则运行Conditional组件,显示错误信息。直接显示错误信息,会显得笨拙,界面不友好。所以Conditional与Delegator两个组件配合,以便输出格式化的错误信息。在上面这个例子中,验证代理同OGNL表达式被捕获,OGNL表达式beans.delegate是通过helperbean定义的引用。验证代理返回的是“错误对象”,而不仅仅是一个string,因此我们不能够简单地用Insert组件来显示错误信息。取而代之的是Delegator组件,这个组件调用render()方法来显示那个“错误对象”。实际上,这些“错误对象”是可表现对象,而不仅仅是一个简单的string。做一个全面的假设,那么“错误对象”能够表现HTML的所有类型,而不仅仅是images, JavaScript pop-up windows, links, formating-----任何HTML。为应用程序自定义validator能够提供自定义的“错误对象”。验证代理为了方便,包含一个firstError属性,作为一个表现对象,当第一个field发生错误的时候,被传递给Delegator,Delegator决定错误信息的显示。3, Form组件开始:与前面介绍的Form组件不同,这里它使用了另外一个参数delegate,这个参数用来连接Form和验证代理。在Form中的每一个FieldLabel和ValidField组件必须使用用一个验证代理。<form jwcid="@Form"listener="ognl:listeners.formSubmit"delegate="ognl:beans.delegate">OGNL表达式beans.delegate解释一个验证代理。所有这个Form中的组件都将共享这个代理。4, FieldLabel和ValidField的使用HTML模板中每个ValidField需FieldLabel包含,FieldFabel通过参数field将数据传送给ValidField。<span jwcid="@FieldLabel"field="ognl:components.inputFirstName">FirstName</span>OGNL表达式components.inputFirstName是一个引用指向页面的inputFirstName组件。每个页面提供一个只读Map包含所有的组件。这个Map的keys就是组件的ID。使用OGNL,你可以很容易地访问Map中的value。当然,建立一个引用,你必须知道组件的ID,这就是为什么ValidField组件使用显示ID的原因。一旦开始表现(render),FieldLabel抛弃它的body(First Name),并且从ValidField中获得正确的field名字作为label显示。这也许很麻烦,但是却非常有用。第一,使得显示的label与用来显示错误信息的field的名字匹配。第二,如果ValidField局部化名字,Fieldlabel仍然能够使用locale-specificvalue来匹配。在这个例子中,每个ValidField都作为显示组件来构造,而不是隐式组件。每个ValidField出现在HTML模板中,但是类型以及大多数参数在页面规范中声明。这些ValidField不是匿名的,而是拥有真实ID。<input type="text" jwcid="inputLastName" size="50"/>bsf-2.3.0.jarcommons-beanutils-1.6.1.jarcommons-codec-1.2.jarcommons-collections-2.1.jarcommons-digester-1.5.jarcommons-fileupload-1.0.jarcommons-lang-1.0.jarcommons-logging-1.0.2.jarjakarta-oro-2.0.6.jarjavassist-2.5.1.jarognl-2.6.7.jartapestry-3.0.3.jartapestry-contrib-3.0.3.jarxercesImpl.jarxmlParserAPIs.jarHome.javapackagemo.org.cpttm.album;importorg.apache.tapestry.*;importorg.apache.tapestry.html.*;publicabstractclassHomeextendsBasePage{publicStringgetImageUrl(intimageId){returngetEngine().getService("image").getLink(getRequestCycle(),null,newObject[]{newInteger(imageId)}).getURL();}publicabstractStringgetImageId();publicvoidonOk(IRequestCyclecycle){cycle.activate("Upload");}}ImageDB.javapackagemo.org.cpttm.album;importjava.io.*;importjavax.servlet.*;publicclassImageDB{publicstaticbyte[]loadImage(intimageId,ServletContextcontext){FileimageFile=newFile(context.getRealPath("/WEB-INF/imagedb"),imageId+".jpg");try{FileInputStreaminput=newFileInputStream(imageFile);try{byte[]imageData=newbyte[(int)imageFile.length()];input.read(imageData);returnimageData;}finally{input.close();}}catch(IOExceptione){thrownewRuntimeException(e);}}publicstaticvoidsaveImage(intimageId,byte[]imageData,ServletContextcontext){FileimageFile=newFile(context.getRealPath("/WEB-INF/imagedb"),imageId+".jpg");try{FileOutputStreamoutput=newFileOutputStream(imageFile);try{output.write(imageData);}finally{output.close();}}catch(IOExceptione){thrownewRuntimeException(e);}}}ImageService.javapackagemo.org.cpttm.album;importjava.io.*;importjavax.servlet.*;importjavax.servlet.http.*;importorg.apache.tapestry.*;importorg.apache.tapestry.engine.*;importorg.apache.tapestry.request.*;publicclassImageServiceextendsAbstractService{publicStringgetName(){return"image";}publicvoidservice(IEngineServiceViewengine,IRequestCyclecycle,ResponseOutputStreamoutput)throwsServletException,IOException{intimageId= ((Integer)getParameters(cycle)[0]).intValue();byteimageData[]=ImageDB.loadImage(imageId,cycle.getRequestContext().getServlet().getServletContext());HttpServletResponseresponse=cycle.getRequestContext().getResponse();response.setContentType("image/jpeg");response.setHeader("Content-disposition","attachment;filename=foo.jpg");response.setContentLength(imageData.length);try{OutputStreamout=response.getOutputStream();out.write(imageData);}catch(IOExceptione){thrownewApplicationRuntimeException(e);}}publicILinkgetLink(IRequestCyclecycle,IComponentcomponent,Object[]args){returnconstructLink(cycle,getName(),null,args,false);}}Upload.javapackagemo.org.cpttm.album;importjava.io.*;importorg.apache.tapestry.*;importorg.apache.tapestry.html.*;importorg.apache.tapestry.request.*;publicabstractclassUploadextendsBasePage{publicabstractIUploadFilegetFile();publicvoidonOk(IRequestCyclecycle){if(getFile().getFileName().length()==0){return;}byteimageData[]=newbyte[(int)getFile().getSize()];InputStreamfileInput=getFile().getStream();try{fileInput.read(imageData);}catch(IOExceptione){thrownewRuntimeException(e);}ImageDB.saveImage(101,imageData,cycle.getRequestContext().getServlet().getServletContext());cycle.activate("Home");}}Album.application<?xmlversion="1.0"encoding="UTF-8"?><!DOCTYPEapplicationPUBLIC"-//ApacheSoftwareFoundation//TapestrySpecification3.0//EN""http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd"><!--generatedbySpindle,http://spindle.sourceforge.net--><applicationname="Album"engine-class="org.apache.tapestry.engine.BaseEngine"><descriptiong>>addadescription</description><pagename="Home"specification-path="Home.page"/><servicename="image"class="mo.org.cpttm.album.ImageService"/></application>Home.html<html><ahref=""jwcid="download">Downloadphoto#101</a><imgsrc="/Album/app?service=image&sp=101"/><formjwcid="form"><inputtype="Submit"value="OK"/></form></html>Home.page<?xmlversion="1.0"encoding="UTF-8"?><!DOCTYPEpage-specificationPUBLIC"-//ApacheSoftwareFoundation//TapestrySpecification3.0//EN""http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd"><!--generatedbySpindle,http://spindle.sourceforge.net--><page-specificationclass="mo.org.cpttm.album.Home"><property-specificationname="imageId"type="java.lang.String"/><componentid="download"type="ServiceLink"><static-bindingname="service"value="image"/><bindingname="parameters"expression="101"/></component><componentid="form"type="Form"><bindingname="listener"expression="listeners.onOk"/></component></page-specification>Upload.html<html><formjwcid="uploadForm"><inputtype="File"jwcid="upload"/><p><inputtype="Submit"value="OK"/></form></html>Upload.page<property-specificationname="file"type=<?xmlversion="1.0"encoding="UTF-8"?><!DOCTYPEpage-specificationPUBLIC"-//ApacheSoftwareFoundation//TapestrySpecification3.0//EN""http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd"><!--generatedbySpindle,http://spindle.sourceforge.net--><page-specificationclass="mo.org.cpttm.album.Upload">"org.apache.tapestry.request.IUploadFile"class=tag>/><componentid="uploadForm"type="Form"><bindingname="listener"expression="listeners.onOk"/></component><componentid="upload"type="Upload"><bindingname="file"expression="file"/></component></page-specification>web.xml<?xmlversion="1.0"?><web-appxmlns="http://java.sun.com/xml/ns/j2ee"xmlns:xsi="http://www.w3.org/TR/xmlschema-1/"xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"version="2.4"><display-name>Album</display-name><servlet><servlet-name>Album</servlet-name><servlet-class>org.apache.tapestry.ApplicationServlet</servlet-class><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>Album</servlet-name><url-pattern>/app</url-pattern></servlet-mapping></web-app>
转载于:https://www.cnblogs.com/halfisland/p/4871754.html
相关资源:垃圾分类数据集及代码