tapestry介绍


Tapestry 1)概述: Tapestry 是一个全面 web application 框架,是使用 JAVA 写的。 Tapestry 不是一个 application server,Tapestry 是一个使用在 application server 中的框架。 Tapestry 不是一个 application,Tapestry 是一个用来创建 web application 的框 架。 Tapestry 不是 JSP 的一种使用方式, Tapestry 和JSP 只能够选择一种。 Tapestry不是一个脚本环境 ,Tapestry使用一种组件对象模式 (component object model),这并不是一种简单的脚本,而是用于生成高动态性高互交性的 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与MVC Tapestry组件扮演着控制器 Controller的角色,是模式层( Model)中 pure-domain objects和包含有组件的 HTML模板之间的媒介。大多数情况下, 这种方式应用于页面 (页面也是 Tapestry组件 ),但是在某些情况中 ,一个组 件拥有自己的模板,包含着更多的组件,并且支持与使用者的互交。 页面通过配置一系列属性表达式 (Property expressions)连接模式层和表 现层。属性表达式使用另外一种开源框架 OGNL(Object Graph Navigation Language)。OGNL的开源工程( project)独立于 Tapestry,但是在 Tapestry 中起很重要的作用 。OGNL主要的目的在于读取和更新对象的 Java Bean属性 。 4)Tapestry classes Tapestry框架由 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,必须声明: 3 一个页面类; 该类必须继承 BasePage类。 Public hangman1 extends BasePageBasePageBasePageBasePage { } 只要 HTML模板中使用 Tapestry,就必须声明页面规范和页面类,即便页 面规范和页面类都没有任何属性或方法,变量。 7)关于属性标签: a) jwcid属性:( Java Web Component ID)在模板中用来指定组件。 b) 标签: HTML标签是一个用来包容 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自己会 生成一个子类来实现你的方法 。你甚至不用声明变量 ,只需要在页面规范中指明 类型即可。如: Tapestry会自动创建一个子类来继承 Login类,并实现以 JavaBean方式实现变 量。这样做有三个好处: 第一:减轻程序员负担; 第二: Tapestry可以确保自动重置属性,当请求失效过期的时候。 第三:属性可以被定义为 persistent。 这种机制与 EJB的container-managed persistence(CMP)一样。 12)组件的分类: 按照组件的使用方式: a) 隐式组件:组件类型和其结构直接在 HTML模板中申明的组件。通常, Tapestry已经定义好的组件都是以隐式组件的方式使用。 b) 显示组件 :其组件类型和结构储存在页面规范中 。通常 ,自定义的组件都 是以显示组件的方式使用。 我个人倾向于将组件按照其工作方式分为三类: 1)容器组件:指组件中可以包含其它组件的组件。 目前接触到的容器组件有 :Shell,Body,Conditional和Form四 种。其中最简单的是: Shell和Body,它们仅仅是用在 HTML模 板的开头,用来声明 ,和元素和 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 再次表现时,这些名称和关系会被重新获得,这种方式称为重新解析( rewiind phase)。当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"> Iaccept the terms and conditions. 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参数用于标明是否被选中 。Select and 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 用于按钮的显示。 关于失效链接( stalestalestalestale linklinklinklink): 当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 ,也就是一旦的 request 2,错误显示 Conditioal组件可以作为 Validation的容器 ,显示错误信息 。代理 (delegate) 的hasErrors属性,默认状态下为 false,但是当验证代理发现页面中存在错误 的时候,会将这个属性设为 true。如: 其中,监听方法总是询问代理的 hasErrors属性。如果该属性为 false,则 代表输入验证为安全,并进行下一步。如果为 true为真,则运行 Conditional 组件,显示错误信息。 直接显示错误信息,会显得笨拙,界面不友好。所以 Conditional与 Delegator两个组件配合,以便输出格式化的错误信息。在上面这个例子中 , 验证代理同 OGNL表达式被捕获, OGNL表达式 beans.delegate是通过 helper bean定义的引用。验证代理返回的是 “错误对象 ”,而不仅仅是一个 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">First Name </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-specific value来匹配。 在这个例子中,每个 ValidField都作为显示组件来构造,而不是隐式 组件 。每个 ValidField出现在 HTML模板中 ,但是类型以及大多数参数在页 面规范中声明。这些 ValidField不是匿名的,而是拥有真实 ID。 <input type="text" jwcid="inputLastName" size="50"/> bsf-2.3.0.jar commons-beanutils-1.6.1.jar commons-codec-1.2.jar commons-collections-2.1.jar commons-digester-1.5.jar commons-fileupload-1.0.jar commons-lang-1.0.jar commons-logging-1.0.2.jar jakarta-oro-2.0.6.jar javassist-2.5.1.jar ognl-2.6.7.jar tapestry-3.0.3.jar tapestry-contrib-3.0.3.jar xercesImpl.jar xmlParserAPIs.jar Home.java packagemo.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.java packagemo.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,ServletCont extcontext){ 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.java packagemo.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().getRespo nse(); response.setContentType("image/jpeg"); response.setHeader("Content-disposition","attachment;filename=fo o.jpg"); response.setContentLength(imageData.length); try{ OutputStreamout=response.getOutputStream(); out.write(imageData); }catch(IOExceptione){ thrownewApplicationRuntimeException(e); } } publicILinkgetLink(IRequestCyclecycle,IComponentcomponent,Obje ct[]args){ returnconstructLink(cycle,getName(),null,args,false); } } Upload.java packagemo.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().get Servlet().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.engi ne.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"encodi ng="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-clas s> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Album</servlet-name> <url-pattern>/app</url-pattern> </servlet-mapping> </web-app> </article> <div class="alert alert-warning" role="alert">...</div> <div class="thumbnail text-center"> <div class="more"> <span>还剩33页未读</span> <p class="go mt10"> <span class="btn btn-default" id="showMore" data-page="1"><i class="fa fa-chevron-down"></i> 继续阅读</span> </p> </div> </div> <div class="thumbnail box-line"> <div class="l1 line"></div> <div class="l2 line"></div> <div class="l3 line"></div> <div class="l4 line"></div> <div class="l5 line"></div> <div class="l6 line"></div> <div id="reader-more"> <p class="title">下载pdf到电脑,查找使用更方便</p> <p class="gray"> pdf的实际排版效果,会与网站的显示效果略有不同!!</p> <p class="download-info"> <span style="font-size: 14px;color: #888888">需要</span> <span style="font-size: 24px;">10</span> <span style="font-size: 14px;padding-right: 20px;color: #888888">金币</span> <a href="javascript:void(null);" onclick="JC.redirect('/pdf/create')" style="color: #cf6a07"> [ 分享pdf获得金币 ] </a> <span class="fcff">0 人已下载</span> </p> <p> <a class="btn btn-danger download buy circle80 fs30" href="javascript:void(null);" data-type="3" data-num="10" data-download="true"><i aria-hidden="true" class="fa fa-yen"> </i> 下载pdf</a> </p> </div> </div> </div> <!--right--> <div class="col-md-3"> <div class="thumbnail"> <h4>pdf贡献者</h4> <div class="ui items"> <div class="item"> <a class="ui tiny image" style="width: 50px;"> <img src="https://static.open-open.com/img/avatar/privary/default.png" width="50"> </a> <div class="content"> <a class="header" href="https://user.open-open.com/u/491474"> tumeimey </a> <div class="description"> <p>贡献于2016-05-09</p> </div> </div> </div> </div> <div> 下载需要 <span style="font-size: 24px;">10</span> <span style="font-size: 14px;padding-right: 20px;color: #888888">金币</span> <a href="javascript:void(null);" onclick="JC.redirect('https://user.open-open.com/pay')" style="color: #cf6a07"> [金币充值 ] </a> <div class="kind-tip">亲,您也可以通过 <a href="javascript:void(0) " onclick="JC.redirect('/pdf/create')">分享原创pdf</a> 来获得金币奖励!</div> </div> </div> <div> <a class="btn btn-block buy btn-danger download" href="javascript:void(null);" data-type="3" data-num="10" data-download="true"><i aria-hidden="true" class="fa fa-yen"></i> 下载pdf</a> </div> <div class="box side-box mt20"> <div class="title"> <h3><i class="fa fa-tags" aria-hidden="true"></i> 关键词</h3> </div> <p class="tags mt10"> <a class="" href="/pdf/tag/tapestry.html">Tapestry</a> <a class="" href="/pdf/tag/web-kuangjia.html">Web框架</a> </p> </div> <div class="ad"> <script>(function() {var s = "_" + Math.random().toString(36).slice(2);document.write('<div id="' + s + '"></div>');(window.slotbydup=window.slotbydup || []).push({id: '4133327', container: s, size: '0,0', display: 'inlay-fix'});})();</script><script src="https://dup.baidustatic.com/js/os.js"></script> </div> <div class="box side-box mt20"> <div class="title"> <h3>相关pdf</h3> </div> <ul> <li class="ellipsis"> <a href="/pdf/846d7613c3a5484a82c59167f00533c9.html"><i class="fa fa-file-word-o" aria-hidden="true"></i>  tapestry介绍</a> </li> <li class="ellipsis"> <a href="/pdf/25099c51ff04482d9252d22134305bb5.html"><i class="fa fa-file-word-o" aria-hidden="true"></i>  Tapestry in Action</a> </li> <li class="ellipsis"> <a href="/pdf/3a8396ba633e4191a5e46c28628e79a3.html"><i class="fa fa-file-word-o" aria-hidden="true"></i>  Tapestry in Action</a> </li> <li class="ellipsis"> <a href="/pdf/1ed2ab631de84048a116ca35e4ecc16a.html"><i class="fa fa-file-word-o" aria-hidden="true"></i>  Tapestry 字典</a> </li> <li class="ellipsis"> <a href="/pdf/9dc1358c2d224995a45fe64cbcdb61be.html"><i class="fa fa-file-word-o" aria-hidden="true"></i>  awk 介绍</a> </li> <li class="ellipsis"> <a href="/pdf/a41782da5ca7406cacb59f71e38646ed.html"><i class="fa fa-file-word-o" aria-hidden="true"></i>  Velocity 介绍</a> </li> <li class="ellipsis"> <a href="/pdf/6f02569a3e5d44dcbb4e2658047277bb.html"><i class="fa fa-file-word-o" aria-hidden="true"></i>  Nodejs介绍</a> </li> <li class="ellipsis"> <a href="/pdf/2a4314d7457942eea1ddd405bf065e24.html"><i class="fa fa-file-word-o" aria-hidden="true"></i>  Openstack 介绍</a> </li> <li class="ellipsis"> <a href="/pdf/0af6a3649037463f9a0d2be283401945.html"><i class="fa fa-file-word-o" aria-hidden="true"></i>  JSON介绍</a> </li> <li class="ellipsis"> <a href="/pdf/9d35db0b298941f5bea34498f350c21f.html"><i class="fa fa-file-word-o" aria-hidden="true"></i>  openstack介绍</a> </li> </ul> </div> </div> </div> </div> </div> <footer > <div class="container py-5"> <div class="row"> <div class="col-md-3"> <h5>社区</h5> <div class="row"><div class="col-md-6"><a class="text-muted" href="/project/">项目</a></div><div class="col-md-6"><a class="text-muted" href="/solution/">问答</a></div><div class="col-md-6"><a class="text-muted" href="/wenku/">文库</a></div><div class="col-md-6"><a class="text-muted" href="/code/">代码</a></div><div class="col-md-6"><a class="text-muted" href="/lib/">经验</a></div><div class="col-md-6"><a class="text-muted" href="/news/">资讯</a></div></div> <ul class="list-unstyled text-small ut-mt20"><li><a class="text-muted" title=" 安卓开发专栏" target="_blank" href="http://www.open-open.com/lib/list/177">安卓开发专栏</a></li><li><a class="text-muted" href="http://www.open-open.com/lib/tag/开发者周刊" target="_blank" rel="tag">开发者周刊</a></li><li><a class="text-muted" href="http://www.open-open.com/lib/view/open1475497562965.html" target="_blank" rel="tag">Android Studio 使用推荐</a></li><li><a class="text-muted" href="http://www.open-open.com/lib/view/open1475497355674.html" target="_blank" rel="tag">Android开发推荐</a></li></ul> </div> <div class="col-md-3"> <h5>帮助中心</h5> <ul class="list-unstyled text-small"><li><a class="text-muted" href="/upload.html">文档上传须知</a></li></ul> <h5>关于我们</h5> <ul class="list-unstyled text-small"><li><a class="text-muted" href="/about.html">关于深度开源</a></li><li><a class="text-muted" href="/duty.html">免责声明</a></li><li><a class="text-muted" href="/contact.html">联系我们</a></li></ul> </div> <div class="col-md-6 text-center"><img class=center-block src="https://static.open-open.com/img/logo01.svg" width=190px alt="深度开源"><small class="d-block mb-3 text-muted ut-mt40">© 2006-2019 深度开源 —— 开源项目,开源代码,开源文档,开源新闻,开源社区  杭州精创信息技术有限公司  <br/><br/><img src="https://static.open-open.com/img/beian.png"/><a target="_blank" href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=33010602002439">  浙公网安备 33010602002439号</a>  <a target="_blank" href="https://www.miibeian.gov.cn/">浙ICP备09019653号-31</a></small></div> </div> </div> </footer> <div id="fTools"><span id="gotop"> <i class="fa fa-arrow-up" aria-hidden="true"></i> </span><span id="feedback" title="建议反馈"> <i class="fa fa-inbox" aria-hidden="true"></i></span></div> <!-- Bootstrap core JavaScript ================================================== --> <!-- Placed at the end of the document so the pages load faster --> <script type="text/javascript" src="https://static.open-open.com/js/lib.js"></script> <script type="text/javascript" src="https://static.open-open.com/assets/jquery-confirm/jquery-confirm.js?v=4.7.0"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"></script> <script src="https://static.open-open.com/js/bootstrap.min.js"></script> <script type="text/javascript" src="https://static.open-open.com/js/base.js?v=2.002"></script> <script type="text/javascript" src="https://static.open-open.com/js/jq-plug.js?v=2.002"></script> <script> $(function () { JC.reminderPop();//弹出用户信息 $(".link-login").click(function(){ JC.lORr('login'); }); $("#topSearch").searchInit(); //用户登录状态 JC.setLogin(false); }); </script> <!-- JavaScript at the bottom for fast page loading --> <!-- end scripts --> </body> </html>