Notifications

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

Spring’s JMX offering includes comprehensive support for JMX notifications.

Registering Listeners for Notifications

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

Spring’s JMX support makes it easy to register any number of NotificationListeners with any number of MBeans (this includes MBeans exported by Spring’s MBeanExporter and MBeans registered through some other mechanism). For example, consider the scenario where one would like to be informed (through a Notification) each and every time an attribute of a target MBean changes. The following example writes notifications to the console:

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

The following example adds ConsoleLoggingNotificationListener (defined in the preceding example) to 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

With the preceding configuration in place, every time a JMX Notification is broadcast from the target MBean (bean:name=testBean1), the ConsoleLoggingNotificationListener bean that was registered as a listener through the notificationListenerMappings property is notified. The ConsoleLoggingNotificationListener bean can then take whatever action it deems appropriate in response to the Notification.

您也可以将直奔 Bean 名称作为导出 Bean 与监听器之间的链接,如下面的示例所示:

You can also use straight bean names as the link between exported beans and listeners, as the following example shows:

<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 属性映射中的键,如下面的示例所示:

If you want to register a single NotificationListener instance for all of the beans that the enclosing MBeanExporter exports, you can use the special wildcard (*) as the key for an entry in the notificationListenerMappings property map, as the following example shows:

<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 通知方案中使用的任意回传对象。

If you need to do the inverse (that is, register a number of distinct listeners against an MBean), you must instead use the notificationListeners list property (in preference to the notificationListenerMappings property). This time, instead of configuring a NotificationListener for a single MBean, we configure NotificationListenerBean instances. A NotificationListenerBean encapsulates a NotificationListener and the ObjectName (or ObjectNames) that it is to be registered against in an MBeanServer. The NotificationListenerBean also encapsulates a number of other properties, such as a NotificationFilter and an arbitrary handback object that can be used in advanced JMX notification scenarios.

使用 NotificationListenerBean 实例时的配置与之前提供的配置并没有很大不同,如下面的示例所示:

The configuration when using NotificationListenerBean instances is not wildly different to what was presented previously, as the following example shows:

<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。以下示例完成了这些目标:

The preceding example is equivalent to the first notification example. Assume, then, that we want to be given a handback object every time a Notification is raised and that we also want to filter out extraneous Notifications by supplying a NotificationFilter. The following example accomplishes these goals:

<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 通知模型”的部分。)

(For a full discussion of what a handback object is and, indeed, what a NotificationFilter is, see the section of the JMX specification (1.2) entitled 'The JMX Notification Model'.)

Publishing Notifications

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

Spring provides support not only for registering to receive Notifications but also for publishing Notifications.

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

This section is really only relevant to Spring-managed beans that have been exposed as MBeans through an MBeanExporter. Any existing user-defined MBeans should use the standard JMX APIs for notification publication.

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

The key interface in Spring’s JMX notification publication support is the NotificationPublisher interface (defined in the org.springframework.jmx.export.notification package). Any bean that is going to be exported as an MBean through an MBeanExporter instance can implement the related NotificationPublisherAware interface to gain access to a NotificationPublisher instance. The NotificationPublisherAware interface supplies an instance of a NotificationPublisher to the implementing bean through a simple setter method, which the bean can then use to publish Notifications.

如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 中注册后设置。

As stated in the javadoc of the NotificationPublisher interface, managed beans that publish events through the NotificationPublisher mechanism are not responsible for the state management of notification listeners. Spring’s JMX support takes care of handling all the JMX infrastructure issues. All you need to do, as an application developer, is implement the NotificationPublisherAware interface and start publishing events by using the supplied NotificationPublisher instance. Note that the NotificationPublisher is set after the managed bean has been registered with an MBeanServer.

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

Using a NotificationPublisher instance is quite straightforward. You create a JMX Notification instance (or an instance of an appropriate Notification subclass), populate the notification with the data pertinent to the event that is to be published, and invoke the sendNotification(Notification) on the NotificationPublisher instance, passing in the Notification.

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

In the following example, exported instances of the JmxTestBean publish a NotificationEvent every time the add(int, int) operation is invoked:

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

The NotificationPublisher interface and the machinery to get it all working is one of the nicer features of Spring’s JMX support. It does, however, come with the price tag of coupling your classes to both Spring and JMX. As always, the advice here is to be pragmatic. If you need the functionality offered by the NotificationPublisher and you can accept the coupling to both Spring and JMX, then do so.