3.2.5.1. Создание JMX-бина

Рассмотрим процесс создания JMX-бина на примере.

  • Интерфейс JMX-бина:

    package com.sample.sales.core;
    
    import org.springframework.jmx.export.annotation.*;
    
    @ManagedResource(description = "Performs operations on Orders")
    public interface OrdersMBean {
    
        @ManagedOperation(description = "Recalculates an order amount")
        @ManagedOperationParameters({@ManagedOperationParameter(name = "orderId", description = "")})
        String calculateTotals(String orderId);
    }
    • Интерфейс и его методы могут содержать аннотации для задания описания JMX-бина и его операций. Это описание будет отображаться во всех инструментах, работающих с данным JMX-интерфейсом, тем самым помогая администратору системы.

    • Аннотацию @JmxRunAsync можно использовать для указания длительных операций. Если такая операция запускается через встроенную консоль JMX, платформа отображает диалог с неопределенным индикатором прогресса и кнопкой Cancel. Пользователь может прервать операцию и продолжить работу с приложением. Аннотация может также содержать параметр timeout, который устанавливает максимальное время выполнения в миллисекундах, например:

      @JmxRunAsync(timeout = 30000)
      String calculateTotals();

      Если таймаут превышен, диалог закрывается с сообщением об ошибке.

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

    • Так как инструменты JMX поддерживают ограниченный набор типов данных, параметры и результат метода желательно задавать типа String, и при необходимости выполнять конвертацию внутри метода. Помимо String, поддерживаются следующие типы параметров: boolean, double, float, int, long, Boolean, Integer.

  • Класс JMX-бина:

    package com.sample.sales.core;
    
    import com.haulmont.cuba.core.*;
    import com.haulmont.cuba.core.app.*;
    import com.sample.sales.entity.Order;
    import org.apache.commons.lang.exception.ExceptionUtils;
    import org.springframework.stereotype.Component;
    import javax.inject.Inject;
    import java.util.UUID;
    
    @Component("sales_OrdersMBean")
    public class Orders implements OrdersMBean {
    
        @Inject
        protected OrderWorker orderWorker;
    
        @Inject
        protected Persistence persistence;
    
        @Authenticated
        @Override
        public String calculateTotals(String orderId) {
            try {
                try (Transaction tx = persistence.createTransaction()) {
                    Order entity = persistence.getEntityManager().find(Order.class, UUID.fromString(orderId));
                    orderWorker.calculateTotals(entity);
                    tx.commit();
                };
                return "Done";
            } catch (Throwable e) {
                return ExceptionUtils.getStackTrace(e);
            }
        }
    }

    Аннотация @Component определяет, что данный класс является управляемым бином с именем sales_OrdersMBean. Имя указано напрямую в аннотации, а не в константе, так как доступ к JMX-бину из кода Java не требуется.

    Рассмотрим реализацию метода calculateTotals().

    • Метод имеет аннотацию @Authenticated, т.е. при входе в метод и при отсутствии в потоке выполнения пользовательской сессии выполняется системная аутентификация.

    • Тело метода обернуто в блок try/catch, так что метод в случае успешного выполнения возвращает строку "Done", а в случае ошибки - stacktrace исключения в виде строки.

      В данном случае все исключения обрабатываются, а значит, не попадают в MBeanInterceptor и не выводятся в журнал автоматически. Поэтому при необходимости логировать исключения здесь нужно добавить вызов логгера в секции catch.

    • Логика метода заключается в том, что он стартует транзакцию, загружает экземпляр сущности Order по идентификатору и передает управление бину OrderWorker для обработки.

  • Регистрация JMX-бина в spring.xml:

    <bean id="sales_MBeanExporter" lazy-init="false"
          class="com.haulmont.cuba.core.sys.jmx.MBeanExporter">
        <property name="beans">
            <map>
                <entry key="${cuba.webContextName}.sales:type=Orders"
                       value-ref="sales_OrdersMBean"/>
            </map>
        </property>
    </bean>

    Все JMX-бины проекта объявляются в одном экземпляре MBeanExporter в элементах map/entry свойства beans. Ключом элемента здесь является JMX ObjectName, значением - имя бина, заданное в аннотации @Component. ObjectName начинается с имени веб-приложения, так как в одном экземпляре сервера приложения (т.е. в одной JVM) может быть развернуто несколько веб-приложений, экспортирующих одинаковые JMX-интерфейсы.