精通 Grails: 在企业中使用 Grails

将 Grails 与 JMX、Spring 和 log4j 结合使用

在本期的 精通 Grails中,作者 Scott Davis 将解释为何 Grails 已经可以在企业中使用。您将看到如何将 Grails 与企业级库结合使用,包括 Java™管理扩展(Java Management Extensions,JMX)、Spring 和 log4j。

Scott Davis, 主编, AboutGroovy.com

Scott DavisScott Davis 是国际著名的作家、演讲家和软件开发人员。他出版的书籍有 Groovy Recipes: Greasing the Wheels of JavaGIS for Web Developers: Adding Where to Your ApplicationThe Google Maps APIJBoss At Work



2008 年 12 月 16 日

常常有人问我 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 动态调整它们。

关于本系列

Grails 是一种新型 Web 开发框架,它将常见的 Spring 和 Hibernate 等 Java 技术与当前流行的配置优于约定等实践相结合。Grails 是用 Groovy 编写的,它可以与遗留 Java 代码无缝集成,同时又加入了脚本编程语言的灵活性和动态性。学习完 Grails 之后,您将彻底改变看待 Web 开发的方式。

实现 JMX 工具

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

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

有关 JMX 的更多背景信息,请参阅 “Java 理论与实践 : 用 JMX 检测应用程序”。

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

两种方法都是可行的,但是对生产环境而言它们都不是最好的。我发现最好的方法是在服务器的启动脚本中设置这些值。如果每次重新启动服务器时都要输入这些复杂的标志,则表明这是一个不好的解决方案。应避免设置 CLASSPATHJAVA_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.jmxremote 
 set 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 代理
JConsole 列出了本地 JMX 代理

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

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

图 2. JConsole 摘要页面
JConsole 摘要页面

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

启用远程 JMX 代理

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

永远不要在生产中使用这个配置。出于演示目的,我关闭了所有身份验证和加密。要了解如何保护 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 代理
在 JConsole 中连接到远程 JMX 代理

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

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

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

图 5. 监控两个不同的 JMX 代理
监控两个不同的 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 的核心。它是控制所有类如何交互的依赖项注入框架(有关 Spring 的更多信息,请参阅 参考资料)。

从 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 基础设施
 <beans xmlns="http://www.springframework.org/schema/beans"
       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"> 

  <bean id="mbeanServer" 
        class="org.springframework.jmx.support.MBeanServerFactoryBean"> 
   	 <property name="locateExistingServerIfPossible" value="true" /> 
  </bean> 
  
  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> 	
    <property name="server" ref="mbeanServer"/> 
    <property name="beans"> 
      <map> 
      </map> 
    </property> 
  </bean>   
 </beans>

如果想确保基本配置是正确的,那么现在可重新启动 Grails,但只解决问题的一半:您有了一台 MBean 服务器,但是没有任何 MBean。此时看到的两个 bean(mbeanServerexporter)是需要注册 MBean 的基础设施。mbeanServerbean 保存一个到现有 MBean 服务器的引用。mbeanServerbean 被注入到 exporterbean —将 MBean 列表呈现给 JMX 客户机(比如 JConsole)的类。现在仅需将 MBean 添加到 exporterbean 内部的 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 的基础知识,请参阅 参考资料)。

注册 log4j MBean

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

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

清单 7. 将 MBean 注入到 MBean 服务器
 <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> 	
  <property name="server" ref="mbeanServer"/> 
  <property name="beans"> 
    <map> 
      <entry key="log4j:hierarchy=default"> 
        <bean class="org.apache.log4j.jmx.HierarchyDynamicMBean"/> 
      </entry>        
    </map> 
  </property> 
 </bean>

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

图 6. 查看默认 MBean 信息
查看默认 MBean 信息

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

动态更改 log4j 设置

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

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

图 7. 将根登录程序优先级从 ERROR更改为 TRACE
将根登录程序优先级从 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 中的 stdoutMBean。将 conversionPattern属性从它的原始值更改为 [%5p] %d{hh:mm:ss} (%F:%M:%L)%n%m%n%n。生成一些新的日志输出后,我将描述这些奇怪的符号的含义(请参阅 参考资料,了解设置 conversionPattern的更多信息)。

图 8. 在 PatternLayout中更改 conversionPattern
在 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.hibernateMBean 并将优先级属性更改为 DEBUG,如图 9 所示:

图 9. 更改 org.hibernateMBean 上的优先级
更改 org.hibernateMBean 上的优先级

现在返回到 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.MBeanServerFactoryBean 
 import org.springframework.jmx.export.MBeanExporter 
 import org.apache.log4j.jmx.HierarchyDynamicMBean 

 beans = { 
  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.MBeanServerFactoryBean 
 import org.springframework.jmx.export.MBeanExporter 
 import org.apache.log4j.jmx.HierarchyDynamicMBean 
 import 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.MBeanServerConnection 
 import javax.management.remote.JMXConnectorFactory 
 import javax.management.remote.JMXServiceURL 

 def agentUrl = "service:jmx:rmi:///jndi/rmi://localhost:9004/jmxrmi"
 def connector = JMXConnectorFactory.connect(new JMXServiceURL(agentUrl)) 
 def server = connector.mBeanServerConnection 

 println "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.groovy 
 Number of registered MBeans: 20 

 Registered Domains: 
 java.util.logging 
 JMImplementation 
 java.lang 
 log4j 

 Registered MBeans: 
 java.lang:type=MemoryManager,name=CodeCacheManager 
 java.lang:type=Compilation 
 java.lang:type=GarbageCollector,name=Copy 
 java.lang:type=MemoryPool,name=Eden Space 
 log4j:appender=stdout 
 java.lang:type=Runtime 
 log4j:hierarchy=default 
 log4j:logger=root 
 log4j:appender=stdout,layout=org.apache.log4j.PatternLayout 
 java.lang:type=ClassLoading 
 java.lang:type=MemoryPool,name=Survivor Space 
 java.lang:type=Threading 
 java.lang:type=GarbageCollector,name=MarkSweepCompact 
 java.util.logging:type=Logging 
 java.lang:type=Memory 
 java.lang:type=OperatingSystem 
 java.lang:type=MemoryPool,name=Code Cache 
 java.lang:type=MemoryPool,name=Tenured Gen 
 java.lang:type=MemoryPool,name=Perm Gen 
 JMImplementation: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.MBeanServerjavax.management.MBeanServer是调用 connector.mBeanServerConnection时返回的(记住,Java 中的 getFoo()方法调用在 Groovy 中可以简写为 foo)。调用 server.mBeanCount返回已注册 MBean 的数量。调用 server.domains返回一个由域名组成的 String[]。域名是 MBean 标识符的第一部分 —用逗号分隔的名 / 值对列表完全限定名称。调用 server.queryNames(null, null)返回一个由所有已注册 MBean 组成的 Set(要更多地了解 MBeanServer类的可用方法,请参阅 参考资料)。

为了获得某个特定 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=Runtime 
  
 Attributes: 
  (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

有关 GroovyMBean的更多信息,请参阅 参考资料


结束语

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 版本。

讨论

条评论

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=Java technology, Web development, Open source
ArticleID=363900
ArticleTitle=精通 Grails: 在企业中使用 Grails
publish-date=12162008