20.7. 通知

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

20.7.1. 为通知注册监听器

Spring的JMX支持使得用任意数量MBean注册任意数量的 NotificationListeners 监听器(包括由Spring的 MBeanExporter 输出和其他机制注册的MBean)都非常容易。 通过例子,考虑当目标MBean发生了变化都想得到通知(通过 Notification)的场景。

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 Notification 时, 通过 notificationListenerMappings 属性注册的 ConsoleLoggingNotificationListener 都能得到通知。 ConsoleLoggingNotificationListener 就可以采取任何它认为合适的行为来响应 Notification

你也可以直接使用Bean名作为输出的Bean和监听器直接的链接。

<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 属性映射中一个实体的键值;如下:

<property name="notificationListenerMappings">
  <map>
    <entry key="*">
      <bean class="com.example.ConsoleLoggingNotificationListener"/>
    </entry>
  </map>
</property>

如果想做相反的事情(也就是,为一个MBean注册多个不同的监听器),那么他就要使用 notificationListeners 列表属性来替代(优先于 notificationListenerMappings 属性)。 这时就要配置多个 NotificationListenerBean 实例,而不仅仅是一个了…… 一个 NotificationListenerBean 不但封装了一个或者多个 NotificationListener 和已注册到一个 MBeanServerObjectName,它也封装了许多其他属性,例如一个 NotificationFilter 和一个可以用于JMX高级通知场景的回传对象。

当使用多个 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>

上面例子等同与第一个通知示例。假设每次 Notification 发生时,我们想得到一个回传对象, 且想通过提供一个 NotificationFilter 过滤出无关的 Notifications。 至于什么是一个回传对象,NotificationFilter 到底又是什么的全面的探讨,请参考JMX规范(1.2)'The JMX Notification Model' 章节。

<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>
  
  
  <!-- 实现了NotificationListenerNotificationFilter接口 -->
  
  <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>

20.7.2. 发布通知

Spring不但提供了注册接收通知的支持,也提供了对发布通知的支持。

注意

要注意的是,本章节仅仅与通过 MBeanExporter 暴露的,被Spring管理的Bean相关。 任何现存的,用户定义的MBean应当使用标准JMX API来做通知发布。

Spring的JMX通知发布支持中的关键接口是 NotificationPublisher(定义于 org.springframework.jmx.export.notification 包中)。 任意要通过 MBeanExporter 实例输出为MBean的Bean都可以实现 NotificationPublisherAware 接口来获得对 NotificationPublisher 实例的访问。 NotificationPublisherAware 仅仅提供通过一个简单的setter方法给实现了这个接口的Bean注入一个 NotificationPublisher 实例,那些Bean就因此可以发布 Notification 了。

就如 NotificationPublisher 类的Javadoc描述的一样,通过 NotificationPublisher 机制发布事件的受控Bean是 需要对任何通知的监听器或者其他诸如此类的监听器的状态管理负责的。Spring的JMX支持将处理与JMX架构相关的所有问题。 作为一个应用程序开发者,他所需要做的只是实现 NotificationPublisherAware 接口,然后利用注入的 NotificationPublisher 实例发布事件。要注意,受控Bean注册到一个 MBeanServer 后,NotificationPublisher 才被设置。

使用 NotificationPublisher 实例的方法很直观,人们只要构建一个 Notification 实例(或者一个合适的 Notification 子类的实例),接着填充与将要发布的事件相关的数据到通知里,然后传入 Notification,调用NotificationPublisher实例的方法 sendNotification(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;

    // 清晰起见,忽略了其他getter和setter

    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的紧耦合,那么就行动吧。