技术开发 频道

扩展 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 通知。 
0
相关文章