Статьи

Представление POJO как MBean JMX с Spring

Вот очень хороший урок о том, как наш последний партнер JCG , Якуб из блога «Святая Ява » (классное имя), может сделать « Представление POJO как JMX MBean легко с помощью Spring ».

(ПРИМЕЧАНИЕ: оригинальный пост был слегка отредактирован для улучшения читабельности)

Технология Java Management Extensions (JMX) — отличный способ проверить или изменить состояние переменных или вызвать метод в (удаленном) запущенном приложении через графический интерфейс управления, такой как JConsole . А Spring делает тривиальным представление любого POJO в виде JMX MBean с минимальной конфигурацией за несколько минут. Документация Spring JMX очень хороша, однако есть несколько моментов, с которыми я боролся некоторое время и поэтому хотел бы записать здесь правильные решения.

Мне нужно было контролировать Java-приложение командной строки, используя Spring 2.5 на IBM JVM 1.5, работающей на сервере. Мониторинг будет выполняться с помощью jconsole в Sun JVM 1.6 в качестве клиента JMX на моем ПК. Все следующие фрагменты XML взяты из соответствующего Spring application-context.xml.

Превращение POJO в MBean

JMX позволяет выставлять методы получения, установки и операции, принимающие примитивы или сложные типы данных в качестве параметров (хотя типы, отличные от нескольких специальных, требуют, чтобы у клиента были классы). Вы говорите Spring, чтобы выставить POJO как MBean следующим образом:

01
02
03
04
05
06
07
08
09
10
11
<bean id="myMBean"
        class="my.package.JobPerformanceStats"
        factory-method="instance" />
 
<bean class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
        <property name="beans">
          <map>
                <entry key="bean:name=MyMBeanName" value-ref="myMBean"/>
          </map>
        </property>
</bean>

Сначала вы объявляете экземпляр класса POJO — myMBean (по другим причинам у меня есть старомодный синглтон и используется JobPerformanceStats.instance () для доступа к бину). Затем вы объявляете MBeanExporter с помощью lazy-init = ”false” и рассказываете о своем бине. (Существуют также другие способы сделать это, включая автообнаружение.) Затем компонент будет виден под его ключом, т.е. «bean: name = MyMBeanName», который JConsole отображает как «MyMBeanName».

Обратите внимание, что MBeanExporter работает только в JVM 1.5+, поскольку использует новый пакет java.lang.management. В JDK 1.4 Spring завершится ошибкой со следующей ошибкой:

java.lang.NoClassDefFoundError: javax / management / MBeanServerFactory
в org.springframework.jmx.support.MBeanServerFactoryBean.createMBeanServer

По умолчанию он предоставляет все открытые методы и атрибуты. Вы можете изменить это различными способами, например, с помощью интерфейса.

Если вы не работаете в контейнере, который уже предоставляет сервер MBean, как в моем случае здесь, вы должны указать Spring, чтобы запустить его:

1
<bean class="org.springframework.jmx.support.MBeanServerFactoryBean"/>

Включение удаленного доступа

Чтобы сделать MBean доступным с другого компьютера, вы должны представить его миру, объявив ConnectorServerFactoryBean, настроенный с соответствующим механизмом связи.

Удаленный доступ через JMXMP

По умолчанию ConnectorServerFactoryBean предоставляет MBeans через протокол обмена сообщениями JMX (JMXMP) с адресом

Сервис: JMX: jmxmp: // локальный: 9875

1
<bean class="org.springframework.jmx.support.ConnectorServerFactoryBean" />

Однако этот протокол не поддерживается сразу после установки, и вы должны включить jmxremote_optional.jar, часть OpenDMK , в путь к классам как приложения MBean, так и клиента jconsole, чтобы избежать следующего исключения:

org.springframework.beans.factory.BeanCreationException: Ошибка создания бина с именем org.springframework.jmx.support.ConnectorServerFactoryBean # 0? определено в ресурсе пути к классу [application-context.xml]: сбой вызова метода init; вложенным исключением является java.net.MalformedURLException: неподдерживаемый протокол: jmxmp

Удаленный доступ через RMI

В качестве альтернативы вы можете выставить MBeans поверх RMI, который не имеет дополнительных зависимостей:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
<!--
Now expose the server for remote access via RMI
Local access:   service:jmx:rmi://localhost/jndi/rmi://localhost:10099/myconnector
Remote access:  service:jmx:rmi:///jndi/rmi://your.host:10099/myconnector
or service:jmx:rmi://localhost/jndi/rmi://localhost:10099/myconnector
-->
<bean
 class="org.springframework.jmx.support.ConnectorServerFactoryBean"
 depends-on="rmiRegistry">
 <property name="objectName" value="connector:name=rmi" />
 <property name="serviceUrl"
 value="service:jmx:rmi://localhost/jndi/rmi://localhost:10099/myconnector" />
</bean>
 
<bean id="rmiRegistry"
 class="org.springframework.remoting.rmi.RmiRegistryFactoryBean">
 <property name="port" value="10099" />
</bean>

Однако есть также некоторые уловы, которые вы должны избегать:

1. Вы должны запустить реестр RMI, чтобы соединитель мог зарегистрировать MBean там; это не начнется для вас
2. Необходимо убедиться, что реестр запущен до того, как соединитель попытается использовать, либо объявив его перед соединителем, либо сделав эту зависимость явной с атрибутом зависимости от

Если вы не настроите его правильно, вы получите исключение, подобное этому:

org.springframework.beans.factory.BeanCreationException: Ошибка создания бина с именем org.springframework.jmx.support.ConnectorServerFactoryBean # 0? определено в ресурсе пути к классу [application-context.xml]: сбой вызова метода init; вложенное исключение — java.io.IOException: невозможно связать с URL [rmi: // localhost: 10099 / jmxrmi]: javax.naming.ServiceUnavailableException [Исключением корня является java.rmi.ConnectException: соединение с хостом отказано: localhost; Вложенное исключение: java.net.ConnectException: соединение отклонено: соединение].

Доступ к локальному серверу MBean через туннель SSH

Для повышения безопасности вы можете предпочесть не предоставлять свои MBeans удаленному доступу, сделав их доступными только с локального компьютера (127.0.0.1), и использовать туннель SSH, чтобы удаленная JConsole могла обращаться к ним как к локальному приложению. Это, конечно, возможно, но может быть трудным, потому что обычно JMX проходит через RMI, который использует два порта : один для Реестра RMI и другой для фактической службы (здесь сервер MBean), который обычно выбирается случайным образом во время выполнения, и вы ‘ Мне нужно туннелировать оба. К счастью, Spring позволяет настроить оба порта :

01
02
03
04
05
06
07
08
09
10
11
12
<bean
        class="org.springframework.jmx.support.ConnectorServerFactoryBean"
        depends-on="rmiRegistry">
        <property name="objectName" value="connector:name=rmi" />
        <property name="serviceUrl"
                value="service:jmx:rmi://127.0.0.1:STUBPORT/jndi/rmi://localhost:REGISTRYPORT/myconnector" />
</bean>
 
<bean id="rmiRegistry"
        class="org.springframework.remoting.rmi.RmiRegistryFactoryBean">
        <property name="port" value="REGISTRYPORT" />
</bean>

Замените STUBPORT и REGISTRYPORT подходящими номерами и туннелируйте эти два. Обратите внимание, что номер REGISTRYPORT совпадает в serviceUrl соединителя и в атрибуте порта реестра RMI.
ПРЕДУПРЕЖДЕНИЕ. Приведенная выше конфигурация фактически не препятствует прямому доступу из удаленного приложения. Чтобы действительно заставить реестр RMI только прослушивать соединение с локального хоста, нам, вероятно, потребуется установить — под Sun JVM без Spring — системное свойство com.sun.management.jmxremote. Кроме того, чтобы заставить реестр использовать IP 120.0.0.1, нам нужно установить java.rmi.server.hostname = localhost (также относится и к Spring). Смотрите это обсуждение о принудительном локальном доступе . Я не уверен, как добиться того же результата с Spring, сохраняя при этом возможность указать оба порта RMI. Проверьте также JavaDoc для Spring RmiServiceExporter .

Связанные посты и документы:

Соединение с Jconsole

Запустите JConsole и введите соответствующий удаленный адрес, например

Сервис: JMX: RMI: /// JNDI / RMI: //your.server.com: 10099 / myconnector

при подключении к приложению на удаленной машине your.server.com, доступной через RMI.

Что касается URL-адреса подключения, если у вас есть соединитель с serviceUrl

Сервис: JMX: RMI: // MyHost: 9999 / JNDI / RMI: // локальный: 10099 / myconnector

затем с клиента вы можете использовать либо

Сервис: JMX: RMI: // MyHost: 9999 / JNDI / RMI: //your.server.com: 10099 / myconnector

или просто

Сервис: JMX: RMI: /// JNDI / RMI: //your.server.com: 10099 / myconnector

потому что в соответствии со спецификацией JMX 1.2 Remote API (стр. 90):

… Имя хоста и номер порта
# (myhost: 9999 в примерах) не используются клиентом и, если
# настоящее, по сути, комментарии. Адрес сервера соединителя
# фактически хранится в сериализованной заглушке (/ заглушка / форма) или в
# запись в каталоге (/ jndi / form).

Конфигурация IBM JVM, JConsole и JMX

В руководстве IBM JVM 5 SDK указано, что IBM SDK также содержит JConsole и распознает те же системные свойства , связанные с JMX , а именно com.sun.management.jmxremote. * (Хотя сам «com.sun.management.jmxremote» не упоминается ).
Обратите внимание, что IBM JConsole немного отличается, например, отсутствует вкладка Локальная, которая заменяется указанием параметра командной строки connection = localhost (поищите в руководстве по SDK «Локальная вкладка инструмента мониторинга JConsole»).

Дальнейшие улучшения

JVM 1.5: экспонирование MemoryMXBean

Начиная с Java 5.0 существует несколько полезных платформ MBean, которые предоставляют информацию о JVM, включая также java.lang.management.MemoryMXBean, которые позволяют вам видеть использование кучи, вызывать GC и т. Д.

Вы можете сделать его доступным для JConsole и других агентов JMX следующим образом (хотя должен быть более простой способ):

01
02
03
04
05
06
07
08
09
10
11
12
13
<bean class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
        <property name="beans">
          <map>
                <entry key="bean:name=Memory2" value-ref="memProxy"/>
                <!-- other exported beans may follow ... -->
          </map>
        </property>
</bean>
 
<bean id="memProxy"
        class="java.lang.management.ManagementFactory"
        factory-method="getMemoryMXBean"
        />

Обновление: действительно, кажется, есть лучший способ выставить MBeans платформы напрямую , заменив MBeanServerFactoryBean в Spring на java.lang.management.ManagementFactory, используя фабричный метод getPlatformMBeanServer. Конечно, для этого требуется JVM 1.5+.

Повышение безопасности с помощью аутентификации по паролю

Доступ к вашим MBeans через RMI может быть защищен паролем. Согласно обсуждению, аутентификация настроена на соединителе сервера :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
<bean
        class="org.springframework.jmx.support.ConnectorServerFactoryBean"
        depends-on="rmiRegistry">
        <property name="objectName" value="connector:name=rmi" />
        <property name="serviceUrl"
                value="service:jmx:rmi://localhost/jndi/rmi://localhost:10099/myconnector" />
    <property name="environment">
    <!-- the following is only valid when the sun jmx implementation is used -->
        <map>
            <entry key="jmx.remote.x.password.file" value="etc/security/jmxremote.password"/>
            <entry key="jmx.remote.x.access.file" value="etc/security/jmxremote.access"/>
        </map>
    </property>
</bean>

Файлы passwd и access следуют шаблонам, которые можно найти в папке JDK / jre / lib / management.

Резюме

Экспонировать POJO как MBean с Spring очень просто, просто не забудьте запустить сервер MBean и коннектор. Для JMXMP включите jmxmp_impl. jar на classpath и для RMI убедитесь, что запустили реестр RMI перед соединителем.

Статьи по Теме: