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

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

 
 
Tiger

JMX を離れたところから使用する

 
 

基礎編で JMX の基本的なところを解説したので、今回はもう 1 つの JMX の JSR に関して解説したいと思います。

基礎編の時は主に HTML プロトコルアダプタを使用して管理をすることを想定していました。しかし、プロトコルアダプタではなく、コネクタに関してはほとんど触れていません。今回はこのコネクタに焦点を当ててみたいと思います。コネクタを使うということはリモートから MBeanServer にアクセスすることになりますが、リモートから JMX を使用するための標準を策定しているのは JSR-160 JMX Remote です。

JMX Remote では基本的に RMI を使用したコネクタに関して標準策定されています。Tiger でも RMI を利用したコネクタが使用できるので、この解説でも RMI のコネクタを使って進めていきたいと思います。とはいっても RMI を意識することはほとんどありません。

 

 
 
Tiger エージェント・レベルの実装
 
 

今回も基礎編と同様に Trivia アプリケーションを管理していきます。MBean も基礎編と同じものを使用します。

サンプルのソース TriviaMBeanStarter.java

前回と異なるのは HTML プロトコルアダプタを使用しないで、コネクタを使用するということです。とはいうもののコネクタを MBeanServer に登録するのはたいしたことではありません。

    public static final String TRIVIA_SERVER_ADDRESS
                                 = "service:jmx:rmi:///jndi/rmi://localhost/trivia";

    MBeanServer server;
    JMXConnectorServer connector;

    public TriviaMBeanStarter() {
        System.out.println("MBeanServer の起動");
        server = MBeanServerFactory.createMBeanServer();
 
        startAgent();
 
        try {
            System.out.println("Connector の起動");
            JMXServiceURL url = new JMXServiceURL(TRIVIA_SERVER_ADDRESS);
            connector = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server);
            connector.start();
        } catch (MalformedURLException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

リモートから JMX を使用するには何らかのアドレスが必要となります。それをあらわすのが JMXServiceURL クラスです。JMXServiceURL であらわす URL は service:jmx:[使用するプロトコル]:[アドレス] のように表記します。

このサンプルでは RMI を使用するので service:jmx の後には rmi と続きます。上記の例のように書くのがお約束のようです。

JMXServiceURL オブジェクトが生成できたら、後は JMXConnectorServerFactory クラスの newJMXConnectorServer メソッドを使用してサーバ側のコネクタである JMXConnectoServer オブジェクトを生成します。

newJMXConnectorServer メソッドの第 1 引数が URL、第 2 引数がコネクタに引きわたすための属性をマップにしたもの、第 3 引数が MBeanServer オブジェクトになります。コネクタに渡す属性はコネクタによって異なるので、コネクタのドキュメントを参照してください。属性がない場合は上記のように null にします。

後は JMXConnectorServer オブジェクトに対して start メソッドをコールするだけです。

これだけで、エージェント・レベルの実装はおしまいです。あっけないですね。

RMI を使用しているので、実行するときにはお約束の rmiregistry を起動させてから行ってください。

 

 
 
Tiger サービス・レベルの実装
 
 

JMX Remote でメインとなるのは実際に管理を行う管理アプリケーションになります。

とはいうものの MBeanServer クラスをプロキシを介して使用するようなイメージなので、MBeanServer クラスの使い方が分かっていればそれほど大変ではありません。

はじめは登録されている MBean の情報を表示することからやってみましょう。

サンプルのソース TriviaMBeanRemoteTest1.java

まず第 1 に行うことはリモートのコネクタに接続することです。

    public static final String TRIVIA_SERVER_ADDRESS
                                  = "service:jmx:rmi:///jndi/rmi://localhost/trivia";
 
    private MBeanServerConnection connection;
 
    public TriviaMBeanRemoteTest1() {
        try {
            JMXServiceURL url = new JMXServiceURL(TRIVIA_SERVER_ADDRESS);
            JMXConnector connector = JMXConnectorFactory.connect(url, null);
            connection = connector.getMBeanServerConnection();
  
            queryMBeans();
        } catch (MalformedURLException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

JMXServiceURL クラスを使用してサーバの URL を指定するのは先ほどと同じです。その後に JMXConnectoFactory クラスを使用して JMXConnector オブジェクトを生成します。

JMXConnectorFactory#connect メソッドの第 1 引数は URL、第 2 引数はコネクタに引き渡す属性です。

JMXConnector クラスがサーバ側の JMXConenctorServer クラスと対になるクラスです。JMXConnector オブジェクトは単にサーバとリモートを結びつけるだけなので、実際のオペレーションは JMXConnector オブジェクトから MBeanServerConnection オブジェクトを取り出して行います。

MBeanServerConnection はインタフェースで、MBeanServer インタフェースの親インタフェースになります。したがって、queryMBean メソッドや addNotificationListener メソッドなど MBeanServer インタフェースで使っていた大半のメソッドをそのまま使用することができます。

ですから、ここから後は JMX Remote で策定されたものではなく、JMX の基本的な使い方になります。ただ、基礎編ではあまり MBeanServer インタフェースについては解説しなかったので、ここでそれについて説明しようと思います。

 

 
 
Tiger MBean の情報を取得する
 
 

MBeanServerConnection オブジェクトを取得できたので、登録されている MBean を調べてみましょう。これは queryMBean メソッドで行っています。

    private void queryMBeans() {
        try {
            Set mbeans = connection.queryMBeans(null, null);
 
            for (Object obj: mbeans) {
                ObjectInstance objInstance = (ObjectInstance)obj;
                System.out.println("Name: " + objInstance.getObjectName());
                System.out.println("Class: " + objInstance.getClassName());

                showMBeanInfo(objInstance.getObjectName());
            }
            System.out.println();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

登録した MBean を検索するには MBeanServerConnection#queryMBeans メソッドを使用します。第 1 引数が ObjectName オブジェクトで、第 2 引数が QueryExp オブジェクトです。QueryExp はインタフェースでフィルターの役目をします。

通常は第 1 引数に MBean の名前を指定するのですが、上のコードのように null にすると登録されているすべての MBean を検索してくれます。

戻り値は Set オブジェクトになり、第 1 引数で指定された名前の MBean の集合が入ります。この要素は ObjectInstance オブジェクトになります。

それにしてもせっかく Tiger で使うのですから、はじめから Set<ObjectInstance> にしてくれればどんなに楽なことかと思いますね。

ObjectInstance クラスは MBean の名前 (ObjectName オブジェクト) とそのクラス名を保持しています。それぞれ getObjectName メソッド、getClassName メソッドで取得することができるので、上の例ではそれを出力しています。

もうすこし MBean の詳しい情報を見てみましょう。MBean の情報は MBeanInfo クラスで保持しています。MBeanInfo オブジェクトを取得するには MBeanServerConnection#getMBeanInfo メソッドを使用します。引数は ObjectName オブジェクトです。

    private void showMBeanInfo(ObjectName name) {
        try {
            MBeanInfo info = connection.getMBeanInfo(name);
            String description = info.getDescription();
            System.out.println("Description: " + description);
 
            showMBeanAttributeInfo(info);
            showMBeanConstructorInfo(info);
            showMBeanOperationInfo(info);
            showMBeanNotificationInfo(info);
 
        } catch (IntrospectionException ex) {
            ex.printStackTrace();
        } catch (ReflectionException ex) {
            ex.printStackTrace();
        } catch (InstanceNotFoundException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

基礎編で Model MBean を作成するときに ModelMBeanInfoSupport クラスを使用して MBean の情報を記述したのを覚えておられるでしょうか。MBeanInfo クラスはその ModelMBeanInfoSupport クラスの親クラスになります。

Model MBean を作成するときに属性、コンストラクタ、オペレーションのそれぞれの情報をまとめて ModelMBeanInfoSupport オブジェクトを作成しました。MBeanInfo オブジェクトも同じように属性、コンストラクタ、オペレーションの情報を保持しています。また、Model MBean のときには扱わなかったのですが、ノティフィケーションの情報も同じように保持しています。

それぞれの情報も表示してみましょう。まずは属性からです。

属性の情報は MBeanAttributeInfo クラスで表します。MBeanAttributeInfo オブジェクトを取得するためには MBeanInfo クラスの getAttributes メソッドを使用します。属性がない場合は空の配列が帰ってきます。

    private void showMBeanAttributeInfo(MBeanInfo info) {
        MBeanAttributeInfo[] attributes = info.getAttributes();
 
        if (attributes.length > 0) {
            System.out.println("Attributes:");
 
            for (MBeanAttributeInfo attribute: attributes) {
                System.out.println("    Name: " + attribute.getName());
                System.out.println("    Description: " + attribute.getDescription());
                System.out.println("    Type: " + attribute.getType());
                if (attribute.isReadable()) {
                    if (attribute.isWritable()) {
                        System.out.println("    Access: RW");
                    } else {
                        System.out.println("    Access: RO");
                    }
                } else {
                    if (attribute.isWritable()) {
                        System.out.println("    Access: WO");
                    }
                }
                System.out.println();
            }
        }
    }

属性を表す MBeanAttributeInfo クラスやオペレーションを表す MBeanOperationInfo クラスなどはすべて MBeanFeatureInfo クラスの派生クラスとなっています。

MBeanFeatureInfo クラスでは getName メソッドと getDescription メソッドが定義されているので、この 2 つのメソッドは MBeanXXXXXInfo クラスではどれでも使えます。

その他の情報として、属性の型 (getType メソッド)、getter メソッドがあるか setter メソッドがあるかどうか (isReadable メソッド、isWritable メソッド) などの情報を取得することができます。

また、属性の場合、型が boolean や Boolean クラスであると getter メソッドが isXXXX となることがあります。それを調べるために isIs メソッドも定義されています。

次はコンストラクタです。

    private void showMBeanConstructorInfo(MBeanInfo info) {
        MBeanConstructorInfo[] constructors = info.getConstructors();
 
        if (constructors.length > 0) {
            System.out.println("Constructors:");
 
            for (MBeanConstructorInfo constructor: constructors) {
                System.out.println("    Name: " + constructor.getName());
                System.out.println("    Description: " + constructor.getDescription());
                System.out.println("    Signature: ");
                MBeanParameterInfo[] arguments = constructor.getSignature();
                for (MBeanParameterInfo argument: arguments) {
                    System.out.println("        Name: " + argument.getName()
                                       + " Type: " + argument.getType());
                }
                System.out.println();
            }
        }
    }

コンストラクタは MBeanConstructorInfo クラスで表されます。

属性と違うのは引数があるということです。引数は MBeanConstructorInfo#getSignature メソッドで取得でき、戻り値は MBeanParameterInfo クラスの配列となります。

MBeanParameterInfo クラスは型が保持されているので、それを出力しました。

コンストラクタとほとんど同じなのがオペレーションの情報です。

    private void showMBeanOperationInfo(MBeanInfo info) {
        MBeanOperationInfo[] operations = info.getOperations();
 
        if (operations.length > 0) {
            System.out.println("Operations:");
 
            for (MBeanOperationInfo operation: operations) {
                System.out.println("    Name: " + operation.getName());
                System.out.println("    Description: " + operation.getDescription());
                System.out.println("    Signature: ");
                MBeanParameterInfo[] arguments = operation.getSignature();
                for (MBeanParameterInfo argument: arguments) {
                    System.out.println("        Name: " + argument.getName()
                                       + " Type: " + argument.getType());
                }
                System.out.println("    Return Type: " + operation.getReturnType());
                System.out.println();
            }
        }
    }

オペレーションの情報は MBeanOperationInfo クラスに保持されますが、MBeanConstructorInfo クラスと違うのは戻り値があるということです。戻り値は MBeanOperationInfo#getReturnType メソッドで取得できます。戻り値は文字列です。

最後にノティフィケーションです。

    private void showMBeanNotificationInfo(MBeanInfo info) {
        MBeanNotificationInfo[] notifications = info.getNotifications();
 
        if (notifications.length > 0) {
            System.out.println("Notifications:");
 
            for (MBeanNotificationInfo notification: notifications) {
                System.out.println("    Name: " + notification.getName());
                System.out.println("    Description: " + notification.getDescription());
                System.out.println("    NotifType: ");
                String[] types = notification.getNotifTypes();
                for (String type: types) {
                    System.out.println("        Type: " + type);
                }
                System.out.println();
            }
        }
    }

名前と説明以外にノティフィケーションのタイプを取得することができます (MBeanNotificationInfo#getNotifTypes メソッド)。これは Notification クラスのコンストラクタの第 1 引数で指定したものになります。

さっそく実行してみましょう。

実行するにはまず rmiregistry を起動し、その後 TriviaMBeanStarter を実行します。Trivia アプリケーションのクライアントは今の段階では必要ありません。もちろん、実行してもかまいませんが。

最後に TriviaMBeanRemoteTest1 を実行します。

そのままだと出力結果がかなり長くなってしまうので、TriviaServerStandardMBean に関するところだけを示しておきます。

C:\examples>java -cp trivia.jar jp.gr.java_conf.skrb.game.trivia.mclient.TriviaM
BeanRemoteTest1

   ... 省略 ...

Name: MBean:name=TriviaStandard
Class: jp.gr.java_conf.skrb.game.trivia.mserver.TriviaServerStandard
Description: Information on the management interface of the MBean
Attributes:
    Name: ClientSize
    Description: Attribute exposed for management
    Type: int
    Access: RO

    Name: TotalCount
    Description: Attribute exposed for management
    Type: int
    Access: RW

    Name: Clients
    Description: Attribute exposed for management
    Type: [Ljava.lang.String;
    Access: RO

Constructors:
    Name: jp.gr.java_conf.skrb.game.trivia.mserver.TriviaServerStandard
    Description: Public constructor of the MBean
    Signature:

Operations:
    Name: reset
    Description: Operation exposed for management
    Signature:
    Return Type: void

    Name: getCount
    Description: Operation exposed for management
    Signature:
        Name: p1 Type: java.lang.String
    Return Type: int


C:\examples>

オペレーションの引数の名前は p1, p2, ... のようになってしまうようですね。実害はないのでこれはこれでかまわないと思います。

TriiaServerStandardMBean はノティフィケーションを使用しないので、ノティフィケーションの部分だけ CounterMonitor の情報を示しましょう。

Notifications:
    Name: javax.management.monitor.MonitorNotification
    Description: Notifications sent by the CounterMonitor MBean
    NotifType:
        Type: jmx.monitor.error.runtime
        Type: jmx.monitor.error.mbean
        Type: jmx.monitor.error.attribute
        Type: jmx.monitor.error.type
        Type: jmx.monitor.error.threshold
        Type: jmx.monitor.counter.threshold

CounterMonitor クラスが発生させるノティフィケーションは 1 種類ではなく 6 種類もあることが分かります。

さて、MBean の様子が分かったので、次は実際に MBean にアクセスしてみましょう。

 

 
 
Tiger MBean の属性にアクセスする
 
 

それでは属性からいきましょう。属性の情報は前章の方法で取得できるので、実際の属性の値を取得してみます。

これを行うサンプルが TriviaMBeanRemoteTest2 です。

サンプルのソース TriviaMBeanRemoteTest2.java

属性の値を取得するのもやはり MBeanServerConnection インタフェースのメソッドで行います。複数の属性値をまとめて取得する getAttributes メソッドと、単一の属性を取得する getAttribute メソッドの 2 種類の方法があります。

はじめに getAttributes メソッドの方から見ていきます。

    private void showAttributes() {
        try {
            // すべての属性の名前を取り出す
            MBeanInfo info = connection.getMBeanInfo(triviaName);
            MBeanAttributeInfo[] attributes = info.getAttributes();
            String[] attributeNames = new String[attributes.length];
       
            for (int i = 0; i < attributes.length; i++) {
                attributeNames[i] = attributes[i].getName();
            }
             
            // 属性値の取得
            AttributeList attributeList
                             = connection.getAttributes(triviaName, attributeNames);
 
            for (int i = 0; i < attributeList.size(); i++) {
                Attribute attribute = (Attribute)attributeList.get(i);
 
                System.out.print(attribute.getName() + " = ");
  
                if (attributes[i].getType().startsWith("[")) {
                    // 値が配列の場合
                    Object[] values = (Object[])attribute.getValue();
 
                    System.out.print("[");
                    for (int j = 0; j < values.length - 1 ; j++) {
                        System.out.print(values[j] + ", ");
                    }
                    System.out.println(values[values.length - 1] + "]");
                } else {
                    System.out.println(attribute.getValue());
                }
            }
        } catch (IntrospectionException ex) {
            ex.printStackTrace();
        } catch (ReflectionException ex) {
            ex.printStackTrace();
        } catch (InstanceNotFoundException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

getAttributes メソッドの第 1 引数は ObjectName オブジェクトになります。第 2 引数がこの名前の MBean が持つ属性のうち取得したいものを文字列の配列にしたものです。

上のコードではすべての属性を取得しようとしているので、まず MBeanInfo オブジェクトから MBeanAttributeInfo オブジェクトを取得し、そこから属性の名前を配列に入れています。

getAttributes メソッドの戻り値は AttributeList クラスになります。このクラスは Attribute クラスを要素に持つというだけのリストになっています。そのため add メソッドの引数が Attribute クラスにオーバロードされています。

しかし、get メソッドは戻り値の型だけを変更したオーバロードはできないので、Object クラスのままになっています。

こんな中途半端なクラスをつくらないで、Generics を使用して List<Attribute> としたほうがよっぽど使いやすくなるはずです。

上の例でも Attribute オブジェクトを取得するために AttributeList#get メソッドの戻り値にキャストしています。Tiger で使用するのですから、もうこんな使い方やめればいいのにと思うのですが、そうもいかないのでしょうか。

閑話休題。

Attribute クラスは属性の名前を値を保持したクラスです。getName メソッドで名前、getValue メソッドで値を取得できます。

上記の例では値が配列の場合には展開して表示するようにしています。属性の型は MBeanAttributeInfo クラスを使用して調べることができるので、それによって配列かどうかを判定しています。

次は単一の属性値の取得です。

    private void showTotalCount() {
        try {
            // 属性値の取得
            Object value = connection.getAttribute(triviaName, "TotalCount");
            System.out.println("TotalCount = " + value);
        } catch (AttributeNotFoundException ex) {
            ex.printStackTrace();
        } catch (MBeanException ex) {
            ex.printStackTrace();
        } catch (ReflectionException ex) {
            ex.printStackTrace();
        } catch (InstanceNotFoundException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

単一の属性値の取得は getAttribute の第 2 引数に属性の名前を指定するだけです。getAttribute メソッドの戻り値は Attribute オブジェクトではなくて、属性の値が直接戻ってきます。

 

 
 
Tiger MBean の属性の値変更とオペレーションの実行
 
 

属性値を取得できたので、次は属性値の変更してみます。

サンプルのソース TriviaMBeanRemoteTest3.java

属性値の設定は MBeanServerConnection#setAttributes メソッドもしくは MBeanServerConnection#setAttribute メソッドです。

両者の違いは getAttributes メソッドと getAttribute メソッドの違いと同様に、複数属性の値設定と単一属性の値設定の違いになります。

引数も前者が AttributeList クラス、後者が Attribute クラスになります。setAttributes メソッドだけは戻り値があり、AttributeList オブジェクトが戻ってきます。

TriviaMBeanRemoteTest3 クラスでは setAttribute メソッドを使用して値設定しています。

    private void setTotalCount() {
        try {
            // setTotalCount の実行
            Attribute attribute = new Attribute("TotalCount", 10);
            connection.setAttribute(triviaName, attribute);
        } catch (InvalidAttributeValueException ex) {
            ex.printStackTrace();
        } catch (AttributeNotFoundException ex) {
            ex.printStackTrace();
        } catch (MBeanException ex) {
            ex.printStackTrace();
        } catch (ReflectionException ex) {
            ex.printStackTrace();
        } catch (InstanceNotFoundException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

このサンプルでは TotalCount を 10 にしています。実際には Attribute クラスのコンストラクタの第 2 引数は Object クラスなのですが、autoboxing が使用されて Integer オブジェクトに変換されています。

ついでにオペレーションも実行してみましょう。オペレーションの実行はリフレクションを使ったメソッド実行と似たような感じになります。

メソッド実行は MBeanServerConnection#invoke メソッドを使用します。

    private void invokeReset() {
try {
// reset オペレーションの実行
connection.invoke(triviaName, "reset", null, null);
} catch (MBeanException ex) {
ex.printStackTrace();
} catch (ReflectionException ex) {
ex.printStackTrace();
} catch (InstanceNotFoundException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
}
}

invoke メソッドの第 1 引数は ObjectName オブジェクトなのはいつもと同じです。第 2 引数はオペレーションの名前、第 3 引数はオペレーションの引数を配列にしたものです。最後の引数がオペレーションの引数の型を文字列の配列にしたものです。

reset メソッドは引数なしのオペレーションなので、第 3, 第 4 引数は null になっています。しかし、実際に引数をとる場合にはここで引数を指定するわけです。

引数がプリミティブの場合はラッパークラスを使用します。int だったら Integer クラス、double だったら Double クラスを使用します。しかし、引数の型は int や double のままにしておきます。

invoke メソッドの戻り値はオペレーションの戻り値になります。

これを実行させてみたらちゃんとリセットがかかることが確認できると思います。

この方法で任意のオペレーションを実行できるわけです。

 

 
 
Tiger ノティフィケーションを受けとる
 
 

ノティフィケーションも忘れてはならない機能です。MBean に対してリスナ登録を行うのも MBeanServerConnection オブジェクト経由で行います。

サンプルのソース TriviaMBeanRemoteTest4.java

リスナ登録は MBeanServerConnection#addNotificationListener メソッドを使用して行います。

TriviaServerStandardMBean はノティフィケーションを扱わないので、代わりに CounterMonitor に対してリスナ登録してみました。

    private void addNotificationListener() {
        try {
            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();
                }
            };
            connection.addNotificationListener(monitorName, listener, null, null);
        } catch (InstanceNotFoundException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

addNotificationListener メソッドは基礎編でも使用しましたね。このメソッドの第 3 引数はフィルタ、第 4 引数はノティフィケーションと一緒にリスナに渡すオブジェクトになります。

これで、ノティフィケーションが発生したら、受け取ることが可能になります。

実行させてみた結果を次に示しておきます。

C:\examples>java -cp trivia.jar jp.gr.java_conf.skrb.game.trivia.mclient.TriviaM
BeanRemoteTest4

Received notification: javax.management.monitor.MonitorNotification[source=MBean
:name=TriviaMonitor][type=jmx.monitor.counter.threshold][message=]
MBean: MBean:name=TriviaStandard
Attribute: TotalCount
Trigger: 20
Gauge: 20

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

 

 
 
Tiger プロキシを使用した MBean の管理
 
 

今まで説明してのはすべて MBeanServerConnection インタフェースを介して MBean にアクセスしていました。これはこれでいいのですが、これとは違う方法で MBean にアクセスする方法があります。

それはプロキシを使用する方法です。J2SE 1.3 で導入された動的プロキシを使用して MBean のプロキシを作成し、このプロキシを使用して MBean にアクセスするという方法です。

サンプルのソース TriviaMBeanRemoteTest5.java

プロキシの生成には MBeanServerInvokationHandler クラスを使用します。それを行っているのが accessMBeanThroughProxy メソッドです。

    private void accessMBeanThroughProxy() {
        TriviaServerStandardMBean proxy
            = (TriviaServerStandardMBean)MBeanServerInvocationHandler.newProxyInstance(
                                             connection,
                                             triviaName,
                                             TriviaServerStandardMBean.class,
                                             false);
 
        System.out.println("TotalCount = " + proxy.getTotalCount());
        System.out.println("Invoke Reset");
        proxy.reset();
    }

MBeanServerInvocation クラスの static なメソッド newProxyInstance を使用してプロキシを生成します。

このメソッドの第 1 引数は MBeanServerConnection オブジェクト、第 2 引数が ObjectName オブジェクトです。第 3 引数は MBean のインタフェースのクラスを指定します。最後の引数はプロキシを作成する MBean がノティフィケーションを扱うかどうかを示すためのフラグになります。

TriviaServerStandardMBean はノティフィケーションは扱わないので、ここは false にしておきます。

プロキシができてしまえば、後は普通のオブジェクトと同様にアクセスすることができます。

プロキシを使用して MBean にアクセスすると、例外処理が簡素化できて見た目はいいかもしれませんが、実際には例外が発生する可能性は残っています。

動的プロキシは InvocationHandler インタフェースの invoke メソッドを使用してメソッドコールを行うのですが、このメソッドは Throwable 例外をスローします。結局、すべての例外の親クラスになるわけですから、チェックすべき例外も実行時例外と同じように try ... catch しなくてすんでしまいます。

逆にこれが例外を隠すことにもなりかねません。実際にプロキシを使用する場合はこの点を注意する必要があります。

 

 
 
Tiger 顔を作ってみる
 
 

ずっとコンソールでキャラクタベースで行ってきたので、最後はもう少しましな顔を作ってみました。

サンプルのソース TriviaMBeanClient.java
MBeanClient.java
MBeanClientView.java
AttributeTable.java
OperationTable.java

メインのクラスが TriviaMBeanClient クラスで、MBeanClientView クラス、AttributeTable クラス、OperationTable クラスが GUI 関連のクラスになります。

実行した結果は次のようになります。

図 1 TriviaMBeanClient

MBean ごとにフレームを表示し、フレームには表形式で属性とオペレーションの一覧があります。一番下は発生したノティフィケーションに関して出力します。

Reload ボタンは属性の再取得を行うためのものです。ただし、このボタンを押さなくても 10 秒ごとに属性の値を自動的に取得しています。

本来ならば Writable の属性は値を設定できるようにすればいいのでしょうが、面倒くさかったので省略してしまいました。同じような理由でオペレーションの実行も引数がないものしかできないようになっています。ようするにテーブルから文字列を拾って、それからオブジェクトを生成するのが面倒くさかっただけなんですが。

アプリケーションの中でやっていることは今までやってきたこととなんら変わりありません。

MBeanServerConnection オブジェクトを取得し、MBean の MBeanInfo オブジェクトを使用してテーブルの構成などを決定し、GUI を描画します。

属性の値は getAttributes メソッドと getAttribute メソッドを使用し、オペレーションの実行は invoke メソッドを使用しています。

また、ノティフィケーションも受けられるようにリスナ登録をして、ノティフィケーションが発生したら表示するようにしました。

かなり手抜きな管理アプリケーションですが、こんなものでも結構役に立つものです。

 

 
 
Tiger おわりに
 
 

今回は JMX リモートで策定されている RMI コネクタに関して、また後半では MBeanServerConnection インタフェースの使い方に関して説明を行いました。

JXM リモートはここで解説した基本的な使い方以外に、JNDI などを利用した MBeanServer の検索やセキュリティに関しても定義されています。本格活用するならば、これらも考慮しなくてはいけないと思います。

とはいうものの、MBean を作ることはあったとしても、なかなか自分で管理アプリケーションを作ろうという方は少ないと思います。しかし、自分なりのカスタマイズした管理アプリケーションを作ってみるのもなかなか面白いのではないでしょうか。

 

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

 

参考

 

(May. 2004)

 
 
Go to Contents Go to Java Page