扩展 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 都是很大的主题,每一个都值得进一步研究。