程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 精通Grails: 在企業中使用Grails

精通Grails: 在企業中使用Grails

編輯:關於JAVA

常常有人問我 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.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 代理

出於安全考慮,只能在使用 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 基礎設施

<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(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 服務器

<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 信息

現在可以通過 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.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.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=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 

結束語

Grails 是已經可以在企業中使用。常用企業庫,比如 JMX、Spring 和 log4j 都可以在 Grails 中使 用,因為雖然表面不像,但您仍然是進行傳統的 Java EE 開發。

本文是 Trip Planner 應用程序專欄今年的最後一篇文章。我希望保持這系列文章的主題的一致性, 以重點講解核心 Grails 功能。在明年的專欄中,我將繼續保留這種風格,但同時我還想納入各種 Grails 應用程序,以擴大視野。

例如,下個月我將介紹一種新的博客系統。您學習關於如何啟動新的 Grails 應用程序的新技術,但 它絕不是 “新瓶裝舊酒”。您將重溫 Grails 的 RESTful,但是是在設置完全 Atom 基礎設施的環境中 。您將再次使用 JSON 和 Ajax,??過這次是啟用日程表和標志雲。幾個月之後,我將使用另一種新思維 。

Grails 繼續獲得了每個新 Web 站點的大力支持。成熟 Web 框架的標志是能夠以多種方式使用它。明 年的精通 Grails 文章將演示 Grails 中可能實現的各種 Web 站點。到那時,您將享受到精通 Grails 帶來的樂趣。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved