精通Grails:在企业中使用Grails

常常有人问我 Grails 是否已经可以在企业中使用。简单的回答是 “是”。而我通常给出更加详细的 回答:“只要您觉得 Spring 和 Hibernate(Grails 所依赖的底层技术)已经就绪;只要您觉得 Tomcat 或 JBoss(或 Java 企业版[Java EE])应用服务器已经就绪;只要您觉得 MySQL 或 PostgreSQL(或者 您使用的数据库)已经就绪;只要您觉得 Java 编程已经企业就绪,那么 Grails 就已经企业就绪” 。

British Sky Broadcasting Group 最近将它的 Web 站点迁移到了 Grails。他们现在每月的点击量达 到 1.1 亿次。LinkedIn.com 在其站点的某些商业部分使用 Grails。Tropicana Juice 在英国有一个 Web 站点,该站点几年来一直在 Grails 上运行。Grails.org 本身就是用 Grails 编写的,每月支持 70,000 多次下载。而 SpringSource 最近有关 G2One(Groovy 和 Grails 所在的公司)的问卷调查结果 完全可以打消 Groovy 和 Grails 是否适合企业使用的任何疑虑。

Groovy 有时候看起来比较奇怪,最重要的是要记住,它完全是用普通的 Java 代码实现的。尽管 Grails 开发与其他典型的 Java Web 框架看起来很不一样,但最终您仍然会得到一个与 Java EE 兼容的 WAR 文件。

在这篇文章中,您将探讨一些用于监控和配置的企业级工具。学习如何使用 JMX 调整 Grails 应用程 序。本文将简要介绍 Grails 中的 Spring 配置。您还会看到如何在 Config.groovy 中首次指定 log4j 设置,以及如何使用 JMX 动态调整它们。

实现 JMX 工具

JMX 是 2000 年推出的。更确切地说,它是最古老的 JSR 之一 — JSR 3。随着 Java 语言在服务器 上越来越流行,远程优化和配置实时运行应用程序成为平台的关键部分。在 2004 年,Sun 使用 JMX 实 现了 JVM 并推出了支持工具,比如针对 Java 1.5 JDK 的 JConsole。

JMX 通过一个统一的接口提供 JVM 内省机制、应用服务器和类。这些不同的组件通过受管 bean(简 写为 MBean)呈现给管理控制台。

MBeans 就像汽车仪表板上的各种仪表、刻度盘和开关。有些仪器是只读的,就像速度计一样;有些仪 器是 “可写的”,就像加速器一样。但 MBean 是远程管理工具,所以这个仪表板比喻不是很不恰当。可 以将其想象为远程打开汽车的转向灯或改变车里的电台频道。

启用本地 JMX 代理

本地还是远程?

对开发和测试而言,在本地同时运行 JMX 代理和客户机通常是最简单的事情。但在实际生产环境中远 程监控代理时,JMX 的好处就会凸显出来。JConsole 与其他任何 Java 进程一样占用系统资源(RAM、 CPU 周期等)。这会出现问题,特别是监控的生产服务器的负载压力较大时。但更重要的是,能够从一个 地方监控多台服务器将使您成为数字领域的佼佼者。

当然,远程监控生产服务器还可以恰当保护它们的安全。您可以设置密码保护或使用更好的公/私钥身 份验证。

要使用 JMX 进行监控,则必须先启用它。在 Java 5 中,您必须在运行时为 JVM 提供几个与 JMX 相 关的标志(在 Java 6 中,这些设置已经就绪,不过您一定要自己设置的话,也是可以的)。在 JMX 中 ,要设置一个 JMX 代理。清单 1 显示了 JVM 参数:

清单 1. 启用 JMX 监控的 JVM 参数

-Dcom.sun.management.jmxremote-Djava.rmi.server.hostname=localhost

一些教程建议创建一个全局 JAVA_OPTS 环境变量来保存 JMX 标志。其他教程则建议在命令行输入标 志:java -Dcom.sun.management.jmxremote -Djava.rmi.server.hostname=localhost someExampleClass。

两种方法都是可行的,但是对生产环境而言它们都不是最好的。我发现最好的方法是在服务器的启动 脚本中设置这些值。如果每次重新启动服务器时都要输入这些复杂的标志,则表明这是一个不好的解决方 案。应避免设置 CLASSPATH 和 JAVA_OPTS 等全局变量,原因有两个:在复制服务器(在服务器之间复制 一个一致的启动脚本更容易)时增加了不必要的配置步骤,而且它们强制同一机器上的所有 Java 进程共 享同一配置。是的,您可以创建一个详细的清单来提醒您这些琐碎的配置细节,但是记录复杂的东西远不 如将复杂去掉有效。

对于 UNIX®、Linux® 和 Mac OS X 系统,Grails 启动脚本是 $GRAILS_HOME/bin/grails。编辑这个文件,添加两个 JAVA_OPTS 行,如清单 2 所示:

清单 2. 在 Grails 启动脚本中为 UNIX、Linux 和 Mac OS X 启用 JMX 监控#!/bin/sh DIRNAME='dirname "$0"'. "$DIRNAME/startGrails"export JAVA_OPTS="-Dcom.sun.management.jmxremote"export JAVA_OPTS="$JAVA_OPTS - Djava.rmi.server.hostname=localhost"startGrails  org.codehaus.groovy.grails.cli.GrailsScriptRunner "$_cnnew1_cnnew1@"

对于 Windows®,Grails 启动脚本是 $GRAILS_HOME/bin/grails.bat。在调用 startGrails.bat 之前,向 grails.bat 添加两行,如清单 3 所示:

清单 3. 在 Grails 中为 Windows 启用 JMX 监控set JAVA_OPTS=-Dcom.sun.management.jmxremoteset JAVA_OPTS=%JAVA_OPTS% - Djava.rmi.server.hostname=localhost

在两个脚本中,注意第一个 JAVA_OPTS 变量赋值覆盖了全局环境变量(如果有的话)。这个设置只覆 盖着一个进程 — 它不会对整个系统的全局变量进行赋值。我这样做的目的是防止全局设置影响本地设置 。如果您依赖于已经正确设置的全局值,请确保在开始赋值时包含现有变量,像我在清单 2 和清单 3 的 第二行中那样。

现在,输入 grails run-app 启动 Grails。您看到的内容与控制台输出中的完全相同,不过应用服务 器现在已经可以进行监控。

使用一个 JMX 客户机来监控 JMX 代理。这是一个类似 JConsole 的桌面 GUI(包含在 Java 5 及更 高版本中)或 Web UI(包含在大多数服务器中,比如 Tomcat 和 JBoss)。甚至可以编写代码来监控代 理,在本文快结束时将提到。

打开第二个命令行窗口,输入 jconsole。您将在本地 JML 代理列表中看到 Grails,如图 1 所示。 单击 Grails,然后单击 Connect 按钮。

图 1. JConsole 列出了本地 JMX 代理

出于安全考虑,只能在使用 NTFS 的 Windows 系统上访问本地 JMX。如果系统使用的是 FAT 或 FAT32,可能会出现问题。但不要担心。在接下来的部分中,我将说明如何设置 JMX 代理进行远程访问。 就算代理和客户机刚好位于同一机器上,也不会遇到本地安全问题。

连接之后,您应该看到类似图 2 所示的摘要页面:

图 2. JConsole 摘要页面

单击 Memory、Threads、Classes 和 VM 选项卡。您可以实时查看 JVM 的内部情况。如果服务器是在 物理内存上运行,那么您可以看到实时线程数,甚至能够看到服务器的已经运行时间。这些选项卡非常有 趣,不过您马上要将注意力转向 MBeans 选项卡 — 这里将会出现您需要的类。

启用远程 JMX 代理

不要在工作时尝试这个操作

永远不要在生产中使用这个配置。出于演示目的,我关闭了所有身份验证和加密。

要设置 JMX 代理以接受远程连接,需要向 JVM 传递另外几个与 JMX 相关的标志。这几个标志打开一 个管理端口并配置安全设置(或本例中的 lack thereof)。

向 Grails 启动脚本添加三个新行,如清单 4 所示:

清单 4. 在 Grails 启动脚本中启用远程 JMX 监控

export JAVA_OPTS="- Dcom.sun.management.jmxremote"export JAVA_OPTS=" $JAVA_OPTS -Djava.rmi.server.hostname=localhost"export JAVA_OPTS=" $JAVA_OPTS -Dcom.sun.management.jmxremote.port=9004"export JAVA_OPTS=" $JAVA_OPTS -Dcom.sun.management.jmxremote.authenticate=false"export JAVA_OPTS=" $JAVA_OPTS -Dcom.sun.management.jmxremote.ssl=false"

使用这些设置重新启动 Grails。还要重新启动 JConsole。这次,单击 Remote 选项卡并连接到端口 9004 上的 localhost,如图 3 所示:

图 3. 在 JConsole 中连接到远程 JMX 代理

这是一种验证是否连接上远程 JVM(即使它在同一系统上运行)的快速方法。单击 MBeans 选项卡。 展开左边的 java.lang 树。单击 Runtime 元素。然后在屏幕右侧的属性窗口中双击 InputArguments。 您应该看到所有远程 JMX 设置,如图 4 所示:

图 4. 传递给 JVM 的 JMX 远程代理标志

让这个窗口保持打开。单击 Connection 菜单打开一个新的连接。单击 Remote 选项卡,这次接受默 认值(端口 0 上的 localhost)。展开 Runtime MBean 的 InputArguments。注意这里没有远程 JMX 标 志(如图 5 所示):

图 5. 监控两个不同的 JMX 代理

如果标题栏(监控本身)的提示不够清楚,注意您刚打开的第二个 JConsole 窗口,它监控 JConsole 应用程序本身。

现在您启动了 JConsole 并监控 Grails 应用程序,此时应该使用它进行一些实际操作了,比如调整 登录设置,不过在进行该操作之前,必须先理解最后一个 JMX 难点:MBean 服务器。

MBean 服务器、Grails 和 Spring

您在 JConsole 上单击的 Runtime 元素是一个 MBean。为了让 MBean 呈现给 JMX 客户机,必须使用 一个内部运行有 JMX 代理的 MBean 服务器注册它。有些人将术语 “JMX 代理” 等同于 “MBean 服务 器”,但从技术上讲,MBean 服务器是在 JMX 代理内部运行的众多组件中的一个。

要以编程方式注册 MBean,需调用 MBeanServer.registerMBean()。不过,在 Grails 中,更准确地 说,这是由一个配置文件(一个 Spring 配置文件)管理的。

Spring 是 Grails 的核心。它是控制所有类如何交互的依赖项注入框架。

从 JMX 角度,您可能会想:我在用 MBean 服务器注册这个 MBean。但从 Spring 角度,您应该这样 考虑:我在将 MBean 注入到 MBean 服务器中。动作对象可能不同,但最终结果是一样的:MBean 变为对 JMX 客户机是可视的。

首先在 grails-app/conf/spring 中创建一个名为 resources.xml 的文件(在本文后面,您将明白 resources.groovy 和 resources.xml 的关系)。设置 resources.xml,如清单 5 所示:

清单 5. 在 resources.xml 中设置 Spring/JMX 基础设施

     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"     xsi:schemaLocation="http://www.springframework.org/schema/beans       http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">       class="org.springframework.jmx.support.MBeanServerFacToryBean">                

   

    

如果想确保基本配置是正确的,那么现在可重新启动 Grails,但只解决问题的一半:您有了一台 MBean 服务器,但是没有任何 MBean。此时看到的两个 bean(mbeanServer 和 exporter)是需要注册 MBean 的基础设施。mbeanServer bean 保存一个到现有 MBean 服务器的引用。mbeanServer bean 被注 入到 exporter bean — 将 MBean 列表呈现给 JMX 客户机(比如 JConsole)的类。现在仅需将 MBean 添加到 exporter bean 内部的 bean 映射中,以注册它。下一小节将进行此操作。

通过 Grails 使用 log4j

打开 grails-app/conf/Config.groovy 查看 log4j 设置(如清单 6 所示):

清单 6. Config.groovy 中的 log4j 设置

log4j {   appender.stdout = "org.apache.log4j.ConsoleAppender"   appender.'stdout.layout'="org.apache.log4j.PatternLayout"   appender.'stdout.layout.ConversionPattern'='[%r] %c{2} %m%n'   // and so on...}

启动 Grails 应用程序时,命令提示符上出现的大多数消息是 log4j 消息。这要归功于 org.apache.log4j.ConsoleAppender。

注册 log4j MBean

如果需要在没有 JMX 的情况下调整 Grails 的登录设置,只需简单地编辑这个文件并重新启动服务器 ,但如果更愿意调整这些设置而不重新启动服务器,或者想远程调整它们,那应该怎样做呢?这看起来似 乎是 JMX 可选的完美方法。幸运的是,log4j 附带一个方便执行这些任务 MBean。您所需做的只是注册 log4j MBean。

将 entry 的 XML(如清单 7 所示)添加到 resources.xml。这将把 log4j MBean 注入到 MBean 服 务器。

清单 7. 将 MBean 注入到 MBean 服务器

      

               

 

重新启动 Grails,然后重新启动 JConsole。如果连接到端口 9004 上的 localhost,新的 log4j MBean 应该显示在 MBeans 选项卡中。展开 log4j 树元素,单击默认值,然后单击 Info 选项卡。从刚 添加到 resources.xml(参见图 5)的条目中,可以看到配置片段:

图 6. 查看默认 MBean 信息

现在可以通过 JMX 看到 log4j 了,下一步是调整一些登录设置。

动态更改 log4j 设置

假设现在 Grails 应用程序表现异常。您应该查找问题的根源。查看 grails- app/conf/Config.groovy,您会发现根登录程序将它的输出发送到控制台,但过滤器被设置为 error — rootLogger=”error,stdout”。您希望将登录级别更改为 trace 来提高控制台的输出量。

看一下 JConsole。在 log4j 文件夹下,您应该可以看到根 MBean。可以看到优先级属性被设置为 ERROR,就像在 Config.groovy 中一样。双击 ERROR 值并输入 TRACE,如图 6 所示:

图 7. 将根登录程序优先级从 ERROR 更改为 TRACE

为了验证控制台比以前更好用,在浏览器中,在 Grails 应用程序的主页上单击到 AirportMappingController 的链接。在大量新的输出中,您应该可以找到一些有关 Grails 如何导入初 始列表的详细信息。请参阅清单 8 中的样例:

清单 8. 增加 log4j 输出

[11277653] metaclass.RedirectDynamicMethod  Dynamic method [redirect] looking up URL mapping for  controller [airportMapping] and action [list] and  params [["action":"index", "controller":"airportMapping"]]  with [URL Mappings------------org.codehaus.groovy.grails.web.mapping.ResponseCodeUrlMapping@1bab0b/rest/airport/(*)?/(*)/(*)?/(*)?][11277653] metaclass.RedirectDynamicMethod Dynamic method  [redirect] mapped to URL [/trip/airportMapping/list][11277653] metaclass.RedirectDynamicMethod Dynamic method  [redirect] forwarding request to [/trip/airportMapping/list][11277653] metaclass.RedirectDynamicMethod Executing redirect  with response  [com.opensymphony.module.sitemesh.filter.PageResponseWrapper@19243f]

什么时候可以安全忽略 Fatal Error?

如果您曾经运行过 Grails 1.0.3,可能会注意到一条频繁出现在控制台输出中的奇怪错误 — [Fatal Error] :-1:-1: Premature end of file。大多数人仅仅是忽略它,因为它似乎真的不会引起任何错误( 不管致命与否)。

如果将登录级别转变为 trace,您就会看到有关致命错误的详细信息: converters.XMLParsingParameterCreationListener Error parsing incoming XML request: Error parsing XML。

正如大量冗长的日志输出所解释的那样,Grails 试图解析每个传入请求,把请求当作 XML。大多数请 求不是 XML,因此请求处理程序将根据实际情况报告错误,但仍然会正确地处理请求。

这个 “谎报军情的小 bug” 在版本 1.0.4 中得到了修复。

更改 log4j ConversionPattern

现在需要更改输出模式。在 Config.groovy 中,使用下面这一行设置模式: appender.’stdout.layout.ConversionPattern’='[%r] %c{2} %m%n’。查看 log4j 文档,您决定将它设 置为更具描述性的东西。

单击 JConsole 中的 stdout MBean。将 conversionPattern 属性从它的原始值更改为 [%5p] %d {hh:mm:ss} (%F:%M:%L)%n%m%n%n。生成一些新的日志输出后,我将描述这些奇怪的符号的含义,了解设 置 conversionPattern 的更多信息)。

图 8. 在 PatternLayout 中更改 conversionPattern

现在再次在 Web 浏览器中单击主页链接和 AirportMappingController 链接。输出的格式发生了很大 变化,如清单 9 所示:

清单 9. 使用新的 conversionPattern 的控制台输出

[DEBUG] 09:04:47  (RedirectDynamicMethod.java:invoke:127)Dynamic method [redirect] looking up URL mapping for controller[airportMapping] and action [list] and params[["action":"index", "controller":"airportMapping"]] with [URL Mappings------------org.codehaus.groovy.grails.web.mapping.ResponseCodeUrlMapping@e73cb7/rest/airport/(*)?/(*)/(*)?/(*)?][DEBUG] 09:04:47 (RedirectDynamicMethod.java:invoke:144)Dynamic method [redirect] mapped to URL [/trip/airportMapping/list][DEBUG] 09:04:47 (RedirectDynamicMethod.java:redirectResponse:162)Dynamic method [redirect] forwarding request to [/trip/airportMapping/list][DEBUG] 09:04:47 (RedirectDynamicMethod.java:redirectResponse:168)Executing redirect with response   [com.opensymphony.module.sitemesh.filter.PageResponseWrapper@47b2e7]

现在您可以看到输出,以下是详细过程:%p 写出优先级别。这些消息很明显是 DEBUG 级别。%d {hh:mm:ss} 以小时:分钟:秒的格式显示日期戳。(%F:%M:%L) 将文件名、方法和行编号放在括号内。最 后,%n%m%n%n 写入一个新行、消息和其他两行。

通过 JMX 对 log4j 所做的更改不是持久化的。如果重新启动 Grails,它会恢复到 Config.groovy 中的持久化设置。这意味着您可以任意处理 JMX 设置,而不用担心会永久打乱事情。对于 ConversionPattern,使用 JMX 是体验设置的很好方法,您可以找到最喜欢的设置。但是不要忘了将模式 复制到 Config.groovy,以使更改是持久化的。

查看 Hibernate DEBUG 输出

回到先前的假设,您正在调试一个实时 Grails 应用程序,但还没有找到您需要的东西。将根 MBean 的优先级属性设置回 ERROR 来减少干扰。

可能问题就出在 Hibernate。再回过头看看 Config.groovy,您会发现 org.hibernate 包的登录输出 被设置为 off。不要改变整个应用程序的输出级别,而是集中于特定的包,这样可能会获得更多的信息。

在 JConsole 中,单击默认 MBean。除了更改属性值以外,您还可以调用 MBean 上的方法。单击 Operations 选项卡。为名称参数输入 org.hibernate 并单击 addLoggerMBean 按钮。您应该会看到一个 新的 MBean 出现在左边的树中。

单击新的 org.hibernate MBean 并将优先级属性更改为 DEBUG,如图 9 所示:

图 9. 更改 org.hibernate MBean 上的优先级

现在返回到 Web 浏览器,单击主链接,并再次单击 AirportMappingController 。应该会看到一大串 DEBUG 日志语句,如清单 10 所示:

清单 10. Hibernate log4j 输出

[DEBUG] 10:05:52  (AbstractBatcher.java:logOpenPreparedStatement:366)about to open PreparedStatement (open PreparedStatements: 0, globally: 0)[DEBUG] 10:05:52 (ConnectionManager.java:openConnection:421)opening JDBC connection[DEBUG] 10:05:52 (AbstractBatcher.java:log:401)select this_.airport_id as airport1_0_0_, this_.locid as locid0_0_,this_.latitude as latitude0_0_, this_.longitude as longitude0_0_,this_.airport_name as airport5_0_0_, this_.state as state0_0_from usgs_airports this_ limit ?[DEBUG] 10:05:52 (AbstractBatcher.java:logOpenResults:382)about to open ResultSet (open ResultSets: 0, globally: 0)[DEBUG] 10:05:52 (Loader.java:getRow:1173)result row: EntityKey[AirportMapping#1][DEBUG] 10:05:52 (Loader.java:getRow:1173)result row: EntityKey[AirportMapping#2]

花一点时间查看 Hibernate DEBUG 输出。您详细了解到何时从数据库挑选数据,并转换为一个由 bean 组成的 ArrayList。

使用 Spring Bean Builder

现在您已经知道了如何通过 resources.xml 配置 JMX,因此可以进行新的实践了。Grails 通过一个 替代文件 resources.groovy 支持 Spring 配置。将 grails-app/conf/spring/resources.xml 重命名为 resources.xml.old。将如清单 11 所示的代码添加到 resources.groovy 中:

清单 11. 使用 Bean Builder 配置 Spring

import  org.springframework.jmx.support.MBeanServerFacToryBeanimport org.springframework.jmx.export.MBeanExporterimport org.apache.log4j.jmx.HierarchyDynamicMBeanbeans = {  log4jBean(HierarchyDynamicMBean)  mbeanServer(MBeanServerFacToryBean) {   locateExistingServerIfPossible=true  }  exporter(MBeanExporter) {   server = mbeanServer   beans = ["log4j:hierarchy=default":log4jBean]  }}

如您所见,Spring bean 使用 Groovy 代码(而不是 XML)配置的。您已经在 “Grails 与遗留数据 库” 和 “RESTful Grails” 中看到现实中的 Groovy MarkupBuilder。主题有点变化 — 一个专门为 Spring 配置定义 bean 的 Bean Builder。

重新启动 Grails 和 JConsole。确认 XML 配置中没有任何更改。

使用 XML 来配置 Spring 可以轻松运用 Web 各种优势 — 可以从大量源复制粘贴代码片段。但是使 用 Bean Builder 更符合 Grail 中的其余配置。使用 Grails 到现在,您已经看到了 DataSource.groovy、Config.groovy、BootStrap.groovy 和 Events.groovy,这只是其中一小部分。在 代码中进行配置,这意味着您可以执行一些操作,比如基于运行的环境有条件地呈现 MBean。

例如,清单 12 显示了如何在生产环境中呈现 log4jBean,但在开发环境中隐藏它:

清单 12. 有条件地呈现 JMX bean

import  org.springframework.jmx.support.MBeanServerFacToryBeanimport org.springframework.jmx.export.MBeanExporterimport org.apache.log4j.jmx.HierarchyDynamicMBeanimport grails.util.GrailsUtil beans = {  log4jBean(HierarchyDynamicMBean)  mbeanServer(MBeanServerFacToryBean) {   locateExistingServerIfPossible=true  }  switch(GrailsUtil.environment){   case "development":   break   case "production":    exporter(MBeanExporter) {     server = mbeanServer     beans = ["log4j:hierarchy=default":log4jBean]    }   break  }}

输入 grails run-app 并在 JConsole 中确定 log4j MBean 没有出现在开发模式中。现在输入 grails prod run-app(或 grails war 并将 WAR 文件部署到您选择的应用服务器)。这个 MBean 会一 直等待您重新启动 JConsole。

Groovy 中的 JMX

我最后要向您展示的是如何以编程方式调试 JMX MBean。JConsole GUI 非常漂亮,能够从 Groovy 脚 本进行更改更是增加了它的魅力。

开始之前,创建一个名为 testJmx.groovy 的文件,将清单 13 中的代码添加到该文件中:

清单 13. 在 Groovy 中调用一个远程 JMX 代理

import  javax.management.MBeanServerConnectionimport javax.management.remote.JMXConnecTorFacToryimport javax.management.remote.JMXServiceURL def agentUrl = "service:jmx:rmi:///jndi/rmi://localhost:9004/jmxrmi"def connecTor = JMXConnecTorFacTory.connect(new JMXServiceURL(agentUrl))def server = connecTor.mBeanServerConnectionprintln "Number of registered MBeans: ${server.mBeanCount}"println "/nRegistered Domains:"server.domains.each{println it}println "/nRegistered MBeans:"server.queryNames(null, null).each{println it}

如果 Grails 正在运行,应该可以看到如清单 14 所示的输出:

清单 14. testJmx.groovy 脚本的输出

$ groovy testJmx.groovyNumber of registered MBeans: 20 Registered Domains:java.util.loggingJMImplementationjava.langlog4j Registered MBeans:java.lang:type=MemoryManager,name=CodeCacheManagerjava.lang:type=Compilationjava.lang:type=GarbageCollecTor,name=Copyjava.lang:type=MemoryPool,name=Eden Spacelog4j:appender=stdoutjava.lang:type=Runtimelog4j:hierarchy=defaultlog4j:logger=rootlog4j:appender=stdout,layout=org.apache.log4j.PatternLayoutjava.lang:type=ClassLoadingjava.lang:type=MemoryPool,name=Survivor Spacejava.lang:type=Threadingjava.lang:type=GarbageCollecTor,name=MarkSweepCompactjava.util.logging:type=Loggingjava.lang:type=Memoryjava.lang:type=OperatingSystemjava.lang:type=MemoryPool,name=Code Cachejava.lang:type=MemoryPool,name=Tenured Genjava.lang:type=MemoryPool,name=Perm GenJMImplementation:type=MBeanServerDelegate

警告

testJmx.groovy 脚本可能会抛出一条类似清单 15 所示的 groovy.lang.MissingMethodException:

清单 15. 可能抛出的 JMX 异常

Caught: groovy.lang.MissingMethodException: No signature of method:  javax.management.remote.rmi.RMIConnecTor$RemoteMBeanServerConnection.queryNames()   is applicable for argument types: (java.lang.String, null)

如果发生这种情况,请从 $GROOVY_HOME/lib 中删除 mx4j-3.0.2.jar。它包含在 Groovy 发布版中, 以通过 JMX 支持 1.4 JDK,但它与 Java 平台的更高版本冲突。

这个脚本中有趣的部分来自 javax.management.MBeanServer,javax.management.MBeanServer 是调 用 connecTor.mBeanServerConnection 时返回的(记住,Java 中的 getFoo() 方法调用在 Groovy 中可 以简写为 foo)。调用 server.mBeanCount 返回已注册 MBean 的数量。调用 server.domains 返回一个 由域名组成的 String[]。域名是 MBean 标识符的第一部分 — 用逗号分隔的名/值对列表完全限定名称 。调用 server.queryNames(null, null) 返回一个由所有已注册 MBean 组成的 Set。

为了获得某个特定 MBean,请将清单 16 中的代码添加到脚本底部:

清单 16. 获得一个 MBean

println "/nHere is the Runtime MBean:"def mbean = new GroovyMBean(server, "java.lang:type=Runtime")println mbean

有了一个到 MBean 服务器的连接并知道 MBean 的名称后,使用一行即可获取一个新的 GroovyMBean 。清单 17 显示了脚本输出:

清单 17. GroovyMBean 输出

Here is the Runtime MBean:MBean Name:  java.lang:type=RuntimeAttributes:  (r) javax.management.openmbean.TabularData SystemProperties  (r) java.lang.String VmVersion  (r) java.lang.String VmName  (r) java.lang.String SpecName  (r) [Ljava.lang.String; InputArguments  (r) java.lang.String ManagementSpecVersion  (r) java.lang.String SpecVendor  (r) long Uptime  (r) long StartTime  (r) java.lang.String LibraryPath  (r) java.lang.String BootClassPath  (r) java.lang.String VmVendor  (r) java.lang.String ClassPath  (r) java.lang.String SpecVersion  (r) java.lang.String Name  (r) boolean BootClassPathSupported

是否还记得本文前面提到的 InputArguments?它们是传递给 JVM 的所有 -D 参数。使用这些参数来 确认您确实连接到了远程 JMX 代理。再添加两行代码(如清单 18 所示)以输出 String[]:

清单 18. 从运行时 MBean 获取 InputArguments

println "/nHere are the  InputArguments:"mbean.InputArguments.each{println it}

如果看到清单 19 所示的输出,就说明您圆满完成了所有任务:

清单 19. 显示 InputArguments

Here are the InputArguments:-Xserver-Xmx512M -Dcom.sun.management.jmxremote-Djava.rmi.server.hostname=localhost-Dcom.sun.management.jmxremote.port=9004-Dcom.sun.management.jmxremote.authenticate=false-Dcom.sun.management.jmxremote.ssl=false-Dprogram.name=grails-Dgroovy.starter.conf=/opt/grails/conf/groovy-starter.conf-Dgrails.home=/opt/grails-Dbase.dir=.-Dtools.jar=/Library/Java/Home/lib/tools.jar 

结束语

Grails 是已经可以在企业中使用。常用企业库,比如 JMX、Spring 和 log4j 都可以在 Grails 中使 用,因为虽然表面不像,但您仍然是进行传统的 Java EE 开发。

本文是 Trip Planner 应用程序专栏今年的最后一篇文章。我希望保持这系列文章的主题的一致性, 以重点讲解核心 Grails 功能。在明年的专栏中,我将继续保留这种风格,但同时我还想纳入各种 Grails 应用程序,以扩大视野。

例如,下个月我将介绍一种新的博客系统。您学习关于如何启动新的 Grails 应用程序的新技术,但 它绝不是 “新瓶装旧酒”。您将重温 Grails 的 RESTful,但是是在设置完全 Atom 基础设施的环境中 。您将再次使用 JSON 和 Ajax,??过这次是启用日程表和标志云。几个月之后,我将使用另一种新思维 。

Grails 继续获得了每个新 Web 站点的大力支持。成熟 Web 框架的标志是能够以多种方式使用它。明 年的精通 Grails 文章将演示 Grails 中可能实现的各种 Web 站点。到那时,您将享受到精通 Grails 带来的乐趣。

你可能付出一定的代价,但日后你得到的,远比付出的多得多。

精通Grails:在企业中使用Grails

相关文章:

你感兴趣的文章:

标签云: