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