IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  Open source  >

Apache Geronimo on Grails

创建要部署到 Apache Geronimo 上的 Grails 应用程序

developerWorks
文档选项

未显示需要 JavaScript 的文档选项

样例代码

英文原文

英文原文


级别: 中级

Michael Galpin, 开发人员, eBay
Michael Galpin, 开发人员, eBay

2008 年 8 月 07 日

您是否既想更快更节省地构建 Web 站点,又想利用拥有行业优势的技术?只要使用 Grails 和 Apache Geronimo 就能实现目的。Grails 能够利用动态语言 Groovy 的强大力量加速开发。不过,它运行在 Java® Virtual Machine 上并且利用成熟的 Java 技术。通过将 Grails 应用程序部署到杰出的经过 Java EE V5 认证的开源应用服务器 Apache Geronimo 上,可以使 Grails 应用程序轻松地上升更高的层次。在本文中,您将了解如何使用 Grails 轻松地进行 Web 开发以及如何使用 Geronimo 轻松地进行 Grails 部署。您还将了解 Grails 应用程序如何利用 Geronimo 所提供的资源和服务。

入门

在本文中,您将使用 Grails 构建 Web 应用程序,并将其部署到 Apache Geronimo 上。下面是学习本文需要安装的软件:

Java Development Kit
Grails 和 Geronimo 都需要使用 Java Development Kit。Grails 只需使用 Java V1.4,但是 Geronimo 需要使用 V1.5。因为它是经过 Java EE V5 认证的应用服务器,而 Java EE V5 使用诸如注释和范型之类的 Java V1.5 功能。在本文中,我们使用 Java SE V1.6_05。
Groovy 编程语言
Grails 使用 Groovy 编程语言,但是 Grails 附带了该编程语言,因此无需额外下载。Grails 还使用了许多优秀的产品,例如 Hibernate 和 Spring,这些产品也都包含在 Grails 中。
Grails
本文使用的是 Grails V1.0.2
Apache Geronimo
本文使用的是 Apache Geronimo V2.1.1。您还可以将 Geronimo 与 Tomcat 或 Jetty 结合使用,本文使用的是 Jetty 发行版。
MySQL
本文使用的是 MySQL V5.0.41,但是您应当能够使用 Hibernate 所支持的任何一个数据库。

本文不是 Grails 的介绍性文章。您应当熟悉 Grails,但是如果您熟悉 Ruby on Rails 和 Java 也可以。参考资料 部分有一些优秀文章可以让您更加熟悉 Grails。





回页首


Grails 广告网络

为了演示如何同时使用 Grails 与 Geronimo,我们将使用 Grails 构建一个简单应用程序,然后用 Geronimo 部署和增强该应用程序。对于这个应用程序,我们将构建一个简单的广告网络。下面是这个应用程序的用例的简单分类:

  • 广告客户可以在网络中注册。注册过程只需要提供名称和密码。
  • 广告客户可以登录到网络中。
  • 广告客户可以创建广告。广告将包含标题、文本和图像的 URL,还包含开始日期和结束日期、报价和关键词。关键词将决定何时显示广告,而报价用于在多个广告拥有相同关键词时确定排名顺序。
  • 会员可以调用 Web 服务获取广告列表。他将提供一个关键词和广告的最大数量。

这是一个非常简单的应用程序。Grails 广告网络无法取代 Google 或者 Yahoo!,但是它能够让我们接触到 Grails 的很多优秀功能。现在,让我们看一看其中的一些功能,并了解这些功能如何支持使用 Grails 快速开发广告网络应用程序。

Grails 利用 “约定优于配置(convention over configuration)” 和 “不要重复自己(don't repeat yourself)” 的原则,极大地减少了编写典型 Web 应用程序所需的代码量。我们将使用一些 Grails 代码生成脚本开始开发。

创建应用程序

我们将该应用程序称为 adserver。首先,使用 Grails 命令 generate-app。我们不会花费很多时间讨论这条命令的工作原理(如果您不熟悉这条命令,请参阅 参考资料)。重要的是您的应用程序是根据 Grails 约定建立的。这一点不但对于让 Grails 框架知道在哪里查找类和配置元数据至关重要,而且能够使应用程序的打包和部署更加轻松。图 1 显示了此目录结构的截图。


图 1. adserver 应用程序结构
adserver 应用程序结构

Grails 鼓励采用自下而上的开发过程,在这种开发过程中通常从定义域模型开始。您可以使用 Grails 命令 create-domain-class 帮助完成这项工作。将要创建的第一个模型是 Advertiser 模型,如清单 1 所示:


清单 1. Advertiser
                
class Advertiser {
    static hasMany = [ads:Ad]
    String name
    String password
}

该类十分简单,如果您大体上熟悉 Grails 或 Groovy,这样的类正是您所需的类。其中最复杂的是 hasMany 行。此行表示 Advertiser 可以有多个 Ad。让我们看看 Ad 类。


清单 2. Ad
                
class Ad {
    static belongsTo = [advertiser:Advertiser]
    String title
    String imageUrl
    String text
    String keywords
    Date startDate
    Date endDate
    Integer bid
}

这个类也十分简单,它与 Advertiser 类存在着一对多的关系。现在可以使用 Grails 脚本 generate-all 为这些类中的每个类创建 scaffolding 代码(所有 CRUD 操作的控制器类和 GNU Server Pages (GSP) 视图),然后使用 run-app 命令运行该应用程序。这个操作是可选的,但是如果您刚开始使用 Grails,这样做尤为有用。它为您提供了如何使用域类以及如何编写控制器和视图的优秀示例。我们需要使用这些知识为应用程序创建更加个性化的控制器和视图。

自定义应用程序

首先需要完成的是让用户(广告客户)注册。创建一个 RegisterController。其代码如清单 3 所示。


清单 3. RegisterController
                
class RegisterController {

    def index = { }

    def save = {
      def exists = Advertiser.findWhere(name:params.name)
      if (exists){
        flash.message = "The name " + params.name + " is already taken"
        redirect(action:index)
      }
        def advertiser = new Advertiser()
        advertiser.properties = params
        advertiser.save(flush:true)
      session.advertiser=advertiser
      redirect(controller:"ad",action:"adsFor")
    }
}

这段代码完成的主要工作是检查广告客户挑选的名称是否已被占用。如果是,将创建一个错误消息并重定向到索引页面。如果名称未被占用,则创建广告客户。我在会话中加入了 Advertiser 实例,因此无需再次查找它,而且我重定向到 Ad 控制器。您很快就需要查找该类,但是首先需要让广告客户登录。清单 4 中显示了 Login 控制器。


清单 4. Login 控制器
                
class LoginController {

    def index = { }
    def login = {
    def advertiser = Advertiser.findWhere( 
                          name:params.name , password:params.password )
    if (!advertiser){
        flash.message = "The password does not match the name of the advertiser"
        redirect(action:index)
    }
    session.advertiser=advertiser
    redirect(controller:"ad",action:"adsFor")
   }
}

这是另一个简单的 Groovy 类。它只是检查名称和密码是否与数据库匹配。如果匹配,则把广告客户设置到会话中,并前进到在 Register 控制器中看到的 Ad 控制器中的相同操作。在转到 Ad 控制器之前,您可能注意到了控制器方法直接使用域对象数据访问代码。替代方法是使用服务层。

Grails 服务层

这里的最佳实践是将在 RegisterControllerLoginController 中看到的 Advertiser.findWhere 代码提取到服务层中。为此,您可以使用 Grails 命令 create-service 并重构代码以将业务逻辑移到服务类中。上面显示的注册/登录场景可能类似于清单 5。


清单 5. 样例 AdvertiserService
                
class AdvertiserService {

    boolean transactional = true

    def advertiserExists(String name){
    def exists = Advertiser.findWhere(name:name)
        exists != null
    }
    
    def login(String name, String password){
        Advertiser.findWhere(name:name, password:password)
    }
}

记住,Grails 构建在成熟的 Java 技术(例如 Spring 框架)之上。Grails 服务类将变成一个 Spring Bean。默认情况下,服务是一个单体,就像直接使用 Spring 一样。还可以将它注入到其他 Spring Bean 中,并且所有控制器也都是 Spring Bean。约定优于配置原则在这里再次发挥了作用,并且使您可以轻松地在控制器中引用该服务。


清单 6. 在控制器中使用服务
                
class LoginController {
    def advertiserSerivce
    def index = { }
    def login = {
    def advertiser = advertiserService.login(params.name, params,password)
    if (!advertiser){
        flash.message = "The password does not match the name of the advertiser"
        redirect(action:index)
    }
    session.advertiser=advertiser
    redirect(controller:"ad",action:"adsFor")
   }
}

只要拥有遵循 xyzService 约定的成员变量,Grails 就知道需要注入 xyzService 单体。在此样例中,将使样例保持简单,并且无需过于担心服务层。但是,必须承认这是 Grails 的特点,从而使它实际上有别于许多其他快速开发/约定优于配置框架。让我们返回到应用程序代码,查看如何创建和处理广告。

创建和处理广告

到目前为止,我们有两个控制器 — 一个用于注册,另一个用于登录到广告网络。两个控制器都把控制权转给另一个控制器:AdsController


清单 7. AdsController
                
import grails.converters.XML
            
class AdController {
    
    def index = { redirect(action:list,params:params) }

    // the delete, save and update actions only accept POST requests
    def allowedMethods = [delete:'POST', save:'POST', update:'POST', placement:'GET']

    def adsFor = {
        render(view:"list", model:[adList: Ad.findAllWhere(advertiser : 
session.advertiser )]);
    }
    def list = {
        if(!params.max) params.max = 10
        [ adList: Ad.list( params ) ]
    }

    def placement = {
        def ads = Ad.findAll("from Ad as ad where ad.keywords like 
'%"+params.keyword+"%' order by ad.bid desc")
        render ads as XML
    }

    def show = {
        def ad = Ad.get( params.id )

        if(!ad) {
            flash.message = "Ad not found with id ${params.id}"
            redirect(action:list)
        }
        else { return [ ad : ad ] }
    }

    def delete = {
        def ad = Ad.get( params.id )
        if(ad) {
            ad.delete()
            flash.message = "Ad ${params.id} deleted"
            redirect(action:list)
        }
        else {
            flash.message = "Ad not found with id ${params.id}"
            redirect(action:list)
        }
    }

    def edit = {
        def ad = Ad.get( params.id )

        if(!ad) {
            flash.message = "Ad not found with id ${params.id}"
            redirect(action:list)
        }
        else {
            return [ ad : ad ]
        }
    }

    def update = {
        def ad = Ad.get( params.id )
        if(ad) {
            ad.properties = params
            if(!ad.hasErrors() && ad.save()) {
                flash.message = "Ad ${params.id} updated"
                redirect(action:show,id:ad.id)
            }
            else {
                render(view:'edit',model:[ad:ad])
            }
        }
        else {
            flash.message = "Ad not found with id ${params.id}"
            redirect(action:edit,id:params.id)
        }
    }

    def create = {
        def ad = new Ad()
        ad.properties = params
        return ['ad':ad]
    }

    def save = {
        def ad = new Ad(params)
        if (session.advertiser){
            ad.advertiser = session.advertiser
        }
        if(!ad.hasErrors() && ad.save()) {
            flash.message = "Ad ${ad.id} created"
            redirect(action:show,id:ad.id)
        }
        else {
            render(view:'create',model:[ad:ad])
        }
    }
}

如果使用过 scaffolding 命令 (generate-all ad),您将会认出其中的大部分代码。但这段代码已经过多种方式进行了自定义。我们添加了 adsFor 方法,因为这是从注册和登录控制器转发的内容。该方法使用来自会话的广告客户获得该广告客户拥有的所有广告,然后用 “列表” 视图呈现这些信息。还将 save 方法修改为从会话中使用广告客户。最后,添加了布局方法。这是会员检索广告时将使用的方法。它获取一个 keyword 参数并获得带有该关键词的所有广告。它使用 Hibernate Query Language 进行自定义查询,这是一种使用 XML 来序列化数据的 REST Web 服务。Grails 通过以 XML 形式返回呈现广告简化了这一过程。您不必添加很多代码来自定义应用程序以处理所有用例。使用 Grails stats 命令,您很快就会发现实际编写的代码非常少。


清单 8. adserver 应用程序 stats
                
$ grails stats

Welcome to Grails 1.0.2 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: /Users/michael/lib/grails-1.0.2        
        
Base Directory: /Users/michael/code/grails/adserver
Note: No plugin scripts found
Running script /Users/michael/lib/grails-1.0.2/scripts/Stats.groovy
Environment set to development

    +----------------------+-------+-------+
    | Name                 | Files |  LOC  |
    +----------------------+-------+-------+
    | Controllers          |     4 |   179 | 
    | Domain Classes       |     2 |    15 | 
    | Services             |     1 |    10 | 
    | Integration Tests    |     5 |    20 | 
    +----------------------+-------+-------+
    | Totals               |    12 |   224 | 
    +----------------------+-------+-------+

这段代码非常少。实际上,其中的大部分是使用 generate-all scaffolding 为 advertiserad 生成的代码。使用 Grails 不必编写很多代码就可以轻松地进行开发。它还通过简化测试来使开发更加轻松。

测试应用程序

要运行应用程序,只需发出 Grails 命令 run-app。该命令将调用使用嵌入式内存中数据库和嵌入式 Jetty Web 容器的脚本,因此无需设置独立的服务器或数据库。您应当可以立即转到 http://localhost:8080/adserver 并访问控制器。在测试和调试应用程序之后,就可以将它与 Geronimo 结合使用了。





回页首


利用 Geronimo

现在,我们开发了一个能满足所有用例的 Web 应用程序。由于利用了 Grails,因此您能够快速开发该应用程序,而且只需编写较少的代码。当然,较少只是相对的 — 那么相对什么而言呢?答案显然是典型的 Java Web 应用程序。毫无疑问,与典型的 Java Web 应用程序相比,使用 Grails 可以更快速地进行开发,并且需要编写的代码更少,但是这样需不需要成本?好消息是 Grails 应用程序属于 Java Web 应用程序。使用 Grails 可以把它封装成标准的 Java Web 应用程序(一个 WAR),并将其部署到任何 Java Web 容器中 — 包括 Apache Geronimo。让我们看看如何将 Grails adserver 应用程序部署到 Geronimo 上。

部署 Grails WAR

将 Grails 应用程序部署到 Geronimo 上的第一步是创建一个 WAR。查看一下 Grails 目录结构,编写一个 Ant 脚本完成此工作不会很难,但是更幸运的是,Grails 提供了一个简单的 Grails 命令 war,可以让您更轻松地完成此工作。该脚本编译 grails-app 树中的代码并且将其与基本 Grails ($GRAILS_HOME) 目录中的代码组合在一起。其结果被添加到 /web-app 目录中。由于使用的是 Geronimo,因此需要添加 Geronimo 部署计划。您可以在 /web-app/WEB-INF 中创建一个 geronimo-web.xml 文件。


清单 9. Geronimo 部署计划
                
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://geronimo.apache.org/xml/ns/j2ee/web-1.1">
    <environment xmlns="http://geronimo.apache.org/xml/ns/deployment-1.1">
        <moduleId>
            <groupId>grailsApps</groupId>
            <artifactId>AdServer</artifactId>
            <version>0.1</version>
            <type>war</type>
        </moduleId>
        <hidden-classes>
            <filter>org.springframework</filter>
            <filter>org.apache.cxf</filter>
            <filter>org.apache.commons</filter>
        </hidden-classes>        
    </environment>
    <context-root>/adserver</context-root>
</web-app>

在这里,有一点值得注意,也就是隐藏类部分。默认情况下,这些包包含在 Geronimo 中,但也包含在 Grails 中。这将告诉装载 Grails 应用程序的类装载程序忽略这些包中可用于父类装载程序(即,容器的类装载程序)的所有类。这将确保装载这些类的 Grails 版本,并且不会出现任何烦人的类装载程序冲突。

现在可以运行 Grails war 命令了。清单 10 显示了需要运行的命令及其输出。


清单 10. 使用 war 命令
                
$ grails war

Welcome to Grails 1.0.2 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: /Users/michael/lib/grails-1.0.2        
        
Base Directory: /Users/michael/code/grails/adserver
Note: No plugin scripts found
Running script /Users/michael/lib/grails-1.0.2/scripts/War.groovy
Environment set to production
   [delete] Deleting: /Users/michael/.grails/1.0.2/projects/
adserver/resources/web.xml
   [delete] Deleting directory /Users/michael/.grails/1.0.2/projects/adserver/classes
   [delete] Deleting directory /Users/michael/.grails/1.0.2/projects/adserver/resources
    [mkdir] Created dir: /Users/michael/.grails/1.0.2/projects/adserver/classes
  [groovyc] Compiling 13 source files to /Users/michael/.grails/1.0.2/
projects/adserver/classes
    [mkdir] Created dir: /Users/michael/.grails/1.0.2/projects/adserver/
resources/grails-app/i18n
[native2ascii] Converting 10 files from /Users/michael/code/grails/adserver/
grails-app/i18n to /Users/michael/.grails/1.0.2/projects/adserver/resources/
grails-app/i18n
     [copy] Copying 1 file to /Users/michael/.grails/1.0.2/projects/adserver/classes
     [copy] Copying 1 file to /Users/michael/.grails/1.0.2/projects/adserver/resources
    [mkdir] Created dir: /Users/michael/code/grails/adserver/staging
     [copy] Copying 93 files to /Users/michael/code/grails/adserver/staging
     [copy] Copied 19 empty directories to 1 empty directory under /Users/michael/
code/grails/adserver/staging
     [copy] Copying 23 files to /Users/michael/code/grails/adserver/staging/
WEB-INF/grails-app
     [copy] Copying 55 files to /Users/michael/code/grails/adserver/staging/
WEB-INF/classes
    [mkdir] Created dir: /Users/michael/code/grails/adserver/staging/
WEB-INF/spring
     [copy] Copying 1 file to /Users/michael/code/grails/adserver/staging/
WEB-INF/classes
    [mkdir] Created dir: /Users/michael/code/grails/adserver/staging/
WEB-INF/templates/scaffolding
     [copy] Copying 6 files to /Users/michael/code/grails/adserver/staging/
WEB-INF/templates/scaffolding
     [copy] Copying 50 files to /Users/michael/code/grails/adserver/staging/
WEB-INF/lib
     [copy] Copying 1 file to /Users/michael/code/grails/adserver/staging/
WEB-INF
   [delete] Deleting: /Users/michael/.grails/1.0.2/projects/adserver/resources/web.xml
     [copy] Warning: /Users/michael/code/grails/adserver/plugins not found.
[propertyfile] Updating property file: /Users/michael/code/grails/adserver/staging/
WEB-INF/classes/application.properties
    [mkdir] Created dir: /Users/michael/code/grails/adserver/staging/WEB-INF/plugins
     [copy] Warning: /Users/michael/code/grails/adserver/plugins not found.
      [jar] Building jar: /Users/michael/code/grails/adserver/adserver-0.1.war
   [delete] Deleting directory /Users/michael/code/grails/adserver/staging
Done creating WAR /Users/michael/code/grails/adserver/adserver-0.1.war

要部署 WAR,只需将其添加到 Geronimo 部署目录中($GERONIMO_HOME/deploy,其中 $GERONIMO_HOME 是 Geronimo 的安装位置),也可以使用 Geronimo Console。


图 2. 使用 Geronimo Console 部署 Grails WAR
使用 Geronimo Console 部署 Grails WAR

在清单 9 的 Geronimo 部署计划中,我们把应用程序的上下文路径设为 /adserver,因此可以输入 http://localhost:8080/adserver 打开已部署的应用程序。现在 Grails 应用程序已经运行在 Geronimo 上,让我们看看如何使用 Geronimo 增强该应用程序。

创建数据库池

众所周知,使用数据库连接池可以获得许多性能优势。不用在每次请求 Web 应用程序时都创建数据库连接。因此,常常会在 Java Web 应用程序中建立数据库连接池。使用 Geronimo 之类的应用服务器,可以更加轻松地创建在 Geronimo 中部署的所有应用程序都可以重用的数据库连接池。您可以通过 Geronimo Console 轻松创建池,如图 3 所示:


图 3. 在 Geronimo 中创建数据库池
在 Geronimo 中创建数据库池

现在已经创建了数据库池,我们只需要从 Grails 应用程序访问它。为此,需要执行两个步骤。

从 Grails 访问数据库池

数据库池被表示为 Java DataSource 对象,并且绑定在 JNDI 中,因此任何一个应用程序都可以使用。要让数据库池出现在 Web 应用程序环境中,需要在 web.xml 文件中引用它。那么 web.xml 是什么呢?Grails 基于 $GRAILS_HOME/conf/webdefault.xml 为我们创建一个 web.xml 文件。您可以编辑该文件,也可以生成 WAR 并解压缩,然后编辑生成的 web.xml 文件。不管采用哪种方法,您都需要向 web.xml 中添加清单 11 中所示的部分:


清单 11. 在 web.xml 引用 DataSource
                
<resource-ref>
    <res-ref-name>jdbc/MyDataSource</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
    <res-auth>Container</res-auth>
    <res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>

通常,这段代码都添加到 web.xml 文件的末尾。接下来,需要将一个引用添加到 Geronimo 部署计划中。


清单 12. 新的 Geronimo 部署计划
                
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://geronimo.apache.org/xml/ns/j2ee/web-1.1">
    <environment xmlns="http://geronimo.apache.org/xml/ns/deployment-1.1">
        <moduleId>
            <groupId>grailsApps</groupId>
            <artifactId>AdServer</artifactId>
            <version>0.1</version>
            <type>war</type>
        </moduleId>
        <hidden-classes>
            <filter>org.springframework</filter>
            <filter>org.apache.cxf</filter>
            <filter>org.apache.commons</filter>
        </hidden-classes>
        <dependencies>
            <dependency>
                <groupId>console.dbpool</groupId>
                <artifactId>adserver</artifactId>
            </dependency>
        </dependencies>        
    </environment>
    <context-root>/adserver</context-root>
    <resource-ref>
        <ref-name>jdbc/MyDataSource</ref-name>
        <resource-link>adserver</resource-link>
    </resource-ref>    
</web-app>

注意,我们把数据库池称为 adserver,并且这是您在 resource-ref 和依赖关系中看到的 resource-link。部署计划中的 ref-name 必须与清单 11 的 web.xml 代码片段中的 res-ref-name 相匹配。这将 Geronimo 与 Web 应用程序连接起来。

现在,应用程序可以获得连接池。那么如何访问连接池?事实证明,使用 Grails 可以轻松地访问连接池。我们只需编辑 /grails-app/conf/DataSource.groovy。


清单 13. DataSource.groovy
                
dataSource {
    jndiName = "java:comp/env/jdbc/MyDataSource"
}
hibernate {
    cache.use_second_level_cache=true
    cache.use_query_cache=true
    cache.provider_class='org.hibernate.cache.EhCacheProvider'
}
// environment specific settings
environments {
    development {
        dataSource {
            dbCreate = "update"
        }
    }
    test {
        dataSource {
            dbCreate = "create-drop"
        }
    }
    production {
        dataSource {
            dbCreate = "create"
        }
    }
}
  

通常,第一个 dataSource 代码段是定义诸如驱动程序类、用户名、密码之类的 JDBC 属性的位置。现在提供 JNDI 名称。此名称必须为 “java:comp/env/” 加上在 web.xml 文件中为 res-ref-name 设置的内容。您可以根据任何环境覆盖所有这些设置,就像通常所做的那样。





回页首


结束语

您已经看到如何使用 Grails 快速构建 Web 应用程序。Grails 的约定优于配置原则可以融合大多数常见技术,而且无需花费太多努力。只要您遵循原则,Grails 就可以让您的工作更加轻松。您仍然需要编写业务逻辑代码,但是即使这段代码很少,也能够凸现 Groovy 编程语言的可表达性。Grails 与 Geronimo 结合使用给这段美好的经历带来了更加皆大欢喜的结局。您可以利用 Geronimo 的所有强大功能,因为 Grails 应用程序能够像任何其他 Java Web 应用程序一样运行,并且可以通过类似方式访问 Geronimo 资源。从这里开始,您可以使用消息传递等更多 Geronimo 功能,把 Grails 应用程序构建得更加复杂;也可以通过将 Grails 应用程序部署到 Geronimo 集群中,使它具有更强的可伸缩性。






回页首


下载

描述名字大小下载方法
样例代码os-ag-grails.adserver.example.zip829KBHTTP
关于下载方法的信息


参考资料

学习

获得产品和技术


作者简介

Michael Galpin's photo

Michael Galpin 从 1998 年开始专业开发 Java 软件,目前为 eBay 工作,他从 California Institute of Technology 获得了数学学位。


Michael Galpin's photo

Michael Galpin 从 1998 年开始专业开发 Java 软件,目前为 eBay 工作,他从 California Institute of Technology 获得了数学学位。




对本文的评价










回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款