27.7 通知

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的耦合。