20.7. 通知

Spring的JMX提供的内容包括了对JMX通知的全面的支持。

20.7.1. 为通知注册监听器

Spring的JMX支持使得用任意数量的MBean(这包括由Spring的 MBeanExporter 输出的MBean和通过其他机制注册的MBean)注册任意数量的 NotificationListeners非常容易。 例子最能阐明影响 NotificationListeners的注册有多么简单。 考虑一个场景,任何时候一个目标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 bean将得到通知。 然后 ConsoleLoggingNotificationListener bean可以采取任何它认为合适的行动来响应这个 Notification

如果想给所有的正在输出的已经装入MBeanExporter的bean注册单个 NotificationListener实例,可以用特殊的通配符'*'(没有引号) 作为属性映射notificationListenerMappings的一个实体的键,例如:

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

如果想与上面相反(即,为一个MBean注册多个不同的监听器),那么就必须使用 notificationListeners 这个列表属性来代替(优先于属性notificationListenerMappings)。 这次,要配置多个 NotificationListenerBean 实例,而不是简单的为单个MBean配置一个 NotificationListener……一个 NotificationListenerBean 封装了一个 NotificationListenerObjectName(或ObjectNames) 这样,它就在一个 MBeanServer 里进行注册。 NotificationListenerBean 也封装了许多其他的属性, 如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>
                        <bean class="javax.management.ObjectName">
                            <constructor-arg value="bean:name=testBean1"/>
                        </bean>
                    </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 我们都想得到一个回传对象(handback object), 此外,我们想通过提供一个 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>
                        <!-- let's handle notifications from two distinct MBeans -->
                        <bean class="javax.management.ObjectName">
                            <constructor-arg value="bean:name=testBean1"/>
                        </bean>
                        <bean class="javax.management.ObjectName">
                            <constructor-arg value="bean:name=testBean2"/>
                        </bean>
                    </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>

20.7.2. 发布通知

Spring不只提供接受 Notifications 的注册,还提供发布 Notifications

注意

请注意这一章只是与Spring的那些通过 MBeanExporter 所谓MBean输出的管理bean有关系;任何现存的,用户定义的MBean必须使用标准的JMX API来发布通知。

在Spring的JMX通知发布支持中,关键的是 NotificationPublisher 接口 (在包 org.springframework.jmx.export.notification 中定义的)。 任一将要通过 MBeanExporter 作为MBean输出的bean实例都可以实现对应的 NotificationPublisherAware接口来获取对接口 NotificationPublisher 的访问。 NotificationPublisherAware 接口只提供了一个 NotificationPublisher 的实例通过简单的setter方法实现bean,这样bean就可以用于发布 Notifications

如同在Javadoc中对类 NotificationPublisher 的描述,通过 NotificationPublisher 机制发布事件的管理bean 负责 任何通知监听器以及诸如此类……的状态管理。Spring的JMX支持谨慎的处理所有的JMX架构问题。应用程序开发者所需要的 就是实现接口 NotificationPublisherAware,用提供的接口 NotificationPublisher 开始发布事件。 注意,在已经用一个 MBeanServer 将管理bean注册之后, 再设置NotificationPublisher

NotificationPublisher 的实例非常简单的…… 仅仅创建一个 Notification 实例 (或一个适当的 Notification 子类的实例),带有与事件相关联数据的通知将被发布, 然后在 NotificationPublisher 实例上调用sendNotification(Notification), 在Notification中传递它。

让我们来看一个简单的例子……在下面的场景里,每当操作 add(int, int) 被调用时, 输出的 JmxTestBean 实例将发布一个 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;
    }
}

Spring的JMX提供的接口 NotificationPublisher 和让其工作的辅助装置是一个非常好的特性。 的确,伴随而来的是你的类与Spring和JMX的耦合开销,跟平时一样,这里的建议也是比较实际的, 如果你需要 NotificationPublisher 提供的功能,并且你可以接受与Spring和JMX的耦合,那么就去做。