级别: 中级 Michael Galpin, 软件架构师, eBay
2009 年 4 月 16 日 作为 Java™ 开发人员,您是否希望利用开放的 Web 来创建富 Internet 应用程序(Rich Internet Applications,RIA)?您非常幸运,因为 JavaFX 现在为开发人员提供在 Java 平台上创建 RIA 的功能。在本文中,学习如何使用 JavaFX 创建 mashup。了解 JavaFX 如何让您接触 Flickr 等流行的 Web 服务,以及如何使用它创建交互式用户界面。在这一过程中,您还将了解 JavaFX 为客户端开发带来的新功能。
开始之前 您将在本文中使用 JavaFX 开发一个富 Internet 应用程序(RIA)。JavaFX 不仅仅是一种用于创建 RIA 的新技术,它还是一种新的编程语言。它从 Java 语言借鉴了很多东西,并且能够在 Java 虚拟机上运行。创建 JavaFX 应用程序并不要求您是专业 Java 程序员,但是熟悉 Java 非常有帮助。您需要用到 JavaFX SDK;本文使用 JavaFX SDK Version 1.0 开发代码。您还需要 Java Development Kit;本文使用 JDK 1.6.0_07 编译和运行代码。可以查看 下载目录 获得下载链接。
JavaFX 模型 我们将通过开发一个典型的 mashup 应用程序来研究 JavaFX。该应用程序将使用来自流行照片共享网站 Flickr 的一个 Web 服务。您将在 Flickr 中搜索照片并将其显示在 JavaFX 应用程序中。首先,将使用 JavaFX 从 Flickr Web 服务中对数据进行建模。
使用 JavaFX 建模 Flickr 提供了可以从任何代码(包括 JavaFX 代码)调用的 RESTful Web 服务。调用 Flickr 的服务需要用到 API 键,这个键可以从 Flickr 免费获得。可以从 Flickr 指定响应格式。默认格式是 XML,因此本文也将使用 XML 格式。清单 1 展示了示例输出。 清单 1. 示例 Flickr XML
<?xml version="1.0" encoding="utf-8" ?>
<rsp stat="ok">
<photos page="1" pages="132147" perpage="6" total="792880">
<photo id="3196302982" owner="14994333@N08" secret="7c4e209fe9"
server="3536" farm="4" title="Pond" ispublic="1" isfriend="0" isfamily="0" />
<photo id="3196288734" owner="19764560@N00" secret="5142dd0a2d" server="3514"
farm="4" title="Pull In Case Of Fire" ispublic="1" isfriend="0" isfamily="0" />
<photo id="3195451611" owner="25304693@N00" secret="4ef1d2b63d" server="3528"
farm="4" title="Domein Nieuwenhoven" ispublic="1" isfriend="0" isfamily="0" />
<photo id="3196281786" owner="51865294@N00" secret="bed01bff0c" server="3403"
farm="4" title="Uitdam skyline" ispublic="1" isfriend="0" isfamily="0" />
<photo id="3196281752" owner="27734999@N03" secret="a81c2e53a5" server="3301"
farm="4" title="IMG_7642" ispublic="1" isfriend="0" isfamily="0" />
<photo id="3196281596" owner="51865294@N00" secret="c0f18f3aef" server="3510"
farm="4" title="Warm scene on a cold day" ispublic="1" isfriend="0" isfamily="0" />
</photos>
</rsp>
|
可以从清单 1 看到,每张照片都包含几个重要属性:id、secret、server、farm 和 title。前四个属性用于创建图像的 URL。使用 JavaFX 对对象进行建模非常简单,如清单 2 所示。 清单 2. 使用 JavaFX 建模的 Flickr 图像模型
public class FlickrImage{
var id:String;
var secret:String;
var server:String;
var farm:String;
var title:String;
public function getUrl():String{
"http://farm{farm}.static.flickr.com/{server}/{id}_{secret}.jpg"
}
}
|
这个简单的类演示了 JavaFX 语法的一些方面。关键字 var 用于表示 JavaFX 中的一个实例变量。随后将看到,在构建类实例时,类声明中的每个 var 将成为一个命名的参数。注意,JavaFX 是静态输入的,其类型被声明为变量名的后缀。
类还提供一个单一方法 getUrl。方法在 JavaFX 中被声明为函数,并且它们也是静态输入的。JavaFX 有一个可选的关键字 return,它可以根据要求进行工作。清单 2 忽略了这个关键字,因此函数的最后一行将被返回。在本例中为一个字符串表达式。JavaFX 将把实例变量插入到使用花括号表示的字符串的占位符中。注意,这是静态输入的。因此如果在上例中拼错了 “secret”,或尝试调用根本不存在的方法,那么将出现编译时错误。
注意,在 清单 2 中,var 没有任何访问控制修饰符(modifier)。JavaFX 在默认情况下是脚本私有的,因此无法从其他类访问脚本。lone 方法被声明为 public,因此可以供其他类访问。现在您已经拥有一个可以对 Flickr 图像建模的类,因此只需使用某种方法获取数据来填充这些类。为此,需要调用 Flickr Web 服务。
调用 Web 服务
JavaFX 应用程序可以对 Web 进行远程调用。javafx.io.http 包含了一些用于异步调用 Web 服务的类。这些类反过来依赖于 Java 的网络栈。这是 JavaFX 和其他依赖 Web 浏览器进行远程调用的 RIA 技术的显著区别。鉴于此,下面显示的示例应用程序的模型将访问 Flickr 的 Web 服务来获取数据(如清单 3 所示)。 清单 3. 数据访问代码的模型
var pics:FlickrImage[] = [];
public class Model {
public var tag:String;
public var onSuccess: function(FlickrImage[]):Void;
public function searchFlickr(){
delete pics;
var url=
"http://api.flickr.com/services/rest/?method=flickr.photos.
search&per_page=6&api_key={API_KEY}&tags={tag}";
var req = HttpRequest{
location : url
onInput: function(stream:InputStream){
parser.input = stream;
parser.parse();
onSuccess(images);
}
}
req.enqueue();
}
def API_KEY = "Put Your API Key Here";
var images:FlickrImage[] = bind pics;
}
|
这段代码演示了 JavaFX 语言的更多关键特性。Model 类有两个公共 var。其中一个是称为 tag 的简单字符串。它是用于对 Flickr 进行搜索查询的标记。另一个公共 var onSuccess 是一个函数。函数在 JavaFX 中是一级类。onSuccess 的声明表示它是一个函数,接受 FlickrImage 对象数组作为输入参数并且不返回任何内容(Void)。传递给该参数的任何函数都必须满足这个签名,否则将得到一个编译时错误。
这两个公共 var 都用于公共函数 searchFlickr。在这里使用 HttpRequest 类对 Flickr Web 服务发出调用。注意创建 HttpRequest 的实例的语法。这是 JavaFX 的声明式语法。在本例中,您将两个命名参数传递给 HttpRequest 的构造函数。其中一个是名为 location 的字符串,另一个是名为 onInput 的函数。参数名/值对使用 name : value 表示。因此对于 onInput 参数,将创建一个匿名函数。JavaFX 的静态输入特性允许您知道 onInput 将接受一个函数,该函数接受 java.io.InputStream 作为其惟一输入参数并且具有一个 Void 返回类型。清单 3 中的 onInput 方法将 InputStream 传递给一个解析器对象,随后调用 onSuccess 方法,该方法被传入到 Model 实例。最后,在构建好 HttpRequest 之后,调用其 enqueue 方法。这告诉 JavaFX 运行时在获得空闲线程后立即调用该方法。不要使用主应用程序线程发出远程调用,因为这将使应用程序在等待远程服务器响应时失去响应性。
在研究解析器代码之前,您可能已经注意到 Model 类及其 searchFlickr 方法。它有一个称为 images 的 FlickrImage 对象数组(序列)。这些内容被绑定到称为 pics 的脚本级数组。数据绑定是 JavaFX 的一个重要部分,允许对一个对象进行修改,然后将修改传播给其他对象。在本例中,对 pics 的任何修改都将传播给 images。searchFlickr 方法的第一行使用 JavaFX 关键字 “delete” 清除 pics 序列中的数据。最后,注意 API_KEY 是如何被声明为 def 的。这实际上将其变成一个常量。现在我们已经了解了 Model 类中的所有内容,接下来我们继续解析来自 Flickr 的数据。
JavaFX 中的 XML 解析 在清单 3 中引用了一个解析器,并且将一个 java.io.InputStream 传递给该解析器。在 JavaFX 中使用来自 Java 的核心类非常普遍。在本例中,将 InputStream 传递给 javafx.data.pull.PullParser。这类似于 Java 中的 StAX 解析器。它允许您从解析器接收事件流(类似于 SAX 风格的解析),但是能够通过选择所需的事件来控制事件流。JavaFX 的 PullParser 可以同时恰当地处理 XML 和 JSON 数据 — 这是用于 Web 服务的最常见格式。这使得 JavaFX 非常适合用于创建 mashup,比如本文创建的 mashup。清单 4 展示了解析来自 Flickr 的 XML 的代码。 清单 4. 在 JavaFX 中解析 XML
def ID = QName {
name : "id"
}
def SECRET = QName{
name : "secret"
}
def SERVER = QName{
name : "server"
}
def FARM = QName{
name : "farm"
}
def TITLE = QName{
name : "title"
}
def parser = PullParser{
documentType : PullParser.XML
onEvent : function(event:Event){
if (event.type == PullParser.START_ELEMENT){
if (event.qname.name.equals("photo")){
var image = FlickrImage{
id : event.getAttributeValue(ID)
secret : event.getAttributeValue(SECRET)
server : event.getAttributeValue(SERVER)
farm : event.getAttributeValue(FARM)
title : event.getAttributeValue(TITLE)
}
insert image into pics;
}
}
}
}
|
在清单 4 中,首先要注意的是我们定义了一些 QName 对象。它们在 XML 术语中被称为限定名,表示来自 Flickr 的 XML 属性限定名。如您所料,JavaFX 的 QName 类允许使用名称空间、前缀和名称。Flickr XML(清单 1)没有使用名称空间,因此只需要对清单 4 中的 QNames 使用一个简单名称。 PullParser 实例是使用 JavaFX 的声明语法创建的。您应该很熟悉!在清单 4 中,只需设置 documentType 属性表示正在解析 XML。然后将一个匿名函数(closure)传入到 onEvent 属性。然后查找 START_ELEMENT 事件,如果元素为 “photo”,就从其属性提取数据并使用数据创建一个新的 FlickrImage 实例。然后需要将该实例添加到 pics 序列。注意如何通过 JavaFX 保留字 insert 实现这一过程。此实例将始终添加到序列的末尾。最后要注意,所有 QNames 和 PullParser 都被定义为 def(也就是常量)。您可以将它们设为 var,但是设为 def 更好。这类似于在 Java 中将其声明为 final。
现在我们已经了解所有数据处理代码,知道如何调用一个远程 Web 服务以及如何解析该服务返回的 XML。接下来我们看一看更有魅力的用户界面代码,JavaFX 是凭借这些代码才出名的。
JavaFX 的用户界面代码 当 JavaFX 在 2007 年首次引入 JavaOne 时,它被当作一种可以创建丰富用户体验的技术。JavaFX 中的 UI 一直关注的焦点,因此 JavaFX 在这方面的表现十分出众。让我们看一下 mashup 的用户界面。首先看看扩展 JavaFX 中的一些核心类有多么简单。 扩展 JavaFX 应用程序需要显示来自 Flickr 的图像,因此需要使用 JavaFX 的 Image 和 ImageView 类。工作流程非常简单。使用 FlickrImage 对象计算图像的 URL,并使用该 URL 创建一个 Image 实例,然后再使用这个 Image 实例创建一个 ImageView 对象。随后可将 ImageView 对象添加到 Scene(稍后详细介绍)。让我们使用清单 5 所示的新类封装这种逻辑。 清单 5. FlickrView
public class FlickrView extends ImageView{
public var flickrImage:FlickrImage;
init {
this.image = Image {
url : flickrImage.getUrl()
}
}
}
|
可以看到,FlickrView 类是 ImageView 的子类。它添加了一个新的 var flickrImage。init 方法类似于 Java 中的构造函数,调用方设置完所有 var 后将立即调用该方法。在本例中,它使用 FlickrImage 实例创建一个 Image 实例(其 URL 由 FlickrImage 计算得出)。在 JavaFX 中创建子类非常简单;就像使用 Java 创建子类一样。构造函数也可以方便地使用 init 块。使用 FlickrView 可以方便地显示 Flickr 中的图像,因此您现在可以准备创建用户界面的其余部分。 创建用户界面 到目前为止,您已经了解如何在不同位置使用 JavaFX 声明式语法构造新的对象。这对于开发人员非常方便。如果您从事过大量 JavaScript 编程,应该会非常熟悉这种编程风格。这种编程风格在创建用户界面时才真正表现出自己的魅力。应用程序的主要用户界面如清单 6 所示。 清单 6. 用户界面代码
Stage {
title: "FlickrFX"
width: 500
height: 500
scene: Scene {
fill: LinearGradient {
startX: 0.0, startY: 0.0, endX: 0.0, endY: 1.0
proportional: true
stops: [ Stop { offset: 0.0 color: Color.ORANGE },
Stop { offset: 1.0 color: Color.BLUE } ]
}
content: VBox{
spacing : 50
content : [
HBox {
spacing : 20
content : bind topRow
},
HBox{
spacing : 10
content : [
Text {
font: Font {
size: 24
}
x: 10,
y: 30
content: "Tag"
},
input,
SwingButton {
text: "Search"
action: function() {
var model = Model {
tag : input.text
onSuccess : displayImages
}
model.searchFlickr();
}
}
]
},
HBox {
spacing : 20
content : bind bottomRow
}
]
}
}
}
|
清单 6 的代码乍看起来有些复杂,实际上非常简单。让我们先从代码顶部开始研究。本例中,Stage 是应用程序的主要显示窗口。代码构造了一个新的 javafx.stage.Stage 实例。它将 Stage 实例的 title 变量设置为 FlickrFX,并将它的 height 和 width 变量设置为 500。这时的应用程序应该如图 1 所示。
图 1. JavaFX
应用程序的骨架
这个应用程序并不是十分有趣,但是可以从中受到启发。接下来代码将要设置 Stage 实例的 scene 变量。它将这个变量设置为 javafx.scene.Scene 类的一个新实例。Scene 执行的第一个操作是创建一个背景填充。在本例中它将 Scene 实例的 fill 变量的值设置为 javafx.scene.paint.LinearGradient 的新实例,从而创建一个渐变效果填充。这种渐变效果从顶部的橙色开始,逐渐渐变为底部的蓝色。LinearGradient 的 startX、startY、endX 和 endY 决定渐变的起始点和终止点。这种渐变呈垂直型,因为 startX 和 endX 是相同的,但是 startY 和 endY 将从 0.0 变为 1.0。最终,LinearGradient 的 stops 变量被设为一组 javafx.scene.paint.Stop 对象。通过这种方式就可以设置橙色和蓝色的起始点。添加了这些渐变代码后,应用程序将如图 2 所示。
图 2. 添加渐变效果的骨架应用程序
应用程序现在已经添加了一些颜色!因此您只需设置 Scene 实例的 content 变量。将其设置为 javafx.scene.Node 对象。Node 是一个抽象类,是 JavaFX 中所有图形对象的基类。在本例中您构建了一个 javafx.scene.layout.VBox 实例。VBox 是一个容器,它将以从上到下的垂直方式铺设内容。我们将它的 spacing 变量设置为 50,因此它将在所包含的每个对象之间放置 50 个像素。 将对象添加到 VBox 很简单:把它们放入其值为 VBox 的 content 变量的 Node 序列中。在本例中您构造了三个 javafx.scene.layout.HBox 实例。从名称可以看出,HBox 类似于 VBox,但它是从左到右水平放置内容的。这样,通过在一个 VBox 内堆叠三个 HBox 实例,就得到一个灵活的网格。 顶部和底部的 HBox 实例被绑定到另外两个脚本变量 topRow 和 bottomRow。您再一次使用了 JavaFX 强大的绑定构造。当 topRow 或 bottomRow 被修改时,HBox 实例也会相应地改变,因此用户界面的各个部分将执行刷新。那么 topRow 和 bottomRow 的值是什么呢?代码如清单 7 所示。 清单 7. 网格的首行和末行
def LOGO = Image {
url: "http://lh3.ggpht.com/_XmwdENwf53s/SW06N4aWEDI/AAAAAAAAAgo/
nXzkz7IBEnk/javafx.png"
}
var topRow = for (i in [1..3]) {
ImageView{
image: LOGO
fitWidth : 150
fitHeight : 150
preserveRatio: true
smooth: true
cache: true
}
}
var bottomRow = for (i in [1..3]) {
ImageView{
image: LOGO
fitWidth : 150
fitHeight : 150
preserveRatio: true
smooth: true
cache: true
}
}
|
我们在这里使用了 JavaFX 的另一个有用特性。topRow 和 bottomRow 变量通过一个循环表达式构建(通常为列表包含)。这是一个出色的 JavaFX 特性。等效的 Java 代码不仅更加冗长,而且在执行时不是很明确。在本例中,JavaFX 的语法使其在创建 ImageView 实例序列时非常明确,其中每个 ImageView 实例使用称为 LOGO 的常量 Image 实例。 了解顶部行和底部行之后,那么什么是中间行呢?从 清单 6 可以看到,它创建了一个值为 “Tags” 的标签(javafx.scene.text.Text 的实例)和一个按钮。通常情况下,按钮为 javafx.ext.swing.SwingButton 的实例。JavaFX 利用了很多 Swing 对象,但是得益于其方便的语法,这些对象更加易于使用。按钮显示为 “Search”,它的 action 变量被设置为一个闭包。我们将在下一小节深入探讨它。最后,中间行还引用了另一个脚本变量 input。由于是显式创建的,因此可以在代码的其他部分中引用它(即在按钮的 action 闭包中)。为了保持完整性,其代码显示在清单 8 中。 清单 8. input 控件
def input = SwingTextField {
columns: 10
editable: true
}
|
类似于按钮,这也是一个基于 Swing 的控件。在本例中是 javafx.ext.swing.SwingTextField 的实例。注意这里再次使用了 def,因此不能被修改。现在已经准备好所有控件,让我们看一看应用程序是什么样的,如图 3 所示。
图 3. 初步完成的 UI
现在应用程序具备了所有 UI 元素。接下来将把视图与前面创建的模型关联起来。您需要使用一些控制器代码将所有内容组合起来,并使这个 RIA 具有交互性。
将所有内容关联起来:JavaFX 控件 回到 清单 6,我们使用了一个按钮,它的 action 变量被设置为一个内联函数。现在我们仔细研究一下。该函数创建了一个 Model 实例(清单 3),它传递输入对象的 text 变量的值和对另一个函数 displayImages 的引用。随后调用 Model 实例的 searchFlickr 方法。从 清单 3 中我们了解到,当对 Flickr 的远程调用完成并且结果被解析到 FlickrImage 对象之后,将调用 displayImages 函数。看一下清单 9 中的 displayImages 函数。 清单 9. 显示来自 Flickr 的图像
function displayImages(images:FlickrImage[]){
delete topRow;
delete bottomRow;
for (i in [1..3]){
insert FlickrView {
flickrImage : images[i-1]
fitWidth : 150
fitHeight : 150
preserveRatio : true
smooth : true
rotate : 60*(i-2)
} into topRow
}
for (i in [1..3]){
insert FlickrView {
flickrImage : images[i+2]
fitWidth : 150
fitHeight : 150
preserveRatio : true
smooth : true
rotate : -60*(i-2)
} into bottomRow
}
}
|
JavaFX 使您能够编写非常简洁的代码。这个函数删除 topRow 和 bottomRow,并清除该序列中的所有 Node。然后使用 JavaFX 的 for 循环遍历图像对象。对于每个 FlickrImage 实例,将创建一个 FlickrView 实例并使用 JavaFX 的 insert 语句将其添加到 topRow 或 bottomRow。您还注意到,每个 FlickrView 实例都设置了自己的 rotate 变量。这是来自 Node 的变量(因此任何图像对象都可以旋转)。它执行一定角度的旋转以应用到 Node。这使您可以对每个图像进行巧妙的处理,如图 4 所示。
图 4. 显示来自 Flickr 的图像
注意,搜索结果会随时间而变化。回过头看看 清单 8,您将发现每个图像都有自己的 fitWidth 和 fitHeight 变量,并且被设置为 150,而 preserveRatio 变量则被设置为 true。可以看到图 4 所示的结果。JavaFX 运行时动态调整图像的大小。图像的高度和宽度的最大值被设置为 150 像素,但是保持高宽比例不变,因此图像没有变形。
结束语 在本文中,您使用 JavaFX 构建了一个 mashup 应用程序。它展示了如何调用远程 Web 服务以及如何解析远程服务提供的数据。您了解了如何使用 JavaFX 创建用户界面,以及如何将控制器逻辑与用户界面元素关联起来。这些都是创建 mashup 过程中最常见的任务。此外,您还理解 JavaFX 如何简化这些任务。这个过程提供了一些演示 JavaFX 语法的重要特性的例子:类型系统、闭包支持、序列语法、强大的数据绑定,以及用于构造对象的声明式语法。现在,您应该能够使用 JavaFX 构建自己的 mashup 应用程序了。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| mashup 源代码 | FlickrFX.zip | 3KB | HTTP |
|---|
参考资料 学习
获得产品和技术
-
JavaFX:本文使用的是 JavaFX 1.0。
-
Java SDK:本文使用的是 Java SE 1.6_05。
- IBM 试用软件:使用可通过下载获得的 IBM 试用软件改进您的下一个开源开发项目。
- 下载 IBM 产品评估版:使用来自 DB2、Lotus、Rational、Tivoli 和 WebSphere 的应用程序开发工具和中间件产品。
讨论
关于作者  | 
|  | Michael Galpin 从二十世纪九十年代末就开始开发 Web 应用程序。他从 California Institute of Technology 获得了数学学位,目前是位于美国加州圣何塞的 eBay 公司的一名架构师。 |
对本文的评价
|