Spring JMX提供了对JMX通知的全面支持。
27.7.1 注册通知监听器
Spring JMX的支持使得将对任意数量的NotificationListeners注册到任意数量的MBean(包括通过Spring MBeanExporter 导出的MBean和通过其他机制注册的MBean)。示例,考虑这么一个场景,当目标MBean的每个属性每次改变的时候都会通知(通过通知)。
package com.example;
import javax.management.AttributeChangeNotification;
import javax.management.Notification;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
public class ConsoleLoggingNotificationListener
implements NotificationListener, NotificationFilter {
public void handleNotification(Notification notification, Object handback) {
System.out.println(notification);
System.out.println(handback);
}
public boolean isNotificationEnabled(Notification notification) {
return AttributeChangeNotification.class.isAssignableFrom(notification.getClass());
}
}
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=testBean1" value-ref="testBean"/>
</map>
</property>
<property name="notificationListenerMappings">
<map>
<entry key="bean:name=testBean1">
<bean class="com.example.ConsoleLoggingNotificationListener"/>
</entry>
</map>
</property>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
</beans>
通过上面的配置,目标MBean(bean:name=testBean1)每次以广播形式发送JMX通知,通过notificationListenerMappings属性注册为ConsoleLoggingNotificationListener的监听器将被通知。 ConsoleLoggingNotificationListener可以采取任何它认为合适的动作来响应通知。
你可以直接使用bean的名称作为导出bena的监听器之间的连接:
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=testBean1" value-ref="testBean"/>
</map>
</property>
<property name="notificationListenerMappings">
<map>
<entry key="testBean">
<bean class="com.example.ConsoleLoggingNotificationListener"/>
</entry>
</map>
</property>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
</beans>
如果你想对封闭的MBeanExporter导出的所有bean注册一个NotificationListener实例,可以使用特殊通配符‘*’(无引号)作为notificationListenerMappings属性map中的key;例如:
<property name="notificationListenerMappings">
<map>
<entry key="*">
<bean class="com.example.ConsoleLoggingNotificationListener"/>
</entry>
</map>
</property>
如果需要执行反转(针对MBean注册一些不同的监听器),那么必须使用notificationListeners的属性列表(不是notificationListenerMappings属性)。这次,不是简单配置单个MBean的NotificationListener,而是配置NotificationListenerBean实例,NotificationListenerBean在MBeanServer中封装了NotificationListener和ObjectName(或ObjectNames)。NotificationListenerBean也封装了其他属性,例如NotificationFilter和用于高级JMX通知场景的任意handback对象。
使用NotificationListenerBean实例时和前面的不同配置:
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=testBean1" value-ref="testBean"/>
</map>
</property>
<property name="notificationListeners">
<list>
<bean class="org.springframework.jmx.export.NotificationListenerBean">
<constructor-arg>
<bean class="com.example.ConsoleLoggingNotificationListener"/>
</constructor-arg>
<property name="mappedObjectNames">
<list>
<value>bean:name=testBean1</value>
</list>
</property>
</bean>
</list>
</property>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
</beans>
上面的例子和第一个例子是等价的。现在假设我们想每发出一个通知就给出一个handback对象,除此之外我们还想通过一个NotificationFilter过滤外来的通知。(关于什么是handback对象,什么是NotificationFilter,请参考JMX规范(1.2)的“JMX通知模型”)。
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=testBean1" value-ref="testBean1"/>
<entry key="bean:name=testBean2" value-ref="testBean2"/>
</map>
</property>
<property name="notificationListeners">
<list>
<bean class="org.springframework.jmx.export.NotificationListenerBean">
<constructor-arg ref="customerNotificationListener"/>
<property name="mappedObjectNames">
<list>
<!-- handles notifications from two distinct MBeans -->
<value>bean:name=testBean1</value>
<value>bean:name=testBean2</value>
</list>
</property>
<property name="handback">
<bean class="java.lang.String">
<constructor-arg value="This could be anything..."/>
</bean>
</property>
<property name="notificationFilter" ref="customerNotificationListener"/>
</bean>
</list>
</property>
</bean>
<!-- implements both the NotificationListener and NotificationFilter interfaces -->
<bean id="customerNotificationListener" class="com.example.ConsoleLoggingNotificationListener"/>
<bean id="testBean1" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
<bean id="testBean2" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="ANOTHER TEST"/>
<property name="age" value="200"/>
</bean>
</beans>
27.7.2 发布通知
Spring不仅提供了对注册接受通知的支持,而且还用于发布通知。
请注意,本节仅与通过MBeanExporter暴露的Spring管理的MBean相关。任何现有的用户定义的MBean都应该使用标准的JMX API来发布通知。
Spring JMX支持的通知发布的关键接口为NotificationPublisher(定义在org.springframework.jmx.export.notification包下面)。任何通过MBeanExporter实例导出为MBean的bean都可以实现NotificationPublisherAware的相关接口来获取NotificationPublisher实例。NotificationPublisherAware接口通过一个简单的setter方法将NotificationPublisher的实例提供给实现bean,这个bean就可以用来发布通知。
如javadoc中的NotificationPublisher类所述,通过NotificationPublisher机制来发布事件被管理的bean是对任何通知监听器状态管理的不负责。Spring JMX支持将处理所有JMX基础问题。所有人需要做的就是和应用开发人员一样实现NotificationPublisherAware接口并通过NotificationPublisher实例开始发布事件。注意,NotificationPublisher将在管理bean被注册到MBeanServer之后被设置。
使用NotificationPublisher实例非常简单,创建一个简单的JMX通知实例(或一个适当的Notification子类实例),通知中包含发布事件相关的数据 ,然后在NotificationPublisher实例上调用sendNotification(Notification),传递Notification。
下面是一个简单的例子,在这种场景下,导出的JmxTestBean实例在每次调用add(int, int)时会发布一个NotificationEvent。
package org.springframework.jmx;
import org.springframework.jmx.export.notification.NotificationPublisherAware;
import org.springframework.jmx.export.notification.NotificationPublisher;
import javax.management.Notification;
public class JmxTestBean implements IJmxTestBean, NotificationPublisherAware {
private String name;
private int age;
private boolean isSuperman;
private NotificationPublisher publisher;
// other getters and setters omitted for clarity
public int add(int x, int y) {
int answer = x + y;
this.publisher.sendNotification(new Notification("add", this, 0));
return answer;
}
public void dontExposeMe() {
throw new RuntimeException();
}
public void setNotificationPublisher(NotificationPublisher notificationPublisher) {
this.publisher = notificationPublisher;
}
}
NotificationPublisher接口和使其全部运行的机制是Spring JMX支持的良好的功能之一。然而它带来的代价是你的类和Spring,JMX耦合在一起;与以往一样,我们给出实用的建议,如果你需要NotificationPublisher提供的功能,那么你需要接受Spring和JMX的耦合。