程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 擴展Spring的JMX支持

擴展Spring的JMX支持

編輯:關於JAVA

Spring 框架將體系結構依賴性降至最低,並且將應用程序中得組成部分進行了具體化,但是應用程序仍然是需要管理的。幸運的是,Spring 1.2 包括高級的 JMX 集成支持,並且 JMX 為應用程序提供了一種實用的管理基礎架構。在本文中,Claude Duguay 從 Spring JMX 更進一步,向您展示了如何為方法和屬性透明地增加通知事件。最後得到的代碼使您可以監視狀態變化,同時不會搞亂 Java? 對象。

雖然 Spring 框架的 JMX 管理基礎架構的默認配置已經很不錯了,但是仍然有定制的余地,特別是涉及 Model MBean 提供的更高層功能時。在本文中,我使用了一種相對簡單的操作 —— 為基於 Spring 的應用程序的方法和屬性增加通知事件 —— 以幫助您熟悉對 Spring JMX 的定制。從頭到尾完成我的例子後,您將可以根據自己應用程序的需要調整 Spring JMX 管理基礎架構。

我首先對 JMX API、Spring 框架和 Spring JMX 進行簡單回顧,然後轉入開發擴展。第一個擴展讓我可以用一個外部 XML 格式配置 MBean 元數據,這個格式(像 Hibernate 映射文件)可以與 Java 對象一起存儲在類路徑中。我的第二個擴展為 ModelMBean 類增加一個簡單的命名規范,以透明地配置定制的通知消息。在屬性改變時或者調用了特定的方法之前或者之後觸發新的通知消息。

文章的最後是一個基於 mockup 服務對象的實際例子,需要管理它的啟動和停止方法和讀寫屬性。我用一個專門為此設計的小型客戶機/服務器應用程序測試了這個實現。應用服務器是一個標准 Java 5.0 MBeanServer,並補充了源自 MX4J 開放源碼項目的 HTTP 適配器。

JMX 概述

Java Management Extensions(JMX)是管理和監視網絡上的服務的、基於 Java 的標准。JMX API 的核心是受管 bean,即 MBean。MBean 為受管資源(如應用程序、服務和設備)提供了設施層。簡而言之,MBean 提供了一種靈活的、基於適配器的體系結構,用於開放基於 Java 的(或者 Java 包裝的)資源的屬性和操作。開放後,就可以用浏覽器和 HTTP 連接或者通過像 SMTP 或者 SOAP 這樣的協議監視和管理這些資源。

編寫和部署的 MBean 是通過 MBeanServer 接口開放的,以使不同的應用程序視圖具有交互性。MBeanServer 實例還可以結合到任意的聯合關系中,構成更復雜的分布式環境。

JMX 標准提供了四種不同的 MBean: Standard MBean 直接實現用於管理對象的方法,既可以通過實現一個由程序員定義的、類名以 “MBean” 結束的接口,也可以使用一個以一個類作為構造函數參數的 Standard MBean 實例,加上一個可選的接口類規范。這個接口可以開放用於管理的部分對象方法。

Dynamic MBean 用屬性訪問器動態地訪問屬性,並用一個一般化的 invoke() 方法調用方法。可用的方法是在 MBeanInfo 接口中指定的。這種方式更靈活,但是不具有像 Standard MBean 那樣的類型安全性。它極大地降低了耦合性,可管理的 POJO(純粹的老式 Java 對象)不需要實現特定的接口。

Model MBean 提供了一個改進的抽象層,並擴展了 Dynamic MBean 模型以進一步減少對給定實現的依賴性。這對於可能使用多個版本的 JVM 或者需要用松散耦合管理第三方類的情況會有幫助。Dynamic MBean 與 Model MBean 之間的主要區別是,在 Model MBean 中有額外的元數據。

Open MBean 是受限的 Model MBean,它限制類型為固定的一組類型,以得到最大的可移植性。通過限制數據類型,可以使用更多的適配器,並且像 SMTP 這樣的技術可以更容易適應 Java 應用程序的管理。這種變體還指定了數組和表等標准結構以改進復合對象的管理。

如果要同時控制客戶機和服務器,那麼 Standard MBean 是最容易實現的一種變體。它們的優點是有類型,但是如果在更一般化的管理控制台環境中使用時會缺少一些靈活性。如果計劃使用 Dynamic MBean,那麼您也可以更一步使用 Model MBean,在大多數情況下它會改善抽象層而幾乎不會增加復雜性。Open MBean 是是可移植性最高的一種變體,如果需要開放復合對象,那麼它是惟一的方法。不幸的是,在 Open MBean 中開放復合結構所需要的代碼數量過多,只有在需要高級的商業管理解決方案時才合算。

JMX 還支持使用帶過濾器和廣播器的事件模型的通知。為此目的,Standard MBean 需要聲明一個 MBeanInfo 元數據描述。 Standard MBean 實現通常在內部構造這些內容,開發人員不能直接看到它們。在本文後面,您會看到如何用 Model MBean 元數據的 XML 描述符格式和 Spring 的 JMX 支持進行實際上透明的配置。

Spring 提供幫助

像 J2EE 一樣,Spring 框架在一個體系結構中提供了許多強大的 Java 開發功能。與 J2EE 不同的是,Spring 開放型的技術來源提供了范圍廣泛的選擇,不再有依賴性的負擔。例如,Spring 的對象關系映射工具可以復制 Enterprise JavaBean 的行為,同時不會導致不靈活。雖然 EJB 規范限制了這種方式,但是 Spring 提供了大量技術接口,使您可以選擇最適合應用程序要求的接口,或者在需要時創建自己的接口。與此類似,利用 Spring 的動態代理類為 Java 對象增加事務性或者安全限制,使它們保持整潔並針對應用程序空間而不是基礎架構要求。

Spring 的支持 AOP 的、以復合為中心的(IOC)bean 可以很大程度上使基礎架構和業務對象彼此分離。因此,橫切關注點(如日志、事務和安全)不會再干擾應用程序代碼。

IOC(控制反轉)是減少耦合度的主要策略。Spring 的 IOC 實現使用依賴性注入有效地將控制從應用程序代碼 “反轉”到 Spring 容器。Spring 不是在創建時將類耦合到應用程序的對象圖,它使您可以用 XML 或者屬性文件(盡管 XML 被認為是最好的方法)配置類及它們的依賴性。然後用標准訪問器將引用“注入”到類所依賴的對象中。可以將它看成具體化復合(externalizing composition),在典型應用程序中,它的比重遠遠大於繼承。

AOP 是在應用程序開發中管理橫切關注點的關鍵。就像在傳統面向對象編程中實現的那樣,這些關注點是作為單獨的實例處理的,有可能在應用程序類中產生互不相關的代碼(就是混亂)。 Spring 使用 AOP 規范和一個 XML 配置文件具體化橫切關注點,因而保持了 Java 代碼的純潔性。

介紹 Spring JMX

Spring 1.2 中的 JMX 支持使用容易配置的 bean 代理提供了自動 MBeanServer 注冊,並支持標准 JSR-160 遠程連接器。在最簡單的情況下,可以用 Spring JMX 以 MBeanExporter 類注冊對象。Spring 自動識別 StandardMBean 或者用 ModelMBean 代理包裝對象,在默認情況下使用內省。可以以顯式引用使用 BeanExporter 以聲明 bean,或者可以用默認策略或更復雜的策略自動檢測 bean。

Spring 1.2 提供的大量裝配器使得透明地構造 MBean 成為可能,包括使用內省、Standard MBean 接口、元數據(使用類級別注釋)和顯式聲明的方法名。Spring 的基於導出器和裝配器的模型容易擴展,並在創建注冊的 MBean 時提供所需要的控制能力。

JMX 使用 ObjectName 語言注冊和訪問管理對象。如果選擇使用自動注冊,那麼 Spring 提供了不同的命名策略。使用“鍵”命名策略時,可以使用一個屬性把 MBean 名與 NameObject 實例關聯起來。如果實現 ManagedResource 接口,那麼可以使用元數據命名規范。由於 Spring 高度靈活的體系結構和大量擴展點,還可以實現自已的策略。

在默認情況下,Spring 會發現運行的 MBeanServer 實例,如果沒有實例在運行或者沒有顯式聲明的話,它會創建一個默認實例。用 Spring 配置直接實例化自己的 MBeanServer 與使用各種連接器同樣容易。Spring 通過客戶機和服務器連接提供控制,並提供客戶機代理以協助客戶端編程。

所有這些功能都是 Spring 1.2 默認提供的。雖然 Spring JMX 提供了大量選項,但是默認的導出器對於許多項目來說已經足夠了,使您可以很快地投入運行。不過,使用 JMX 時,在使用隱式 MBean 構造時會注意到一些特性。結果,可能會慢慢地從 Standard MBean 轉移到 Model MBean,它允許對應用程序的屬性、操作和通知元數據施加更多的控制。要保留松散耦合的好處(也就是 Spring 靈活的體系結構內在的優點),需要在 Java 對象之外實現這個控制。

Spring 的 IOC 使得從外部連接(wire)對象依賴性容易了,在 Spring 的體系結構中很容易利用這種優點。IOC 保持對象依賴性的可注入性,這使得增加、替換或者補充對象的行為(包括 Spring 的 JMX 支持)變得輕而易舉。在本文的其余部分,我將重點放到擴展 Spring JMX 以得到更細化的應用程序管理,而不會搞亂應用程序代碼或者破壞 Spring 固有的靈活性。

擴展 Spring JMX

Spring 框架提供了許多處理 JMX 的有用工具,包括用於擴展 JMX 功能的擴展點。我將利用它們獲得對 MBeanInfo 聲明的更多控制,同時不用對 Java 對象施加注釋。為此,我將要以兩種方式擴展 Spring JMX:第一種方法可以用一個外部 XML 格式(類似於 JBoss 微內核)配置 MBean 元數據,第二種方法可以與其相關聯的 Java 對象一同存儲在在類路徑中(很像 Hibernate 映射文件)。

我將擴展 RequiredModelMBean 類,讓它使用一個簡單的命名規范,以 <ClassName>.mbean.xml 格式尋找相關的 MBean 部署描述符。定義這種基於 XML 的 MBean 描述符改進了對應用程序元數據的控制,而不會失去基於 POJO 的設計和 Spring 復合的靈活性。為了實現這一點,我將實現自己的裝配器並擴展基本的 Spring JMX 導出器。擴展的導出器可以創建擴展的 ModelMBean,它支持截獲屬性的改變以及 before 和 after 方法執行的通知。我可以用 Spring 的 AOP 機制完成這一切,但是 ModelMBean 已經是底層受管資源的代理,因此我將用更直接的方式擴展 RequiredModelMbean 類。

管理基礎

不管使用什麼技術,在管理資源時有三個要關注的主要領域:

屬性(attribute) (有時稱為屬性(property)、字段或者變量)。只讀屬性對於開放度量或者狀態很有用。讀/寫屬性使管理員可以改變配置。

動作(action) (可執行調用,對於 Java 代碼來說就是方法)。動作用於觸發像啟動和關閉這樣的事件,或者其他特定於應用程序的操作。

事件(event) (向監視系統發出的通知,反映狀態改變或者一些操作的執行)。通知確認操作或者狀態改變確實發生了。通知還可以用於觸發事件,如對於超過設置閥值(比如內存或者磁盤空間等資源不足)的狀態改變做出反應。這種通知可以用於在系統需要關注時向應用程序管理員發送電子郵件或者傳呼。

在擴展 Spring JMX 時,我將用一個簡單的案例分別展示這三個需要關注的領域:一個帶有開始和停止方法並且有一個讀寫屬性要管理的示例服務。我還要用一個小型的客戶機/服務器應用程序測試這個實現,並開放一個使用 MX4J 適配器的 HTTP 管理接口。所有例子將有必要的限制,但是足以使您理解相應的概念。您會看到在基於 Spring 的應用程序方法和屬性中加入 JMX 通知事件有多麼容易,結果是可以監視狀態改變,同時不會在 Java 對象中加入不必要的代碼。

MBeanInfo 模型

如果下載與本文有關的代碼,您會發現一個名為 com.claudeduguay.mbeans.model 的包,它包含一組用於建模 MBeanInfo XML 文件的類。這個包包含大量類,因此我不會詳細討論所有類,但是其基本內容還是有必要說明的。

模型的根是 MBeanDescriptor 類,它提供了一個 createMBeanInfo() 方法,負責用應用程序元數據創建一個兼容 JMX 的 ModelMBeanInfo 實例。MBeanDescriptorUtil 類提供了兩個靜態的 read() 方法,它裝載並保存這個 XML 文檔模型。這個模型中使用的 Document 和 Element 實例基於 JDOM 框架。

我使用的基本 MBeanInfo 相關類和描述符模型是密切相關的。基類中的所有基本屬性都是在 XML 中建模的,並可以用簡單的格式定義。例如,<mbean> 標記是我的文檔的根。它與 ModelMBeanInfo 直接相關,它期待一個 type 和 description 屬性。類型是受管 bean 的完全限定類名。不過,在使用我的 Sping 解決方案時,這個類完全可以在上下文中派生。<mbean> 標記期待零個或者多個 attribute、operation、constructor 和 notification 子類型。它們每一個都提供了基 MBeanInfo 類 XML 屬性。

圖 1. MBean XML 格式

MBean 模型實現利用了 com.claudeduguay.util.jdom 包中幾個與 JDOM 相關的工具類。它們主要是解析和構建 Document 和 Element 對象的接口,一個工具類使讀和寫 Document 流更容易。要查看的大多數代碼在 com.claudeduguay.mbeans.spring 包中。

已經做了足夠的預備工作,讓我們開始擴展 Spring JMX!

改進 ModelMBean 中的通知

我要做的第一件事是擴展 Spring JMX ModelMBean 實現,這樣就可以發送通知而不用在管理的資源中直接實現這個行為。為此,還需要擴展 Spring 導出器以創建改進的 ModelMBean 實例。最後,還需要用一個新的裝配器從映射文件中提取 MBeanInfo 元數據。

ModelMBeanExtension 類

擴展 RequiredModelMBean 類的一個目的是在管理代理中透明地啟用通知。這個應用程序需要三種通知:設置屬性值、方法調用之前以及方法調用之後。因為消息是我自己配置的,在每一種情況下,它都可以按照我需要的那樣提供信息。要實現這一點,我對類型通知使用了一個命名規范,其中對於樣式 <matchingType>.<methodOrAttributeName> 檢查點分隔的類型名。匹配的類型必須為 set、before 或者 after 之一。如果類型是 set,那麼就認為是一個屬性名,否則,就認為是一個方法名。

擴展的 ModelMBean 代碼使用額外的類幫助進行耦合。第一個是 NotificationInfoMap,它是一個用通知元數據構建的、簡單的 Map,並與前綴(set|before|after)點名(method|attribute)樣式相關聯,這樣就可以更有效地得到匹配的通知元數據。第二個是工具方法的一個靜態集合。清單 1 顯示了為通知而擴展的 RequiredModelMBean:

清單 1. ModelMBeanExtension

package com.claudeduguay.mbeans.spring;
import java.lang.reflect.*;
import javax.management.*;
import javax.management.modelmbean.*;
public class ModelMBeanExtension extends RequiredModelMBean
{
  protected NotificationInfoMap notificationInfoMap;
  protected ModelMBeanInfo modelMBeanInfo;
  protected Object managedBean;
 
  public ModelMBeanExtension() throws MBeanException {}
  public ModelMBeanExtension(ModelMBeanInfo modelMBeanInfo)
   throws MBeanException
  {
   super(modelMBeanInfo);
   this.modelMBeanInfo = modelMBeanInfo;
   notificationInfoMap = new NotificationInfoMap(modelMBeanInfo);
  }
 
  public void setModelMBeanInfo(ModelMBeanInfo modelMBeanInfo)
   throws MBeanException
  {
   this.modelMBeanInfo = modelMBeanInfo;
   notificationInfoMap = new NotificationInfoMap(modelMBeanInfo);
   super.setModelMBeanInfo(modelMBeanInfo);
  }
 
  public MBeanNotificationInfo[] getNotificationInfo()
  {
   return modelMBeanInfo.getNotifications();
  }
 
  public void setManagedResource(Object managedBean, String type)
   throws MBeanException, RuntimeOperationsException,
    InstanceNotFoundException, InvalidTargetObjectTypeException
  {
   super.setManagedResource(managedBean, type);
   this.managedBean = managedBean;
  }
 
  protected void maybeSendMethodNotification(
  String type, String name) throws MBeanException
  {
   MBeanNotificationInfo info = notificationInfoMap.
    findNotificationInfo(type, name);
   if (info != null)
   {
    long timeStamp = System.currentTimeMillis();
    String notificationType = ModelMBeanUtil.
     matchType(info, "." + type + "." + name);
    sendNotification(new Notification(
     notificationType, this, timeStamp,
     info.getDescription()));
   }
  }
  protected void maybeSendAttributeNotification(
   Attribute attribute)
   throws MBeanException, AttributeNotFoundException,
   InvalidAttributeValueException, ReflectionException
  {
   String name = attribute.getName();
   MBeanNotificationInfo info = notificationInfoMap.
    findNotificationInfo("set", attribute.getName());
   if (info != null)
   {
    Object oldValue = getAttribute(name);
    Object newValue = attribute.getValue();
    long timeStamp = System.currentTimeMillis();
    String notificationType = ModelMBeanUtil.
     matchType(info, ".set." + name);
    sendNotification(new AttributeChangeNotification(
     this, timeStamp, timeStamp,
     info.getDescription(), info.getName(),
     notificationType, oldValue, newValue));
   }
  }
 
  public Object invoke(
  String name, Object[] args, String[] signature)
    throws MBeanException, ReflectionException
  {
   maybeSendMethodNotification("before", name);
   Object returnValue = super.invoke(name, args, signature);
   maybeSendMethodNotification("after", name);
   return returnValue;
  }
  public Object getAttribute(String name) throws MBeanException,
   AttributeNotFoundException, ReflectionException
  {
   try
   {
    Method method = ModelMBeanUtil.findGetMethod(
     modelMBeanInfo, managedBean, name);
    return method.invoke(managedBean, new Object[] {});
   }
   catch (IllegalAccessException e)
   {
    throw new MBeanException(e);
   }
   catch (InvocationTargetException e)
   {
    throw new MBeanException(e);
   }
  }
  public void setAttribute(Attribute attribute)
   throws MBeanException, AttributeNotFoundException,
    InvalidAttributeValueException, ReflectionException
  {
   try
   {
    Method method = ModelMBeanUtil.findSetMethod(
     modelMBeanInfo, managedBean, attribute.getName());
    method.invoke(managedBean, attribute.getValue());
    maybeSendAttributeNotification(attribute);
   }
   catch (InvocationTargetException e)
   {
    throw new MBeanException(e);
   }
   catch (IllegalAccessException e)
   {
    throw new MBeanException(e);
   }
  }
}

不需要代理代理!

因為 ModelMBean 已經是一種代理,所以不需要使用 Spring 的代理機制和 AOP 截獲器來截獲感興趣的方法。ModelMBean 接口需要 setAttribute 和 invoke 方法的實現以管理對底層受管資源的調用。可以繼承 RequiredModelMBean 類,保證它出現在所有 JMX 實現中,並增加我所需要的功能。

我的 ModelMBeanExtension 實現了同樣的構造函數,但是在一個實例變量中存儲了 ModelMBeanInfo 的一個副本。因為這個值可以通過構造函數或者調用 setModelMBeanInfo 方法設置,所以我覆蓋了這個方法以存儲這個值,調用超類以完成默認的行為。在默認情況下,RequiredModelMBean 類增加兩個一般性通知描述符,因此我覆蓋了 getNotificationInfo() 方法,只返回我描述的通知。仍然會發送一般性通知,但是要求特定通知的客戶不會看到它們。

為了發送通知,我覆蓋了 setAttribute() 和 invoke() 方法並檢查調用是否匹配我的通知信息描述符。每次都遍歷列表應該不會帶來很大的開銷,因為大多數類只會發送有限的一組通知,但是我需要測試每一個通知可能的許多通知類型字符串,而重復這一過程看來是個浪費。為了保證不會遇到性能問題,我實例化了一個通知信息映射,這是一個名稱/信息映射,可以用來進行快速查詢。關鍵是一個具有類型前綴(set、before 或者 after)和所涉及的屬性和方法的簡單字符串。可以使用 findNotificationInfo() 方法在 setAttribute() 調用或者方法調用時查找通知信息實例。

完成了基礎架構後,就可以截獲對 setAttribute() 和 invoke() 方法的調用了。屬性改變需要發送一個 AttributeChangeNotification 實例,它要求有舊的屬性值和新的值,以及從通知信息描述符中可以得到的細節。在發送通知時,如果消息順序是混亂的,則要發送序列號,讓客戶機應用程序可以對消息排序。為了簡化,我使用了當前時間戳而不是管理一個計數器。創建通知對象時,sendNotification() 方法保證它會發布。對於 invoke() 方法使用同樣的思路,盡管在這裡我使用了更簡單的 Notification 對象。可以調用超類中的 invoke() 方法同時檢查這兩者(before 和 after),並根據查找結果發送 before 和 after 通知。

擴展 Spring JMX 導出器

為了使用擴展的 ModelMBean,需要覆蓋 Spring MBeanExporter 中的 createModelMBean() 方法。因為可以注入裝配器屬性,所以必須知道它可能不是我所期待的這一事實。可以在構造函數中設置所需要的裝配器,但是當裝配器改變時需要返回一個普通 ModelMBean。所要做的就是緩存一個 MBeanInfoAssembler 的本地引用,並在創建新的 ModelMBean 時檢查它是什麼類型的。清單 2 顯示了所有這些改變:

清單 2. MBeanDescriptorEnabledExporter

package com.claudeduguay.mbeans.spring;
import javax.management.*;
import javax.management.modelmbean.*;
import org.springframework.jmx.export.*;
import org.springframework.jmx.export.assembler.*;
public class MBeanDescriptorEnabledExporter extends MBeanExporter
{
  protected MBeanInfoAssembler mBeanInfoAssembler;
 
  public MBeanDescriptorEnabledExporter()
  {
   setAssembler(new MBeanDescriptorBasedAssembler());
  }
 
  public ModelMBean createModelMBean() throws MBeanException
  {
   if (mBeanInfoAssembler instanceof MBeanDescriptorBasedAssembler)
   {
    return new ModelMBeanExtension();
   }
   return super.createModelMBean();
  }
 
  public void setAssembler(MBeanInfoAssembler mBeanInfoAssembler)
  {
   this.mBeanInfoAssembler = mBeanInfoAssembler;
   super.setAssembler(mBeanInfoAssembler);
  }
}

在使用這個擴展的類時,可以用標准 Spring 語言改變裝配器,並在需要時回到默認的行為。在大多數情況下,如果最終繞過擴展,那麼就不值得使用這個版本。不過,如果想要以新的定制裝配器使用擴展的 ModelMBean,那麼現在可以這樣做。

構建一個定制的裝配器

這個定制裝配器的主要任務是查找與管理的類有關的元數據映射文件。找到這個文件後,就裝載它並生成必要的 ModelMBeanInfo 實例。為此,我只是實現了 Spring MBeanInfoAssembler 實例建立這個文件的相關類路徑,用靜態 MBeanDescriptorUtil.read() 方法裝載它並返回結果,如清單 3 所示:

清單 3. MBeanDescriptorBasedAssembler

package com.claudeduguay.mbeans.spring;
import java.io.*;
import javax.management.modelmbean.*;
import org.springframework.core.io.*;
import org.springframework.jmx.export.assembler.*;
import com.claudeduguay.mbeans.model.*;
public class MBeanDescriptorBasedAssembler
  implements MBeanInfoAssembler
{
  public ModelMBeanInfo getMBeanInfo(
   Object managedBean, String beanKey)
  {
   String name = managedBean.getClass().getName();
   String path = name.replace('.', '/') + ".mbean.xml";
   ClassPathResource resource = new ClassPathResource(path);
   InputStream input = null;
   try
   {
    input = resource.getInputStream();
    MBeanDescriptor descriptor = MBeanDescriptorUtil.read(input);
    return descriptor.createMBeanInfo();
   }
   catch (Exception e)
   {
    throw new IllegalStateException(
     "Unable to load resource: " + path);
   }
   finally
   {
    if (input != null)
    {
     try { input.close(); } catch (Exception x) {}
    }
   }
  }
}

這個 MBeanDescriptorBasedAssembler 忽略 bean 鍵參數並直接用受管 bean 引用創建所需的 ModelMBeanInfo 實例。

示例

在本文其余部分,我將著重展示這個 Spring JMX 擴展的使用。為此,使用一個假想的服務,它開放兩個方法和一個屬性,因此表現了典型的用例。

ExampleService 是一個 Java 對象,它在被調用時只是向控制台進行輸出,如清單 4 所示:

清單 4. ExampleService

package com.claudeduguay.jmx.demo.server;
public class ExampleService
{
  protected String propertyValue = "default value";
 
  public ExampleService() {}
 
  public String getPropertyValue()
  {
   System.out.println("ExampleService: Get Property Value");
   return propertyValue;
  }
  public void setPropertyValue(String propertyValue)
  {
   System.out.println("ExampleService: Set Property Value");
   this.propertyValue = propertyValue;
  }
  public void startService()
  {
   System.out.println("ExampleService: Start Service Called");
  }
  public void stopService()
  {
   System.out.println("ExampleService: Stop Service Called");
  }
}

對管理員友好的消息

這個擴展的描述符可以幾乎直接關聯屬性和操作。描述符方法優於內省式方法的主要一點是可以提供更特定的消息。通知描述符的配置選項有賴於類型(XML)屬性的命名規范。實際的名字是任意的,但是代碼會被類型中的 set.name、before.name 和 after.name 樣式觸發。在這種情況下,我將 set 通知與 propertyValue (JMX)屬性關聯,將 before 與 after 通知與 startService() 與 stopService() 方法關聯。同樣,這些擴展使我可以很好利用描述性的消息。

在清單 5 中,可以看到定義了一個屬性和兩個方法。通知描述符定義了方法的之前和之後事件以及一個屬性設置通知:

清單 5. ExampleService.mbean.xml

<?xml version="1.0"?>
<mbean name="ExampleService" description="Example Service"
  type="com.claudeduguay.jmx.demo.server.ExampleService">
  <attribute name="propertyValue"
   description="Property Value Access" type="java.lang.String"
   readable="true" writable="true" />
  <operation name="stopService"
   description="Stop Example Service" />
  <operation name="startService"
   description="Start Example Service" />
  <notification name="PropertyValueSet"
   types="example.service.set.propertyValue"
   description="PropertyValue was set" />
  <notification name="BeforeStartService"
   types="example.service.before.startService"
   description="Example Service is Starting" />
  <notification name="AfterStartService"
   types="example.service.after.startService"
   description="Example Service is Started" />
  <notification name="BeforeStopService"
   types="example.service.before.stopService"
   description="Example Service is Stopping" />
  <notification name="AfterStopService"
   types="example.service.after.stopService"
   description="Example Service is Stopped" />
 
</mbean>

配置服務器

要在客戶機/服務器環境中運行這個例子,需要配置和啟動一個 MBeanServer 實例。為此,我使用 Java 5.0 MBeanServer 實例,它保證我可以使用 JVM 中提供的管理擴展,同時管理自己的代碼。如果願意,還可以運行 MBeanServer 的多個實例,您願意的話也可以自己試一試作為練習。

就像 Java 5.0 一樣,Spring 框架使您可以配置自己的 MBeanServer 實例。我選擇使用 Java 5.0,因為它支持 JSR-160 連接器,我的客戶機代碼會需要它。

清單 6. SpringJmxServer

package com.claudeduguay.jmx.demo.server;
import org.springframework.context.*;
import org.springframework.context.support.*;
import mx4j.tools.adaptor.http.*;
/*
* To use the SpringJmxServer, use the following command line
* arguments to activate the Java 1.5 JMX Server.
* 
* -Dcom.sun.management.jmxremote.port=8999
* -Dcom.sun.management.jmxremote.ssl=false
* -Dcom.sun.management.jmxremote.authenticate=false
*/
public class SpringJmxServer
{
  public static void main(String[] args)
   throws Exception
  {
   String SPRING_FILE =
    "com/claudeduguay/jmx/demo/server/SpringJmxServer.xml";
   ApplicationContext context =
    new ClassPathXmlApplicationContext(SPRING_FILE);
   HttpAdaptor httpAdaptor =
    (HttpAdaptor)context.getBean("HttpAdaptor");
   httpAdaptor.start();
  }
}

由於有了 MBeanDescriptorEnabledExporter,服務器的 Spring 配置文件非常簡單。除了聲明 ExampleService,我增加了開放一個 HTTP 適配器和連接 XSLTProcessor 到 HttpAdaptor 所需要的 MX4J 項。注意這是 Spring 的 IOC 實現非常有用的一個領域。清單 7 顯示了我的 SpringJmxServer 實例的 Spring 配置文件:

清單 7. SpringJmxServer.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
  "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
  <bean id="exporter" class=
   "com.claudeduguay.mbeans.spring.MBeanDescriptorEnabledExporter">
   <property name="beans">
    <map>
     <entry key="Services:name=ExampleService"
      value-ref="ExampleService" />
     <entry key="MX4J:name=HttpAdaptor"
      value-ref="HttpAdaptor" />
     <entry key="MX4J:name=XSLTProcessor"
      value-ref="XSLTProcessor" />
    </map>
   </property>
  </bean>
 
  <bean id="XSLTProcessor"
   class="mx4j.tools.adaptor.http.XSLTProcessor" />
  <bean id="HttpAdaptor"
   class="mx4j.tools.adaptor.http.HttpAdaptor">
   <property name="processor" ref="XSLTProcessor"/>
   <property name="port" value="8080"/>
  </bean>
 
  <bean id="ExampleService"
   class="com.claudeduguay.jmx.demo.server.ExampleService" />
</beans>

如果願意(假定您遵循了我的設置),那麼現在就可以運行這個服務器了。它會注冊 ExampleService 並運行 HTTP 適配器。不要忘記使用注釋中提到的命令行參數啟動 Java 5.0 MBeanServer,否則會得到默認實例,客戶機示例就不能工作了。

運行客戶機代碼

啟動服務器後,可以運行如清單 8 所示的客戶機代碼看看會發生什麼。這段代碼實現了 JMX NotificationListener 接口,這樣就可以交互式地看到所發生的事情。連接後,可以注冊監聽器,然後觸發幾個調用、啟動和停止服務、設置和取得屬性。在每一種情況下,都應當在控制台上看到一個確認操作的通知消息。

清單 8. SpringJmxClient

package com.claudeduguay.jmx.demo.client;
import java.util.*;
import javax.management.*;
import javax.management.remote.*;
public class SpringJmxClient implements NotificationListener
{
  public void handleNotification(
   Notification notification, Object handback) 
  {
   System.out.println(
    "Notification: " + notification.getMessage());
  }
 
  public static void main(String[] args)
   throws Exception
  {
   SpringJmxClient listener = new SpringJmxClient();
   String address =
    "service:jmx:rmi:///jndi/rmi://localhost:8999/jmxrmi";
   JMXServiceURL serviceURL = new JMXServiceURL(address);
   Map<String,Object> environment = null;
   JMXConnector connector =
    JMXConnectorFactory.connect(serviceURL, environment);
   MBeanServerConnection mBeanConnection =
    connector.getMBeanServerConnection();
   ObjectName exampleServiceName =
    ObjectName.getInstance("Services:name=ExampleService");
   mBeanConnection.addNotificationListener(
    exampleServiceName, listener, null, null);
   mBeanConnection.invoke(
    exampleServiceName, "startService", null, null);
   mBeanConnection.setAttribute(exampleServiceName,
    new Attribute("propertyValue", "new value"));
   System.out.println(mBeanConnection.getAttribute(
    exampleServiceName, "propertyValue"));
   mBeanConnection.invoke(
    exampleServiceName, "stopService", null, null);
  }
}

由於 HTTP 適配器也是可用的,可以試著使用 MX4J (通過一個到端口 8080 的浏覽器連接)管理同樣的方法和屬性。如果同時讓客戶機代碼運行,那麼也會看到這些操作的通知。

結束語

在本文中,我展示了如何擴展 Spring 的 JMX 支持以滿足應用程序的特定需求。在這裡,我使用了 Spring 的基於容器的體系結構和 AOP 框架來為 JMX 方法和屬性增加通知事件。當然,我只觸及到了 Spring JMX 能力的皮毛。還可以有許多其他擴展,Spring 和 JMX 都是很大的主題,每一個都值得進一步研究。

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