Продукт Spring для JMX включает в себя всестороннюю поддержку уведомлений JMX.

Регистрация слушателей для уведомлений

Средства поддержки JMX в Spring позволяют с легкостью регистрировать любое количество NotificationListeners с любым количеством бинов MBean (сюда входят бины MBean, экспортированные MBeanExporter для Spring и бины MBean, зарегистрированные через другой механизм). Например, рассмотрим сценарий, в котором кому-то нужно получать информацию (через Notification) каждый раз, когда атрибут целевого бина 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());
    }
}

В следующем примере в notificationListenerMappings добавлен ConsoleLoggingNotificationListener (определенный в предыдущем примере):

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

При использовании предыдущей конфигурации всякий раз, когда Notification из JMX рассылается из целевого бина MBean (bean:name=testBean1), бин ConsoleLoggingNotificationListener, который был зарегистрирован в качестве слушателя через свойство notificationListenerMappings, получает уведомление. После этого бин ConsoleLoggingNotificationListener может предпринять любое действие, которое будет сочтено необходимым, в ответ на Notification.

Также можно использовать прямые имена бинов в качестве связки между экспортируемыми бинами и слушателями, как показано в следующем примере:

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

Если необходимо зарегистрировать один экземпляр NotificationListener для всех бинов, которые экспортирует вложенный MBeanExporter, можно использовать специальный подстановочный знак (*)в качестве ключа для записи в Map свойств notificationListenerMappings, как показано в следующем примере:

<property name="notificationListenerMappings">
    <map>
        <entry key="*">
            <bean class="com.example.ConsoleLoggingNotificationListener"/>
        </entry>
    </map>
</property>

Если требуется сделать обратное (то есть зарегистрировать несколько разных слушателей для бина MBean), нужно использовать свойство notificationListeners (вместо свойства notificationListenerMappings). На этот раз вместо конфигурирования NotificationListener для одного бина MBean мы конфигурируем экземпляры NotificationListenerBean. NotificationListenerBean инкапсулирует NotificationListener и ObjectName (или ObjectNames), для которого он должен быть зарегистрирован на MBeanServer. NotificationListenerBean также инкапсулирует ряд других свойств, таких как NotificationFilter и произвольный передаваемый обратно (handback) объект, которые могут быть использованы в расширенных сценариях использования 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, и что нам также нужно отфильтровать лишние Notifications, передав NotificationFilter. В следующем примере показано, как достичь этих целей:

<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>
                            <!-- обрабатывает уведомления от двух разных бинов MBean -->
                            <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>
    <!-- реализует оба интерфейса NotificationListener и NotificationFilter -->
    <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-уведомлений").

Публикация уведомлений

Spring обеспечивает поддержку не только регистрации для получения Notifications, но и для публикации Notifications.

Этот раздел фактически относится только к бинам, управляемым Spring, которые были открыты как бины MBean через MBeanExporter. Любые существующие пользовательские бины MBean должны использовать стандартные API-интерфейсы фреймворка JMX для публикации уведомлений.

Ключевым интерфейсом в средствах поддержки публикации JMX-уведомлений в Spring является интерфейс NotificationPublisher (определенный в пакете org.springframework.jmx.export.notification). Любой бин, который будет экспортирован как MBean через экземпляр MBeanExporter, может реализовывать соответствующий интерфейс NotificationPublisherAware, чтобы получать доступ к экземпляру NotificationPublisher. Интерфейс NotificationPublisherAware передает экземпляр NotificationPublisher реализующему бину через простой сеттер, который бин может затем использовать для публикации Notifications.

Как указано в javadoc для интерфейса NotificationPublisher, управляемые бины, которые публикуют события через механизм NotificationPublisher, не отвечают за управление состоянием слушателей уведомлений. Средства поддержки JMX в Spring отвечают за решение всех вопросов, связанных с JMX-инфраструктурой. Все, что вам требуется сделать как разработчику приложения, это реализовать интерфейс NotificationPublisherAware и начать публиковать события с помощью предоставленного экземпляра NotificationPublisher. Обратите внимание, что NotificationPublisher устанавливается после того, как управляемый бин был зарегистрирован на MBeanServer.

Использовать экземпляр NotificationPublisher достаточно просто. Вы создаете JMX-экземпляр Notification (или экземпляр соответствующего подкласса Notification), заполняете уведомление данными, относящимися к событию, которое должно быть опубликовано, и вызываете sendNotification(Notification) для экземпляра NotificationPublisher, передавая Notification.

В следующем примере экспортированные экземпляры JmxTestBean публикуют NotificationEvent каждый раз, когда происходит вызов операции add(int, int):

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;
    // другие геттеры и сеттеры опущены для простоты понимания
    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 и механизм, обеспечивающий его работу, – одна из самых приятных особенностей поддержки JMX в Spring. Однако за это приходится платить, связывая ваши классы как со Spring, так и с JMX. Как всегда, посоветовать здесь можно лишь одно – быть прагматичным. Если вам нужна функциональность, предлагаемая NotificationPublisher, и вы готовы принять связь как со Spring, так и с JMX, то вперёд.

5.7. Дополнительные источники

Этот раздел содержит ссылки на дополнительные ресурсы по JMX: