擴充 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 都是很大的主題,每一個都值得進一步研究。