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


书籍推荐