Vaadin 的体系结构介绍
Vaadin 应用程序实质上是运行在 Java Web 服务器上的 Servlet,专门响应 HTTP 请求,它包含一套 web 应用编程 API,一组 UI 用户界面组件,用来控制外观的主题和一个与用户界面直接绑定的数据模型。此外 Vaadin 还包含一个终端适配器 (terminal adapter), 它的作用是接受 web 浏览器发送的请求并将响应渲染到程序的页面上。终端适配器通过 Web 服务器的 Java Servlet API 接收到客户端的请求,并且把这些请求转换为特定会话 (session) 中的用户事件。 每个用户事件都与一个 Server 端 UI 组件关联在一起并且被交付到应用程序中。当应用程序的逻辑改变了 Server 端 UI 组件时,终端适配器生成一个相应的响应 , 并把它发送到客户端的 Web 浏览器中。位于客户端浏览器中的一个 Javascript 组件 (Client-Side Engine) 负责接收这些响应并且根据响应内容在 Web 页面上渲染程序的 UI 界面。图 1 是 Vaadin 的体系结构示意图 , 展示了该体系结构中几个主要的部分之间的作用关系。下文将介绍这些组成部分的特点和功能。
Vaadin 应用的运行时机制
在 Vaadin 应用程序中基本上所有的逻辑都是运行在服务器端的 Java Servlet API 上的,如下图 2 Vaadin 的运行时结构图所示,Vaadin 运行时结构主要由服务器端框架 (server-side framework) 和客户端引擎 (client-side engine) 两部分构成。服务器端框架包含了用来与客户端引擎通讯的服务器端集成层 (server-side integration) 以及一系列的 server 端 UI 组件。客户端引擎则由 Google Web toolkit(GWT) 页面渲染模块和客户端集成层 (client-side integration) 两部分组成。
图 3 显示了 Vaadin 运行时服务器端框架和客户端引擎的通讯原理。所有的通讯统一由位于服务器端集成层中的 CommunicationManager类和位于客户端集成层中的 ApplicationConnection类使用用户界面定义语言 (UIDL) 和 JSON 来进行。当包含 Vaadin 应用程序的网页初始化时,首先会在空白页面中加载 Vaadin 客户端引擎的 JavaScript 代码。当这些 JavaScript 代码加载完成后,客户端引擎开始处理与服务器之间的通讯。ApplicationConnection 类负责向 server 端发送 Ajax 请求并且根据 server 端 CommunicationManager 类的响应来渲染用户界面。Vaadin 非常有效的隐藏了客户端 - 服务器通讯的细节,所有的通讯操作对开发人员都是透明的,在开发过程中无须关注。因此可以在应用程序服务器端的 Java 代码中处理所有的用户交互逻辑,只通过在服务器端的开发即可完成全部的应用程序。这种开发模式极大的简化了 Ajax Web 应用程序的结构。
Vaadin 的服务器端框架
Vaadin 的服务器端框架运行在 Web 服务器的 Servlet 中。当 Web 容器接受到第一个对 Vaadin 应用程序的请求时,它会创建一个 ApplicationServlet类的实例,ApplicationServlet 是 Vaadin 服务器端框架的应用程序入口,它继承了 Java Servlet API 中的 HttpServlet。ApplicationServlet 会使用 HttpSession接口将应用程序的每个实例都关联到一个会话 (session) 上。在每个 session 的生命周期中,服务器端框架将用户交互信息传播到相应的应用程序实例中供服务器端 UI 组件使用。这种服务器端 UI 组件和客户端引擎之间的信息通讯由服务器端集成层 (server-side integration) 中的 CommunicationManager类来完成。除了服务器端集成层外,在服务器端框架中还包含有一整套 Vaadin 服务器端 UI 组件库,这些服务器端组件提供了创建 Vaadin 应用程序用户界面所需的编程 API。每一个服务器端组件都有两个基本任务:为对应的客户端组件序列化自身的状态变量信息,以及将接受到的客户端已序列化的用户输入信息解序。这两项任务中的大部分操作都由服务器端框架完成。
Vaadin 的客户端引擎
Vaadin 的客户端引擎使用 Google Web Toolkit (GWT) 技术在 Web 浏览器中渲染用户界面并且处理浏览器中用户交互的底层任务。通过使用 GWT,在 Vaadin 中可以统一使用 Java 语言来进行引擎和客户端组件的开发。客户端引擎实际上是一个运行在 Web 浏览器中的 JavaScript 程序,主要功能为根据从服务器端接收到的状态信息来渲染用户界面组件。引擎由 GWT 和客户端集成层 (client-side integration) 两部分组成。每一个 server 端的 UI 组件在客户端浏览器中都有一个对应的客户端小部件 (widget),这个小部件会在 web 页面中渲染 UI 组件的实际内容。Vaadin 的客户端引擎和所有的内置客户端小部件都通过使用 GTW 技术用 Java 语言编写,再使用 GWT 编译器编译成 JavaScript 代码。Vaadin 只是在其框架内部的实现中使用了 GWT,在标准的 Vaadin 程序开发中,对 GWT 的使用是不可见的。客户端集成层的主要功能为从 server 端接收组件的状态信息、当发生用户界面交互时向 server 端发送组件的状态变化,以及管理 Vaadin 应用使用的 CSS 样式表。
用户界面定义语言 UIDL (User Interface Definition Language)
用户界面定义语言 UIDL(User Interface Definition Language) 是一种用来定义用户界面内容以及用户界面变化的语言。它按照特定的格式将这些界面内容和变化相关的信息序列化。这些序列化后的 UIDL 消息会作为响应从 Web 服务器发送到浏览器中。在 Vaadin 应用程序运行时,服务器端组件首先会将渲染自身所需的信息和参数生成为 UIDL 消息,之后这些消息会通过服务器端框架传入浏览器中的客户端引擎。最终在浏览器中 UIDL 消息被解析并转换为 GWT 小组件 (widgets)。UIDL 支持渲染全部的用户界面或只渲染界面的局部,当从一个空白的 Web 页面启动 Vaadin 应用时,全部的用户界面都会被通过 UIDL 渲染。而当只有某一个 UI 组件发生变化时,UIDL 只会重画变化的部分。从 Vaadin 版本 5 开始,UIDL 使用 JSON(JavaScript Object Notation) 来作为通讯的负载消息格式,在客户端引擎中,vaadin 使用 GWT 内置的 JSON 工具库来解析、封装 UIDL 消息。对于终端用户和开发者,JSON 的使用是透明的,无须关注。
清单 1. Button UI 组件的 JSON 格式 UIDL 消息示例
["button", {"id": "PID", "immediate":true, "caption": "My Button", "focusid":1, "v":{"state":false} } ] |
使用 Vaadin 的 Add-On 插件扩展系统功能
在 Vaadin 中内置了大量的 UI 组件,界面布局,主题和数据源实现。除了这些开箱即用的资源外,还可以通过使用 Add-On 插件来扩展 Vaadin 框架的功能。Vaadin 有大量的 Add-On 插件,可以访问 Vaadin 的 Add-On 插件目录网页 Vaadin Directory来下载使用。图 4 是 Vaadin Directory 页面的截图。在这个页面上列出了大量可以下载使用的 Add-On 插件,其中有商用付费插件,也有免费的插件。
安装主题、数据源和其他只使用了服务器端构件的 Add-On 插件的方法非常简单,只需将 Add-On 插件的 jar 文件放入应用程序的类路径即可。安装包含客户端组件的 Add-On 时,还需要编译插件包含的部件集 (widget set)。以下为获取并安装一个 Add-On 插件的通用步骤:
在使用了一个 Add-On 插件之后,可以通过评星级的方式给 Add-On 的作者发送反馈,也可以直接添加文字评论。Add-On 插件的开发独立于 Vaadin 的开发之外 , 所以不同的 Add-On 插件可能使用不用的开源或商业许可证。多数的商用 Add-On 插件也可以直接的下载,使用这些 Add-On 之前需要注意它们使用的许可证和其他附加条件。还有很多的商用 Add-On 插件使用双许可证,这些插件可以在开源的项目中免费使用。当 Add-On 插件安装完成后,就可以在开发环境中编程使用插件提供的新功能了,下文以一个真实的 Add-On 插件 Vaadin Timeline为例演示在下载完成后如何安装、编译 widget set 并编程使用它。
编译 Vaadin Add-On 插件的 Widget Set
Vaadin timeline 是一个用来展示事件和时间趋势的图形组件,它可以将同时间相关的统计数据图形化的显示在用户界面上。在下载完成后会得到一个名为 vaadin-timeline-1.0.2.zip(Vaadin timeline 1.02 版 ) 的压缩文件。将这个文件解压缩后可以获得 Add-On 的 Jar 文件 (vaadin-timeline-1.0.2.jar),用户手册 (Vaadin Timeline Manual.pdf) 以及许可证说明文件。Vaadin timeline 是一个含有客户端 UI 组件的 Add-On,所以需要编译 widget set。Vaadin 的客户端引擎和自定义 widget set 都使用 GWT Compiler来将 Java 代码编译成可在浏览器中执行的 JavaScript 程序,在编译 widget set 之前需要在项目中导入 Google Web Toolkit 的类库。在 Vaadin SDK 的 gwt子目录中包含了一个 GWT 的完全安装版本,可以将整个 gwt 目录或其中包含的 GWT Jar 库文件导入 Vaadin 项目工程。Vaadin 框架提供了两种方式来编译 widget set:使用 Ant 构建脚本或使用 Eclipse plug-in 开发插件。
使用 Ant 构建脚本:使用 Vaadin SDK 中的 Ant 脚本编译 widget set 需要如下几个步骤。
<target name="configure-widgetset"> <echo>Build Vaadin timeline widget set.</echo> <!-- Name of the widget set --> <property name="widgetset" value="com.vaadin.addon.timeline.gwt.TimelineWidgetSet"/> <!-- Define if the widget set be generated automatically --> <!-- from all widget sets included in the class path. --> <!-- <property name="generate.widgetset" value="1"/> --> <!-- Path to the widgetset directory. Required only for --> <!-- generated widget sets. Must be relative to --> <!-- $src-location, that is, under the first entry in --> <!-- class path. --> <property name="widgetset-path" value="com/vaadin/addon/widgetset"/> </target> |
configure: init: [echo] Requirements for classpath: [echo] ../../../gwt/gwt-dev.jar [echo] ../../../gwt/gwt-user.jar [echo] ../../../WebContent/WEB-INF/lib/vaadin-6.4.1.jar [echo] ../../../WebContent/WEB-INF/src [echo] Output will be written into ../../../WebContent/VAADIN/widgetsets compile-server-side: ...... compile-widgetset: [echo] Compiling com.vaadin.addon.timeline.gwt.TimelineWidgetSet... [java] Compiling module com.vaadin.addon.timeline.gwt.TimelineWidgetSet ...... [java] Compile of permutations succeeded [java] Linking into ../WebContent/VAADIN/widgetsets/com.vaadin.addon.timeline.gwt.TimelineWidgetSet. [java] Link succeeded [java] Compilation succeeded -- 201.306s BUILD SUCCESSFUL Total time: 3 minutes 28 seconds |
使用 Eclipse plug-in 插件:Eclipse 的 Vaadin 插件提供了 widget set 编译工具,可以快捷的编译 Add-On 插件。当启动了自动编译功能后,Vaadin 插件能够自动检测项目中的 Add-On,当发现新的 Add-On 插件代码时会自动启动 widget set 的编译。它还在工具栏中提供了专门的编译命令按钮,可以随时手动启动 Widget set 的编译。使用 Vaadin 的 eclipse 插件编译 widget set 的步骤非常简单,只需要把 Add-On 的 jar 文件 ( 本文示例中该文件为 vaadin-timeline-1.0.2.jar) 拷贝到 Eclipse 中的 Vaadin 应用工程中即可。Vaadin plug-in 检测到新的 Add-On 后会自动弹出编译 widgetset 的确认窗口,点击 Yes 按钮确认后可以立即启动编译。编译进程信息会在 Eclipse 的 console 视图中显示。清单 4 是编译成功后中 console 视图中的显示信息。
Compiling widgetset com.vaadin.addon.timeline.gwt.TimelineWidgetSet Updating GWT module description file... 2010-8-22 20:17:15 com.vaadin.terminal.gwt.widgetsetutils.ClassPathExplorer getAvailableWidgetSets 信息 : Widgetsets found from classpath: com.vaadin.terminal.gwt.DefaultWidgetSet in jar:file: D:/DEV_Workspace/eclipse/Vaadin_DW/WebContent/WEB-INF/lib/vaadin-6.4.1.jar!/ com.vaadin.addon.timeline.gwt.TimelineWidgetSet in file: //D/DEV_Workspace/eclipse/Vaadin_DW/src Done. Starting GWT compiler Widgetset compilation completed |
编译完成的 widget set 存储在项目的 WebContent/VAADIN/widgetsets路径下。
在应用中使用 Vaadin Add-On 插件
为了在 Vaadin 应用程序中使用 Add-On 插件,首先需要将 Add-On 的 jar 文件放置在 Web 应用程序的类路径中。在本例中,按照 Java Web 应用程序规范将文件 vaadin-timeline-1.0.2.jar 放置在应用程序的 WEB-INF/lib/路径中即可。如果 Add-On 插件包含了客户端组件,除了放置 jar 文件外,还需要注册已经完成编译的 widget set。Vaadin 应用程序本质上是运行在 Java Web 服务器中的 Servlet 程序,为了在 Vaadin 中使用 widget set,需要将包含 widget set 编译结果的文件夹放置在 Vaadin 的 widget set 搜索目录中 ( 该目录位于 web 应用根路径下的"VAADIN/widgetsets"目录中 ),并且在 Servlet 配置文件 web.xml中配置 widget set 的信息。清单 5 是在 web.xml 中注册 Vaadin Timeline 的 widget set 的配置信息片段。
清单 5. 在 web.xml 中注册 Vaadin Timeline 的 widget set 信息
<servlet> <servlet-name>Vaadintimeline Application</servlet-name> <servlet-class> com.vaadin.terminal.gwt.server.ApplicationServlet</servlet-class> <init-param> <description>Vaadin application class to start</description> <param-name>application</param-name> <param-value>com.ibm.vaadinTimelineDemo.VaadintimelineApplication </param-value> </init-param> <init-param> <description>Application widgetset</description> <param-name>widgetset</param-name> <param-value> com.vaadin.addon.timeline.gwt.TimelineWidgetSet </param-value> </init-param> </servlet> |
当 Add-On 的 jar 文件以及 widget set 的添加和配置完成后,就可以像使用其他 Vaadin 内置的组件一样在 java 代码中直接调用 Add-On 组件的 Java 类。清单 6 中的代码演示了如何在 java 代码中使用 Vaadin Timeline 的类。图 5 为这个示例程序的运行结果。
清单 6. 使用 Vaadin Timeline 的 java 代码示例
package com.ibm.vaadinTimelineDemo; import com.vaadin.Application; import com.vaadin.ui.*; import java.util.Calendar; import java.util.Date; import java.util.Random; import com.vaadin.Application; import com.vaadin.addon.timeline.Timeline;// 导入 Timeline 的类 import com.vaadin.data.Container; import com.vaadin.data.Item; import com.vaadin.data.util.IndexedContainer; import com.vaadin.ui.Window; public class VaadintimelineApplication extends Application { @Override public void init() { Window mainWindow = new Window("Timelinedemo Application"); setMainWindow(mainWindow); // 创建 timeline 组件 Timeline timeline = new Timeline("My timeline"); timeline.setWidth("600px"); timeline.setHeight("400px"); // 为 timeline 添加数据容器 timeline.addGraphDataSource(createContainer()); mainWindow.addComponent(timeline); } /** * 为数据容器添加随机数据 */ private Container.Indexed createContainer() { // 创建容器 IndexedContainer container = new IndexedContainer(); container.addContainerProperty(Timeline.PropertyId.TIMESTAMP,Date.class, null); container.addContainerProperty(Timeline.PropertyId.VALUE, Float.class,0); // 添加月份和随机数据 Calendar cal = Calendar.getInstance(); cal.add(Calendar.MONTH, -1); Date today = new Date(); Random generator = new Random(); while (cal.getTime().before(today)) { Item item = container.addItem(cal.getTime()); item.getItemProperty(Timeline.PropertyId.TIMESTAMP).setValue(cal.getTime()); item.getItemProperty(Timeline.PropertyId.VALUE).setValue(generator .nextFloat()); cal.add(Calendar.DAY_OF_MONTH, 1); } return container; } } |