(ПРИМЕЧАНИЕ: оригинальный пост был слегка отредактирован для улучшения читабельности)
Технология 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 .
Связанные посты и документы:
- Отладка туннелирования и JMX для Alfresco (А. использует Spring) — см. Второй раздел, Туннелирование SSH для JMX
- Пользовательский туннельный агент RMI — использует настроенный порт вместо случайного
- Мониторинг ActiveMQ с использованием JMX через SSH
- Спецификация JMX 1.2 и спецификация JMX 1.2 Remote API ; из спецификации JMX: «Сервер MBean использует адаптеры и соединители протокола, чтобы сделать агент доступным из приложений управления вне JVM агента». С другой стороны, страница Oracle JMX гласит, что если вы установите com.sun.management.jmxremote (в отличие от… jmxremote.port), вы сможете «контролировать локальную платформу Java, то есть JVM, работающую на та же самая машина »- таким образом, не обязательно от той же JVM.
Соединение с 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 перед соединителем.
Статьи по Теме: