Поддержка JMX (Java Management Extensions) в Spring предусматривает возможности, которые позволяют легко и прозрачно интегрировать ваше приложение Spring в инфраструктуру JMX.

JMX?

Данная глава не является введением в JMX. Она не преследует цель пояснить, почему вам, возможно, стоило бы использовать JMX.

В частности, поддержка JMX в Spring предусматривает четыре основные возможности:

  • Автоматическая регистрация любого бина Spring в качестве JMX MBean.

  • Гибкий механизм управления интерфейсом для контроля бинов.

  • Декларативная передача MBeans через удаленные соединители JSR-160.

  • Простое проксирование как локальных, так и удаленных MBean-ресурсов.

Данные функции предназначены для работы без привязки компонентов приложения к интерфейсам и классам Spring или JMX. Действительно, по большей части классы приложения не обязательно должны ориентироваться на Spring или JMX, чтобы была возможность пользоваться функциями JMX в Spring.

Экспорт ваших бинов в JMX

Основным классом JMX-фреймворка в Spring является MBeanExporter. Этот класс отвечает за получение ваших бинов Spring и их регистрацию на MBeanServer из JMX. Например, рассмотрим следующий класс:

package org.springframework.jmx;
public class JmxTestBean implements IJmxTestBean {
    private String name;
    private int age;
    private boolean isSuperman;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public int add(int x, int y) {
        return x + y;
    }
    public void dontExposeMe() {
        throw new RuntimeException();
    }
}

Чтобы открыть свойства и методы этого бина как атрибуты и операции MBean, можно настроить экземпляр класса MBeanExporter в конфигурационном файле и передать в него бин, как это показано в следующем примере:

<beans>
    <!-- этот бин не должен быть отложено инициализированным, если будет происходить экспорт -->
    <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
        <property name="beans">
            <map>
                <entry key="bean:name=testBean1" value-ref="testBean"/>
            </map>
        </property>
    </bean>
    <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
        <property name="name" value="TEST"/>
        <property name="age" value="100"/>
    </bean>
</beans>

Соответствующее определение бина из предыдущего фрагмента конфигурации – это бин exporter. Свойство beans указывает MBeanExporter, какие именно ваши бины нужно экспортировать на MBeanServer из JMX. В конфигурации по умолчанию ключ каждой записи в beans Map используется как ObjectName для бина, на который ссылается соответствующее значение записи.

При такой конфигурации бин testBean отображается как MBean под именем ObjectName bean:name=testBean1. По умолчанию все публичные свойства бина открыты как атрибуты, а все публичные методы (кроме унаследованных от класса Object) открыты как операции.

MBeanExporter – это бин Lifecycle. По умолчанию MBeans экспортируются как можно позже в течение жизненного цикла приложения. Можно сконфигурировать phase, на которой происходит экспорт, или отключить автоматическую регистрацию, установив флаг autoStartup.

Создание MBeanServer

Конфигурация, показанная в предыдущем разделе, предполагает, что приложение работает в окружении, в котором уже запущен один (и только один) MBeanServer. В этом случае Spring пытается найти запущенный MBeanServer и зарегистрировать ваши бины на этом сервере (если таковой имеется). Такая логика работы практична, если ваше приложение работает внутри контейнера (например, Tomcat или WebSphere от IBM), который имеет свой собственный MBeanServer.

Однако этот подход бесполезен в автономном окружении или при запуске внутри контейнера, который не предоставляет MBeanServer. Чтобы решить эту проблему, можно создать экземпляр MBeanServer декларативно, добавив экземпляр класса org.springframework.jmx.support.MBeanServerFactoryBean в вашу конфигурацию. Вы также можете обеспечить использование определенного MBeanServer, установив значение свойства server экземпляра MBeanExporter в значение MBeanServer, возвращаемое MBeanServerFactoryBean, как показано в следующем примере:

<beans>
    <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean"/>
    <!--
    этот бин должен быть предварительно инициализирован, чтобы произошел экспорт;
    и это означает, что он не должен быть помечен как инициализируемый отложено
    -->
    <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="server" ref="mbeanServer"/>
    </bean>
    <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
        <property name="name" value="TEST"/>
        <property name="age" value="100"/>
    </bean>
</beans>

В предыдущем примере экземпляр MBeanServer создается MBeanServerFactoryBean и передается MBeanExporter через свойство server. Если вы передадите собственный экземпляр MBeanServer, MBeanExporter не будет пытаться найти работающий MBeanServer и использует переданный экземпляр MBeanServer. Для того чтобы это работало правильно, нужно иметь реализацию JMX в вашем classpath.

Повторное использование существующего MBeanServer

Если сервер не задан, MBeanExporter попытается автоматически обнаружить работающий MBeanServer. Это работает в большинстве окружений, где используется только один экземпляр MBeanServer. Однако, если существует несколько экземпляров, экспортер может выбрать не тот сервер. В таких случаях следует использовать MBeanServer agentId, чтобы указать, какой экземпляр следует использовать, как это показано в следующем примере:

<beans>
    <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean">
        <!-- указываем, где изначально искать сервер -->
        <property name="locateExistingServerIfPossible" value="true"/>
        <!-- ищем экземпляр MBeanServer с заданным agentId -->
        <property name="agentId" value="MBeanServer_instance_agentId>"/>
    </bean>
    <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
        <property name="server" ref="mbeanServer"/>
        ...
    </bean>
</beans>

Для платформ или случаев, где существующий MBeanServer имеет динамический (или неизвестный) agentId, который извлекается через методы поиска, следует использовать factory-method, как показано в следующем примере:

<beans>
    <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
        <property name="server">
            <!-- Кастомный MBeanServerLocator -->
            <bean class="platform.package.MBeanServerLocator" factory-method="locateMBeanServer"/>
        </property>
    </bean>
    <!-- другие бины здесь -->
</beans>

Бины MBean с отложенной инициализацией

Если бин конфигурируется с MBeanExporter, который также сконфигурировать на отложенную инициализацию, MBeanExporter не нарушает этот контракт и избегает создания экземпляра бина. Вместо этого он регистрирует прокси на MBeanServer и задерживает получение бина из контейнера до первого вызова прокси.

Автоматическая регистрация бинов MBean

Любые бины, экспортированные через MBeanExporter и уже являющиеся действительными бинами MBean, регистрируются "как есть" на MBeanServer без дальнейшего вмешательства со стороны Spring. Можно сделать так, чтобы MBeanExporter автоматически обнаруживал бины MBean, установив свойство autodetect в true, как показано в следующем примере:

<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="autodetect" value="true"/>
</bean>
<bean name="spring:mbean=true" class="org.springframework.jmx.export.TestDynamicMBean"/>

В предыдущем примере бин с именем spring:mbean=true уже является валидным JMX MBean и автоматически регистрируется Spring. По умолчанию, бин, который автоматически определен для регистрации в JMX, имеет свое собственное имя бина, используемое в качестве ObjectName.

Управление логикой работы регистрации

Рассмотрим сценарий, в котором MBeanExporter из Spring пытается зарегистрировать MBean при помощи MBeanServer, используя ObjectName bean:name=testBean1. Если экземпляр MBean уже был зарегистрирован под тем же ObjectName, логика работы по умолчанию – ошибка (и генерация InstanceAlreadyExistsException).

Можно управлять тем, что именно происходит, когда MBean регистрируется на MBeanServer. Поддержка JMX в Spring позволяет использовать три различных варианта логики работы при регистрации для управления логикой регистрации, если при регистрации обнаруживается, что MBean уже был зарегистрирован под тем же ObjectName. В следующей таблице приведена краткая информация об этих вариантах логики работы при регистрации:

Таблица 6. Варианты логики работы при регистрации
Логика работы при регистрации Пояснение

FAIL_ON_EXISTING

Это логика работы при регистрации по умолчанию. Если экземпляр MBean уже был зарегистрирован под тем же ObjectName, то регистрируемый MBean не будет зарегистрирован, и будет сгенерировано исключение InstanceAlreadyExistsException. Уже существующий MBean не будет затронут.

IGNORE_EXISTING

Если экземпляр MBean уже был зарегистрирован под тем же ObjectName, то регистрируемый MBean не будет зарегистрирован. Существующий MBean не будет затронут, а Exception не будет сгенерировано. Это полезно в ситуациях, когда несколько приложений пытаются совместно использовать общий MBean на общем MBeanServer.

REPLACE_EXISTING

Если экземпляр MBean уже был зарегистрирован под тем же ObjectName, то для существующего MBean, который был зарегистрирован ранее, регистрация отменяется, а новый MBean регистрируется вместо него (новый MBean фактически заменяет предыдущий экземпляр).

Значения в предыдущей таблице определены как перечисляемые типы в классе RegistrationPolicy. Если требуется изменить логику работы при регистрации по умолчанию, то нужно установить значение свойства registrationPolicy в определении MBeanExporter в одно из этих значений.

В следующем примере показано, как изменить логику работы при регистрации по умолчанию на логику REPLACE_EXISTING:

<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="registrationPolicy" value="REPLACE_EXISTING"/>
    </bean>
    <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
        <property name="name" value="TEST"/>
        <property name="age" value="100"/>
    </bean>
</beans>