扩展Spring的JMX支持

Spring框架将体系结构依赖性降至最低,并且将应用程序中的组成部分进行了具体化,但是应用程序仍然是需要管理的。幸运的是,Spring1.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对象一起存储在类路径中。我的第二个扩展为Model MBean类增加一个简单的命名规范,以透明地配置定制的通知消息。在属性改变时或者调用了特定的方法之前或者之后触发新的通知消息。

文章的最后是一个基于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代码的纯洁性。

#p#

介绍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的包,它包含一组用于建模MBeanInfoXML文件的类。这个包包含大量类,因此我不会详细讨论所有类,但是其基本内容还是有必要说明的。

模型的根是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属性。

扩展Spring的JMX支持_第1张图片

图1:MBean XML格式

#p#

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);     }   } }


 不需要代理代理!

#p#

因为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实例。

#p#

示例

在本文其余部分,我将着重展示这个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();   } }

#p#

由于有了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所示的客户机代码看看会发生什么。这段代码实现了JMXNotificationListener接口,这样就可以交互式地看到所发生的事情。连接后,可以注册监听器,然后触发几个调用、启动和停止服务、设置和取得属性。在每一种情况下,都应当在控制台上看到一个确认操作的通知消息。

清单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方法和属性增加通知事件。当然,我只触及到了SpringJMX能力的皮毛。还可以有许多其他扩展,Spring和JMX都是很大的主题,每一个都值得进一步研究。



你可能感兴趣的:(JMX)