20.7. 通知

From Spring Framework

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 封装了一个 NotificationListener 和 ObjectName(或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的耦合,那么就去做。