使用 Grails 构建富 Internet 应用程序,第 1 部分: 使用 Grails 和 Flex 构建 Web 应用程序

富 Internet 应用程序(Rich Internet Applications,RIA)通过浏览器保证桌面应用程序的动态性和功能。RIA 的主要特征之一就是将表示层移动到客户机,并使用服务器上健壮的 RESTful 服务层支持它。这种想法借助 SOUI(Service Oriented User Interface)和 SOFEA(Service Oriented Front End Architecture)之类的热门词汇得到传播。本文是包含两个部分的系列文章的第一部分,它让您看到使用 Groovy 的 Grails Web 应用程序框架创建 Web 服务后端多么简单。您将把这个后端与用 Adobe 的 Flex 框架开发的 RIA 连接起来。

Michael Galpin, 软件架构师, eBay

Michael Galpin 的照片Michael Galpin 从二十世纪九十年代末就开始开发 Web 应用程序。他从 California Institute of Technology 获得了数学学位,目前是位于美国加州圣何塞的 eBay 公司的一名架构师。



2009 年 4 月 23 日

关于本系列

这个系列探索一些应用程序架构,它们在后端使用由 Grails 框架实现的面向服务架构(SOA)。了解 Grails 如何大大简化了 Web 应用程序的创建,尤其是 Web 服务的创建。这种后端可以轻松连接到任意纯客户端应用程序。在第 1 部分中,您将使用 Adobe Flex 创建一个可以使用 Flash Player 的应用程序。在第 2 部分中,您将通过 Google Web Toolkit 用纯 JavaScript 创建前端。

先决条件

在这篇文章中,您将使用 Grails 和 Flex 构建 Web 应用程序。Grails 框架基于 Groovy 编程语言,这是针对 Java™平台的动态语言。熟悉 Groovy 会更好,但不是必要的。了解 Java 或其他动态语言(比如 Ruby 或 Python)会有很大帮助。本文使用 Grails 1.0.3(参见 参考资料)。Grails 可用于许多数据库或应用服务器,但本文不需要提供它们,因为它们已经随 Grails 附带。前端使用 Flex 构建。Flex 是一个使用 ActionScript 编程语言的应用程序框架,它在 Flash Player 上运行。再声明一下,不熟悉 Flex 和 ActionScript 没有关系。熟悉 Java 和 JavaScript 有利于学习 Flex。需要 Flex SDK 3.2 或更高版本来编译本文的代码(参见 参考资料)。要运行本文构建的应用程序,必须具有 Flash Player 10.0 或更高版本。

架构

许多企业都争相采用 Service Oriented Architecture (SOA)。SOA 让架构更加敏捷,方便业务的快速发展。当然,您的企业可能有另一个紧迫的计划:将用户界面实现为现代的富 Internet 应用程序。SOA 和 RIA 这两种流行的技术并不容易结合在一起。但事实证明,它们可以很好地协同工作。您可以使用 SOA 设计将服务部署到应用服务器。也可以将所有表示逻辑移动到客户机,并利用强大的前端技术(比如 Flex)创建 RIA。这正是您在本系列中需要完成的事情,现在从使用 Grails 创建一个 Web 服务开始。

Web 服务

当许多开发人员听见术语 Web 服务时,他们就会想到 SOAP(Simple Object Access Protocol)。SOAP 在很多开发人员脑中具有消极的意义,因为他们认为 SOAP 是一种大型的复杂技术。不过 Web 服务不是这样的。REST(Representational State Transfer)式的 Web 服务受到普遍欢迎,因为它们的语义很简单。它们的创建和使用都很容易。它们可以像 SOAP 一样使用 XML,但使用的是 Plain Old XML(POX),不像 SOAP 那样带有奇特的包装器和报头。Grails 框架使得创建这种 Web 服务非常简单,所以我们现在从 Grails 领域模型开始。

Grails 域模型

Grails 是通用的 Web 开发框架。许多 Web 应用程序都使用关系数据库存储和获取在某个应用程序中使用的数据,因此 Grails 采用了强大的 Object Relational Modeling (ORM) 技术,即 GORM。通过 GORM,您可以轻松地对域对象进行建模,并将它们持久化到任意关系数据库中,但不再需要处理 SQL。GORM 使用流行的 Hibernate 库生成特定于数据库的经过优化的 SQL,以及管理域对象的生命周期。在使用 GORM 之前,我们先快速讨论一下将要创建的应用程序,以及需要使用 GORM 进行建模的东西。

在示例应用程序中,您将创建一个模仿流行站点 Digg 的功能的 Web 应用程序(见 参考资料)。在 Digg 上,用户可以提交新闻链接(Web 页面)。然后其他用户可以阅读这些新闻,并投票支持或反对它们。您的应用程序将具有这些基本功能。它允许人们匿名地提交新闻并对其进行投票,因此不需要对用户进行建模,对新闻建模即可。下面是针对示例应用程序中的新闻的 GORM 模型,如清单 1 所示。

清单 1. 新闻模型
 class Story { 
    String link 
    String title 
    String description 
    String tags 
    String category 
    int votesFor 
    int votesAgainst 
 }

这就是对域对象进行建模所需的所有代码。您要声明它的属性和这些属性的类型。这将允许 Grails 为您创建表,并且为从该表读写数据动态创建方法。这是 Grails 提供的主要好处之一。您仅需将数据建模代码放在某个地方,而不需要为简单的读写编写任何模板代码。现在已经准备好域模型,您可以创建一些使用该域模型的业务服务了。

业务服务

SOA 的好处之一就是它允许您以非常自然的方式对系统进行建模。您希望执行的一些操作是什么?您要以此为依据来定义应用程序的业务服务。例如,您需要浏览和搜索新闻,所以要为此创建一个服务,如清单 2 所示。

清单 2. 搜索服务
 class SearchService { 

    boolean transactional = false 

    def list() { 
        Story.list() 
    } 

    def listCategory(catName){ 
        Story.findAllWhere(category:catName) 
    } 
    
    def searchTag(tag){ 
        Story.findAllByTagsIlike("%"+tag+"%") 
    } 
 }

您首先注意到的可能是布尔标志 transactional。Grails 构建在大量成熟的技术之上,包括 Spring。它使用 Spring 的声明性事务来修饰带有事务的服务。如果您想执行数据更新,或希望其他事务服务能够使用这个服务,那么就要启用这个功能。您希望这个服务是只读的。它不会取代数据访问,因为您已经拥有处理这方面内容的域模型。

第一个服务操作获取所有新闻。第二个操作获取特定类别的新闻。在这里用到 GORM 的 where- 样式的查找程序。它允许您传入一个名称 / 值对映射,以在 SQL 查询中构造一个 WHERE子句。第三个操作让您可以使用特定标记搜索新闻。在这里使用的是 GORM 的动态查找程序。这样,您就可以将查询子句作为查找程序方法的名称的一部分。所以使用 findAllByTags在标记列上执行查询。此外,还有注意 like后缀;这是 区分大小写的 SQL LIKE子句。它允许您查找所有标记参数作为标记属性的子字符串的新闻。例如,如果一个新闻被标记为 “BarackObama”,它将在搜索 “obama” 时显示出来。

您创建了 3 种简单但强大的新闻搜索方式。注意,它们的语法都很简洁。如果您是一位 Java 程序员,就知道正常情况下要编写多少代码才能实现这些功能。搜索服务是很强大的,但现在还没有可供搜索的内容。您还需要一个用于管理每则新闻的服务,我称之为 Story 服务,如清单 3 所示。

清单 3. Story 服务
 class StoryService { 

    boolean transactional = true 

    def create(story) { 
        story.votesFor = 0 
        story.votesAgainst = 0 
        log.info("Creating story="+story.title) 
        if(!story.save(flush:true) ) { 
            story.errors.each { 
                log.error(it) 
            } 
        } 
        log.info("Saved story="+story.title + " id=" + story.id) 
        story 
    } 
    def voteFor(storyId){ 
        log.info("Getting story for id="+storyId) 
        def story = Story.get(storyId) 
        log.info("Story found title="+story.title + " 
 votesFor="+story.votesFor) 
        story.votesFor += 1 
        if(!story.save(flush:true) ) { 
            story.errors.each { 
                log.error(it) 
            } 
        } 
        story 
    } 
    def voteAgainst(storyId){ 
        log.info("Getting story for id="+storyId) 
        def story = Story.get(storyId) 
        log.info("Story found title="+story.title + " 
 votesAgainst="+story.votesAgainst) 
        story.votesAgainst += 1 
        if(!story.save(flush:true) ) { 
            story.errors.each { 
                log.error(it) 
            } 
        } 
        story        
    } 
 }

Story 服务有 3 个操作。首先,您可以创建一则新的新闻。其次,有一个通过新闻 ID 对其好坏进行投票的操作。无论那种情况,您都记录了一些日志。Grails 使得日志记录非常容易 —仅需使用隐式日志对象和普通的 log4j 样式的方法:log.debuglog.infolog.error等等。使用 Story实例上的 save 方法保存(或插入和删除)这则新闻。您要注意如何通过检测新闻实例的属性错误来检查错误。例如,如果新闻所需的字段的值缺失,它将作为 story.errors 的一部分显示出来。最后需要注意的是,这个服务是事务性的。这将告诉 Grails(和 Spring)使用现有的事务(如果已经存在的话),或在调用这些操作时创建一个新的事务。这对投票操作十分重要,因为在这些操作中您必须先从数据库读取新闻,然后再更新一个列。现在已经创建了基本的业务服务,您可以围绕它们创建一个 Web 服务,以将它们公开为 API,如下所示。

公开 API

作为开发人员,我们通常认为 API 是为供其他开发人员调用而编写的代码。在面向服务架构中,这仍然是正确的。不过这个 API 不是 Java 接口或类似的东西;它是一个 Web 服务签名。Web 服务公开 API,让他人可以调用它。Grails 让 Web 服务的创建变得非常简单 —它不过是 Grails 应用程序中的另一个控制器。清单 4 给出了应用程序的 API。

清单 4. API 控制器
 import grails.converters.* 

 class ApiController { 
    // injected services 
    def searchService 
    def storyService 
    
    // set the default action 
    def defaultAction = "stories"

    def stories = { 
        stories = searchService.list() 
        render stories as XML 
    } 
    
    def submit = { 
        def story = new Story(params) 
        story = storyService.create(story) 
        log.info("Story saved story="+story) 
        render story as XML 
    } 
    
    def digg = { 
        def story = storyService.voteFor(params.id) 
        render story as XML 
    } 
    
    def bury = { 
        def story = storyService.voteAgainst(params.id) 
        render story as XML 
    } 
 }

在这里,您将看到在前面小节创建的业务服务。这些服务由 Grails 自动注入。实际上,它们是由 Spring 框架(Grails 构建在这种技术之上)注入的。您仅需遵循命名约定(searchService的实例变量,用于引用 SearchService类的实例等等),其他事情由 Grails 完成。

API 包含四个可能调用的操作(行为):storiessubmitdiggbury。在每种情况下,您将委托给一个业务服务,然后获取调用结果并将其序列化到发送给客户机的 XML。Grails 使呈现变得很简单,仅使用呈现函数和 XML 转换器。最后需要注意,submitdiggbury操作都使用 params对象。这是 HTTP 请求参数及其值的散列表。对于 diggbury,仅需获取 id参数。对于 submit操作,需要将整个 params对象传递给 Story类的构造器。这用到 Grails 数据绑定 —只要参数名与该类的属性名匹配,Grails 将替您设置它们。这是 Grails 使开发更加容易的另一个例子。您仅编写了很少的代码,但这就是创建服务并将其公开为 Web 服务所需的所有代码。现在您可以创建一个使用这些服务的富表示层。


表示

您已经在应用程序的后端使用了 SOA。这将允许您在其上创建许多不同类型的表示层。首先要使用 Flex 框架构建一个基于 Flash 的用户界面。不过这像使用其他客户端表示技术一样简单。您甚至还可以创建一个 “胖” 桌面客户端。不过这是不必要的,因为您可以从 Web 客户端上获得丰富的用户体验。看看下面提供的一些简单用例,并使用 Flex 通过它们构建用户界面。现在,让我们列出所有新闻。

列出新闻

要列出新闻,您必须知道应该从后端调用哪个 API。您知道了吗?在创建后端时,您是否将特定的 URL 映射到 API 控制器及其方法?很明显您还没有这样做,但 Grails 能帮助您轻松完成。它使用约定优于配置的原则完成此操作。因此,要在 API 控制器上为 digg 应用程序调用新闻操作,URL 应该为 http://<root>/digg/api/stories。因为这是一个 RESTful Web 服务,所以它应该是一个 HTTP GET。您可以在浏览器上直接执行这个操作,并获得类似于清单 5 的 XML。

清单 5. 示例 XML 响应
 <?xml version="1.0" encoding="UTF-8"?><list> 
  <story id="12"> 
    <category>technology</category> 
    <description>This session discusses approaches and techniques to 
 implementing Data Visualization and Dashboards within Flex 3.</description> 
    <link>http://onflash.org/ted/2008/10/360flex-sj- 
 2008-data-visualization-and.php</link> 
    <tags>flash, flex</tags> 
    <title>Data Visualization and Dashboards</title> 
    <votesAgainst>0</votesAgainst> 
    <votesFor>0</votesFor> 
  </story> 
  <story id="13"> 
    <category>technology</category> 
    <description>Make your code snippets like really nice when you blog about 
 programming.</description> 
    <link>http://bc-squared.blogspot.com/2008/07/ 
 syntax-highlighting-and-code-snippets.html</link> 
    <tags>programming, blogging, javascript</tags> 
    <title>Syntax Highlighting and Code Snippets in a blog</title> 
    <votesAgainst>0</votesAgainst> 
    <votesFor>0</votesFor> 
  </story> 
  <story id="14"> 
    <category>miscellaneous</category> 
    <description>You need a get notebook if you are going to take 
 good notes.</description> 
    <link>http://www.randsinrepose.com/archives/2008/06/01/ 
 sweet_decay.html</link> 
    <tags>notebooks</tags> 
    <title>Sweet Decay</title> 
    <votesAgainst>0</votesAgainst> 
    <votesFor>0</votesFor> 
  </story> 
  <story id="16"> 
    <category>technology</category> 
    <description>If there was one thing I could teach every engineer, it 
 would be how to market. </description> 
    <link>http://www.codinghorror.com/blog/archives/001177.html</link> 
    <tags>programming, funny</tags> 
    <title>The One Thing Every Software Engineer Should Know</title> 
    <votesAgainst>0</votesAgainst> 
    <votesFor>0</votesFor> 
  </story> 
 </list>

很明显,具体内容取决于您在数据库中保存什么样的数据。重要的是 Grails 以什么样的结构将 Groovy 对象序列化为 XML。现在,可以为服务编写一些 ActionScript 代码了。

数据访问

将表示层移动到客户端的最大好处就是让架构更加干净。现在可以轻松采用传统的模型 - 视图 - 控制器(MVC)模式,因为服务器和客户机之间很干净。所以,首先构建一个表示数据结构的模型,然后封装对数据的访问。这如清单 6 中的 Story类所示。

清单 6. Story
 public class Story extends EventDispatcher 
 { 
    private static const LIST_URL:String = 
    "http://localhost:8080/digg/api/stories";    
                    
    [Bindable] public var id:Number;        
    [Bindable] public var title:String; 
    [Bindable] public var link:String; 
    [Bindable] public var category:String; 
    [Bindable] public var description:String; 
    [Bindable] public var tags:String; 
    [Bindable] public var votesFor:int; 
    [Bindable] public var votesAgainst:int; 
        
    private static var listStoriesLoader:URLLoader; 
    private static var dispatcher:Story = new Story(); 
        
        public function Story(data:XML=null) 
        { 
            if (data) 
            { 
                id = data.@id; 
                title = data.title; 
                link = data.link; 
                category = data.category; 
                description = data.description; 
                tags = data.tags; 
                votesFor = Number(data.votesFor); 
                votesAgainst = Number(data.votesAgainst); 
            } 
        } 

 }

清单 6 给出了 Story类的基础内容。它具有几个与您将从服务获取的数据结构对应的字段。这个构造器采用一个可选的 XML 对象,并通过这个对象填充字段。您可能已经看到,访问 XML 数据的语法非常简单。ActionScript 将 E4X 标准实现为处理 XML 的简化方式。这类似于 XPath,但使用对面向对象编程语言更加自然的语法。此外,每个属性都使用 [Bindable]修饰。这是一个 ActionScript 注释。它允许将 UI 组件绑定到字段,以在字段发生变更时自动更新 UI。最后需要注意的是静态变量 listStoriesLoader。这是 URLLoader的一个实例,即用于发送 HTTP 请求的 ActionScript 类。Story类中的一个静态方法使用它通过 API 加载所有新闻。如清单 7 所示。

清单 7. 列出 Stories 方法
 public class Story extends EventDispatcher 
 { 
    public static function list(loadHandler:Function, errHandler:Function=null):void 
    { 
        var req:URLRequest = new URLRequest(LIST_URL); 
        listStoriesLoader = new URLLoader(req); 
        dispatcher.addEventListener(DiggEvent.ON_LIST_SUCCESS, loadHandler); 
        if (errHandler != null) 
        { 
            dispatcher.addEventListener(DiggEvent.ON_LIST_FAILURE, errHandler); 
        } 
        listStoriesLoader.addEventListener(Event.COMPLETE, listHandler); 
        listStoriesLoader.addEventListener(IOErrorEvent.IO_ERROR, listErrorHandler); 
        listStoriesLoader.load(req); 
    } 
    private static function listHandler(e:Event):void 
    { 
        var event:DiggEvent = new DiggEvent(DiggEvent.ON_LIST_SUCCESS); 
        var data:Array = []; 
        var storiesXml:XML = XML(listStoriesLoader.data); 
        for (var i:int=0;i<storiesXml.children().length();i++) 
        { 
            var storyXml:XML = storiesXml.story[i]; 
            var story:Story = new Story(storyXml); 
            data[data.length] = story; 
        } 
        event.data = data; 
        dispatcher.dispatchEvent(event); 
    } 
 }

list是控制器将要调用的方法。它发出一个 HTTP 请求,并为该请求的结束注册一个事件监听器。这是必要的,因为 Flash 中的所有 HTTP 请求都是异步的。当请求完成时,将调用 listHandler方法。在这里将再次使用 E4X 解析来自服务的 XML 数据。它创建一个 Story实例数组,并将该数组附加到一个将要发出的定制事件。看看清单 8 中的定制事件。

清单 8. DiggEvent
 public class DiggEvent extends Event 
 { 
    public static const ON_STORY_SUBMIT_SUCCESS:String = "onStorySubmitSuccess"; 
    public static const ON_STORY_SUBMIT_FAILURE:String = "onStorySubmitFailure"; 
    public static const ON_LIST_SUCCESS:String = "onListSuccess"; 
    public static const ON_LIST_FAILURE:String = "onListFailure"; 
    public static const ON_STORY_VOTE_SUCCESS:String = "onStoryVoteSuccess"; 
    public static const ON_STORY_VOTE_FAILURE:String = "onStoryVoteFailure"; 
        
    public var data:Object = {}; 
    public function DiggEvent(type:String, bubbles:Boolean=false, 
 cancelable:Boolean=false) 
    { 
        super(type, bubbles, cancelable); 
    } 
    
 }

在 ActionScript 开发中经常用到定制事件类,因为所有服务器交互都必须是异步的。控制器可以在模型类上调用该方法,并且注册用于查找定制事件的事件处理程序。您可以使用额外的字段修饰定制事件。在这个例子中,您仅添加了一个通用的可重用的数据字段。了解模型的表示层之后,我们看看控制器如何使用它。

应用程序控制器

该控制器负责协调模型的调用,将模型提供给视图,然后通过视图响应事件。我们看看控制器的代码,如清单 9 所示。

清单 9. DiggController
 public class DiggController extends Application 
 { 
    [Bindable] 
    public var stories:ArrayCollection; 
    [Bindable] 
    public var subBtnLabel:String = 'Submit a new Story'; 
    
    public function DiggController() 
    { 
        super(); 
        init(); 
    } 
    
    private function init():void 
    { 
        Story.list(function(e:DiggEvent):void{  
            stories = new ArrayCollection(e.data as Array);});            
    } 
 }

控制器扩展了核心 Flex 类 Application。这是 Flex 的常见用法,它使您可以通过简单的方式将控制器与视图关联起来,如下一小节所示。控制器具有一组可以绑定的新闻。因此可以将这组新闻绑定到 UI 控件。应用程序加载之后将自动调用 Story.list方法。它将一个匿名函数或 lambda 作为处理程序传递给 Story.list方法。lambda 表达式仅将来自定制事件的数据转储到新闻集合中。现在看看如何在视图中使用它。

视图

我在前面提到,控制器扩展了 Flex Application类。它是所有 Flex 应用程序的基类。Flex 允许使用 MXML(一种 XML 变体)声明式地创建 UI。控制器可以利用这种语法,如清单 10 所示。

清单 10. 用户界面
 <?xml version="1.0" encoding="utf-8"?> 
 <ctrl:DiggController xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" 
    xmlns:works="components.*" xmlns:ctrl="controllers.*"> 
    <mx:Script> 
        <![CDATA[ 
            import org.developerworks.digg.Story; 

            private function digg():void 
            { 
                this.diggStory(results.selectedItem as Story); 
            } 
            private function bury():void 
            { 
                this.buryStory(results.selectedItem as Story); 
            } 
        ]]> 
    </mx:Script> 
    <ctrl:states> 
        <mx:State name="SubmitStory"> 
            <mx:AddChild relativeTo="{buttons}" position="after"> 
                <works:StoryEditor successHandler="{this.submissionHandler}"/> 
            </mx:AddChild> 
        </mx:State> 
    </ctrl:states> 
    <mx:DataGrid id="results" dataProvider="{stories}" doubleClickEnabled="true" 
        doubleClick="openStory(results.selectedItem as Story)"/> 
    <mx:HBox id="buttons"> 
        <mx:Button label="Digg the Story!" click="digg()"/> 
        <mx:Button label="Bury the Story!" click="bury()"/> 
        <mx:Button label="{this.subBtnLabel}" click="toggleSubmitStory()"/> 
    </mx:HBox> 
 </ctrl:DiggController>

注意,根文档使用 “ctr1” 名称空间。这被声明为指向 “controller” 文件夹,即存储 controller 类的位置。因此任何来自 controller 类的内容在 UI 代码中都是可用的。例如,一个 dataProvider属性设置为 stories的数据网格。这是来自 controller 类的 stories 变量。它直接绑定到 DataGrid 组件。现在可以将 Flex 代码编译成 SWF 文件。您仅需使用静态 HTML 文件来嵌套 SWF。它的运行效果如图 1 所示。

图 1. Digg 应用程序
Digg 应用程序

这是 Flex 应用程序的默认外观。您可以使用标准的 CSS 改变它的颜色,就像在 HTML 应用程序中一样。此外,还可以定制 DataGrid 组件,指定它的列、顺序等等。“Digg the Story!” 和 “Bury the Story!” 按钮都调用 controller 类上的函数。注意,您已经为 DataGrid 的双击事件添加一个 controller 函数。所有 controller 方法都使用这个模型,就像在 MVC 架构中一样。最后一个按钮 “Submit a new Story” 使用了 Flex 的几个关键特性。看看它是如何工作的。


提交新闻

如您在清单 10 所见,单击 “Submit a new Story” 按钮将调用 controller 类上的 toggleSubmitStory方法。代码如清单 11 所示。

清单 11. toggleSubmitStory方法
 public class DiggController extends Application 
 { 

    public function toggleSubmitStory():void 
    { 
        if (this.currentState != 'SubmitStory') 
        { 
            this.currentState = 'SubmitStory'; 
            subBtnLabel = 'Nevermind'; 
        } 
        else 
        { 
            this.currentState = ''; 
            subBtnLabel = 'Submit a new Story'; 
        } 
    }        
 }

这个函数将应用程序的 currentState属性更改为 SubmitStory。现在回头看看 清单 10,找到这个状态是在什么位置定义的。状态允许您添加或删除组件,或在现有组件上设置属性。在这个例子中,您为 UI 添加一个新组件。这个组件正是提交新闻所需的定制组件。如清单 12 所示。

清单 12. StoryEditor 组件
 <?xml version="1.0" encoding="utf-8"?> 
 <mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="100%"> 
    <mx:Form> 
        <mx:FormHeading label="Submit a New Story"/> 
        <mx:FormItem label="What's the URL?" required="true"> 
            <mx:TextInput id="linkBox" toolTip="Keep it Short and Sweet" 
                text="{story.link}"/> 
        </mx:FormItem> 
        <mx:FormItem label="Give it a Title" required="true"> 
            <mx:TextInput id="titleBox" text="{story.title}"/> 
        </mx:FormItem> 
        <mx:FormItem label="Pick a Category" required="true"> 
            <mx:ComboBox dataProvider="{Story.CATEGORIES}" id="categoryBox" 
 selectedIndex="0"/> 
        </mx:FormItem> 
        <mx:FormItem label="Give a Short Description"> 
            <mx:TextArea height="60" width="320" id="descripBox" 
                text="{story.description}"/> 
        </mx:FormItem> 
        <mx:FormItem label="Tag It"> 
            <mx:TextInput id="tagBox" text="{story.tags}"/> 
        </mx:FormItem> 
        <mx:Button label="Submit It!" click="submitStory()"/> 
    </mx:Form> 
    <mx:Binding source="linkBox.text" destination="story.link"/> 
    <mx:Binding source="titleBox.text" destination="story.title"/> 
    <mx:Binding source="categoryBox.selectedItem.data" 
 destination="story.category"/> 
    <mx:Binding source="descripBox.text" destination="story.description"/> 
    <mx:Binding source="tagBox.text" destination="story.tags"/>    
 </mx:VBox>

清单 12 仅给出了定制组件的 UI 元素。这仅是一个简单的表单,但要注意其中的绑定声明。它允许您直接将 UI 表单元素绑定到 Story实例。这已在一个脚本块中声明,如清单 13 所示。

清单 13. StoryEditor 脚本
 <?xml version="1.0" encoding="utf-8"?> 
 <mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="100%"> 
    <mx:Script> 
        <![CDATA[ 
            import mx.controls.Alert; 
            import org.developerworks.digg.*; 
            
            [Bindable] 
            private var story:Story = new Story(); 
            public var successHandler:Function; 
            
            private function submitStory():void 
            { 
                story.addEventListener(DiggEvent.ON_STORY_SUBMIT_SUCCESS, 
 successHandler); 
                story.addEventListener(DiggEvent.ON_STORY_SUBMIT_FAILURE, 
 errorHandler); 
                story.save(); 
                // reset 
                story = new Story(); 
            } 
                        
            private function errorHandler(e:Event):void 
            { 
                Alert.show("Fail! : " + e.toString());                
            } 
        ]]> 
    </mx:Script> 
 </mx:VBox>

该脚本块用作组件的控制器代码。您可以轻松地将它们放到该组件的独立 controller 类中,因为这两种样式在 Flex 中都是很常见的。回头看看 图 1,单击 “Submit new Story” 按钮将显示这个定制组件,如图 2 所示。

图 2. 显示定制组件
显示定制组件

您可以使用这个组件添加一则新的新闻。它将调用后端服务,该服务返回新闻所需的 XML。反过来,这将被转换回 Story实例,并添加到绑定到 DataGrid 的新闻集合中,使其能够自动显示在 UI 中。这样,表示代码就完成了,并连接到后端服务。


结束语

在这篇文章中,您看到了使用 Grails 创建 Web 服务有多么简单!您不需要编写任何 SQL 以创建数据库,或对其进行读写。通过使用 Grails,将 URL 映射到 Groovy 代码、调用服务和为 Web 服务创建 XML 都变得非常简单。Flex 前端能够轻松使用 XML。您学会了如何在前端创建干净的 MVC 架构,以及如何使用许多复杂的 Flex 特性,比如 E4X、数据绑定、状态和定制组件。

本系列的第 2 部分将探索在使用 Google Web Toolkit 的服务上使用基于 JavaScript 的 UI。


下载

描述名字大小
示例 Grails 应用程序digg.zip504KB
示例 Flex 应用程序源代码digg-flex-src.zip10KB

参考资料

学习

获得产品和技术

  • Grails:这篇文章使用 1.0.3 版本的 Grails。
  • 获取 Flex 3 SDK
  • 获取 Adobe Flash Player10 或更新版本。
  • Java SDK:这篇文章使用 Java SE 1.6_05。
  • 使用 IBM 试用软件改进您的下一个开源开发项目,可以下载或从 DVD 获得。
  • 下载 IBM 产品评估版,试用这些来自 DB2®、Lotus®、Rational®、Tivoli®和 WebSphere®的应用程序开发工具和中间件产品。

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development, Open source
ArticleID=385092
ArticleTitle=使用 Grails 构建富 Internet 应用程序,第 1 部分: 使用 Grails 和 Flex 构建 Web 应用程序
publish-date=04232009