Go to Contents Go to Java Page
J2SE 1.5 虎の穴
 
 

アプリケーション管理に威力を発揮 JMX 基礎編

 
 
Tiger

アプリケーションを管理する

 
 

ちょっと前までアプリケーションの管理をするなんて思いもよらなかったのではないですか。

サーバ系で動くアプリケーションであっても、ログを書くぐらいで管理云々なんてそれほどいわれてなかったような気がします。

しかし、時代は変わりました。The Times They Are A-Changin' なのです。

24/7 のアプリケーションは当たり前。けしって止めることのできないアプリケーションもたくさんあります。特にコンテナになるようなアプリケーション、たとえば Tomcat などは、その上で動作するコンポーネントの管理をすることが必須になります。

そこで登場するのが Java Management Extension (JMX) です。JMX は JSR-003 で標準策定された、アプリケーション管理のためのフレームワークです。

くしくも J2EE 1.4 では一足先に JMX が取り込まれています。また、Tomcat 5 も JXM を使用してサーブレットの管理を行うようになりました。

J2SE では J2EE に一歩遅れてしまいましたが、JMX をベースにした JSR-174 Monitoring and Management Specification for the Java Virtual Machine という新しい機能も取りいれられ、JMX を使用して JVM の管理も行うことができるようになっています。この機能については項をあらためて解説したいと思います。

さて、JMX がアプリケーションの管理に使えることは分かりましたが、どのように管理を行うのでしょうか。キーになるのは Managemen Bean 略して MBean と呼ばれるものです。

MBean はアプリケーションの情報を抜きだします。管理システムはこの MBean から情報を取得して、アプリケーションがどのような状態にあるのか知ることができ、また MBean をかいして管理対象のアプリケーションにアクセスすることもできます。

たとえば、電気回路をつくることを考えましょう。

あなたが、ある電気回路を作りました。増幅回路でもいいし、交流-直流変換でもなんでもかまいません。この回路がちゃんと動いているかどうかを確かめるためにはどうします?

普通はテスタやオシロスコープなどを使って電圧値や電流値を測って、それが理論的に正しい値かどうかを確かめるのではないでしょうか。このときに擬似的な入力が必要かもしれません。ある入力を与えて、出力が正しいかどうか、また出力にいたる途中の経路で正しい値になっているか確かめるわけです。

JMX の構成
図 1 電気回路のチェック

 

これを JMX に当てはめてみると、電気回路が管理するアプリケーションに相当します。そして、テスタやオシロスコープが MBean に、管理システムが人に相当すると考えられます。また、MBean は擬似的な入力を与えるために使用されることもあります。

こう見ていくと、MBean が管理対象アプリケーションと管理システムとの橋渡し役になっていることがわかります。一種のプロキシと考えられるかもしれません。

しかし、単に MBean があるだけではだめです。MBean を動作させるための仕組みが必要になります。また、複数の MBean を使用してアプリケーションを管理することもあります。

また、監視システムが MBean にアクセスするための仕組みも必要です。

このための仕組みとして MBeanServer が提供されています。管理システムが管理対象アプリケーションと同一 JVM 上で動作している場合は監視システムは直接 MBeanServer とやり取りして、MBean にアクセスすることができます。

しかし、管理システムがアプリケーションとは離れてリモートで管理を行うことも考えられています。リモートから MBeanServer に接続するために Connector もしくは Protocol Adapter という機能が提供されています。

Connector は JMX に特有のプロトコルを使用して MBeanServer と監視システムの通信を行うものです。一方の Protocol Adapter は JMX のプロトコルを他のプロトコルに変換して通信を行うものです。他のプロトコルとして HTML やネットワーク機器を管理するために使用される SNMP (Simple Network Management Protocol) などがあります。

ちょっと分かりにくくなってしまったので、ここで一度整理してみましょう。図 2 に JMX の構成を示しました。

JMX の構成
図 2 JMX の構成

図 2 からは JMX が複数のレベルから構成されていることがわかります。個人的にはレベルという言葉よりレイヤという言葉の方がしっくりくるのですが、JMX のスペックでレベルという言葉が使われているので、ここでもレベルという言葉を使用します。

管理対象アプリケーションと直接やり取りする MBean はインスツルメント・レベル。そして、MBean のコンテナと外部とのやり取りを行うためのエージェント・レベル。

そして、実際に監視をおこなうサービス・レベルです。

エージェントという言葉は聞きなれないものですが、SNMP ではよく使われるのでここでも使われているのだと思います。

実際に JMX を用いて監視を行うには、インスツメント・レベルつまり MBean を実装する必要があります。そして、必要に応じてサービス・レベルの監視システムを作成します。必要に応じてというのは、Protocol Adapter を使用した場合、Web Browser や既存の SNMP を利用した管理システムをそのまま流用できるからです。

また、JMX に対応した管理システムもあるので、これを流用してもいいと思います。

この解説では基礎編として MBean と HTML Protocol Adapter を使用したアプリケーション管理。次に応用編として Connector を使用したリモートでの管理について解説したいと思います。

 

 
 
Tiger 管理対象アプリケーション
 
 

前置きが長くなってしまいました。さっそく JMX を使ってみたいと思うのですが.... その前に、管理を行うアプリケーションがなければ話になりません。

どのようなアプリケーションがいいか考えたのですが、以前宴会の余興用に作ったアプリケーションを使うことにしました。

サンプルのソース trivia.zip

このアプリケーションはサーバと複数のクライアントから構成されます。クライアントは GUI のボタンでカウントを行い、それをサーバに送信します。サーバはクライアントのカウントを集計し、それを GUI で表示するというものです。

とるに足らないようなアプリケーションなので、Trivia アプリケーションと名づけましょう。えっ、どこかのテレビ番組に似ている? たぶん、それは他人の空似です。

トリビアアプリケーション
図 3 トリビア・アプリケーション

もう少し詳しくシステムの動作を説明しましょう。

クライアントは起動時に自分の名前と接続するサーバのアドレスを入力します。起動するとサーバに接続し、自分の名前を送信します。同じ名前を登録しても動作しますが、サーバの表示は分かりにくくなるのでやめたほうがいいと思います。

クライアントでボタンを押すと、それをサーバに送信します。サーバはクライアントごとの表示を更新し、カウントを集計して表示します。

また、サーバではリセットを行うことができます。クライアントでリセットを行うことはできません。

ゲーム中にクライアントが接続されてもすぐにはゲームに参加できず、待ち状態になります。サーバでリセットされたときに、待ち状態のクライアントもゲームに参加できるようになります。

図 3 では さくらば と ゆういち がゲームに参加しており、Sakuraba が待ち状態になっています。

クライアントとサーバで表示している画像はプロパティファイルで指定することが可能です。

図 4 に Trivia アプリケーションのクラス図を示しました。サーバのメインとなるのが TriviaServer クラスです。TriviaServer クラスは通信を行うために ServerCommunication クラスを使用します。MVC のモデルに相当するのが Counter クラスです。このクラスがクライアントごとのカウントを保持しています。そしてトータルを保持するのが Counter クラスの派生クラスの TotalCounter クラスです。

MVC の View に相当するのが ServerView インタフェースで、このインタフェースをインプリメントしているのが DefaultServerView クラスです。

DefaultServerView クラスはクライアントごとの表示に ServerPanel と CounterPanel クラスを使用します。CounterPanel クラスは Counter クラスからカウントを取得して表示を行っています。

この CounterPanel クラスはクライアントでも使用しています。

特にシーケンス図などは示しませんが、興味のある方は直接ソースをご覧ください。

 

 
 
Tiger MBean を作る
 
 

JMX を使用するには MBean がなくてははじまりません。

今まで MBean と一括りにしていましたが、実際には大別して 2 種類に分けることができます。

種類 説明
Standard MBean もっとも基本的な MBean。
MBean の定義は静的でアプリケーション動作時に変更することはできません。
Dynamic MBean 動的に定義を変更することができる MBean。
MBean は必ず javax.management.DynamicMBean インタフェースを実装するか、このインタフェースを派生させたインタフェースを実装する必要があります。
動的に定義を変更できるので、アプリケーション動作中に管理するプロパティやメソッド、イベントなどを変更することが可能になります。

DynamicMBean インターフェースをインプリメントしたクラスは通常の JavaBeans の BeanInfo クラスに相当すると考えられます。DynamicMBean インタフェースはプロパティやメソッドに関する情報を返すためのメソッドが定義されており、これらの戻り値を動的に変更することで、実行時に管理対象を変更することが可能になります。

さらに、Dynamic MBean を直接使用してもいいのですが、Dynamic MBean を簡単に使用するために次の 2 つのサブクラスがあります。

種類 説明
Open MBean 使用する型を String や Integer などに限定した Dynamic MBean。
使用できる型は javax.management.openmbean.OpenType クラスで参照することができます。
Model MBean javax.management.modelmbean.ModelMBean インタフェースを派生させたインタフェースで定義された MBean。
通常は RequiredModelMBean クラスを使用します。RequiredModelMBean クラスを使用することで、簡単に MBean ではない既存のクラスを管理することができます。

ここでは Standard MBean と Dynamic MBean の中の ModelMBean を作ってみたいと思います。

まずは StandardMBean から作っていきます。

MBean を作るにはちょっとしたお約束があります。

  • インタフェース名は管理するクラスに MBean をつけたもの。
  • プロパティにアクセスするには getter/setter を用意すること
  • public なコンストラクタを持つこと

そのほかに、約束とまではいかないのですが、管理したいクラスを直接管理するのではなく、できればアクセスするための仲介役をおきましょう。

今回のサンプルでクライアントを管理してもしょうがないので、サーバを管理したいと思います。つまり TriviaServer クラスを管理対象とします。

管理するときにどのような情報が必要になるでしょうか。サンプルなので何でもいいのですが、ここでは次の情報を管理するようにしました。MBean ではプロパティを属性として管理します。

  • トータルカウント
  • クライアント数
  • クライアントの名前の一覧
  • クライアントごとのカウント数

また、管理側でリセットもできるようにしてみたいと思います。

直接 TriviaServer クラスを管理するのではなく、仲介役のクラスを作成します。Standard MBean なので TriviaServerStandard クラスとしましょう。そして、インタフェースはその名前に MBean をつけたものなので TriviaServerStandardMBean になります。

サンプルのソース TriviaServerStandard.java
TriviaServerStandardMBean.java

TriviaServerStandardMBean インタフェースは前述した情報にアクセスするための getter/setter とリセットのためのメソッドを定義します。

package jp.gr.java_conf.skrb.game.trivia.mserver;
 
public interface TriviaServerStandardMBean {
    public int getClientSize();
    public int getTotalCount();
    public void setTotalCount(int count);
    public String[] getClients();
    public int getCount(String name);
    public void reset();
}

あまり意味はないかもしれませんが、トータルカウントだけは値のセットをできるようにしてみました。といっても、setter を定義しただけですが。

TriviaServerStandard クラスの実装にはいくつかの方法があると思います。

  1. TriviaServer クラスの派生クラスとして実装する
  2. TriviaServer オブジェクトをプロパティとしてもち、処理は TriviaServer オブジェクトに移譲するようにする
  3. リフレクションを使用して TriviaServer オブジェクトにアクセスする

3 の方法はスマートでないので、1 か 2 の方法を使いたいと思います。

もともと TriviaServer クラスは管理することを考えていなかったので、getter/setter が実装されていません。コードにはなるべく手を入れたくなかったので、今回は TriviaServer クラスの派生クラスにすることにしました。コードの変更点はアクセスしたいプロパティを private から protected に変更したことです。

コードに手を入れられればいいのですが、それが許されない場合も多くあると思います。たとえば、他人が作ったコードでクラスファイルしかなく、public な getter/setter などが一切ないような場合などです。

このようなときには 3 の方法を使うしか方法がなさそうです。でも、なるべくやりたくないですね。

さて、TriviaServerStandard クラスは次のようになりました。

package jp.gr.java_conf.skrb.game.trivia.mserver;

import jp.gr.java_conf.skrb.game.trivia.server.TriviaServer;
 
public class TriviaServerStandard extends TriviaServer
                                  implements TriviaServerStandardMBean {
 
    public TriviaServerStandard() {}
 
    public int getClientSize() {
        return clients.size();
    }
 
    public int getTotalCount() {
        return counter.getCount();
    }
 
    public void setTotalCount(int count) {
        counter.setCount(count);
    }
 
    public String[] getClients() {
        String[] names = new String[clients.size()];
 
        int i = 0;
        for (Entry entry: clients.values()) {
            names[i] = entry.getName();
            i++;
        }
 
        return names;
    }
 
    public int getCount(String name) {
        for (TriviaServer.Entry entry: clients.values()) {
            if (name.equals(entry.getName())) {
                return entry.getCounter().getCount();
            }
        }
 
        return 0;
    }
}

setter/getter をつけたものは通常のプロパティではありません。たとえば、トータルカウントは TotalCounter クラスの counter というプロパティが保持しています。

普通のプロパティでなくても全然かまいません。setter/getter でアクセスできる情報がありさえすればいいのです。

クライアントに関する情報は TriviaServer クラスの内部クラス Entry クラスが保持しています。クライアントに関する情報はそこから得るようにしています。

こうして見ると、MBean なんてたいしたことないと思いませんか。名前だけはいかついですが、実装は単純に済ませられることが多いはずです。

これだったら、JMX なんてなんか難しそうと思っていた人でも、すぐに始められそうですね。

 

 
 
Tiger

管理するには - エージェント・レベルの実装

 
 

前章では MBean つまりインスツルメント・レベルの実装を行いました。

しかし、これだけで管理を行うことはできません。つまり、エージェント・レベルとサービス・レベルがなければなりません。

エージェント・レベルでは、MBean のように実装を行う必要がなく、MBeanServer インタフェースを使用するだけです。

後はサービス・レベルの実装ですが、残念なことに Tiger にはこのレベルの実装は含まれていません。もちろん作ることはできるのですがそれは後に譲ることにして、今回は JMX のリファレンスインプリメンテーションに含まれている HTML プロトコルアダプターを使用することにしました。

このプロトコルアダプターを使えば、ブラウザーでアプリケーションの管理を行うことができるというすぐれものです。Tomcat が採用している JMX の実装である MX4J でも同じようなプロトコルアダプタを提供しています (JMX のリファレンスインプリメンテーションのものよりずっと高機能ですが)。

ブラウザで Tomcat の管理画面を見られた方も多いと思いますが、あの画面を作っているのがプロトコルアダプタなのです。

HTML プロトコルアダプタは JMX の Web ページからダウンロードできます。2004.4 時点でのバージョンは 1.2.1 です。

JMX の Web ページ
http://java.sun.com/products/JavaManagement/

JMX Downloads
http://java.sun.com/products/JavaManagement/download.html

リファレンスインプリメンテーションを解凍すると、lib ディレクトリの下に jmxri.jar と jmxtools.jar という 2 つの JAR ファイルがあるはずです。

jmxri.jar は JMX の本体で Tiger に含まれるものと同一です。HTML プロトコルアダプタは jmxtools.jar の方に入っているので、これを使用します。

準備は整ったので、エージェント・レベルの実装を行いましょう。

サンプルのソース TriviaStandardMBeanStarter.java

先ほども述べたようにこのレベルは MBeanServer インタフェースを使用します。MBeanServer インタフェースのインスタンス生成はファクトリクラス MBeanServerFactory の createMBeanServer メソッドを使用します。このメソッドは引数の有無により 2 種類存在します。引数のあるほうはドメインを指定します。

ドメインというのは MBean の区分けのために使う名前のようなものです。引数がない場合はデフォルトのドメインを使用します。

    public  void startAgent(Object mbean) {
        System.out.println("MBeanServer の起動");
        server = MBeanServerFactory.createMBeanServer();
 
        // TriviaServer の登録
        registerMBean(mbean, "MBean:name=TriviaServer");
 
        System.out.println("HTML protocol adaptor の起動");
        HtmlAdaptorServer adaptor = new HtmlAdaptorServer();
        registerMBean(adaptor, "Adaptor:name=adaptor,port=8082");
        adaptor.start();
    }

MBeanServer オブジェクトが生成できたら、次に行うのが MBean をそこに登録することです。TriviaStandardMBeanStarter クラスでは registerMBean メソッドでそれを行っています。

    public void registerMBean(Object mbean, String name) {
        try {
            ObjectName objectName = new ObjectName(name);
            server.registerMBean(mbean, objectName);
        } catch (MalformedObjectNameException ex) {
            ex.printStackTrace();
        } catch (MBeanRegistrationException ex) {
            ex.printStackTrace();
        } catch (NotCompliantMBeanException ex) {
            ex.printStackTrace();
        } catch(InstanceAlreadyExistsException ex) {
            ex.printStackTrace();
        }
    }

MBeanServer オブジェクトに MBean を登録するのは MBeanServer#registerMBean メソッドを使用します。第 1 引数が MBean オブジェクト、第 2 引数が MBean を識別するための名前になります。この名前には javax.management.ObjectName クラスを使用します。

名前はカンマで区切って複数の情報を記述することができます。必須なのはドメイン名と MBean を区別する名前です。ドメインとそれ以外の情報はコロンで区切ります。情報は名前と値を = で結びます。

TriviaServer の場合は MBean:name=TriviaServer としているので、ドメインが MBean、名前が TriviaServer となります。名前は name で記述する必要はなく、type などで記述されているものもあります。

ここでは、すでに生成した MBean を登録しましたが、MBeanServer オブジェクトから MBean を生成することも可能です (createMBean メソッド)。このときにも ObjectName クラスを使用します。その他にもすでに登録されている MBean を検索したり (queryMBean メソッド)、登録した MBean を廃棄する (removeMBean メソッド) などに ObjectName クラスが使用されます。

ところで、このサンプルでは TriviaServerStandard クラスを MBean として登録していますが、もう 1 つ MBean を登録しています。それは HTML プロトコルアダプタです。

さきほど、プロトコルアダプターはリモートから MBeanServer オブジェクトに接続するために使用するものと説明しましたが、MBeanServer オブジェクトから見れば MBean もプロトコルアダプタもコネクタもすべて単なる MBean として扱われます。

このため、HTML プロトコルアダプタも MBeanServer オブジェクトに登録するには MBeanServer#registerMBean を使用します。

これだけで、TriviaServer の状態をブラウザーで見ることができます。

それではコンパイル・実行してみます。コンパイル・実行には先ほど言及したように jmxrools.jar が必要になります。

C:\examples>javac -cp trivia.jar;jmxtools.jar jp\gr\java_conf\skrb\g
ame\trivia\mserver\TriviaStandardMBeanStarter.java
   
C:\examples>java -cp trivia.jar;jmxtools.jar jp.gr.java_conf.skrb.game.trivia.ms
erver.TriviaStandardMBeanStarter
MBeanServer の起動
HTML protocol adaptor の起動

ここまでの表示がされていれば TriviaServer が起動しているはずです。

HTMLプロトコルアダプタはデフォルトで 8082 ポートを使用しているのでブラウザで確かめてみましょう。

HTML Protocol Adapter
図 4 HTML プロトコルアダプタの出力

図 4 がデフォルトの画面です。ここでは 3 つのドメインがあり、それぞれ Adapter, JMImplementation, MBean となっています。Adapter ドメインが HTML プロトコルアダプタ、MBean ドメインに TriviaServer が含まれています。

それぞれの MBean の詳細を知るには名前の部分のリンクをたどります。name=TriviaServer の部分をクリックした結果が図 5 です。

TriviaServerStandardMBean
図 5 TriviaServerStandardMBean の管理画面

上から MBean の名前とクラス名、次が MBean の説明 (デスクリプション) が記述されています。

次の表が属性。一番下がオペレーションを表しています。ここからは TriviaServerStandardMBean が 3 つの属性と 2 つのオペレーションからなっていることが分かります。

クライアントが接続していないので ClientSize も TotalCount も 0 のままです。Clients は配列なので、この URL には直接記述されずにリンクが張られています。

オペレーションは getCount と reset になっています。getCount は getter のつもりだったのですが、引数が必要なためオペレーションに分類されてしまったようです。

ここでクライアントを接続してみましょう。「さくらば」と「ゆういち」という名前のクライアントを起動して、適当にボタンを押してみましょう。

しかし、このままではブラウザの表記は変化しません。そこで、上の方にある "reload" のボタンをクリックしてみます。これで表示が変化するはずです。もし、自動的にリロードするには "reload" の横のフィールドにリロードの周期を記入してから "reload" ボタンをクリックすると、その周期でリロードするようになります。

TriviaServerStandardMBean
図 6 TriviaServerStandardMBean の管理画面 (クライアントあり)

リロードした結果が図 6 です。Clients のリンクを表示させると、ちゃんと「さくらば」と「ゆういち」が出ているはずです。

TotalCount は RW なので書き込みもできます。TotalCount のフィールドに適当な数字を入れて "Apply" を押してみてください。すぐには TriviaServer の表記はすぐには変わらないと思いますが、クライアントでボタンを押してリペイントさせると TotalCount が変化していることがわかるはずです。

次にオペレーションも試してみましょう。

ページの最下部にある "reset" ボタンを押してみましょう。どうですか、Trivia アプリケーションの方はリセットされましたか?

ページの方も遷移しているはずです。

リセット後のページ
図 7 リセット後のページの遷移

このように JMX を使用することで、アプリケーションの状態を調べたり、オペレーションしたるすることができるのです。

 

 
 
Tiger DynamicMBean に挑戦
 
 

今までは StandardMBean を使用してアプリケーションの管理を行ってきました。

StandardMBean の次は DynamicMBean です。

DynamicMBean は StandardMBean と違って、DynamicMBean インタフェースがすでに定義してあり、管理するクラスはこれを実装するようにします。もしくはDynamicMBean インタフェースを派生させたインタフェースを定義しそれを実装するようにします。

DynamicMBean インタフェースで定義されているメソッドは 6 種類です。

  • Object getAttribute(String attribute);
  • AttributeList getAttributes(String[] attributes);
  • void setAttribute(Attribute attribute);
  • AttributeList setAttributes(AttributeList attributes);
  • Object invoke(String actionName, Object[] params, String[] signature);
  • MBeanInfo getMBeanInfo();

getAttribute/setAttribute メソッドは引数で指定された属性の値の取得と設定を行います。DynamicMBean は動作時に動的に管理したい属性の情報を得るので、それまではどの属性が管理されるのか決めることはできません。

そこで、汎用に使用できるように引数で属性を指定するようになっているわけです。

getAttributes/setAttributes メソッドは複数の属性をまとめて取得、設定するためのメソッドです。

引数や戻り値に使用されている javax.management.AttributeList クラスは javax.managemen.Attribute クラスのみを要素にもつ List インタフェースの実装クラスです。

Attribute クラスは属性の名前と値を対にして保持しているクラスです。

次の invoke メソッドもオペレーションするメソッドを動的に決定させるために、引数によってメソッドを指定し、そのメソッドをコールします。

属性と異なり、メソッドはオーバロードが許されているので、メソッドを一意に判別するために名前とメソッドの引数のシグネチャを引数にとります。メソッドの引数は invoke メソッドの第 2 引数になります。

メソッドの引数や戻り値がプリミティブ型のときは、そのラッパクラスが使用されるようです。しかし、シグネチャにはプリミティブであることを指定します。ただし、JNI のように I とか L などを使用するのではなく int, long のように記述するようです。また、オブジェクトの場合はパッケージまでフルに記述するようにします。

ただし、配列の時は通常のシグネチャのように書くようです。たとえば、String の配列であれば [Ljava.lang.String; と記述します。

最後の getMBeanInfo は MBean の情報をまとめた MBeanInfo オブジェクトを返すようにします。MBeanInfo クラスは通常の BeanInfo クラスと同じで、属性、オペレーション、コンストラクタなどの情報を保持しています。

この DynamicMBean インタフェースを実装するクラスを作成してもいいのですが、簡単化のために汎用に使えるクラスが用意されています。それは ModelMBean の RequiredModelMBean クラスです。

ようするにおおざっぱにいってしまえば、RequiredModelMBean クラスを使用すれば ModelMBean となります。

ここでも RequiredModelMBean クラスを使用して ModelMBean を作ってみます。

サンプルのソース

TriviaServerInstrument.java
TriviaModelMBeanStarter.java

RequiredModelMBean は本来ならば MBean となるべきクラスの代わりに MBean になってくれる仕組みということができます。そこで、管理されるクラスを TriviaServerInstrument クラスとしました。

このクラスは TriviaServerStandard クラスとほとんど変わりません。TriviaServerStandardMBean インタフェースを実装せずに単なるクラスとして実装されているという違いだけです。

RequiredModelMBean クラスのコンストラクタの引数は管理対象となるクラスの ModelMBeanInfo オブジェクトになります。そこで、はじめに TriviaServerInstrument クラスの ModelMBeanInfo オブジェクトを作成しましょう。

ModelMBeanInfo インタフェースは基本的には MBeanInfo クラスと同様の機能を持ちます。しかし、インタフェースなので、実際には ModelMBeanInfoSupport というクラスを使用することが多いようです。

ModelMBeanInfoSupport クラスはコンストラクタに MBean の名前、説明、属性情報、オペレーション情報、コンストラクタの情報、また MBean が扱うイベントに関しても指定を行います。イベントに関しては後ほど説明するとして、その他の情報はそれぞれ専用のクラスを使用して記述します。

ModelMBeanAttributeInfo クラス 属性
ModelMBeanConstructorInfo クラス コンストラクタ
ModelMBeanOperationInfo クラス オペレーション
ModelMBeanNotificationInfo クラス イベント

さて、TriviaModelMBeanStarter クラスではこの部分は createMBeanInfo メソッドで行っています。

はじめは属性情報の生成です。属性は Descriptor クラスを使用して、属性の説明を付加します。ModelMBeanAttributeInfo クラスのコンストラクタはいくつかあるので、ここではその一例を示しておきます。

    private ModelMBeanInfo createMBeanInfo() {
        Descriptor description = new DescriptorSupport(
                          new String[] {"name=TotalCount",
                                        descriptorType=attribute",
                                        "default=0",
                                        "displayName=Total Count",
                                        "getMethod=getTotalCount",
                                        "setMethod=setTotalCount"});

        ModelMBeanAttributeInfo [] attributes = new ModelMBeanAttributeInfo[1];
        attributes[0] = new ModelMBeanAttributeInfo("TotalCount", "int",
                                                    "Total Count",  
                                                    true, true, false,
                                                    description);

次がコンストラクタ。

        ModelMBeanConstructorInfo [] constructors 
                        = new ModelMBeanConstructorInfo[1];
        constructors[0] = new ModelMBeanConstructorInfo("TriviaServerModel", 
                                         "constructor for Trivia Server",
                                         null);

同様にオペレーション。引数がある場合には MBeanParameterInfo クラスを使用して記述します。

ModelMBeanOperationInfo クラスのコンストラクタの最後の引数はどのような操作であるかを記述するためで、ACTION, INFO, ACTION_INFO, UNKNOWN の 4 種類の値となります。ACTION がなんらかの処理を行うもの、情報を取得するようなものは INFO。ACTION と INFO の両方を兼ね備えるものが ACTION_INFO、不明の場合は UNKNOWN にします。

        ModelMBeanOperationInfo [] operations = new ModelMBeanOperationInfo[6];
        operations[0] = new ModelMBeanOperationInfo("getClientSize", 
                                                    "クライアント数",
                                                    null, "int",
                                                    ModelMBeanOperationInfo.INFO);
                
        operations[1] = new ModelMBeanOperationInfo("getTotalCount",
                                                    "トータル カウント",
                                                    null, "void",
                                                    ModelMBeanOperationInfo.INFO);
         
        MBeanParameterInfo [] parameters = new MBeanParameterInfo[1];
        parameters[0] =  new MBeanParameterInfo("count", "int", "設定値");
        operations[2] = new ModelMBeanOperationInfo("setTotalCount",
                                                    "Set Total Count",
                                                    parameters, "void", 
                                                    ModelMBeanOperationInfo.ACTION);
         
        operations[3] = new ModelMBeanOperationInfo("getClients",
                                                    "クライアントの一覧",
                                                    null, "[Ljava.lang.String;",
                                                    ModelMBeanOperationInfo.INFO);
 
        parameters = new MBeanParameterInfo[1];
        parameters[0] =  new MBeanParameterInfo("name", "java.lang.String",
                                                "クライアント名");
        operations[4] = new ModelMBeanOperationInfo("getCount",
                                                    "クライアントのカウント",
                                                    parameters, "int",
                                                    ModelMBeanOperationInfo.ACTION);
 
        operations[5] = new ModelMBeanOperationInfo("reset", "リセット",
                                                    null, "void", 
                                                    ModelMBeanOperationInfo.ACTION);

これらの諸情報をまとめて ModelMBeanInfoSupport オブジェクトを生成します。

        return new ModelMBeanInfoSupport(
               "jp.gr.java_conf.skrb.game.trivia.server.TriviaServerModel",
               "ModelMBean example", 
               attributes, constructors, operations, null);

そしてこのオブジェクトを引数にして RequiredModelMBean オブジェクトを生成します。RequiredModelMBean オブジェクトはそのままでは管理するオブジェクトがどれか分からないので、setManagedResource メソッドを使用して管理するオブジェクトを設定します。

最後にMBeanServer オブジェクトに登録します。

        try {
            RequiredModelMBean modelmbean 
                              = new RequiredModelMBean(createMBeanInfo());
            modelmbean.setManagedResource(managedObject, "ObjectReference");
            registerMBean(modelmbean, TRIVIA_MBEAN_NAME);
        } catch (MBeanException ex) {
            ex.printStackTrace();
            System.exit(1);
        } catch(InstanceNotFoundException ex) {
            ex.printStackTrace();
            System.exit(1);
        } catch(InvalidTargetObjectTypeException ex) {
            ex.printStackTrace();
            System.exit(1);
        }

後は TriviaStandardMBeanStarter クラスと同じように HTML プロトコルアダプターも登録して起動させます。

さて、ブラウザで見てみましょう。

ModelMBean
図 8 ModelMBean

StandardMBean の時と基本的な表示は同じですが、属性やオペレーションの数が異なっています。これらは ModelMBeanInfoSupport オブジェクトを生成したときに指定したものになっているはずです。

HTML プロトコルアダプタで見る分には MBean が StandardMBean でも DynamicMBean でも ModelMBean でも OpenMBean でもなんら変わりはありません。統一した手法を用いて MBean にアクセスすることができます。

 

 
 
Tiger 便利なイベント - Notification
 
 

基本的な MBean の使用方法は分かったので、一通りの管理を行えるようになりました。これでおしまい.... とはいかないのです。

今の管理方法だと、管理者が管理アプリケーションを実際に動かして属性値を取得したりすることはできます。しかし、つねに管理者がパソコンの前に座っているわけではありません。

ちょっと席を立っている間にとんでもない障害が起こっていたかもしれません。かといって、ずっとパソコンの前にいるわけにもいかないし。

これまで説明した管理のための手法はすべてサービス側から属性の取得といったような何らかのアクションを起こしていました。いわゆるプル方式になるわけです。

でも、なにか問題がおきそうなときは、エージェント側からサービス側に直接情報を送ってきてほしいのです。こちらはプッシュ方式ですね。

Push と Pull
図 9 Push と Pull

プッシュ方式は Java の言葉でいえばイベントを使った情報配信ということになります。

JMX でも同じようなことを行うための機構としてノティフィケーションというものがあります。具体的には javax.management.Notification クラスと javax.management.NotificationListener インタフェースを使用します。

Notification クラスは java.util.EventObject クラスの派生クラスなので、名前こそ違いますが実質的にはイベントとして扱うことができます。また、NotificationListener インタフェースも java.util.EventListener インタフェースの派生インタフェースなのでイベントとノティフィケーションの違いは名前だけということになります。

ノティフィケーションを発生させるのは MBean で、サービス・レベルのアプリケーションは NotificationListener インタフェースを実装して Notification オブジェクトを受け取ります。

ノティフィケーションを発生することのできる MBean は javax.management.NotificationEmitter インタフェース (もしくは javax.management.NotificationBroadcaster インタフェース) を実装する必要があります。

というのも、サービス側ではリスナ登録も MBeanServer インタフェースを介して行わなければならないため、リスナ登録を統一的に扱う必要があるからです。

また、NotificationEmitter インタフェースを実装した javax.management.NotificationBroadcasterSupport クラスというクラスも提供されています。このクラスを使えば簡単にノティフィケーションを扱うことができます。

それではサンプルを作ってみたいのですが、残念ながら HTML プロトコルアダプタではノティフィケーションを扱うことはできません。http はプル型のプロトコルなのでいたしかたないところでしょう。

しかたがないので、TriviaServer と同一 VM 上で MBeanServer オブジェクトを介してノティフィケーションを扱ってみます。

サンプルのソース TriviaServerNotification.java
TriviaServerNotificationMBean.java
TriviaNotificationMBeanStarter.java

ノティフィケーションを扱うために TriviaServerNotification クラスは NotificationBroadcasterSupport クラスの派生クラスとしました。

TriviaServerNotificationMBean インタフェースは TriviaServerStandardMBean インタフェースと何も変わらないのですが、名前の付け方が決まっているのでしかたありません。

TriviaServerNotification クラスはプロパティとして ModelMBean の時に使用した TriviaServerInstrument クラスを保持しており、MBean として定義されたメソッドはすべて TriviaServerInstrument クラスに移譲するようにしました。

たとえば、こんな感じになっています。

public class TriviaServerNotification extends NotificationBroadcasterSupport
                                    implements TriviaServerNotificationMBean {
    private TriviaServerInstrument instrument;
 
    public TriviaServerNotification() {
        instrument = new TriviaServerInstrument();
    }
 
    public int getClientSize() {
        return instrument.getClientSize();
    }
 
    public int getTotalCount() {
        return instrument.getTotalCount();
    }

ノティフィケーションを扱うためにはリスナ登録の処理を行わなくてはなりませんが、すべて NotificationBroadcasterSupport クラスでやってくれます。そのため MBean で記述しなくてはならないのは実際にノティフィケーションを発生させる部分です。

今回はサービス側からリセットされたらノティフィケーションを発生させるようにしました。

イベントのように Notification クラスを派生させて独自のノティフィケーションを作成してもいいのですが、あらかじめいくつかノティフィケーションが定義されているのでそれを使用してもかまいません。

よく使用されるのが AttributeChangeNotification クラスと TimerNotification クラスだと思います。

AttributeChangeNotification クラスは属性の値が変化したときに使用するノティフィケーションです。TimerNotification クラスは javax.management.timer.Timer クラスが定期的に発生させるノティフィケーションです。

これ以外に Monitor クラスというノティフィケーションもあるのですが、これは後回しにします。

今回は単純に AttributeChangeNotification クラスを使用しました。

    public void reset() {
        AttributeChangeNotification notification =
            new AttributeChangeNotification(this,
                                            0,
                                            0,
                                            "Trivia Server Reset",
                                            "TotalCount",
                                            "int",
                                            new Integer(instrument.getTotalCount()),
                                            new Integer(0));
        instrument.reset();

        sendNotification(notification);
    }

引数が多いのですが、たいしたことはありません。第 1 引数はノティフィケーションを発生させたオブジェクト、次からシーケンシャル番号、時間、ノティフィケーションの説明、属性の名前、型、変化する前の属性の値、そして最後が属性の新しい値です。

Notification オブジェクトが生成できたらあとは NotificationBroadcasterSupport クラスの sendNotification メソッドを使用してノティフィケーションをリスナに送信します。

もう 1 つすることはノティフィケーションの説明を記述することです。これは NotificationEmitter インタフェースの getNotificationInfo メソッドで行います。 NotificationBroadcasterSupport クラスもこのメソッドを実装していますが、戻り値は空です。

ノティフィケーションの説明は MBeanNotificationInfo クラスを使用して記述します。

    public MBeanNotificationInfo[] getNotificationInfo() {
        return new MBeanNotificationInfo[] {
            new MBeanNotificationInfo(
                  new String[] { AttributeChangeNotification.ATTRIBUTE_CHANGE },
                  AttributeChangeNotification.class.getName(),
                  "This notification is emitted when the reset() method is called.")
        };
    }

第 1 引数の文字列の配列はノティフィケーションのタイプを入れます。 第 2 引数がノティフィケーションの名前、最後がノティフィケーションの説明になります。

MBean 側の設定はできたので、MBeanServer インタフェースにリスナ登録してみましょう。リスナ登録は TriviaNotificationMBeanStarter#initNotificationListener メソッドで行っています。

    private void initNotificationListener(String name) {
        NotificationListener listener = new NotificationListener() {
            public void handleNotification(Notification notification,
                                           Object handback) {
                System.out.println("Received notification: " + notification);
                AttributeChangeNotification attrNotification = 
                            (AttributeChangeNotification)notification;
                System.out.println("Old Value: " + attrNotification.getOldValue());
                System.out.println("New Value: " + attrNotification.getNewValue());
            }
        };
 
        try {
            server.addNotificationListener(new ObjectName(name),
                                           listener, null, null);
        } catch (MalformedObjectNameException ex) {
            ex.printStackTrace();
        } catch(InstanceNotFoundException ex) {
            ex.printStackTrace();
        }
    }

NotificationListener インタフェースで定義されているメソッドは handleNotification メソッドです。これを実装する無名クラスを使用してリスナ登録しています。リスナは単純にノティフィケーションを出力しているだけです。

MBeanServer オブジェクトへのリスナ登録には addNotificationListener メソッドを使用します。

このメソッドは第 1 引数が MBean の ObjectName オブジェクト、第 2 がリスナになります。次がフィルタ、最後がハンドバックになります。

フィルタというのはその名のとおり、ノティフィケーションを受け取るかどうかのフィルタになります。フィルタを設定しないときには null にしておきます。

フィルタの詳細は省略。そんなに難しいものではなく Notification オブジェクトが引数となる isNotificationEnabled メソッドを記述するだけです。NotificationFilterSupport クラスもあるので、これを使ってもいいかもしれません。

最後のハンドバッグはノティフィケーションとともにリスナに送信されるオブジェクトです。必要なければ null にしておきます。

これで準備は整いました。さっそく実行です。ブラウザからリセットしてみてください。

C:\examples>java -cp trivia.jar;jmxtools.jar jp.gr.java_conf.skrb.game.trivia.ms
erver.TriviaNotificationMBeanStarter
MBeanServer の起動
HTML protocol adaptor の起動

Received notification: javax.management.AttributeChangeNotification[source=MBean
:name=TriviaServer][type=jmx.attribute.change][message=Trivia Server Reset]
Old Value: 18
New Value: 0

ちゃんとノティフィケーションを受けとることができました。

これで pull でも push でも MBean にアクセスすることが可能になります。

ここではローカルでノティフィケーションを扱いましたが、もちろんコネクタを使えばリモートでも使うことができます。詳細はリモート編で。

 

 
 
Tiger 便利な MBean たち
 
 

今までは自分で MBean を作成してきましたが、はじめから JMX で提供されている MBean もあります。

大別して 2 種類あり、それぞれモニタとタイマといいます。

タイマはノティフィケーションのところでちょっと出てきましたが、定期的にノティフィケーションを発生させるような MBean です。定期的に管理アプリケーションに対して情報を送信するようなときに使用できます。

もう 1 つのモニタには 3 種類の MBean が定義されています。すべて javax.management.monitor.Monitor クラスの派生クラスです。

クラス 説明
javax.management.monitor.CounterMonitor 値が減少しないカウンタの監視
オフセット値を超えたらノティフィケーションが発生する
javax.management.monitor.GaugeMonitor 数値属性の監視
属性値 に対し最大値と最小値を設定でき、それを超えたらノティフィケーションが発生する
javax.management.monitor.StringMonitor 文字列の監視
文字列が変更されたらノティフィケーションが発生する

CounterMonitor
図 10 CounterMonitor
GaugeMonitor
図 11 GaugeMonitor

CounterMonitor クラスは Web サイトへの訪問者数のカウントなどに、GaugeMonitor は同時アクセス数などに使えそうです。

使い方的にはそれほど大差ないので、ここでは CounterMonitor だけを使ってみましょう。

サンプルのソース TriviaMonitorStarter.java

モニタの設定は initMonitor メソッドで行っています。

    private void initMonitor() {
        counterMonitor = new CounterMonitor();
 
        try {
            registerMBean(counterMonitor, MONITOR_NAME);
 
            ObjectName objectName = new ObjectName(TRIVIA_MBEAN_NAME);
 
            counterMonitor.addObservedObject(objectName);
            counterMonitor.setObservedAttribute("TotalCount");
            counterMonitor.setNotify(true);
            counterMonitor.setInitThreshold(20);
            counterMonitor.setOffset(20);
            counterMonitor.setGranularityPeriod(100L);
 
            counterMonitor.start();
        } catch (MalformedObjectNameException ex) {
            ex.printStackTrace();
        }
    }

CounterMonitor クラスのカウンタは何かの値と関連づけなければ意味がありません。モニタが関連づけられるのは MBean のみで、関連づけには Monitor#addObservedObject メソッドを使用します。

関連付けた MBean で、カウンタが参照する属性は setObservedAttribute メソッドを使用して指定します。上の例では TotalCount を指定しています。

setNotify メソッドでノティフィケーションを発生できるようにし、setInitThreshold メソッドと setOffset メソッドでしきい値とオフセットを設定します。

setGranularityPeriod メソッドは参照している属性をどの程度の周期で見に行くかを設定するためのメソッドです。単位はミリ秒なので、上の例では 100ms ごとに TotalCount 属性を参照していることが分かります。

実際に、モニタを開始するのが start メソッドです。これで、しきい値やオフセットを超えたときにノティフィケーションが発生します。

モニタが発生させるノティフィケーションは javax.management.monitor.MonitorNotification です。MonitorNotification クラスにはいくつか情報取得のためのメソッドが用意されていますが、解説するより実際にどのような値が取得できるかを見ていただいたほうが早いと思います。リスナ登録の部分は次のようになっています。

        NotificationListener listener = new NotificationListener() {
                public void handleNotification(Notification notification,
                                               Object handback) {
                    System.out.println("Received notification: " + notification);
 
                    MonitorNotification mNotification
                            = (MonitorNotification)notification;
                    System.out.println("MBean: "
                                       + mNotification.getObservedObject());
                    System.out.println("Attribute: "
                                       + mNotification.getObservedAttribute());
                    System.out.println("Trigger: "
                                       + mNotification.getTrigger());
                    System.out.println("Gauge: "
                                       + mNotification.getDerivedGauge());
                    System.out.println();
                }
            };
            
        try {
            server.addNotificationListener(new ObjectName(MONITOR_NAME),
                                           listener, null, null);
        } catch (MalformedObjectNameException ex) {
            ex.printStackTrace();
        } catch(InstanceNotFoundException ex) {
            ex.printStackTrace();
        }

これを実行してみると

C:\examples>java -cp trivia.jar;jmxtools.jar jp.gr.java_conf.skrb.game.trivia.ms
erver.TriviaMonitorStarter
MBeanServer の起動
HTML protocol adaptor の起動
Received notification: javax.management.monitor.MonitorNotification[source=MBean
:name=CounterMonitor][type=jmx.monitor.counter.threshold][message=]
MBean: MBean:name=TriviaServer
Attribute: TotalCount
Trigger: 20
Gauge: 20

Received notification: javax.management.monitor.MonitorNotification[source=MBean
:name=CounterMonitor][type=jmx.monitor.counter.threshold][message=]
MBean: MBean:name=TriviaServer
Attribute: TotalCount
Trigger: 40
Gauge: 40

モニタを使用することで、自分でノティフィケーションのためのコードを書かずに、属性を監視しノティフィケーションを発生することが可能になります。

 

 
 
Tiger おわりに
 
 

ソフトウェアの管理は時代の流れなのでしょう。その重要性はどんどん高くなってきていると思います。

それに対する Java の答えが JMX です。JMX は JSR-003 という番号を見ても分かるように、ずいぶん早い時から活動を開始していました。それが最近になってやっと花を開いたという感じでしょうか。

Bea の WebLogic のようにずいぶん前から JMX を採用しているものあり、今後も Application Server などに限らずいろいろなアプリケーションで使われそうな予感がします。

J2EE 1.4 は一足早く JMX を採用しましたが、Tiger には J2EE にはない特徴があります。それは、JVM の情報を MBean でアクセスできるということです。メモリやスレッドの情報を MBean を介して取得することができるのです。それに関して項をあらためて解説することにしましょう。

 

今回使用したサンプルはここからダウンロードできます。

 

参考

(May. 2004)

 
 
Go to Contents Go to Java Page