27.7 通知

Spring JMX提供了对JMX通知的全面支持。

27.7.1 注册通知监听器

Spring JMX的支持使得将对任意数量的NotificationListeners注册到任意数量的MBean(包括通过Spring MBeanExporter 导出的MBean和通过其他机制注册的MBean)。示例,考虑这么一个场景,当目标MBean的每个属性每次改变的时候都会通知(通过通知)。

  1. package com.example;
  2. import javax.management.AttributeChangeNotification;
  3. import javax.management.Notification;
  4. import javax.management.NotificationFilter;
  5. import javax.management.NotificationListener;
  6. public class ConsoleLoggingNotificationListener
  7. implements NotificationListener, NotificationFilter {
  8. public void handleNotification(Notification notification, Object handback) {
  9. System.out.println(notification);
  10. System.out.println(handback);
  11. }
  12. public boolean isNotificationEnabled(Notification notification) {
  13. return AttributeChangeNotification.class.isAssignableFrom(notification.getClass());
  14. }
  15. }
  1. <beans>
  2. <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
  3. <property name="beans">
  4. <map>
  5. <entry key="bean:name=testBean1" value-ref="testBean"/>
  6. </map>
  7. </property>
  8. <property name="notificationListenerMappings">
  9. <map>
  10. <entry key="bean:name=testBean1">
  11. <bean class="com.example.ConsoleLoggingNotificationListener"/>
  12. </entry>
  13. </map>
  14. </property>
  15. </bean>
  16. <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
  17. <property name="name" value="TEST"/>
  18. <property name="age" value="100"/>
  19. </bean>
  20. </beans>

通过上面的配置,目标MBean(bean:name=testBean1)每次以广播形式发送JMX通知,通过notificationListenerMappings属性注册为ConsoleLoggingNotificationListener的监听器将被通知。 ConsoleLoggingNotificationListener可以采取任何它认为合适的动作来响应通知。

你可以直接使用bean的名称作为导出bena的监听器之间的连接:

  1. <beans>
  2. <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
  3. <property name="beans">
  4. <map>
  5. <entry key="bean:name=testBean1" value-ref="testBean"/>
  6. </map>
  7. </property>
  8. <property name="notificationListenerMappings">
  9. <map>
  10. <entry key="testBean">
  11. <bean class="com.example.ConsoleLoggingNotificationListener"/>
  12. </entry>
  13. </map>
  14. </property>
  15. </bean>
  16. <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
  17. <property name="name" value="TEST"/>
  18. <property name="age" value="100"/>
  19. </bean>
  20. </beans>

如果你想对封闭的MBeanExporter导出的所有bean注册一个NotificationListener实例,可以使用特殊通配符‘*’(无引号)作为notificationListenerMappings属性map中的key;例如:

  1. <property name="notificationListenerMappings">
  2. <map>
  3. <entry key="*">
  4. <bean class="com.example.ConsoleLoggingNotificationListener"/>
  5. </entry>
  6. </map>
  7. </property>

如果需要执行反转(针对MBean注册一些不同的监听器),那么必须使用notificationListeners的属性列表(不是notificationListenerMappings属性)。这次,不是简单配置单个MBean的NotificationListener,而是配置NotificationListenerBean实例,NotificationListenerBean在MBeanServer中封装了NotificationListener和ObjectName(或ObjectNames)。NotificationListenerBean也封装了其他属性,例如NotificationFilter和用于高级JMX通知场景的任意handback对象。

使用NotificationListenerBean实例时和前面的不同配置:

  1. <beans>
  2. <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
  3. <property name="beans">
  4. <map>
  5. <entry key="bean:name=testBean1" value-ref="testBean"/>
  6. </map>
  7. </property>
  8. <property name="notificationListeners">
  9. <list>
  10. <bean class="org.springframework.jmx.export.NotificationListenerBean">
  11. <constructor-arg>
  12. <bean class="com.example.ConsoleLoggingNotificationListener"/>
  13. </constructor-arg>
  14. <property name="mappedObjectNames">
  15. <list>
  16. <value>bean:name=testBean1</value>
  17. </list>
  18. </property>
  19. </bean>
  20. </list>
  21. </property>
  22. </bean>
  23. <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
  24. <property name="name" value="TEST"/>
  25. <property name="age" value="100"/>
  26. </bean>
  27. </beans>

上面的例子和第一个例子是等价的。现在假设我们想每发出一个通知就给出一个handback对象,除此之外我们还想通过一个NotificationFilter过滤外来的通知。(关于什么是handback对象,什么是NotificationFilter,请参考JMX规范(1.2)的“JMX通知模型”)。

  1. <beans>
  2. <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
  3. <property name="beans">
  4. <map>
  5. <entry key="bean:name=testBean1" value-ref="testBean1"/>
  6. <entry key="bean:name=testBean2" value-ref="testBean2"/>
  7. </map>
  8. </property>
  9. <property name="notificationListeners">
  10. <list>
  11. <bean class="org.springframework.jmx.export.NotificationListenerBean">
  12. <constructor-arg ref="customerNotificationListener"/>
  13. <property name="mappedObjectNames">
  14. <list>
  15. <!-- handles notifications from two distinct MBeans -->
  16. <value>bean:name=testBean1</value>
  17. <value>bean:name=testBean2</value>
  18. </list>
  19. </property>
  20. <property name="handback">
  21. <bean class="java.lang.String">
  22. <constructor-arg value="This could be anything..."/>
  23. </bean>
  24. </property>
  25. <property name="notificationFilter" ref="customerNotificationListener"/>
  26. </bean>
  27. </list>
  28. </property>
  29. </bean>
  30. <!-- implements both the NotificationListener and NotificationFilter interfaces -->
  31. <bean id="customerNotificationListener" class="com.example.ConsoleLoggingNotificationListener"/>
  32. <bean id="testBean1" class="org.springframework.jmx.JmxTestBean">
  33. <property name="name" value="TEST"/>
  34. <property name="age" value="100"/>
  35. </bean>
  36. <bean id="testBean2" class="org.springframework.jmx.JmxTestBean">
  37. <property name="name" value="ANOTHER TEST"/>
  38. <property name="age" value="200"/>
  39. </bean>
  40. </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。

  1. package org.springframework.jmx;
  2. import org.springframework.jmx.export.notification.NotificationPublisherAware;
  3. import org.springframework.jmx.export.notification.NotificationPublisher;
  4. import javax.management.Notification;
  5. public class JmxTestBean implements IJmxTestBean, NotificationPublisherAware {
  6. private String name;
  7. private int age;
  8. private boolean isSuperman;
  9. private NotificationPublisher publisher;
  10. // other getters and setters omitted for clarity
  11. public int add(int x, int y) {
  12. int answer = x + y;
  13. this.publisher.sendNotification(new Notification("add", this, 0));
  14. return answer;
  15. }
  16. public void dontExposeMe() {
  17. throw new RuntimeException();
  18. }
  19. public void setNotificationPublisher(NotificationPublisher notificationPublisher) {
  20. this.publisher = notificationPublisher;
  21. }
  22. }

NotificationPublisher接口和使其全部运行的机制是Spring JMX支持的良好的功能之一。然而它带来的代价是你的类和Spring,JMX耦合在一起;与以往一样,我们给出实用的建议,如果你需要NotificationPublisher提供的功能,那么你需要接受Spring和JMX的耦合。