MBean’y

Ostatnio miałem okazję zetknąć się z bardzo przydatnym mechanizmem, z którym szczerze mówiąc nie miałem nigdy wcześniej do czynienia. Jak się pewnie domyślacie chodzi tutaj o wspomniane w tytule MBean’y. Z jakiegoś powodu niewiele się o nich mówi, a są sytuacje, w których mogą być bardzo pomocne. Postaram się wam przybliżyć ten temat w dalszej części wpisu.

Tworzymy bean’a

W dużym uproszczeniu można powiedzieć, że MBean’y są miejscami w naszej aplikacji, do których możemy się „wpiąć” w celu monitorowania jej stanu, a nawet zmiany zachowania. W praktyce wygląda to tak, że mamy możliwość zdalnego wywołania metody takiego bean’a, badania stanu zmiennych, a także zmiany ich wartości. Najlepiej będzie to widać na przykładzie, więc przejdźmy do części praktycznej. Zaczniemy od stworzenia zwykłego bean’a. Żeby całość zgodna była ze sztuką, w pierwszej kolejności napiszmy interfejs.

public interface MessageServiceMBean
{
    void print();

    int getPrintCount();

    void setMessage(String message);

    String getMessage();
}

Następnie interfejs ten zaimplementujemy.

public class MessageService implements MessageServiceMBean
{
    private int count = 0;

    private String message = "Hello!";

    @Override
    public void print()
    {
        System.out.println(message);
        count++;
    }

    @Override
    public int getPrintCount()
    {
        return count;
    }

    @Override
    public void setMessage(String message)
    {
        if(message.isEmpty())
        {
            throw new IllegalArgumentException("Message can't be empty!");
        }

        this.message = message;
    }

    @Override
    public String getMessage()
    {
        return message;
    }
}

Jak widać kod jest dość prosty i przejrzysty – mamy metodę, która drukuje na standardowe wyjście pewien komunikat, licznik, który zlicza ilość tych wydruków, oraz metodę pozwalającą na zmianę domyślnej treści komunikatu. Mamy także jedno ograniczenie – treść komunikatu nie może być pusta. Jeśli spróbujemy taką treść ustawić, rzucony zostanie wyjątek z odpowiednim komunikatem. Istotna jest tutaj kwestia nazewnictwa interfejsu i klasy. Nazwa tego pierwszego powinna być połączeniem nazwy klasy i członu „MBean”. Generalnie rzecz biorąc interfejs nie jest wymagany, jeśli jako klienta będziemy używać np. jconsole. Inaczej sprawa wygląda, jeśli kod klienta piszemy sami – wtedy dzięki użyciu interfejsu będziemy mogli korzystać z wygenerowanego dla nas na bazie interfejsu proxy. Jeśli chodzi o kod tworzonej przez nas aplikacji, zostało nam tylko zarejestrować naszą klasę jako MBean’a. Jest to bardzo proste.

public class Startup
{
    public static void main( String[] args ) throws Exception
    {
        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
        ObjectName name = new ObjectName("eu.javablog.mbean:type=MessageServiceMBean");

        MessageServiceMBean mbean = new MessageService();
        server.registerMBean(mbean, name);

        Thread.sleep(TimeUnit.MINUTES.toMillis(5));
    }
}

Korzystając z obiektu typu MBeanServer rejestrujemy nasz serwis pod pewną nazwą. Jest ona reprezentowana przy pomocy klasy ObjectName. Kilku słów wyjaśnienia wymaga schemat nazewnictwa. Pierwszą składową jest nazwa domeny, a drugą, która występuje po dwukropku, zbiór par klucz=wartość oddzielonych przecinkami. Prócz oczekiwanej wartości dla klucza type możemy umieścić tam dowolne inne. Na samym końcu przykładowej klasy startowej znajduje się wywołanie Thread.sleep(), tak by program nie zakończył się od razu. Potrzebna nam będzie chwila, by podłączyć się do niego za pomocą jconsole i skorzystać z dobrodziejstw, jakie daje nam nasz MBean.

Łączymy się z maszyną wirtualną

Wspomniane narzędzie jest dostarczane razem z JDK – znajdziemy je standardowo w katalogu bin. Po uruchomieniu pojawi się okienko umożliwiające wybór sesji, do której chcemy się podłączyć. Wybieramy lokalny proces odpowiadający uruchomionej aplikacji testowej. Możliwe jest oczywiście podłączenie się do zdalnej maszyny wirtualnej, jednak  może to wymagać dodatkowej konfiguracji związanej z komunikacją przez sieć.

Po podłączeniu się po lewej stronie lokalizujemy w drzewku naszego bean’a. Po rozwinięciu go widzimy dwie sekcje – atrybuty i operacje.

W pierwszej sekcji widoczne są atrybuty naszej klasy. Ich nazewnictwo jest ustalane zgodnie z konwencją Java Beans – na podstawie metod get i set. Jeśli dla danego atrybutu istnieje tylko metoda dostępowa, uznawany jest za read-only (w naszym przypadku atrybutem takim jest count). Dodatkowa metoda set pozwala na zmianę wartości atrybutu (patrz atrybut message). Wchodząc do okna poświęconego operacjom mamy możliwość ich wykonania. Operacjami są w tym przypadku metody, które nie kwalifikują się jako metody get i set atrybutów. U nas metoda taką jest print. Po kliknięciu w odpowiedni przycisk zostanie ona wywołana, czego dowodem jest wydruk w konsoli naszej aplikacji. Widzimy także, że inkrementowana została wartość atrybutu count.

Jeśli metoda print przyjmowałaby jakieś argumenty, jconsole dałby możliwość ich przekazania. Wspierane są tylko podstawowe typy, Stringi i tablice wspomnianych. Możliwe jest użycie własnych typów, ale jest to już bardziej skomplikowane, i w większości przypadków niepotrzebne.

Wcześniej wspomniałem o tym, że atrybuty dla których stworzona została metoda set są edytowalne z poziomu jconsole. W celu wykonania tej operacji klikamy w aktualną wartość atrybutu, a następnie wpisujemy nową. Poniżej widać, że wartość atrybutu message została zmieniona, co spowodowało zmianę wydruku widocznego w konsoli naszej aplikacji.

Pozostaje odpowiedzieć sobie na jeszcze jedno pytanie – co się stanie, gdy spróbujemy jako komunikat ustawić pustą wartość? W takiej sytuacji nasza aplikacja rzuca wyjątek z odpowiednim komunikatem. Jak zinterpretuje to jconsole?

Obowiązkiem klienta MBean’owego jest obsługa wszelkich wyjątków. jconsole robi to w widoczny powyżej sposób – wyświetla dialog informacyjny z komunikatem znajdującym się w rzuconym wyjątku.

Podsumowanie

W kilku bardzo prostych krokach udostępniliśmy stworzoną przez nas klasę jako MBean’a. Dzięki temu możliwe było wykorzystanie narzędzia dostarczanego razem z JDK, jconsole, do zdalnego monitoringu, a także interakcji z obiektem naszej klasy. Jest to jednak wierzchołek góry lodowej – nie poruszyłem np. tematu notyfikacji, które są trzecim z bytów występujących w świecie MBean’ów, zaraz obok atrybutów i operacji. Ale o tym traktować będzie już inny wpis.

2 myśli nt. „MBean’y

  1. NPE

    Fajne przypomnienie tematu JMX. Ja miałem kontakt z MBeanami tylko na jednych laborkach podczas studiów, a później zupełnie o nich zapomniałem. Myślę, że mogą być przydatne i wygodne w użyciu do jakiegoś prototypu PoC albo wewnętrznego toola, bo łatwo wystawić dostęp do różnych parametrów i operacji bez konieczności ubierania ich w jakiś UI

    Odpowiedz
    1. Łukasz Picur Autor wpisu

      Dokładnie, potrafią też się przydać na etapie developmentu, gdy chcemy przetestować jakiś mechanizm, który normalnie wymaga większego nakładu pracy do uruchomienia, jak np. kolejki komunikatów w skomplikowanych konfiguracjach. Wystawiamy wtedy klasę handlera jako MBean i możemy w prosty sposób sprawdzać nasz kod.

      Odpowiedz

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

Możesz użyć następujących tagów oraz atrybutów HTML-a: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>