Apache Tapestry 快速指南



Apache Tapestry - 概述

Apache Tapestry 是一个用 Java 编写的开源 Web 框架。它是一个基于组件的 Web 框架。Tapestry 组件是 Java 类。它们既不继承自框架特定的基类,也不实现接口,它们只是普通的 POJO(普通旧 Java 对象)。

Tapestry 使用的 Java 的重要特性是注解。Tapestry 网页是通过使用一个或多个组件构建的,每个组件都有一个基于 XML 的模板和用许多 Tapestry 注解装饰的组件类。Tapestry 可以创建任何东西,从小型单页 Web 应用程序到包含数百个页面的大型应用程序。

Tapestry 的优势

Tapestry 提供的一些优势包括:

  • 高度可扩展的 Web 应用程序。
  • 自适应 API。
  • 快速且成熟的框架。
  • 持久状态存储管理。
  • 内置控制反转。

Tapestry 的特性

Tapestry 具有以下特性:

  • 实时类重载
  • 清晰详细的异常报告
  • 静态结构,动态行为。
  • 广泛使用普通旧 Java 对象 (POJO)
  • 少写代码,多交付。

为什么选择 Tapestry?

Java 已经有很多 Web 框架,例如 JSP、Struts 等,那么为什么还需要另一个框架呢?如今的大多数 Java Web 框架都很复杂,学习曲线陡峭。它们过时了,并且每次更新都需要编译、测试和部署周期。

另一方面,Tapestry 通过提供实时类重载,为 Web 应用程序编程提供了一种现代方法。虽然其他框架引入了许多接口、抽象类和基类,但 Tapestry 只引入了一小部分注解,并且仍然能够编写具有丰富的 AJAX 支持的大型应用程序。

Apache Tapestry - 架构

Tapestry 尽可能地使用 Java 的现有特性。例如,所有 Tapestry 页面都只是 POJO。它不强制使用任何自定义接口或基类来编写应用程序。相反,它使用注解(扩展 Java 类功能的轻量级选项)来提供功能。它基于经过实战检验的Java Servlet API,并实现为 Servlet 过滤器。它为 Web 应用程序带来了新的维度,并且编程非常简单、灵活、易懂和健壮。

工作流程

让我们讨论一下请求 Tapestry 页面时发生的一系列操作。

Workflow

步骤 1 - Java Servlet 接收页面请求。此 Java Servlet 以这样一种方式配置,即传入请求将转发到 Tapestry。配置在web.xml中完成,如下程序所示。Filter 和 Filter Mapping 标签将所有请求重定向到Tapestry Filter

<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" 
   "http://java.sun.com/dtd/web-app_2_3.dtd"> 
<web-app> 
   <display-name>My Tapestry Application</display-name> 
   <context-param> 
      <param-name>tapestry.app-package</param-name> 
      <param-value>org.example.myapp</param-value> 
   </context-param> 
   <filter> 
      <filter-name>app</filter-name> 
      <filter-class>org.apache.tapestry5.TapestryFilter</filter-class> 
   </filter> 
   <filter-mapping> 
      <filter-name>app</filter-name> 
      <url-pattern>/*</url-pattern> 
   </filter-mapping> 
</web-app> 

步骤 2 - Tapestry Filter 通过其Service()方法调用HttpServletRequestHandler服务。

步骤 3 - HttpServletRequestHandler 将请求和响应存储在RequestGlobals中。它还将请求和响应包装为 Request 和 Response 对象,并将其发送到 RequestHandler。

步骤 4 - RequestHandler 是 Servlet API 的HttpServletRequest之上的抽象。Tapestry 的一些突出特性是在RequestHandler部分完成的。可以通过在 RequestHandler 中编写过滤器来扩展 Tapestry 的特性。RequestHandler 提供了几个内置过滤器,包括:

  • CheckForUpdates Filter - 负责实时类重载。此过滤器检查 Java 类是否有更改,并在必要时更新应用程序。

  • Localization Filter - 识别用户的地理位置,并为应用程序提供本地化支持。

  • StaticFiles Filter - 识别静态请求并中止进程。一旦进程中止,Java Servlet 就会接管并处理请求。

  • Error Filter - 捕获未捕获的异常并显示异常报告页面。

RequestHandler 还修改并存储 RequestQlobals 中的请求和响应,并调用 MasterDispatcher 服务。

步骤 5 - MasterDispatcher 负责通过按特定顺序调用多个调度程序来呈现页面。MasterDispatcher 调用的四个主要调度程序如下:

  • RootPath Dispatcher - 它识别请求的根路径“/”,并将其呈现为起始页面。

  • Asset Dispatcher - 它通过检查 url 模式 /assets/ 来识别资产(Java 资产)请求,并将请求的资产作为字节流发送。

  • PageRender Dispatcher - Tapestry 的大部分操作都在 PageRender Dispatcher 和下一个调度程序 Component Dispatcher 中完成。此调度程序识别该请求的特定页面及其激活上下文(额外信息)。然后它呈现该特定页面并将其发送到客户端。例如,如果请求 url 为 /product/12123434,则调度程序将检查是否存在名为 product/12123434 的类。如果找到,它会调用 product/12123434 类,生成响应并将其发送到客户端。如果没有找到,它会检查 product 类。如果找到,它会调用 product 类并附带额外信息 121234434,生成响应并将其发送到客户端。此额外信息称为激活上下文。如果没有找到任何类,它只会将请求转发到 Component Dispatcher。

  • Component Dispatcher - Component Dispatcher 将页面的 URL 与模式匹配 – /<class_name>/<component_id>:<event_type>/<activation_context>。例如,/product/grid:sort/asc 表示 product 类、grid 组件、sort 事件类型和 asc 激活上下文。此处,event_type 是可选的,如果未提供,则将触发默认事件类型 action。通常,组件调度程序的响应是向客户端发送重定向。大多数情况下,重定向将在下一个请求中匹配 PageRender Dispatcher,并将正确的响应发送到客户端。

Apache Tapestry - 安装

在本章中,我们将讨论如何在我们的机器上安装 Tapestry。

先决条件

Tapestry 唯一依赖的是核心 Java。Tapestry 是独立开发的,没有使用任何第三方库/框架。即使 Tapestry 使用的 IoC 库也是从头开始开发的。用 Tapestry 编写的 Web 应用程序本身就可以从控制台构建和部署。

我们可以使用Maven、EclipseJetty来改善开发体验。Maven 提供快速启动应用程序模板以及在 Jetty(Java 事实上的开发服务器)中托管应用程序的选项。Eclipse 提供了广泛的项目管理功能,并且与 Maven 集成良好。

理想的 Tapestry 应用程序开发需要以下内容:

  • Java 1.6 或更高版本
  • Apache Maven
  • Eclipse IDE
  • Jetty 服务器

验证 Maven 安装

希望您已经在您的机器上安装了 Maven。要验证 Maven 安装,请键入以下命令:

mvn --version

您可能会看到如下所示的响应:

Apache Maven 3.3.9 (bb52d8502b132ec0a5a3f4c09453c07478323dc5; 2015-1110T22:11:47+05:30) 
Maven home: /Users/workspace/maven/apache-maven-3.3.9 
Java version: 1.8.0_92, vendor: Oracle Corporation 
Java home: /Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre 
Default locale: en_US, platform encoding: UTF-8 
OS name: "mac os x", version: "10.11.4", arch: "x86_64", family: "mac"

如果未安装 Maven,请访问Maven网站下载并安装最新版本的 Maven。

下载 Tapestry

Tapestry 的最新版本是 5.4,可以从Tapestry网站下载。下载二进制包就足够了。如果我们使用 Maven 快速入门模板,则无需单独下载 Tapestry。Maven 会自动下载必要的 Tapestry Jar 文件并配置应用程序。我们将在下一章讨论如何使用 Maven 创建基本的 Tapestry 应用程序。

Apache Tapestry - 快速入门

安装 Tapestry 后,让我们使用 Maven 创建一个新的初始项目,如下所示:

$ mvn archetype:generate -DarchetypeCatalog=http://tapestry.apache.org

您可能会看到如下所示的响应:

[INFO] Scanning for projects... 
[INFO] 
[INFO] --------------------------------------------------------------------------------- 
[INFO] Building Maven Stub Project (No POM) 1 
[INFO] ---------------------------------------------------------------------------------
[INFO] 
[INFO] >>> maven-archetype-plugin:2.4:generate (default-cli) > 
generatesources @ standalone-pom >>> 
[INFO]  
[INFO] <<< maven-archetype-plugin:2.4:generate (default-cli) 
< generatesources @ standalone-pom <<< 
[INFO] 
[INFO] --- maven-archetype-plugin:2.4:generate (default-cli) @ standalone-pom --- 
[INFO] Generating project in Interactive mode 
[INFO] No archetype defined. Using maven-archetype-quickstart 
(org.apache.maven.archetypes:maven-archetype-quickstart:1.0)

在 Maven 完成所有操作后,选择原型以创建Tapestry 5 快速入门项目,如下所示:

选择原型 -

选择一个数字或应用过滤器(格式:[groupId:]artifactId,区分大小写包含)::1

现在您将收到如下所示的响应:

Choose org.apache.tapestry:quickstart version: 
1: 5.0.19
2: 5.1.0.5 
3: 5.2.6 
4: 5.3.7 
5: 5.4.1 

提取快速入门版本号,如下所示:

Choose a number: 5: 5

此处,快速入门项目为选项 5“5.4.1”获取版本。现在,Tapestry 原型会依次询问以下信息:

  • 5.1 groupId - 定义属性“groupId”的值::com.example

  • 5.2 artifactId - 定义属性“artifactId”的值::Myapp

  • 5.3 version - 定义属性“version”的值:1.0-SNAPSHOT:

  • 5.4 package name - 定义属性“package”的值:com.example::com.example.Myapp

现在您的屏幕会向您请求确认:

确认属性配置 -

  • groupId - com.example

  • artifactId - Myapp

  • version - 1.0-SNAPSHOT

  • package - com.example.Myapp

验证所有属性,并使用以下所示的选项确认更改:

 Y: : Y 

您将看到如下所示的屏幕。

[INFO] ---------------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: quickstart:5.4.1 
[INFO] ---------------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: com.example 
[INFO] Parameter: artifactId, Value: Myapp 
[INFO] Parameter: version, Value: 1.0-SNAPSHOT 
[INFO] Parameter: package, Value: com.example.Myapp 
[INFO] Parameter: packageInPathFormat, Value: com/example/Myapp 
[INFO] Parameter: package, Value: com.example.Myapp 
[INFO] Parameter: version, Value: 1.0-SNAPSHOT 
[INFO] Parameter: groupId, Value: com.example 
[INFO] Parameter: artifactId, Value: Myapp 
[WARNING] Don't override file /Users/workspace/tapestry/Myapp/src/test/java 
[WARNING] Don't override file /Users/workspace/tapestry/Myapp/src/main/webapp 
[WARNING] Don't override file /Users/workspace/tapestry/Myapp/src/main/resources/com/
example/Myapp 
[WARNING] Don't override file /Users/workspace/tapestry/Myapp/src/test/resource 
[WARNING] Don't override file /Users/workspace/tapestry/Myapp/src/test/conf 
[WARNING] Don't override file /Users/workspace/tapestry/Myapp/src/site 
[INFO] project created from Archetype in dir: /Users/workspace/tapestry/Myapp 
[INFO] --------------------------------------------------------------------------------- 
[INFO] BUILD SUCCESS 
[INFO] --------------------------------------------------------------------------------- 
[INFO] Total time: 11:28 min 
[INFO] Finished at: 2016-09-14T00:47:23+05:30 
[INFO] Final Memory: 14M/142M 
[INFO] ---------------------------------------------------------------------------------

此处,您已成功构建了 Tapestry 快速入门项目。使用以下命令移动到新创建的Myapp目录的位置,并开始编码。

cd Myapp 

运行应用程序

要运行骨架项目,请使用以下命令。

mvn jetty:run -Dtapestry.execution-mode=development

您将看到如下屏幕,

[INFO] Scanning for projects... 
[INFO] 
[INFO] ---------------------------------------------------------------------------------
[INFO] Building Myapp Tapestry 5 Application 1.0-SNAPSHOT 
[INFO] ---------------------------------------------------------------------------------
........ 
........ 
........ 
Application 'app' (version 1.0-SNAPSHOT-DEV) startup time: 346 ms to build IoC 
Registry, 1,246 ms overall.  
 ______                  __             ____ 
/_  __/__ ____  ___ ___ / /_______ __  / __/ 
 / / / _ `/ _ \/ -_|_-</ __/ __/ // / /__ \  
/_/  \_,_/ .__/\__/___/\__/_/  \_, / /____/ 
        /_/                   /___/  5.4.1 (development mode)   
[INFO] Started SelectChannelConnector@0.0.0.0:8080 
[INFO] Started Jetty Server

目前,我们已经在 Tapestry 中创建了一个基本的快速入门项目。要在 Web 浏览器中查看正在运行的应用程序,只需在地址栏中键入以下 URL 并按 Enter 键:

https://:8080/myapp

此处,myapp是应用程序的名称,开发模式下应用程序的默认端口为 8080。

使用 Eclipse

在上一章中,我们讨论了如何在 CLI 中创建 Tapestry 快速入门应用程序。本章说明如何在Eclipse IDE中创建骨架应用程序。

让我们使用 Maven 原型创建骨架应用程序。要配置新的应用程序,您可以按照以下步骤操作。

步骤 1:打开 Eclipse IDE

打开您的 Eclipse 并选择 File → New → Project… → 选项,如下面的屏幕截图所示。

Open Eclipse

现在,选择 Maven → Maven project 选项。

注意 - 如果 Maven 未配置,则配置并创建一个项目。

选择 Maven 项目后,单击 Next,然后再次单击 Next 按钮。

Maven Project

之后,您将看到一个屏幕,您应该在其中选择配置选项。配置完成后,您将看到以下屏幕。

Configure Option

步骤 2:目录配置

完成第一步后,您应该单击添加远程目录。然后添加以下更改,如下面的屏幕截图所示。

Remote Catalog

现在,Apache Tapestry 目录已添加。然后,选择过滤器选项 org.apache.tapestry quickstart 5.4.1,如下所示。

Added Catalog

然后单击 Next,将出现以下屏幕。

Group Id Field

步骤 3:配置 GroupId、ArtifactId、版本和包

将以下更改添加到 Tapestry 目录配置中。

Changes Configuration

然后单击 Finish 按钮,现在我们创建了第一个骨架应用程序。第一次使用 Maven 时,项目创建可能需要一段时间,因为 Maven 会下载 Maven、Jetty 和 Tapestry 的许多 JAR 依赖项。Maven 完成后,您将在 Package Explorer 视图中看到一个新目录 MyFirstApplication。

步骤 4:使用 Jetty 服务器运行应用程序

您可以使用 Maven 直接运行 Jetty。在 Package Explorer 视图中右键单击 MyFirstApplication 项目,然后选择 Run As → Maven Build…,您将看到如下所示的屏幕。

Jetty Server

在配置对话框中,将 goals 选项输入为“jetty:run”,然后单击 Run 按钮。

Goals Option

Jetty 初始化后,您将在控制台中看到以下屏幕。

Jetty Initialized

步骤 5:在 Web 浏览器中运行

键入以下 URL 以在 Web 浏览器中运行应用程序 -

https://loclhost:8080/MyFirstApplication

Run Web Browser

步骤 6:停止 Jetty 服务器

要停止 Jetty 服务器,请单击控制台中的红色方块图标,如下所示。

Stop Server

Apache Tapestry - 项目布局

这是由Maven Quickstart CLI创建的源代码的布局。此外,这也是标准 Tapestry 应用程序的建议布局。

├── build.gradle 
├── gradle 
│   └── wrapper 
│       ├── gradle-wrapper.jar 
│       └── gradle-wrapper.properties 
├── gradlew 
├── gradlew.bat 
├── pom.xml 
├── src 
│   ├── main 
│   │   ├── java 
│   │   │   └── com 
│   │   │       └── example 
│   │   │           └── MyFirstApplication 
│   │   │               ├── components 
│   │   │               ├── data 
│   │   │               ├── entities 
│   │   │               ├── pages 
│   │   │               └── services 
│   │   ├── resources 
│   │   │   ├── com 
│   │   │   │   └── example 
│   │   │   │       └── MyFirstApplication 
│   │   │   │           ├── components 
│   │   │   │           ├── logback.xml 
│   │   │   │           └── pages 
│   │   │   │               └── Index.properties  
│   │   │   ├── hibernate.cfg.xml 
│   │   │   └── log4j.properties
│   │   └── webapp 
│   │       ├── favicon.ico 
│   │       ├── images 
│   │       │   └── tapestry.png 
│   │       ├── mybootstrap 
│   │       │   ├── css 
│   │       │   │   ├── bootstrap.css 
│   │       │   │   └── bootstrap-theme.css 
│   │       │   ├── fonts 
│                   ├── glyphicons-halflings-regular.eot 
│   │       │   │   ├── glyphicons-halflings-regular.svg 
│   │       │   │   ├── glyphicons-halflings-regular.ttf 
│   │       │   │   ├── glyphicons-halflings-regular.woff 
│   │       │   │   └── glyphicons-halflings-regular.woff2 
│   │       │   └── js 
│   │       └── WEB-INF 
│   │           ├── app.properties 
│   │           └── web.xml 
│   ├── site 
│   │   ├── apt 
│   │   │   └── index.apt 
│   │   └── site.xml 
│   └── test 
│       ├── conf 
│       │   ├── testng.xml 
│       │   └── webdefault.xml 
│       ├── java 
│       │   └── PLACEHOLDER 
│       └── resources 
│           └── PLACEHOLDER 
└── target     
   ├── classes     
   │   ├── com  
   │   │   └── example
   │   │       └── MyFirstApplication     
   │   │           ├── components     
   │   │           ├── data     
   │   │           ├── entities     
   │   │           ├── logback.xml     
   │   │           ├── pages 
   │   │           │   └── Index.properties 
   │   │           └── services     
   │   ├── hibernate.cfg.xml 
   │   └── log4j.properties     
   ├── m2e-wtp 
   │   └── web-resources 
   │       └── META-INF     
   │           ├── MANIFEST.MF 
   │           └── maven 
   │               └── com.example 
   │                   └──MyFirstApplication     
   │                     ├── pom.properties 
   │                       └── pom.xml     
   ├── test-classes 
   │   └── PLACEHOLDER 
   └── work         
      ├── jsp         
      ├── sampleapp.properties 
      └── sampleapp.script

默认布局类似于WAR 内部文件格式。使用 WAR 格式有助于在不打包和部署的情况下运行应用程序。此布局只是一个建议,但如果应用程序在部署时打包成正确的 WAR 格式,则可以以任何格式进行排列。

源代码可以分为以下四个主要部分。

  • Java 代码 - 所有 Java 源代码都放置在/src/main/java文件夹下。Tapestry 页面类放置在“Pages”文件夹下,Tapestry 组件类放置在 components 文件夹下。Tapestry 服务类放置在 services 文件夹下。

  • 类路径资源 - 在 Tapestry 中,大多数类都有关联的资源(XML 模板、JavaScript 文件等)。这些资源放置在/src/main/resources文件夹下。Tapestry 页面类在其关联的资源位于“Pages”文件夹下,Tapestry 组件类在其关联的资源位于 Components 文件夹下。这些资源被打包到 WAR 的WEB-INF/classes文件夹中。

  • 上下文资源 - 它们是 Web 应用程序的静态资源,如图像、样式表和 JavaScript 库/ 模块。它们通常放置在 /src/main/webapp文件夹下,被称为上下文资源。此外,Web 应用程序描述文件(Java Servlet),web.xml 放置在上下文资源的WEB-INF文件夹下。

  • 测试代码 - 这些是用于测试应用程序的可选文件,放置在src/test/javasrc/test/Resources 文件夹下。它们不会打包到 WAR 中。

约定优于配置

Apache Tapestry 在编程的各个方面都遵循约定优于配置。框架的每个功能都具有合理的默认约定。

例如,正如我们在项目布局章节中学到的,所有页面都需要放置在/src/main/java/«package_path»/pages/文件夹下才能被视为 Tapestry 页面。

换句话说,无需将特定的 Java 类配置为 Tapestry 页面。将其放置在预定义的位置就足够了。在某些情况下,遵循 Tapestry 的默认约定很奇怪。

例如,Tapestry 组件可以有一个方法setupRender,该方法将在渲染阶段开始时触发。开发人员可能希望使用他们自己的有见地的名称,例如initializeValue。在这种情况下,Tapestry 提供注释来覆盖约定,如下面的代码块所示。

void setupRender() { 
   // initialize component 
}  
@SetupRender 
void initializeValue() { 
   // initialize component 
}

这两种编程方式在 Tapestry 中都是有效的。简而言之,Tapestry 的默认配置非常少。只有Apache Tapestry 过滤器(Java Servlet 过滤器)需要在“Web.xml”中配置才能使应用程序正常工作。

Tapestry 提供了另一种配置应用程序的方式,称为AppModule.java

Apache Tapestry - 注解

注释是 Tapestry 利用来简化 Web 应用程序开发的一个非常重要的特性。Tapestry 提供了许多自定义注释。它有用于类、方法和成员字段的注释。如上一节所述,注释也可用于覆盖功能的默认约定。Tapestry 注释分为四大类,如下所示。

组件注释

用于 Pages、Components 和 Mixins 类。一些有用的注释包括 -

  • @Property - 它适用于字段。用于将字段转换为 Tapestry 属性。

  • @Parameter - 它适用于字段。用于将字段指定为组件的参数。

  • @Environmental - 它适用于字段。用于在不同组件之间共享私有字段。

  • @import - 它适用于类和字段。用于包含 Assets、CSS 和 JavaScript。

  • @Path - 与 @Inject 注释一起使用,根据路径注入 Asset。

  • @Log - 它适用于类和字段。用于调试目的。可用于发出组件的事件信息,如事件开始、事件结束等。

IoC 注释

用于将对象注入 IoC 容器。一些有用的注释包括 -

  • @Inject - 它适用于字段。用于标记应注入 IoC 容器的参数。它标记应注入组件的字段。

  • @Value - 它适用于字段。与 @inject 注释一起使用,用于注入字面量值而不是服务(这是 @Inject 注释的默认行为)。

用于数据持有类的注释

它用于在类(通常是模型或数据持有类)中为高级组件指定特定于组件的信息,例如

  • Grid(用于创建高级表格数据,如报表、图库等),

  • BeanEditForm(用于创建高级表单)

  • Hibernate(用于高级数据库访问)等。

这些注释被聚合并打包到一个单独的 jar 中,没有任何 tapestry 依赖项。一些注释包括 -

  • @DataType - 用于指定字段的数据类型。Tapestry 组件可以使用此信息在表示层创建设计或标记。

  • @Validate - 用于指定字段的验证规则。

这些分离使 Tapestry 应用程序能够使用多层设计

Apache Tapestry - 页面和组件

Tapestry 应用程序仅仅是 Tapestry 页面的集合。它们协同工作以形成一个定义良好的 Web 应用程序。每个页面都将有一个相应的 XML 模板和零个、一个或多个组件。页面和组件是相同的,只是页面是根组件,通常由应用程序开发人员创建。

组件是根 Pagecomponent 的子组件。Tapestry 具有许多内置组件,并且可以选择创建自定义组件。

Page Component

页面

如前所述,页面是 Tapestry 应用程序的构建块。页面是普通的 POJO,放置在 - /src/main/java/«package_path»/pages/文件夹下。每个页面都将有一个相应的XML 模板,其默认位置为 - /src/main/resources/«package_name»/pages/

您可以在此处看到页面和模板的路径结构相似,只是模板位于资源文件夹中。

例如,Tapestry 应用程序中具有包名称 - com.example.MyFirstApplication的用户注册页面将具有以下页面和模板文件 -

  • Java 类 -

    /src/main/java/com/example/MyFirstApplication/pages/index.java

  • XML 模板 -

    /src/main/resources/com/example/MyFirstApplication/pages/index.tml

让我们创建一个简单的Hello World页面。首先,我们需要在 - /src/main/java/com/example/MyFirstApplication/pages/HelloWorld.java”处创建一个Java 类

package com.example.MyFirstApplication.pages; 
public class HelloWorld { 
}

然后,在 -

“/src/main/resources/com/example/MyFirstApplication/pages/helloworld.html”处创建一个 XML 模板。

<html xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd"> 
   <head> 
      <title>Hello World Page</title> 
   </head> 
   <body> 
      <h1>Hello World</h1> 
   </body> 
</html>

现在,可以通过https://:8080/myapp/helloworld访问此页面。这是一个简单的 Tapestry 页面。Tapestry 提供了更多功能来开发动态网页,我们将在后面的章节中讨论。

Apache Tapestry - 模板

让我们在本节中考虑 Tapestry XML 模板。XML 模板是一个格式良好的 XML 文档。页面的表示(用户界面)层是 XML 模板。除了以下项目外,XML 模板还具有正常的 HTML 标记 -

  • Tapestry 命名空间
  • 扩展
  • 元素
  • 组件

让我们现在详细讨论它们。

Tapestry 命名空间

Tapestry 命名空间只不过是 XML 命名空间。命名空间应在模板的根元素中定义。它用于在模板中包含 Tapestry 组件和相关组件信息。最常用的命名空间如下 -

  • xmlns:t = “https://tapestry.apache.org/schema/tapestry_5_4.xsd” - 用于识别 Tapestry 的元素、组件和属性。

  • xmlns:p = “tapestry:parameter” - 用于将任意代码块传递给组件。

Tapestry 命名空间示例如下 -

<html xmlns:t = "https://tapestry.apache.org/schema/tapestry_5_3.xsd" 
   xmlns:p = "tapestry:parameter"> 
   
   <head> 
      <title>Hello World Page</title> 
   </head>  
   <body> 
      <h1>Hello World</h1> 
      <t:eventlink page = "Index">refresh page</t:eventlink> 
   </body> 
</html>

扩展

扩展是在渲染页面的阶段动态更改 XML 模板的一种简单有效的方法。扩展使用 ${<name>} 语法。在 XML 模板中有许多选项可以表达扩展。让我们看看一些最常用的选项 -

属性扩展

它映射在相应页面类中定义的属性。它遵循 Java Bean 规范在 Java 类中定义属性。它更进一步,忽略属性名称的大小写。让我们使用属性扩展更改“Hello World”示例。以下代码块是修改后的页面类。

package com.example.MyFirstApplication.pages; 
public class HelloWorld {   
   // Java Bean Property 
   public String getName { 
      return "World!"; 
   } 
}

然后,更改相应的 XML 模板,如下所示。

<html xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd"> 
   <head> 
      <title>Hello World Page</title> 
   </head> 
   <body> 
      <!-- expansion --> 
      <h1>Hello ${name}</h1> 
   </body> 
</html>

在这里,我们在页面类中将name定义为Java Bean 属性,并在 XML 模板中使用扩展${name}动态处理它。

消息扩展

每个 Page 类可能拥有或不拥有一个关联的属性文件 – 资源文件夹中的«page_name».properties。属性文件是纯文本文件,每行包含一个键/值对(消息)。让我们为 HelloWorld 页面创建一个属性文件,位于 –

“/src/main/resources/com/example/MyFirstApplication/pages/helloworld.properties” 并添加“Greeting”消息。

Greeting = Hello

Greeting 消息可以在 XML 模板中使用 ${message:greeting}

<html xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd"> 
   <head> 
      <title>Hello World Page</title> 
   </head> 
   <body> 
      <!-- expansion --> 
      <h1>${message:greeting} ${name}</h1> 
   </body> 
</html>

元素

Tapestry 有一套小的元素可以在 XML 模板中使用。元素是在 Tapestry 命名空间下定义的预定义标签 -

https://tapestry.apache.org/schema/tapestry_5_4.xsd

每个元素都是为特定目的创建的。可用的 Tapestry 元素如下 -

<t:body>

当两个组件嵌套时,父组件的模板可能需要包装子组件的模板。<t:body> 元素在这种情况下很有用。<t:body> 的用途之一是在模板布局中。

通常,Web 应用程序的用户界面将具有公共页眉、页脚、菜单等。这些公共项目在 XML 模板中定义,称为模板布局或布局组件。在 Tapestry 中,它需要由应用程序开发人员创建。布局组件只是另一个组件,放置在组件文件夹下,该文件夹具有以下路径 – src/main/«java|resources»/«package_name»/components

让我们创建一个名为 MyCustomLayout 的简单布局组件。MyCustomLayout 的代码如下 -

<!DOCTYPE html> 
<html xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd"> 
   <head> 
      <meta charset = "UTF-8" />
      <title>${title}</title>  
   </head> 
   <body> 
      <div>Sample Web Application</div> 
      <h1>${title}</h1> 
      <t:body/> 
      
      <div>(C) 2016 TutorialsPoint.</div> 
   </body> 
</html> 

package com.example.MyFirstApplication.components;  

import org.apache.tapestry5.*; 
import org.apache.tapestry5.annotations.*; 
import org.apache.tapestry5.BindingConstants;  

public class MyCustomLayout { 
   @Property 
   @Parameter(required = true, defaultPrefix = BindingConstants.LITERAL) 
      private String title; 
}

在 MyCustomLayout 组件类中,我们声明了一个标题字段,并通过使用注解将其设置为必填。现在,更改 HelloWorld.html 模板以使用我们的自定义布局,如下面的代码块所示。

<html>
   t:type = "mycustomlayout" title = "Hello World Test page"
      xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd"> 
   <h1>${message:greeting} ${name}</h1> 
</html>

我们可以在这里看到 XML 模板没有 head 和 body 标签。Tapestry 将从布局组件中收集这些详细信息,并且布局组件的 <t:body> 将被 HelloWorld 模板替换。一旦所有操作都完成,Tapestry 将发出类似于下面指定的标记 -

<!DOCTYPE html> 
<html> 
   <head> 
      <meta charset = "UTF-8" /> 
      <title>Hello World Test Page</title> 
   </head> 
   <body> 
      <div>Sample Web Application</div> 
      <h1>Hello World Test Page</h1> 
      <h1>Hello World!</h1> 
      <div>(C) 2016 TutorialsPoint.</div> 
   </body> 
</html>

布局可以嵌套。例如,我们可以通过包含管理功能来扩展我们的自定义布局,并将其用于如下所示的管理部分。

<html t:type = "MyCommonLayout" 
   xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd"> 
   
   <div><!-- Admin related items --><div> 
   <t:body/> 
  
</html>

<t:container>

<t:container> 是顶级元素,包含 Tapestry 命名空间。这用于指定组件的动态部分。

例如,网格组件可能需要一个模板来识别如何在 HTML 表格中呈现其行 - tr(和列 td)。

<t:container xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd"> 
   <td>${name}</td> 
   <td>${age}</td> 
</t:container>

<t:block>

<t:block> 是模板中动态部分的占位符。通常,block 元素不进行渲染。只有在模板中定义的组件使用 block 元素。组件将动态地将数据注入到 block 元素中并进行渲染。其中一个流行的用例是 AJAX

block 元素提供了要渲染的动态数据的精确位置和标记。每个 block 元素都应该有一个对应的 Java 属性。只有这样才能动态渲染它。block 元素的 id 应该遵循 Java 变量标识符规则。下面提供了部分示例。

@Inject 
private Block block;  
<html t:type = "mycustomlayout" title = "block example" 
   xmlns:t = "https://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter">  
<h1>${title}</h1>  
<!--  
   ... 
   ...  
--> 
<t:block t:id = "block"> 
   <h2>Highly dynamic section</h2> 
   I'v been updated through AJAX call 
   The current time is: <strong>${currentTime}</strong>
</t:block>  
<!--  
   ... 
   ...  
-->  
</html>

<t:content>

<t:content> 元素用于指定模板的实际内容。通常,所有标记都被视为模板的一部分。如果指定了 <t:content>,则只有其中的标记将被考虑。此功能供设计人员在没有布局组件的情况下设计页面使用。

<t:remove>

<t:remove> 恰好与 content 元素相反。remove 元素内的标记不被视为模板的一部分。它可以用于服务器端注释和设计目的。

资产

资产是静态资源文件,例如样式表、图像和 JavaScript 文件。通常,资产放置在 Web 应用程序根目录 /src/main/webapp 中。

<head> 
   <link href = "/css/site.css" rel = "stylesheet" type = "text/css"/>

Tapestry 还将存储在 Java 类路径 中的文件视为资产。Tapestry 提供了高级选项,可以通过扩展选项将资产包含到模板中。

  • Context - 获取 Web 上下文中可用资产的选项。

<img src = "${context:image/tapestry_banner.gif}" alt = "Banner"/>

asset - 组件通常将其自己的资产与 Java 类一起存储在 jar 文件中。从 Tapestry 5.4 开始,在类路径中存储资产的标准路径是 META-INF/assets。对于库,存储资产的标准路径是 META-INF/assets/«library_name»/。asset: 也可以调用 context: 扩展来获取来自 Web 上下文的资产。

<img src = "${asset:context:image/tapestry_banner.gif}" alt = "Banner"/>

可以使用 Inject 和 Path 注解将资产注入 Tapestry 页面或组件中。Path 注解的参数是资产的相对路径。

@Inject 
@Path("images/edit.png") 
private Asset icon;

Path 参数还可以包含在 AppModule.java 部分中定义的 Tapestry 符号。

例如,我们可以定义一个符号 skin.root,其值为 context:skins/basic,并按如下所示使用它 -

@Inject 
@Path("${skin.root}/style.css") 
private Asset style;

本地化

通过 Tapestry 包含资源提供了额外的功能。其中一项功能是“本地化”。Tapestry 将检查当前语言环境并包含适当的资源。

例如,如果当前语言环境设置为 de,则将包含 edit_de.png 而不是 edit.png。

CSS

Tapestry 具有内置的样式表支持。Tapestry 将 tapestry.css 作为核心 Javascript 堆栈的一部分注入。从 Tapestry 5.4 开始,Tapestry 还包括 bootstrap css 框架。我们可以使用普通的链接标签包含我们自己的样式表。在这种情况下,样式表应该位于 Web 根目录 – /src/main/webapp/ 中。

<head> 
   <link href = "/css/site.css" rel = "stylesheet" type = "text/css"/>

Tapestry 提供了高级选项,可以通过前面讨论的扩展选项将样式表包含到模板中。

<head> 
   <link href = "${context:css/site.css}" rel = "stylesheet" type = "text/css"/> 

Tapestry 还提供 Import 注解,可以直接将样式表包含到 Java 类中。

@Import(stylesheet="context:css/site.css") 
public class MyCommonLayout { 
} 

Tapestry 提供了许多通过 AppModule.java 管理样式表的选项。一些重要的选项是 -

  • 可以删除 Tapestry 默认样式表。

@Contribute(MarkupRenderer.class) 

public static void 
deactiveDefaultCSS(OrderedConfiguration<MarkupRendererFilter> configuration) { 
   configuration.override("InjectDefaultStyleheet", null); 
} 
  • 还可以通过覆盖其路径来禁用 Bootstrap。

configuration.add(SymbolConstants.BOOTSTRAP_ROOT, "classpath:/METAINF/assets");
  • 启用资产(CSS 和 JavaScript)的动态最小化。我们还需要包含 tapestry-webresources 依赖项(在 pom.xml 中)。

@Contribute(SymbolProvider.class) 
@ApplicationDefaults 

public static void contributeApplicationDefaults( 
   MappedConfiguration<String, String> configuration) { 
   
   configuration.add(SymbolConstants.MINIFICATION_ENABLED, "true"); 
} 

<dependency> 
   <groupId>org.apache.tapestry</groupId> 
   <artifactId>tapestry-webresources</artifactId> 
   <version>5.4</version> 
</dependency> 

客户端 JavaScript

当前一代的 Web 应用程序严重依赖 JavaScript 来提供丰富的客户端体验。Tapestry 认识到这一点并为 JavaScript 提供一流的支持。JavaScript 支持深深地融入 Tapestry 中,并在编程的每个阶段都可用。

早些时候,Tapestry 仅支持 Prototype 和 Scriptaculous。但是,从 5.4 版本开始,Tapestry 完全重写了 JavaScript 层,使其尽可能通用,并为 JQuery(JavaScript 事实上的库)提供一流的支持。此外,Tapestry 鼓励基于模块的 JavaScript 编程,并支持 RequireJS,这是 AMD(异步模块定义 - JavaScript 规范,用于以异步方式支持模块及其依赖项)的流行客户端实现。

位置

JavaScript 文件是 Tapestry 应用程序的资产。根据资产规则,JavaScript 文件要么放置在 Web 上下文中 /sr/main/webapp/,要么放置在 jar 文件中的 META-INF/assets/ 位置

链接 JavaScript 文件

在 XML 模板中链接 JavaScript 文件的最简单方法是直接使用 script 标签,即 - <script language = "javascript" src = "relative/path/to/js"></script>。但是,Tapestry 不推荐这些方法。Tapestry 提供了几种选项,可以直接在页面/组件本身中链接 JavaScript 文件。其中一些如下所示。

  • @import 注解 - @import 注解提供了使用上下文表达式链接多个 JavaScript 库的选项。它可以应用于 Page 类及其方法。如果应用于 Page 类,则它适用于其所有方法。如果应用于 Page 的方法,则它仅适用于该方法,然后 Tapestry 仅在调用该方法时链接 JavaScript 库。

@Import(library = {"context:js/jquery.js","context:js/myeffects.js"}) 

public class MyComponent { 
   // ... 
}
  • JavaScriptSupport 接口 - JavaScriptSupport 是 Tapestry 定义的接口,它有一个方法 importJavaScriptLibrary 用于导入 JavaScript 文件。JavScriptSupport 对象可以通过简单地声明并使用 @Environmental 注解进行注释轻松创建。

@Inject @Path("context:/js/myeffects.js") 
private Asset myEffects;  

@Environmental 
private JavaScriptSupport javaScriptSupport;  
void setupRender() { 
   javaScriptSupport.importJavaScriptLibrary(myEffects); 
}
  • JavScriptSupport 只能使用 @Environmental 注解注入到组件中。对于服务,我们需要使用 @Inject 注解或将其作为服务构造函数方法中的参数添加。

@Inject 
private JavaScriptSupport javaScriptSupport; 
public MyServiceImpl(JavaScriptSupport support) { 
   // ... 
}
  • addScript 方法 - 这类似于 JavaScriptSupport 接口,不同之处在于它使用 addScript 方法,并且代码直接添加到页面底部的输出中。

void afterRender() { 
   javaScriptSupport.addScript(
      "$('%s').observe('click', hideMe());", container.getClientId()); 
}

JavaScript 堆栈

Tapestry 允许将一组 JavaScript 文件和相关的样式表组合并用作一个实体。目前,Tapestry 包括基于 Prototype 和基于 JQuery 的堆栈。

开发人员可以通过实现 JavaScriptStack 接口并在 AppModule.java 中注册它来开发自己的堆栈。注册后,可以使用 @import 注解导入堆栈。

@Contribute(JavaScriptStackSource.class) 
public static void addMyStack(
   MappedConfiguration<String, JavaScriptStack> configuration) { 
   
   configuration.addInstance("MyStack", myStack.class); 
}  

@Import(stack = "MyStack") 
public class myPage { 
}

Apache Tapestry - 组件

如前所述,组件和页面是相同的,除了页面是根组件并包含一个或多个子组件。组件始终驻留在页面内部,并执行页面几乎所有动态功能。

Tapestry 组件呈现从简单的 HTML 链接到具有 交互式 AJAX 的复杂网格功能。组件还可以包含另一个组件。Tapestry 组件包含以下项目 -

  • 组件类 - 组件的主要 Java 类。

  • XML 模板 - XML 模板类似于页面模板。组件类呈现模板作为最终输出。某些组件可能没有模板。在这种情况下,输出将由组件类本身使用 MarkupWriter 类生成。

  • 主体 - 在页面模板内指定的组件可能具有自定义标记,称为“组件主体”。如果组件模板具有 <body /> 元素,则 <body /> 元素将被组件的主体替换。这类似于前面 XML 模板部分中讨论的布局。

  • 渲染 - 渲染是一个将 XML 模板和组件的主体转换为组件实际输出的过程。

  • 参数 - 用于在组件和页面之间创建通信,从而在它们之间传递数据。

  • 事件 - 将功能从组件委托给其容器/父级(页面或另一个组件)。它广泛用于页面导航目的。

渲染

组件的渲染是在一系列预定义的阶段中完成的。组件系统中的每个阶段都应该有一个相应的约定或注解在组件类中定义的方法。

// Using annotaion 
@SetupRender 
void initializeValues() { 
   // initialize values 
}

// using convention 
boolean afterRender() { 
   // do logic 
   return true; 
}

阶段、其方法名称及其注解列在下面。

注解 默认方法名称
@SetupRender setupRender()
@BeginRender beginRender()
@BeforeRenderTemplate beforeRenderTemplate()
@BeforeRenderBody beforeRenderBody()
@AfterRenderBody afterRenderBody()
@AfterRenderTemplate afterRenderTemplate()
@AfterRender afterRender()
@CleanupRender cleanupRender()

每个阶段都有特定的目的,它们如下 -

SetupRender

SetupRender 启动渲染过程。它通常设置组件的参数。

BeginRender

BeginRender 开始渲染组件。它通常渲染组件的开始/起始标签。

BeforeRenderTemplate

BeforeRenderTemplate 用于装饰 XML 模板,在模板周围添加特殊标记。它还提供跳过模板渲染的选项。

BeforeRenderBody

BeforeRenderTemplate 提供跳过组件的主体元素渲染的选项。

AfterRenderBody

AfterRenderBody 将在组件的主体渲染后被调用。

AfterRenderTemplate

AfterRenderTemplate 将在组件的模板渲染后被调用。

AfterRender

AfterRender 是 BeginRender 的对应项,通常渲染结束标签。

CleanupRender

CleanupRender 是 SetupRender 的对应项。它释放/处置渲染过程中创建的所有对象。

渲染阶段的流程并不仅仅是向前。它根据阶段的返回值在阶段之间来回切换。

例如,如果 SetupRender 方法返回 false,则渲染跳转到 CleanupRender 阶段,反之亦然。要清楚了解不同阶段之间的流程,请查看下面给出的图中的流程。

Annotation List

简单组件

让我们创建一个简单的组件 Hello,其输出消息为“Hello, Tapestry”。以下是 Hello 组件及其模板的代码。

package com.example.MyFirstApplication.components;  
public class Hello {  
}
<html  
   xmlns:t = "https://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter"> 
  
   <div> 
      <p>Hello, Tapestry (from component).</p> 
   </div> 
  
</html>

Hello 组件可以在页面模板中调用,如下所示:

<html title = "Hello component test page" 
   xmlns:t = "https://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter"> 
<t:hello />  
</html>

类似地,组件可以使用 MarkupWriter 而不是模板来呈现相同的输出,如下所示。

package com.example.MyFirstApplication.components; 
  
import org.apache.tapestry5.MarkupWriter; 
import org.apache.tapestry5.annotations.BeginRender;   

public class Hello { 
   @BeginRender 
   void renderMessage(MarkupWriter writer) { 
      writer.write("<p>Hello, Tapestry (from component)</p>"); 
   } 
}

让我们更改组件模板并包含 <body /> 元素,如下面的代码块所示。

<html>  
   xmlns:t = "https://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter"> 
   
   <div> 
      <t:body /> 
   </div> 
</html>

现在,页面模板可以在组件标记中包含 body,如下所示。

<html title = "Hello component test page" 
   xmlns:t = "https://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter"> 
   
   <t:hello> 
      <p>Hello, Tapestry (from page).</p> 
   </t:hello> 
</html>

输出如下所示:

<html> 
   <div> 
      <p>Hello, Tapestry (from page).</p> 
   </div> 
</html>

参数

这些参数的主要目的是在组件的字段和页面的属性/资源之间建立连接。使用参数,组件及其对应的页面之间进行通信并相互传递数据。这称为双向数据绑定

例如,用于在用户管理页面中表示年龄的文本框组件通过参数获取其初始值(在数据库中可用)。同样,在用户更新年龄并提交后,组件将通过相同的参数发送回更新后的年龄。

要在组件类中创建新参数,请声明一个字段并指定@Parameter注解。此 @Parameter 具有两个可选参数,它们是:

  • required - 将参数设为必填。如果未提供,Tapestry 将引发异常。

  • value - 指定参数的默认值。

参数应在页面模板中指定为组件标签的属性。属性的值应使用我们在前面章节中讨论的绑定表达式/扩展来指定。我们之前学到的一些扩展包括:

  • 属性扩展 (prop:«val») - 从页面类的属性中获取数据。

  • 消息扩展 (message:«val») - 从 index.properties 文件中定义的键获取数据。

  • 上下文扩展 (context:«val») - 从 Web 上下文文件夹 /src/main/webapp 获取数据。

  • 资产扩展 (asset:«val») - 从 jar 文件中嵌入的资源获取数据,/META-INF/assets。

  • 符号扩展 (symbol:«val») - 从 AppModule.java 文件中定义的符号获取数据。

Tapestry 还有许多其他有用的扩展,其中一些如下所示:

  • 文字扩展 (literal:«val») - 一个文字字符串。

  • 变量扩展 (var:«val») - 允许读取或更新组件的渲染变量。

  • 验证扩展 (validate:«val») - 用于指定对象验证规则的特殊字符串。例如,validate:required, minLength = 5。

  • 翻译 (translate:«val») - 用于在输入验证中指定 Translator 类(将客户端表示转换为服务器端表示)。

  • 块 (block:«val») - 模板中块元素的 ID。

  • 组件 (component:«val») - 模板中另一个组件的 ID。

除了属性扩展和变量扩展之外,以上所有扩展都是只读的。它们由组件用于与页面交换数据。在使用扩展作为属性值时,${...}不应使用。而应该只使用不带美元符号和括号符号的扩展。

使用参数的组件

让我们通过修改 Hello 组件来创建一个新的组件 HelloWithParameter,通过在组件类中添加name参数并相应地更改组件模板和页面模板来动态渲染消息。

  • 创建一个新的组件类HelloWithParameter.java

  • 添加一个私有字段并使用@Parameter注解命名它。使用 required 参数使其成为必填项。

@Parameter(required = true) 
private String name;
  • 添加一个私有字段 result,并使用@Propery注解。result 属性将在组件模板中使用。组件模板无法访问使用@Parameter注解的字段,只能访问使用@Property注解的字段。组件模板中可用的变量称为渲染变量。

@Property 
 private String result;
  • 添加一个 RenderBody 方法,并将 name 参数的值复制到 result 属性。

@BeginRender 
void initializeValues() { 
   result = name; 
}
  • 添加一个新的组件模板HelloWithParamter.tml,并使用 result 属性渲染消息。

<div> Hello, ${result} </div>
  • 在测试页面(testhello.java)中添加一个新属性 Username。

public String getUsername() { 
   return "User1"; 
}
  • 在页面模板中使用新创建的组件,并在HelloWithParameter组件的 name 参数中设置 Username 属性。

<t:helloWithParameter name = "username" /> 

完整的列表如下所示:

package com.example.MyFirstApplication.components;  

import org.apache.tapestry5.annotations.*;  
public class HelloWithParameter { 
   @Parameter(required = true) 
   private String name; 
     
   @Property 
   private String result; 
   
   @BeginRender 
   void initializeValues() { 
      result = name; 
   } 
}
<html  
   xmlns:t = "https://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter"> 
   
   <div> Hello, ${result} </div> 
  
</html>
package com.example.MyFirstApplication.pages;  

import org.apache.tapestry5.annotations.*;  
public class TestHello { 
   public String getUsername() { 
      return "User1"; 
   } 
}
<html title = "Hello component test page" 
   xmlns:t = "https://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter"> 
   <t:helloWithParameter name = "username" />
   
</html> 

结果如下所示:

<div> Hello, User1 </div>

高级参数

在前面的章节中,我们分析了如何在自定义组件中创建和使用简单参数。高级参数也可以包含完整的标记。在这种情况下,标记应在组件标签内指定,例如页面模板中的子部分。内置的 if 组件具有成功和失败条件的标记。成功标记指定为组件标签的主体,失败标记使用elseparameter指定。

让我们看看如何使用if组件。if 组件有两个参数:

  • test - 基于简单属性的参数。

  • Else - 用于指定备用标记的高级参数,如果条件失败。

Tapestry 将使用以下逻辑检查 test 属性的值并返回 true 或 false。这称为类型强制转换,一种将一种类型的对象转换为具有相同内容的另一种类型的方法。

  • 如果数据类型是String,“True”表示非空白且不是文字字符串“False”(不区分大小写)。

  • 如果数据类型是Number,则非零表示 True。

  • 如果数据类型是Collection,则非空表示 True。

  • 如果数据类型是Object,则为 True(只要它不为空)。

如果条件通过,则组件渲染其主体;否则,它渲染 else 参数的主体。

完整的列表如下所示:

package com.example.MyFirstApplication.pages; 
public class TestIf { 
   public String getUser() { 
      return "User1"; 
   } 
}

<html title = "If Test Page" 
   xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter">  
   
   <body> 
      <h1>Welcome!</h1>  
      <t:if test = "user"> 
         Welcome back, ${user} 
         <p:else>
            Please <t:pagelink page = "login">Login</t:pagelink>  
         </p:else> 
      </t:if>
   </body>
   
</html>

组件事件/页面导航

Tapestry 应用程序是页面集合,它们相互交互。到目前为止,我们已经学习了如何在不进行任何通信的情况下创建各个页面。组件事件的主要目的是使用服务器端事件提供页面之间(以及页面内)的交互。大多数组件事件源自客户端事件。

例如,当用户在页面中点击链接时,Tapestry 将调用相同的页面本身并带有目标信息,而不是调用目标页面并引发服务器端事件。Tapestry 页面将捕获事件,处理目标信息并在服务器端重定向到目标页面。

Tapestry 遵循Post/Redirect/Get (RPG) 设计模式进行页面导航。在 RPG 中,当用户通过提交表单执行 POST 请求时,服务器将处理发布的数据,但不会直接返回响应。相反,它将对另一个页面执行客户端重定向,该页面将输出结果。RPG 模式用于防止通过浏览器后退按钮、浏览器刷新按钮等重复提交表单,Tapestry 通过提供以下两种类型的请求来提供 RPG 模式。

  • 组件事件请求 - 此类型的请求针对页面中的特定组件并在组件内引发事件。此请求仅执行重定向,不输出响应。

  • 渲染请求 - 这些类型的请求针对页面并将响应流回客户端。

要了解组件事件和页面导航,我们需要了解 Tapestry 请求的 URL 模式。两种类型的请求的 URL 模式如下所示:

  • 组件事件请求 -

/<<page_name_with_path>>.<<component_id|event_id>>/<<context_information>>
  • 渲染请求 -

/<<page_name_with_path>>/<<context_information>>

一些 URL 模式的示例如下所示:

  • 可以通过https://«domain»/«app»/index请求索引页面。

  • 如果索引页面位于子文件夹 admin 下,则可以通过https://«domain»/«app»/admin/index请求它。

  • 如果用户点击索引页面中id 为 testActionLink 组件,则 URL 将为https://«domain»/«app»/index.test

事件

默认情况下,Tapestry 为所有请求引发OnPassivateOnActivate事件。对于组件事件请求类型,Tapestry 会根据组件引发一个或多个其他事件。ActionLink 组件引发 Action 事件,而 Form 组件引发多个事件,例如Validate、Success等。

可以使用相应的的方法处理器在页面类中处理事件。方法处理器是通过方法命名约定或@OnEvent注解创建的。方法命名约定的格式为On«EventName»From«ComponentId»

id 为 test的 ActionLink 组件的动作事件可以通过以下两种方法之一进行处理:

void OnActionFromTest() { 
}  
@OnEvent(component = "test", name = "action") 
void CustomFunctionName() { 
} 

如果方法名称没有任何特定组件,则将为所有具有匹配事件的组件调用该方法。

void OnAction() { 
} 

OnPassivate 和 OnActivate 事件

OnPassivate 用于为 OnActivate 事件处理器提供上下文信息。通常,Tapestry 提供上下文信息,它可以用作 OnActivate 事件处理程序中的参数。

例如,如果上下文信息是 int 类型的 3,则可以调用 OnActivate 事件,如下所示:

void OnActivate(int id) { 
} 

在某些情况下,上下文信息可能不可用。在这种情况下,我们可以通过 OnPassivate 事件处理器为 OnActivate 事件处理器提供上下文信息。OnPassivate 事件处理器的返回类型应用作 OnActivate 事件处理程序的参数。

int OnPassivate() { 
   int id = 3; 
   return id; 
} 
void OnActivate(int id) { 
} 

事件处理程序返回值

Tapestry 根据事件处理程序的返回值发出页面重定向。事件处理程序应返回以下值之一。

  • 空响应 - 返回空值。Tapestry 将构造当前页面的 URL 并将其发送到客户端作为重定向。

public Object onAction() { 
   return null; 
}
  • 字符串响应 - 返回字符串值。Tapestry 将构造与该值匹配的页面的 URL 并将其发送到客户端作为重定向。

public String onAction() { 
   return "Index"; 
}
  • 类响应 - 返回页面类。Tapestry 将构造返回的页面类的 URL 并将其发送到客户端作为重定向。

public Object onAction() { 
   return Index.class 
}
  • 页面响应 - 返回使用 @InjectPage 注解的字段。Tapestry 将构造注入页面的 URL 并将其发送到客户端作为重定向。

@InjectPage 
private Index index;  

public Object onAction(){ 
   return index; 
}
  • HttpError - 返回 HTTPError 对象。Tapestry 将发出客户端 HTTP 错误。

public Object onAction(){ 
   return new HttpError(302, "The Error message); 
}
  • 链接响应 - 直接返回链接实例。Tapestry 将从 Link 对象构造 URL 并将其发送到客户端作为重定向。

  • 流响应 - 返回StreamResponse对象。Tapestry 将直接将流作为响应发送到客户端浏览器。它用于直接生成报告和图像并将其发送到客户端。

  • URL 响应 - 返回java.net.URL对象。Tapestry 将从对象获取相应的 URL 并将其发送到客户端作为重定向。

  • 对象响应 - 返回除上述指定值之外的任何值。Tapestry 将引发错误。

事件上下文

通常,事件处理程序可以使用参数获取上下文信息。例如,如果上下文信息是 int 类型的 3,则事件处理程序将是:

Object onActionFromTest(int id) {  
} 

Tapestry 正确处理上下文信息并通过参数将其提供给方法。有时,由于编程的复杂性,Tapestry 可能无法正确处理它。那时,我们可以获取完整的上下文信息并自行处理。

Object onActionFromEdit(EventContext context) { 
   if (context.getCount() > 0) { 
      this.selectedId = context.get(0); 
   } else { 
      alertManager.warn("Please select a document."); 
      return null; 
   } 
}

Apache Tapestry - 内置组件

本章介绍了 Tapestry 具有适当示例的内置组件。Tapestry 支持 65 多个内置组件。您还可以创建自定义组件。让我们详细介绍一些值得注意的组件。

如果组件

if 组件用于有条件地渲染一个代码块。条件由一个测试参数检查。

创建一个名为 IfSample.java 的页面,如下所示:

package com.example.MyFirstApplication.pages;  

public class Ifsample {
   public String getUser() { 
      return "user1"; 
   } 
} 

现在,创建一个对应的模板文件,如下所示:

<html t:type = "newlayout" title = "About MyFirstApplication" 
   xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter"> 
     
   <h3>If-else component example </h3> 
   <t:if test = "user"> 
      Hello ${user} 
      <p:else>
         <h4> You are not a Tapestry user </h4> 
      </p:else> 
   </t:if> 
</html>

请求页面将渲染如下所示的结果。

结果 − https://:8080/MyFirstApplication/ifsample

If Component Result

Unless 和 Delegate 组件

unless 组件 正好与上面讨论的 if 组件相反。而 delegate 组件 本身不做任何渲染。相反,它通常将标记委托给块元素。Unless 和 if 组件可以使用 delegate 和 block 有条件地交换动态内容。

创建一个名为 Unless.java 的页面,如下所示。

package com.example.MyFirstApplication.pages;  

import org.apache.tapestry5.Block; 
import org.apache.tapestry5.annotations.Property; 
import org.apache.tapestry5.ioc.annotations.Inject; 
import org.apache.tapestry5.PersistenceConstants; 
import org.apache.tapestry5.annotations.Persist;  

public class Unless { 
   @Property 
   @Persist(PersistenceConstants.FLASH) 
   private String value;  
   @Property 
   private Boolean bool; 
   @Inject 
   Block t, f, n;  
   
   public Block getCase() { 
      if (bool == Boolean.TRUE ) { 
         return t; 
      } else { 
         return f; 
      } 
   }   
} 

现在,创建一个对应的模板文件,如下所示:

<html t:type = "newlayout" title = "About MyFirstApplication" 
   xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter"> 
  
   <h4> Delegate component </h4> 
   <div class = "div1"> 
      <t:delegate to = "case"/> 
   </div> 
   <h4> If-Unless component </h4>  
   
   <div class = "div1"> 
      <t:if test = "bool"> 
         <t:delegate to = "block:t"/> 
      </t:if> 
      <t:unless test = "bool"> 
         <t:delegate to = "block:notT"/> 
      </t:unless> 
   </div>  
   
   <t:block id = "t"> 
      bool == Boolean.TRUE. 
   </t:block> 
   
   <t:block id = "notT"> 
      bool = Boolean.FALSE. 
   </t:block> 
   
   <t:block id = "f"> 
      bool == Boolean.FALSE. 
   </t:block> 
</html>

请求页面将渲染如下所示的结果。

结果 − https://:8080/MyFirstApplication/unless

Delegate Component

循环组件

循环组件是循环遍历集合项并为每个值/迭代渲染主体的一个基本组件。

创建一个如下所示的循环页面:

Loop.java

package com.example.MyFirstApplication.pages;  

import org.apache.tapestry5.annotations.Property;  
public class Loop { 
   @Property 
   private int i; 
}

然后,创建相应的模板 Loop.tml

Loop.tml

<html t:type = "newlayout" title = "About MyFirstApplication" 
   xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter">  
   
   <p>This is sample parameter rendering example...</p> 
   <ol>
      <li t:type = "loop" source = "1..5" value = "var:i">${var:i}</li> 
   </ol> 
</html>

Loop 组件具有以下两个参数:

  • source − 集合源。1…5 是一个属性扩展,用于创建一个指定范围的数组。

  • var − 渲染变量。用于在模板的主体中渲染当前值。

请求页面将渲染如下所示的结果:

Loop Component

PageLink 组件

PageLink 组件用于在一个页面之间链接页面。创建一个如下所示的 PageLink 测试页面:PageLink.java

package com.example.MyFirstApplication.pages;  
   public class PageLink { 
}

然后,创建一个对应的模板文件,如下所示:

PageLink.tml

<html t:type = "newlayout" title = "About MyFirstApplication" 
   xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter">  
   
   <body> 
      <h3><u>Page Link</u> </h3> 
      <div class = "page"> 
         <t:pagelink page = "Index">Click here to navigate Index page</t:pagelink>
         <br/> 
      </div> 
   </body> 
   
</html>

PageLink 组件有一个 page 参数,该参数应引用目标 Tapestry 页面。

结果 − https://:8080/myFirstApplication/pagelink

Page Link

EventLink 组件

EventLink 组件通过 URL 发送事件名称和相应的参数。创建一个如下所示的 EventsLink 页面类。

EventsLink.java

package com.example.MyFirstApplication.pages;  

import org.apache.tapestry5.annotations.Property;  
public class EventsLink { 
   @Property 
   private int x; 
   void onActivate(int count) { 
      this.x = x; 
   } 
   int onPassivate() { 
      return x; 
   } 
   void onAdd(int value) { 
      x += value; 
   }   
}

然后,创建一个对应的“EventsLink”模板文件,如下所示:

EventsLink.tml

<html t:type = "newlayout" title = "About MyFirstApplication" 
   xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter"> 
   
   <h3> Event link example </h3> 
   AddedCount = ${x}. <br/> 
   <t:eventlink t:event = "add" t:context = "literal:1">
      Click here to add count
   </t:eventlink><br/>  
</html>

EventLink 具有以下两个参数:

  • Event − 要在 EventLink 组件中触发的事件的名称。默认情况下,它指向组件的 id。

  • Context − 这是一个可选参数。它定义了链接的上下文。

结果 − https://:8080/myFirstApplication/EventsLink

Event Link

单击计数值后,页面将在 URL 中显示事件名称,如下面的输出屏幕截图所示。

Event Link Result

ActionLink 组件

ActionLink 组件类似于 EventLink 组件,但它只发送目标组件 id。默认事件名称为 action。

创建一个名为“ActivationLinks.java”的页面,如下所示,

ActivationLinks.java

package com.example.MyFirstApplication.pages;  

import org.apache.tapestry5.annotations.Property;  
public class ActivationLinks {  
   @Property 
   private int x;  
   void onActivate(int count) { 
      this.x = x; 
   }  
   int onPassivate() { 
      return x; 
   } 
   void onActionFromsub(int value) { 
      x -= value; 
   } 
} 

现在,创建一个对应的模板文件,如下所示:

ActivationLinks.tml

<html t:type = "Newlayout" title = "About MyFirstApplication" 
   xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter">  
   
   <div class = "div1"> 
      Count = ${count}. <br/> 
      <t:actionlink t:id = "sub" t:context = "literal:1">
         Decrement
      </t:actionlink><br/> 
   </div> 
   
</html> 

在这里,当单击 ActionLink 组件时,将调用 OnActionFromSub 方法。

结果 − https://:8080/myFirstApplication/ActivationsLink

Action Link

Alert 组件

警报对话框主要用于向用户发出警告消息。例如,如果输入字段需要一些必填文本,但用户未提供任何输入,则作为验证的一部分,您可以使用警报框发出警告消息。

创建一个名为“Alerts”的页面,如下面的程序所示。

Alerts.java

package com.example.MyFirstApplication.pages;  

public class Alerts { 
   public String getUser() { 
      return "user1"; 
   } 
}

然后,创建一个对应的模板文件,如下所示:

Alerts.tml

<html t:type = "Newlayout" title = "About MyFirstApplication" 
   xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd"  
   xmlns:p = "tapestry:parameter">  
   
   <h3>Alerts</h3> 
   <div class = "alert alert-info"> 
      <h5> Welcome ${user} </h5> 
   </div>
   
</html>

Alert 有三个严重级别,它们是:

  • 信息
  • 警告
  • 错误

以上模板是使用信息警报创建的。它定义为 alert-info。您可以根据需要创建其他严重级别。

请求页面将产生以下结果:

https://:8080/myFirstApplication/Alerts

Alerts

表单和验证组件

Form 组件 用于在 Tapestry 页面中创建用于用户输入的表单。表单可以包含文本字段、日期字段、复选框字段、选择选项、提交按钮等。

本章详细介绍了一些值得注意的表单组件。

Checkbox 组件

Checkbox 组件用于在两个互斥选项之间进行选择。使用 Checkbox 创建一个页面,如下所示:

Checkbox.java

package com.example.MyFirstApplication.pages;  

import org.apache.tapestry5.annotations.Property;  

public class Checkbox { 
   @Property 
   private boolean check1; 
   
   @Property 
   private boolean check2; 
}

现在,创建一个对应的模板 Checkbox.tml,如下所示:

<html t:type = "newlayout" title = "About MyFirstApplication" 
   xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter"> 
   
   <h3> checkbox component</h3>  
   <t:form> 
      <t:checkbox t:id = "check1"/> I have a bike <br/> 
      <t:checkbox t:id = "check2"/> I have a car 
   </t:form>  
   
</html> 

这里,复选框参数 id 与相应的布尔值匹配。

结果 − 请求页面后,https://:8080/myFirstApplication/checkbox 将产生以下结果。

Checkbox

TextField 组件

TextField 组件允许用户编辑一行文本。创建一个名为 Text 的页面,如下所示。

Text.java

package com.example.MyFirstApplication.pages;  

import org.apache.tapestry5.annotations.Property; 
import org.apache.tapestry5.corelib.components.TextField;public class Text {  
   @Property 
   private String fname;  
   @Property 
   private String lname; 
}

然后,创建一个对应的模板,如下所示 – Text.tml

<html t:type = "newlayout" title = "About MyFirstApplication" 
   xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter">  
   <p> Form application </p>
   
   <body>  
      <h3> Text field created from Tapestry component </h3> 
      <t:form>  
         <table> 
            <tr> 
               <td> 
                  Firstname: </td> <td><t:textfield t:id = "fname" /> 
               </td> 
               <td>Lastname: </td> <td> <t:textfield t:id = "lname" /> </td> 
            </tr> 
         </table>  
      </t:form>  
   </body> 
   
</html>

这里,Text 页面包含一个名为 fnamelname 的属性。组件 id 由属性访问。

请求页面将产生以下结果:

https://:8080/myFirstApplication/Text

Text Field

PasswordField 组件

PasswordField 是一个专门用于密码的文本字段条目。创建一个名为 Password 的页面,如下所示:

Password.java

package com.example.MyFirstApplication.pages;  

import org.apache.tapestry5.annotations.Property; 
import org.apache.tapestry5.corelib.components.PasswordField;  

public class Password {  
   @Property 
   private String pwd; 
}

现在,创建一个对应的模板文件,如下所示:

Password.tml

<html t:type = "newlayout" title = "About MyFirstApplication" 
   xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter">  
   <p> Form application </p>  
   <h3> Password field created from Tapestry component </h3> 
   
   <t:form> 
      <table> 
         <tr> 
            <td> Password: </td> 
            <td><t:passwordfield t:id = "pwd"/> </td> 
         </tr> 
      </table> 
   </t:form>
   
</html> 

这里,PasswordField 组件具有参数 id,它指向属性 pwd。请求页面将产生以下结果:

https://:8080/myFirstApplication/Password

Password Field

TextArea 组件

TextArea 组件是一个多行输入文本控件。创建一个名为 TxtArea 的页面,如下所示。

TxtArea.java

package com.example.MyFirstApplication.pages;  

import org.apache.tapestry5.annotations.Property; 
import org.apache.tapestry5.corelib.components.TextArea;  

public class TxtArea {  
   @Property 
   private String str;  
}

然后,创建一个对应的模板文件,如下所示。

TxtArea.tml

<html t:type = "newlayout" title = "About MyFirstApplication" 
   xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter"> 
   <h3>TextArea component </h3>
   
   <t:form>
      <table>
         <tr> 
            <td><t:textarea t:id = "str"/>
            </td>
         </tr>
      </table>
   </t:form>
   
</html>

这里,TextArea 组件参数 id 指向属性“str”。请求页面将产生以下结果:

https://:8080/myFirstApplication/TxtArea**

TextArea Component

Select 组件

Select 组件包含一个下拉列表。创建一个名为 SelectOption 的页面,如下所示。

SelectOption.java

package com.example.MyFirstApplication.pages;  

import org.apache.tapestry5.annotations.Property; 
import org.apache.tapestry5.corelib.components.Select;  

public class SelectOption { 
   @Property 
   private String color0; 
   
   @Property 
   
   private Color1 color1; 
   public enum Color1 { 
      YELLOW, RED, GREEN, BLUE, ORANGE 
   } 
}

然后,创建一个对应的模板,如下所示:

SelectOption.tml

<html t:type = "newlayout" title = "About MyFirstApplication" 
   xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter"> 
   <p> Form application </p>
   <h3> select component </h3>  
   
   <t:form> 
      <table> 
         <tr> 
            <td> Select your color here: </td> 
            <td> <select t:type = "select" t:id = "color1"></select></td> 
         </tr> 
      </table> 
   </t:form>
   
</html>

这里,Select 组件有两个参数:

  • Type − 属性的类型是一个枚举。

  • Id − Id 指向 Tapestry 属性“color1”。

请求页面将产生以下结果:

https://:8080/myFirstApplication/SelectOption

Select Component

RadioGroup 组件

RadioGroup 组件为 Radio 组件提供一个容器组。Radio 和 RadioGroup 组件协同工作以更新对象的属性。此组件应围绕其他 Radio 组件。创建一个名为“Radiobutton.java”的新页面,如下所示:

Radiobutton.java

package com.example.MyFirstApplication.pages;  

import org.apache.tapestry5.PersistenceConstants; 
import org.apache.tapestry5.annotations.Persist; 
import org.apache.tapestry5.annotations.Property;  

public class Radiobutton {  
   @Property 
   @Persist(PersistenceConstants.FLASH)  
   private String value; 
}

然后,创建一个对应的模板文件,如下所示:

Radiobutton.tml

<html t:type = "Newlayout" title = "About MyFirstApplication" 
   xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter">  
   <h3>RadioGroup component </h3> 
   
   <t:form>
      <t:radiogroup t:id = "value">
         <t:radio t:id = "radioT" value = "literal:T" label = "Male" /> 
         <t:label for = "radioT"/>    
         <t:radio t:id = "radioF" value = "literal:F" label = "Female"/> 
         <t:label for = "radioF"/>   
      </t:radiogroup>
   </t:form>
   
</html>

这里,RadioGroup 组件 id 与属性“value”绑定。请求页面将产生以下结果。

https://:8080/myFirstApplication/Radiobutton

Radio Group

Submit 组件

当用户单击提交按钮时,表单将发送到标签的 action 设置中指定的地址。创建一个名为 SubmitComponent 的页面,如下所示。

package com.example.MyFirstApplication.pages;  
import org.apache.tapestry5.annotations.InjectPage;  

public class SubmitComponent { 
   @InjectPage 
   private Index page1; 
   Object onSuccess() { 
      return page1; 
   }     
}

现在,创建一个对应的模板文件,如下所示。

SubmitComponent.tml

<html t:type = "newlayout" title = "About MyFirstApplication" 
   xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter"> 
   <h3>Tapestry Submit component </h3> 
   
   <body> 
      <t:form> 
         <t:submit t:id = "submit1" value = "Click to go Index"/> 
      </t:form> 
   </body>
   
</html>

这里,Submit 组件将值提交到 Index 页面。请求页面将产生以下结果:

https://:8080/myFirstApplication/SubmitComponent

Submit Component

表单验证

表单验证通常在客户端输入所有必要数据并提交表单后在服务器端发生。如果客户端输入的数据不正确或丢失,服务器将必须将所有数据发送回客户端,并请求使用正确的信息重新提交表单。

让我们考虑以下简单示例来了解验证过程。

创建一个名为 Validate 的页面,如下所示。

Validate.java

package com.example.MyFirstApplication.pages;  

import org.apache.tapestry5.annotations.Property; 
import org.apache.tapestry5.PersistenceConstants; 
import org.apache.tapestry5.annotations.Persist;  

public class Validate {  
   @Property 
   @Persist(PersistenceConstants.FLASH) 
   private String firstName; 
   
   @Property 
   @Persist(PersistenceConstants.FLASH) 
   private String lastName; 
}

现在,创建一个对应的模板文件,如下所示。

Validate.tml

<html t:type = "newlayout" title = "About MyFirstApplication" 
   xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter"> 
  
   <t:form> 
      <table> 
         <tr> 
            <td><t:label for = "firstName"/>:</td> 
            <td><input t:type = "TextField" t:id = "firstName" 
            t:validate = "required, maxlength = 7" size = "10"/></td>   
         </tr> 
         <tr> 
            <td><t:label for = "lastName"/>:</td> 
            <td><input t:type = "TextField" t:id = "lastName" 
            t:validate = "required, maxLength = 5" size = "10"/></td>  
         </tr>  
      </table>  
      <t:submit t:id = "sub" value =" Form validation"/>  
   </t:form>
   
</html>

表单验证具有以下重要参数:

  • Max − 定义最大值,例如 = «最大值,20»。

  • MaxDate − 定义最大日期,例如 = «最大日期,2013/09/06»。类似地,您也可以分配 MinDate。

  • MaxLength − 最大长度,例如 = «最大长度,80»。

  • Min − 最小值。

  • MinLength − 最小长度,例如 = «最小长度,2»。

  • Email − 电子邮件验证,使用标准电子邮件正则表达式 ^\w[._\w]*\w@\w[-._\w]*\w\.\w2,6$ 或无。

请求页面将产生以下结果:

https://:8080/myFirstApplication/Validate

Form Validation

Apache Tapestry - Ajax 组件

AJAX 代表 异步 JavaScript 和 XML。这是一种利用 XML、JSON、HTML、CSSJavaScript 创建更好、更快、更具交互性的 Web 应用程序的技术。AJAX 允许您异步发送和接收数据,而无需重新加载网页,因此速度很快。

Zone 组件

Zone 组件用于提供内容(标记)以及内容本身的位置。Zone 组件的主体由 Tapestry 内部用于生成内容。生成动态内容后,Tapestry 会将其发送到客户端,在正确的位置重新渲染数据,触发并动画化 HTML 以吸引用户的注意力。

此 Zone 组件与 EventLink 组件一起使用。EventLink 具有使用 t:zone 属性将其绑定到特定区域的选项。配置 EventLink 中的区域后,单击 EventLink 将触发区域更新。此外,EventLink 事件(refreshZone)可用于控制动态数据的生成。

一个简单的 AJAX 示例如下所示:

AjaxZone.tml

<html t:type = "Newlayout" title = "About MyFirstApplication" 
   xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd" 
   xmlns:p = "tapestry:parameter">  
   
   <body> 
      <h1>Ajax time zone example</h1>  
      <div class = "div1">  
         <a t:type = "eventlink" t:event = "refreshZone" href = "#" 
            t:zone = "timeZone">Ajax Link </a><br/><br/> 
         <t:zone t:id = "timeZone" id = "timeZone">Time zone: ${serverTime}</t:zone> 
      </div>  
   </body>
   
</html> 

AjaxZone.java

package com.example.MyFirstApplication.pages;  

import java.util.Date; 
import org.apache.tapestry5.annotations.InjectComponent; 
import org.apache.tapestry5.corelib.components.Zone; 
import org.apache.tapestry5.ioc.annotations.Inject; 
import org.apache.tapestry5.services.Request;  

public class AjaxZone { 
   @Inject 
   private Request request; 
   
   @InjectComponent 
   private Zone timeZone; 
   
   void onRefreshPage() { 
   } 
   
   Object onRefreshZone() { 
      return request.isXHR() ? timeZone.getBody() : null; 
   } 
   
   public Date getServerTime() { 
      return new Date(); 
   } 
} 

结果将显示在:https://:8080/MyFirstApplication/AjaxZone

Ajax Time Zone

Apache Tapestry - Hibernate

在本章中,我们将讨论 BeanEditFormGrid 组件 与 Hibernate 的集成。Hibernate 通过 hibernate 模块集成到 Tapestry 中。要启用 hibernate 模块,请在 pom.xml 文件中添加 tapestry-hibernate 依赖项以及可选的 hsqldb。现在,通过位于资源文件夹根目录下的 hibernate.cfg.xml 文件配置 hibernate。

pom.xml(部分)

<dependency> 
   <groupId>org.apache.tapestry</groupId> 
   <artifactId>tapestry-hibernate</artifactId> 
   <version>${tapestry-release-version}</version> 
</dependency>  

<dependency> 
   <groupId>org.hsqldb</groupId> 
   <artifactId>hsqldb</artifactId> 
   <version>2.3.2</version> 
</dependency>

Hibernate.cfg.xml

<!DOCTYPE hibernate-configuration PUBLIC 
   "-//Hibernate/Hibernate Configuration DTD 3.0//EN" 
   "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> 

<hibernate-configuration> 
   <session-factory> 
      <property name = "hibernate.connection.driver_class">
         org.hsqldb.jdbcDriver
      </property> 
      <property name = "hibernate.connection.url">
         jdbc:hsqldb:./target/work/sampleapp;shutdown = true
      </property> 
      <property name = "hibernate.dialect">
         org.hibernate.dialect.HSQLDialect
      </property> 
      
      <property name = "hibernate.connection.username">sa</property> 
      <property name = "hibernate.connection.password"></property> 
      <property name = "hbm2ddl.auto">update</property>
      <property name = "hibernate.show_sql">true</property> 
      <property name = "hibernate.format_sql">true</property> 
   </session-factory> 
</hibernate-configuration> 

让我们看看如何使用 BeanEditForm 组件创建 员工添加页面,以及如何使用 Grid 组件创建 员工列表页面。持久层由 Hibernate 模块处理。

创建一个员工类,并使用 @Entity 注解对其进行装饰。然后,为相关字段添加验证注解,以及与 hibernate 相关的注解 @Id 和 @GeneratedValue 用于 id 字段。此外,将性别创建为枚举类型。

Employee.java

package com.example.MyFirstApplication.entities;  

import javax.persistence.Entity; 
import javax.persistence.GeneratedValue; 
import javax.persistence.GenerationType; 
import javax.persistence.Id;  
import org.apache.tapestry5.beaneditor.NonVisual; 
import org.apache.tapestry5.beaneditor.Validate;  

@Entity 
public class Employee {  
   @Id 
   @GeneratedValue(strategy = GenerationType.IDENTITY) 
   @NonVisual 
   public Long id;  

   @Validate("required") 
   public String firstName;  
 
   @Validate("required") 
   public String lastName; 

   @Validate("required") 
   public String userName;  

   @Validate("required") 
   public String password;  

   @Validate("required") 
   public String email;  
   public String phone;  

   @Validate("required") 
   public String Street;  

   @Validate("required") 
   public String city;  

   @Validate("required") 
   public String state;  

   @Validate("required,regexp=^\\d{5}(-\\d{4})?$") 
   public String zip; 
} 
Gender.java (enum)  
package com.example.MyFirstApplication.data;  

public enum Gender { 
   Male, Female 
}

在 pages 下面的新文件夹 employee 中创建员工列表页面 ListEmployee.java,并在 /src/main/resources/pages/employee 文件夹中创建相应的模板文件 ListEmployee.tml。Tapestry 通过删除重复数据为子文件夹提供了简短的 URL。

例如,ListEmployee 页面可以通过普通 URL(/employee/listemployee)和简短 URL(/employee/list)访问。

使用 @Inject 注解将 Hibernate 会话注入到列表页面中。在列表页面中定义一个属性 getEmployees,并使用注入的会话对象使用员工填充它。完成员工类的代码,如下所示。

ListEmployee.java

package com.example.MyFirstApplication.pages.employee;  

import java.util.List;  
import org.apache.tapestry5.annotations.Import; 
import org.apache.tapestry5.ioc.annotations.Inject;  
import org.hibernate.Session; 
import com.example.MyFirstApplication.entities.Employee; 
import org.apache.tapestry5.annotations.Import;  
@Import(stylesheet="context:mybootstrap/css/bootstrap.css") 

public class ListEmployee { 
   @Inject 
   private Session session; 
   
   public List<Employee> getEmployees() { 
      return session.createCriteria(Employee.class).list(); 
   } 
} 

为 ListEmployee 类创建模板文件。模板将有两个主要组件,它们是:

  • PageLink − 创建员工链接页面。

  • Grid − 用于渲染员工详细信息。grid 组件具有 sources 属性以注入员工列表,并具有 include 属性以包含要渲染的字段。

ListEmployee.tml(列出所有员工)

<html t:type = "simplelayout" title = "List Employee" 
   xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd"> 
   <h1>Employees</h1> 
   
   <ul> 
      <li><t:pagelink page = "employee/create">Create new employee</t:pagelink></li> 
   </ul>  
   <t:grid source = "employees" 
      include = "userName,firstName,lastName,gender,dateOfBirth,phone,city,state"/>  
</html>

创建员工创建模板文件并包含 BeanEditForm 组件。该组件具有以下属性:

  • object − 包含源。

  • reorder − 定义要呈现的字段的顺序。

  • submitlabel − 表单提交按钮的消息

完整的代码如下所示:

<html t:type = "simplelayout" title = "Create New Address" 
   xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_4.xsd">  
   <t:beaneditform  
      object = "employee"  
      submitlabel = "message:submit-label" 
      reorder = "userName,password,firstName,lastName,
      dateOfBirth,gender,email,phone,s treet,city,state,zip" />  
</html> 

创建员工创建类并包含会话、员工属性、列表页面(导航链接)并定义组件的OnSuccess事件(更新数据的位置)。会话数据使用Hibernate会话持久化到数据库中。

完整的代码如下所示:

package com.example.MyFirstApplication.pages.employee;  

import com.example.MyFirstApplication.entities.Employee; 
import com.example.MyFirstApplication.pages.employee.ListEmployee; 
import org.apache.tapestry5.annotations.InjectPage; 
import org.apache.tapestry5.annotations.Property;  
import org.apache.tapestry5.hibernate.annotations.CommitAfter; 
import org.apache.tapestry5.ioc.annotations.Inject; 
import org.hibernate.Session;  

public class CreateEmployee { 
   @Property 
   private Employee employee;  
   @Inject 
   private Session session;  
   @InjectPage 
   private ListEmployee listPage;  
   @CommitAfter 
   Object onSuccess() { 
      session.persist(employee);  
      return listPage; 
   } 
}

添加CreateEmployee.properties文件并包含在表单验证中使用的消息。完整的代码如下所示:

zip-regexp=^\\d{5}(-\\d{4})?$ 
zip-regexp-message = Zip Codes are five or nine digits. Example: 02134 or 901251655. 
submit-label = Create Employee 

下面显示了员工创建页面和列表页面的屏幕截图:

Employee Creation

Creation Page

Apache Tapestry - 存储

每个Web应用程序都应该有一些方法来存储某些用户数据,例如用户对象、用户偏好等。例如,在购物车应用程序中,用户的选定商品/产品应保存在临时存储区(购物车)中,直到用户选择购买这些商品。我们可以将商品保存在数据库中,但这将过于昂贵,因为并非所有用户都会购买选定的商品。因此,我们需要一个临时的安排来存储/持久化这些商品。Apache Tapestry提供两种持久化数据的方法,它们是:

  • 持久化页面数据
  • 会话存储

两者都有其自身的优点和局限性。我们将在以下部分中进行检查。

持久化页面数据

持久化页面数据是一个简单的概念,用于在请求之间持久化单个页面中的数据,也称为页面级持久化。它可以使用@Persist注解来完成。

@Persist 
public int age; 

一旦某个字段用@Persist进行注解,该字段的值将在请求之间持久化,如果在请求期间更改了该值,则下次访问时将反映出来。Apache Tapestry提供了五种类型的策略来实现@Persist概念。它们如下所示:

  • 会话策略 − 使用会话持久化数据,这是默认策略。

  • 闪存策略 − 也使用会话持久化数据,但它是一个非常短暂的策略。数据仅在下一个请求中可用。

@Persist(PersistenceConstants.FLASH) 
private int age;
  • 客户端策略 − 将数据持久化到客户端,例如URL查询字符串、表单中的隐藏字段等。

@Persist(PersistenceConstants.FLASH) 
private int age; 
  • Hibernate实体策略 − 使用Hibernate模块作为实体持久化数据。实体将存储在Hibernate中,其引用(Java类名及其主键)将作为令牌保存在HttpSession中。实体将通过使用HttpSession中可用的令牌来恢复。

@Persist(HibernatePersistenceConstants.ENTITY) 
private Category category;
  • JPA实体策略 − 使用JPA模块持久化数据。它只能存储实体。

@Persist(JpaPersistenceConstants.ENTITY) 
private User user; 

会话存储

会话存储是一个高级概念,用于存储需要跨页面可用(例如多页面向导中的数据、已登录的用户详细信息等)的数据。会话存储提供两个选项,一个用于存储复杂对象,另一个用于存储简单值

  • 会话存储对象 − 用于存储复杂对象。

  • 会话属性 − 用于存储简单值。

会话存储对象(SSO)

可以使用@SessionStore注解创建SSO。SSO将使用对象的类型存储对象。例如,购物车对象将使用购物车类名作为令牌存储。因此,任何复杂对象都可以在应用程序中存储一次(每个用户一个)。

public class MySSOPage { 
   @SessionState 
   private ShoppingCart cart; 
}

SSO是一个专门的存储,应该仅用于存储复杂/特殊对象。简单数据类型也可以使用SSO存储,但是存储像String这样的简单数据类型只会使应用程序存储一个“String”值。在应用程序中使用单个“String”值是不可能的。您可以使用简单数据类型,因为Apache Tapestry提供了会话属性。

会话属性

会话属性允许按名称而不是类型存储数据。

public class MyPage { 
   @SessionAttribute  
   private String loggedInUsername; 
}

默认情况下,会话属性使用字段名称在会话中引用数据。我们可以通过注解参数更改引用名称,如下所示:

public class MyPage { 
   @SessionAttribute("loggedInUserName") 
   private String userName; 
}

使用名称作为会话引用的主要问题之一是,我们可能会意外地在多个类/页面中使用相同的名称。在这种情况下,存储的数据可能会意外更改。为了解决此问题,最好将名称与类/页面名称和包名称一起使用,例如com.myapp.pages.register.email,其中com.myapp.pages是包名称,register是页面/类名称,最后email是变量(要存储)名称。

Apache Tapestry - 高级功能

在本章中,我们将详细讨论Apache Tapestry的一些高级功能。

控制反转

Tapestry提供内置的控制反转库。Tapestry与IoC深度集成,并将其用于所有功能。Tapestry IoC配置基于Java本身,而不是像许多其他IoC容器那样基于XML。基于Tapestry IoC的模块打包到JAR文件中,只需将其放入类路径中即可,无需任何配置。Tapestry IoC的使用基于轻量级,这意味着:

  • 两个或三个方法的小接口。

  • 两个或三个参数的小方法。

  • 通过事件进行匿名通信,而不是显式方法调用。

模块

模块是扩展Tapestry应用程序功能的一种方式。Tapestry既有内置模块,也有大量第三方模块。Hibernate是Tapestry提供的热门且非常有用的模块之一。它还具有集成了JMX、JPA、Spring Framework、JSR 303 Bean Validation、JSON等模块。一些值得注意的第三方模块是:

  • Tapestry-Cayenne
  • Tapestry5-googleanalytics
  • Tapestry 5 团队 - Tapestry5-HighCharts
  • Tapestry 5 团队 - Tapestry5-jqPlot
  • Tapestry 5 团队 - Tapestry5-Jquery
  • Tapestry 5 团队 - Tapestry5-Jquery-mobile
  • Tapestry 5 团队 - Tapestry5-Portlet

运行时异常

Tapestry最好的功能之一是详细的错误报告。Tapestry通过提供最先进的异常报告来帮助开发人员。Tapestry异常报告是包含详细信息的简单HTML。任何人都可以轻松理解报告。Tapestry以HTML显示错误,并以纯文本格式保存异常,其中包含发生异常的日期和时间。这将有助于开发人员在生产环境中检查异常。开发人员可以确信可以修复任何问题,例如损坏的模板、意外的空值、不匹配的请求等。

实时类和模板重新加载

修改模板和类时,Tapestry将自动重新加载它们。此功能使应用程序更改能够立即反映出来,而无需经历构建和测试周期。此外,此功能极大地提高了应用程序开发的生产力。

假设应用程序的根包是org.example.myfirstapp。然后,将扫描以下路径中的类以进行重新加载。

  • org.example.myfirstapp.pages
  • org.example.myfirstapp.components
  • org.example.myfirstapp.mixins
  • org.example.myfirstapp.base
  • org.example.myfirstapp.services

可以通过在AppModule.java中将生产模式设置为true来禁用实时类重新加载。

configuration.add(SymbolicConstants.PRODUCTION_MODE,”false”);

单元测试

单元测试是一种测试单个页面和组件的技术。Tapestry提供了轻松单元测试页面和组件的选项。

测试页面:Tapestry提供了一个名为PageTester的类来测试应用程序。它充当浏览器和servlet容器。它在服务器端本身渲染页面,无需浏览器,并且可以检查结果文档的正确渲染。考虑一个简单的页面Hello,它渲染hello,并且hello文本包含在id为hello_id的html元素内。要测试此功能,我们可以使用PageTester,如下所示:

public class PageTest extends Assert { 
   @Test 
   public void test1() { 
      Sring appPackage = "org.example.myfirstapp"; // package name 
      String appName = "App1"; // app name 
      PageTester tester = new PageTester(appPackage, appName, "src/main/webapp"); 
      Document doc = tester.renderPage("Hello"); 
      assertEquals(doc.getElementById("hello_id").getChildText(), "hello"); 
   } 
}

除了渲染页面之外,PageTester还提供包含上下文信息、表单提交、链接导航等的选项。

集成测试

集成测试有助于将应用程序作为一个模块进行测试,而不是像单元测试那样检查各个页面。在集成测试中,可以将多个模块作为一个单元一起测试。Tapestry提供了一个名为Tapestry Test Utilities的小型库来执行集成测试。该库与Selenium测试工具集成以执行测试。该库提供了一个基类SeleniumTestCase,它启动并管理Selenium服务器、Selenium客户端和Jetty实例。

集成测试的一个示例如下所示:

import org.apache.tapestry5.test.SeleniumTestCase; 
import org.testng.annotations.Test;  

public class IntegrationTest extends SeleniumTestCase { 
   @Test 
   public void persist_entities() {  
      open("/persistitem"); 
      assertEquals(getText("//span[@id='name']").length(), 0); 
      clickAndWait("link = create item"); 
      assertText("//span[@id = 'name']", "name"); 
   } 
}

开发仪表盘

开发仪表盘是用于识别/解决应用程序中问题的默认页面。可以通过URLhttps://:8080/myfirstapp/core/t5dashboard访问仪表盘。仪表盘显示应用程序中可用的所有页面、服务和组件库。

响应压缩

Tapestry使用GZIP压缩自动压缩响应并将其流式传输到客户端。此功能将减少网络流量并有助于更快地交付页面。可以使用AppModule.java中的符号tapestry.min-gzip-size配置压缩。默认值为100字节。一旦响应的大小超过100字节,Tapestry将压缩响应。

安全性

Tapestry提供了许多选项来保护应用程序免受Web应用程序中已知的安全漏洞的攻击。其中一些选项如下所示:

  • HTTPS − 可以使用@Secure注解Tapestry页面,使其成为安全页面,并且只能通过https协议访问。

  • 页面访问控制 − 控制仅允许特定用户访问页面。

  • 白名单页面 − 可以使用@WhitelistAccessOnly注解Tapestry页面,使其只能通过localhost访问。

  • 资产安全 − 在Tapestry下,只能访问某些类型的文件。其他文件只有在提供文件的MD5哈希时才能访问。

  • 序列化对象日期 − Tapestry将HMAC集成到序列化的Java对象数据中,并将其发送到客户端以避免消息篡改。

  • 跨站点请求伪造 − Tapestry提供了一个名为tapestry-csrf-protection的第三方模块来防止任何CSRF攻击。

  • 安全框架集成 − Tapestry不会锁定到单个身份验证/授权实现。Tapestry可以与任何流行的身份验证框架集成。

日志记录

Tapestry为日志记录提供了广泛的支持,日志记录是在应用程序运行时自动记录其进度。Tapestry使用事实上的Java日志记录库SLF4J@Log注解可以在任何组件方法中使用,以发出方法的入口和出口以及可能的异常。此外,Tapestry提供的日志记录器对象可以使用@Inject注解注入到任何组件中,如下所示:

public class MyPage { 
   @Inject 
   private Logger logger; 
   
   // . . . 
    
   void onSuccessFromForm() { 
      logger.info("Changes saved successfully"); 
   } 
     
   @Log 
   void onValidateFromForm() { 
      // logic 
   } 
}

最后,我们现在可以说Apache Tapestry提供了构建简洁、可扩展、可维护、健壮且支持Ajax的应用程序的最佳方法。Tapestry可以与任何第三方Java应用程序集成。它还可以帮助创建大型Web应用程序,因为它非常简单快捷。

广告

© . All rights reserved.