Notifications

Spring 的 JMX 服务包含对 JMX 通知的全面支持。

Registering Listeners for Notifications

Spring 的 JMX 支持使你可以轻松地用任何数量的 MBean 注册任何数量的 NotificationListeners(这包括 Spring 的 MBeanExporter 导出的 MBean 和通过一些其他机制注册的 MBean)。例如,考虑这样一个场景:希望每次目标 MBean 的属性发生变化,都收到通知(通过 Notification)。以下示例会将通知写入控制台:

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());
	}

}

以下示例将 ConsoleLoggingNotificationListener(在上一个示例中定义)添加到 notificationListenerMappings

<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

您也可以将直奔 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 实例,而不是为单一 MBean 配置 NotificationListenerNotificationListenerBean 封装了 NotificationListener 和在 MBeanServer 中注册它的 ObjectName(或 ObjectNames)。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>
							<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 来过滤不相关的 Notification。以下示例完成了这些目标:

<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>

(有关回传对象是什么以及 NotificationFilter 是什么的完整讨论,请参阅 JMX 规范 (1.2) 中题为“JMX 通知模型”的部分。)

Publishing Notifications

Spring 不仅支持接收 Notification 的注册,还支持发布 Notification

此部分实际上仅与通过 MBeanExporter 作为 MBean 公开的 Spring 管理的 Bean 相关。任何现有的用户定义 MBean 都应使用标准 JMX API 进行通知发布。

Spring 的 JMX 通知发布支持中的关键接口是 NotificationPublisher 接口(在 org.springframework.jmx.export.notification 包中定义)。通过 MBeanExporter 实例导出为 MBean 的任何 Bean 都可以实现相关的 NotificationPublisherAware 接口来访问 NotificationPublisher 实例。NotificationPublisherAware 接口通过一个简单的 setter 方法将 NotificationPublisher 的实例提供给正在实现 Bean,然后 Bean 可以使用该 setter 方法来发布 Notification

如https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/jmx/export/notification/NotificationPublisher.html[NotificationPublisher]接口的 javadoc 中所述,通过 NotificationPublisher`机制发布事件的托管 bean 不负责通知侦听器的状态管理。Spring 的 JMX 支持负责处理所有 JMX 基础架构问题。作为一个应用程序开发人员,你需要做的就是实现 `NotificationPublisherAware 接口,然后使用所提供的 NotificationPublisher 实例开始发布事件。请注意,NotificationPublisher 在托管 bean 在 MBeanServer 中注册后设置。

使用 NotificationPublisher 实例非常简单。创建 JMX Notification 实例(或相应 Notification 子类的实例),用与要发布的事件相关的数据填充通知,并调用 NotificationPublisher 实例上的 sendNotification(Notification),传入 Notification

在以下示例中,JmxTestBean 的导出实例在每次调用 add(int, int) 操作时发布 NotificationEvent

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 的耦合,那就这么做吧。