じゃじゃ馬ならし

 

MXBean を作ってしまおう

MBean は自作できたのですが...

Tiger で導入された MXBean と jconsole は JVM の状態を知るのにとても便利です。

この MXBean は通常の MBean とは少し扱いが異なっています。Tiger のソースを見てみると、なんかとても簡単に作れそうに見えてきます。

Tiger では自作の MXBean はサポートされていなかったのですが、Java SE 6 からは大丈夫です。

何もしなくても jconsole で JVM にアタッチできるようになったのですから、どんどん自作の MXBean を登録してしまいましょう。

さっそく、やってみたいのですが、このドキュメントを書いている 2005 年 11 月には、Java SE 6 でサポートされる JMX 1.3 のスペックが公開されていません。

そのため、ここで書いた方法が正しい方法なのかどうかよく分かっていません。もしかしたら、間違っているかもしれませんが、ご了承ください。

 

MXBean を作ってみる

MXBean では扱える型が OpenMBean で決められているものを使います。

もし、複合型を使用する場合は javax.management.openmbean.CompositeData クラスか javax.management.openmbean.TabularData クラスを使用します。しかし、java.lang.management.ThreadInfo クラスや java.lang.management.MemoryUsage クラスのように複合型をそのまま使う場合もあります。

その場合には、sun.management.LazyCompositeData クラスを派生させた複合型と CompositeData クラスの橋渡し的なクラスを作成するようです。また、複合型のクラスには from メソッドという CompositeData から複合型オブジェクトを生成するメソッドを作成します。

たとえば、ThreadInfo クラスの場合、sun.management.ThreadInfoCompositeData クラスが橋渡し的なクラスになっています。たぶん、クラス名は複合型クラス名称 + CompositeData としなければいけないのでしょう。

LazyCompositeData クラスが abstract クラスなので、その派生クラスは getComposteData メソッドを実装する必要があります。このメソッドでは複合型クラスから CompositeData オブジェクトを作成します。

MXBean のインタフェース名は MXBean で終わるようにします。インタフェースをインプリメントするクラス名は特に制約はありません。ここら辺は Standard MBean などの MBean とは違うところですね。

簡単なサンプルを作ってみましょう。

サンプルのソースコード SampleMXBean.java
SampleImpl.java

まずはインタフェースのほうから。

public interface SampleMXBean {
    public int getCount();
}

単純にカウントを返すというメソッドを定義しています。これを実装している SampleImpl クラスは次のようにしました。

public class SampleImpl implements SampleMXBean {
    private int count = 0;
 
    public int getCount() {
        count++;
 
        return count;
    }
}

簡単すぎて申し訳ないぐらいですが... ^^;;

さて、これを MBeanServer に登録してみましょう。

サンプルのソースコード UserDefinedMXBeanSample1.java

 

import java.lang.management.ManagementFactory;
import java.io.IOException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.MalformedObjectNameException;
import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanRegistrationException;
import javax.management.NotCompliantMBeanException;
 
public class UserDefinedMXBeanSample1 {
    public UserDefinedMXBeanSample1() throws MalformedObjectNameException, 
                                            InstanceAlreadyExistsException,
                                            MBeanRegistrationException,
                                            NotCompliantMBeanException {
        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
 
        SampleMXBean mbean = new SampleImpl();
        ObjectName objName = new ObjectName("skrb:name=sample");
        server.registerMBean(mbean, objName);
 
        try {
            System.in.read();
        } catch (IOException ex) {}
    }
 
    public static void main(String[] args) throws Exception {
        new UserDefinedMXBeanSample1();
    }
}

JMX のごくごく普通の使い方です。ただ、MBeanServer オブジェクトは java.lang.management.ManagementFactory クラスを使用して取得しています。こうしておけば、単に jconsole のような JMX クライアントでもすぐに SampleMXBean を見れるはずです。

実行して、jconsole で見てみましょう。

実行結果

更新ボタンを押すと、Count が増加していきます。

このサンプルのコードにはまったく Java SE 6 での新 API は含まれていません。ということは Tiger でもコンパイルできます。

で、Tiger でコンパイル、実行してみました。すると、次のような例外が発生してしまいます。た。

C:\temp>java UserDefinedMXBeanSample1
Exception in thread "main" javax.management.NotCompliantMBeanException: SampleIm
pl does not implement the SampleImplMBean interface or the DynamicMBean interfac
e
        at com.sun.jmx.mbeanserver.Introspector.testCompliance(Introspector.java
:198)
        at com.sun.jmx.mbeanserver.Introspector.testCompliance(Introspector.java
:150)
        at com.sun.jmx.mbeanserver.StandardMetaDataImpl.buildMBeanInfo(StandardM
etaDataImpl.java:116)
        at com.sun.jmx.mbeanserver.StandardMetaDataImpl.testCompliance(StandardM
etaDataImpl.java:149)
        at com.sun.jmx.mbeanserver.MetaDataImpl.testCompliance(MetaDataImpl.java
:125)
        at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerMBean(D
efaultMBeanServerInterceptor.java:324)
        at com.sun.jmx.mbeanserver.JmxMBeanServer.registerMBean(JmxMBeanServer.j
ava:497)
        at UserDefinedMXBeanSample1.(UserDefinedMXBeanSample1.java:19)
        at UserDefinedMXBeanSample1.main(UserDefinedMXBeanSample1.java:27)
 
C:\temp>

やっぱりだめなんですね。MXBean と認めてくれないようです。

 

複合型を含む MXBean を作ってみる

前の例はあまりにも簡単なので、もう少し複雑な MXBean を作ってみましょう。

複合型を含む MXBean です。

サンプルのソースコード Name.java
NameCompositeData.java
Sample2MXBean.java
Sample2Impl.java
UserDefinedMXBeanSample2.java

まず複合型クラスを作りましょう。とはいってもすごい単純なクラスです。

 

複合型を定義するクラス

このクラスでは名前と苗字を分けて保持します。

import javax.management.openmbean.CompositeData;
 
public class Name {
    private String first;
    private String last;
 
    public static Name from(CompositeData cd) {
        if (cd == null) {
            return null;
        }
 
        if (cd instanceof NameCompositeData) {
            return ((NameCompositeData)cd).getName();
        } else {
            return new Name(cd);
        }
    }
 
    public Name(CompositeData cd) {
        NameCompositeData.validateCompositeData(cd);

        this.first = NameCompositeData.getFirst(cd);
        this.last = NameCompositeData.getLast(cd);
    }
     
    public Name(String first, String last) {
        this.first = first;
        this.last = last;
    }
 
    public String getFirstName() {
        return first;
    }
 
    public String getLastName() {
        return last;
    }
}

getFirstName/getLastName メソッドは単なるゲッターです。

重要なのは from メソッドと CompositeData オブジェクトを引数に取るコンストラクタです。

MemoryUsage クラスや ThreadInfo クラスを見ていただければ分かるのですが、ComposteData オブジェクトとそれぞれのオブジェクトを橋渡ししなければいけません。それをおこなうのが from メソッドです。

from メソッドは引数チェックや例外処理を除くと、単に CompositeData オブジェクトを引数にして Name オブジェクトを生成しています。

コンストラクタの中で出てくるのが、橋渡しをおこなうためのクラス NameComposteData クラスです。

ちょっと長いのですが、全部出してしまいましょう。

import javax.management.openmbean.CompositeData;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenDataException;
 
import sun.management.LazyCompositeData;
import sun.management.MappedMXBeanType;
 
public class NameCompositeData extends LazyCompositeData {
    private final Name name;
 
    public NameCompositeData(Name name) {
        this.name = name;
    }
 
    public Name getName() {
        return name;
    }
     
    public static CompositeData toCompositeData(Name name) {
        NameCompositeData namecd = new NameCompositeData(name);
        return namecd.getCompositeData();
    }
 
    protected CompositeData getCompositeData() {
        final Object[] nameItemValues = {
            new String(name.getFirstName()),
            new String(name.getLastName())
        };
 
        try {
            return new CompositeDataSupport(nameCompositeType,
                                            nameItemNames,
                                            nameItemValues);
        } catch (OpenDataException ex) {
            InternalError e1 = new InternalError(ex.getMessage());
            e1.initCause(ex);
            throw e1;
        }
    }
    
    private static final CompositeType nameCompositeType;
    static {
        try {
            nameCompositeType = (CompositeType)MappedMXBeanType.toOpenType(Name.class);
        } catch (OpenDataException ex) {
            InternalError e1 = new InternalError(ex.getMessage());
            e1.initCause(ex);
            throw e1;
        }
    }
 
    static CompositeType getNameCompositeType() {
        return nameCompositeType;
    }
 
    private static final String FIRST = "first";
    private static final String LAST = "last";
 
    private static final String[] nameItemNames = {
        FIRST,
        LAST,
    };
 
    public static String getFirst(CompositeData cd) {
        return (String)cd.get(FIRST);
    }
 
    public static String getLast(CompositeData cd) {
        return (String)cd.get(LAST);
    }
 
    public static void validateCompositeData(CompositeData cd) {
        if (cd == null) {
            throw new NullPointerException("Null CompositeData");
        }
 
        if (!isTypeMatched(nameCompositeType, cd.getCompositeType())) {
            throw new IllegalArgumentException(
                "Unexpected composite type for Name");
        }
    }
}

このクラスは LazyCompositeData クラスを派生させているのですが、このクラスのパッケージは sun.management になっています。sun 以下のパッケージを使うのはいやなのですが、しかたないですね。

Name オブジェクトから CompositeData オブジェクトを作成しているのが、toCompositeData メソッドです。このメソッドは単に getCompositeData メソッドをコールしているだけです。

getCompositeData メソッドが橋渡しの中心的な役割になります。

CompositeData オブジェクトを作成するには、CompisteType オブジェクトとアイテムの名前を文字列の配列にしたもの、実際の値を Object クラスの配列にしたものの 3 点セットが必要です。

それぞれコードの中の変数名が nameCompositeType, nameItemNames, nameItemValues になります。

nameItemNames と nameItemValues は簡単ですね。問題は nameCompositeType です。でも、これも sun.management.MappedMXBeanType 暮らすというユーティリティクラスを使用すると、簡単に作れます。

後はこの 3 つの変数を CompositeData インタフェースを実装したサポートクラスの CompositeDataSupport クラスのコンストラクタに指定すれば、CompositeData オブジェクト (実際は CompositeDataSupport オブジェクトですが) が生成できます。

後は CompositeData オブジェクトから Name オブジェクトへの変換ですが、これは LazyCompositeData クラスの isTypeMatched メソッドを使用して型チェックした後に、getFirst メソッドと getLast メソッドで CompositeData オブジェクトから値を取り出すだけです。

 

複合型を含んだ MXBean

さて、次は Name クラスを使用した MXBean である Sample2MXBean インタフェースです。

public interface Sample2MXBean {
    public int getCount();
    public Name getUser();
}

User というプロパティを追加してみました。もちろん、型は Name クラスです。

このインタフェースを実装したのが Sample2Impl クラスです。

public class Sample2Impl implements Sample2MXBean {
    private int count = 0;
    private Name user;
 
    public Sample2Impl(String first, String last) {
        user = new Name(first, last);
    }
 
    public int getCount() {
        count++;
 
        return count;
    }
 
    public Name getUser() {
        return user;
    }
}

説明をするまでもないですね。最後にこの MXBean を使用するクラスです。

import java.lang.management.ManagementFactory;
import java.io.IOException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.MalformedObjectNameException;
import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanRegistrationException;
import javax.management.NotCompliantMBeanException;
 
public class UserDefinedMXBeanSample2 {
    public UserDefinedMXBeanSample2() throws MalformedObjectNameException, 
                                            InstanceAlreadyExistsException,
                                            MBeanRegistrationException,
                                            NotCompliantMBeanException {
        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
 
        Sample2MXBean mbean = new Sample2Impl("Yuichi", "Sakuraba");
        ObjectName objName = new ObjectName("skrb:name=sample2");
        server.registerMBean(mbean, objName);
 
        try {
            System.in.read();
        } catch (IOException ex) {}
    }
 
    public static void main(String[] args) throws Exception {
        new UserDefinedMXBeanSample2();
    }
}

このクラスも先に出したサンプルと同様に Sample2Impl オブジェクトを生成して、MBeanServer オブジェクトに登録しているだけです。

 

実行

コードが書けたので、実行して、jconsole で見てみましょう。

実行結果

User アトリビュートの値が ComposteDataSupport になっています。つまり ComposteData でちゃんと扱えていることを示しているわけです。User の値のところをダブルクリックしてみると、詳細が表示されます。

実行結果

ちゃんと firstName と lastName が表示されました。

でも、ちょっとめんどくさいですよね。ところがですね、あるんですよ。簡単な方法が。

 

MXBean も EoD

というわけで、もう少し複合型を含んだ場合でも簡単に MXBean を作りたいわけです。

そんなときに登場するのが、やっぱり Tiger で登場したあれです。

そう、アノテーションです。

Java SE 6 では JMX 関連のアノテーションが 3 つ定義されています。

MXBean アノテーションと、DescriptorKey アノテーションは javax.management パッケージで定義されており、ConstructorProperties アノテーションは java.beans パッケージで定義されています。

最後の DescriptorKey アノテーションはちょっと性格が違うので、項を改めて説明しましょう。詳しくは Descriptors を参照ください。ここでは、残りの MXBean アノテーションと ConstructorProperties アノテーションについて説明しましょう。

実をいうと、ConstructorProperties アノテーションはベータの頃は javax.management.PropertyNames アノテーションでした。それが名前もパッケージも変更されてしまったのです。

それが分かるまで、結構探してしまいました ^^;; だって、まさか java.beans パッケージに移っているとは思いもよらないですから。逆にいえば、ConstuctorProperties アノテーションは MBean に限らず、Java Beans で汎用に使えるようになったということだと思います。

 

MXBean アノテーション

今まで作ってきた MXBean はすべてインタフェース名の最後に MXBean という文字を含んでいました。MBean と同じで、この命名規則により VM は MXBean かどうかを判断していたわけです。

でも、すべてに MXBean とつけるのも何だかなぁというも気がしませんか。

そこで使用するのが MXBean アノテーションです。

このアノテーションを使えば、どんなインタフェース名であろうと MXBean にすることができます。

使い方は簡単。だって、EoD ですから。

単にインタフェースの定義に @MXBean をつけるだけです。

@MXBean
public interface Foo {} // MXBean
  
@MXBean(true)
public interface Foo {} // MXBean
 
public interface FooMXBean {} // MXBean

上記の 3 種類の定義はすべて MXBean と定義されます。

逆に MXBean にならないのは、

public interface Bar {} // NOT MXBean
  
@MXBean(false)
public interface Bar{} // NOT MXBean
 
@MXBean(false)
public interface BarMXBean {} // NOT MXBean

注意すべきは、インタフェース名の最後が MXBean だとしても、@MXBean(false) と定義されていると、MXBean ではなくなってしまうという点です。

 

ConstrucorProperties アノテーション

次は ConstructorProperties アノテーションです。

ConstructorProperties アノテーションは複合型のクラスのプロパティを指定するために使用されるアノテーションです。

前述したように複合型クラスを作成するためには、OpenMBean の CompositeData クラスと橋渡しをするクラスを作成する必要がありました。

しかし、サンプルを見ていただければ分かるように、この橋渡しのためのクラスはいつもほとんど同じようなコードになります。違いはプロパティの名前と型ぐらいで、それさえパラメータとしてあたえれば自動生成できるのではないかと思えるほどです。

えっ、自動生成?

自動生成といえばアノテーションの得意分野です。

ということで使われるのが ConstructorProperties アノテーションなのです。

先ほどの Name クラスを ConstructorProperties アノテーションを使って記述しなおしたものが Name2 クラスです。

サンプルのソースコード Name2.java

 

import java.beans.ConstructorProperties;

public class Name2 {
    private String first;
    private String last;
 
    @ConstructorProperties({"firstName", "lastName"})
    public Name2(String first, String last) {
        this.first = first;
        this.last = last;
    }
 
    public String getFirstName() {
        return first;
    }
 
    public String getLastName() {
        return last;
    }
}

えっ、これだけ? と思った方も多いのではないでしょうか。

from メソッドも、CompositeData クラスを引数にとるコンストラクタもありません。

そして、もっと衝撃的なのが NameCompositeData クラスもいりません。

あの面倒くささはなんだったろうと思われても不思議はないですが、これがアノテーションの威力です。

ConstructorProperties アノテーションの使用法は、コンストラクタの前で定義をおこない、引数にプロパティの名前を列挙した文字列配列を指定します。

たった、これだけです。

もちろん、ConstructorProperties アノテーションに応じたゲッター、また必要であればセッターを用意する必要がありますが、これは従来でも必要だったので特に問題はないでしょう。

さて、Name クラスが ConstructorProperties アノテーションで簡単になったので、それを使用する Simpl2MXBean インタフェースも MXBean アノテーションを使って簡単にしてみましょう。

サンプルのソースコード Sample3.java
Sample3Impl.java
UserDefinedMXBeanSample3.java

MXBean は Sample3 インタフェースです。名前だけだと MXBean だと分からないですね。

import javax.management.MXBean;
 
@MXBean
public interface Sample3 {
    public int getCount();
    public Name2 getUser();
}

MXBean アノテーションを使えばこういうインタフェースでも MXBean に早変わりです。

UserDefinedMXBeanSample3 クラスはほとんど変わりません。

    public UserDefinedMXBeanSample3() throws MalformedObjectNameException, 
                                            InstanceAlreadyExistsException,
                                            MBeanRegistrationException,
                                            NotCompliantMBeanException {
        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
 
        Sample3 mbean = new Sample3Impl("Yuichi", "Sakuraba");
        ObjectName objName = new ObjectName("skrb:name=sample3");
        server.registerMBean(mbean, objName);
 
        try {
            System.in.read();
        } catch (IOException ex) {}
    }

さて、実行してみましょう。

実行結果

User2ComposteData クラスを作成していなくても、User アトリビュートの値が ComposteDataSupport になっていることを確認してください。User の値のところをダブルクリックしてみると...

実行結果

ただしく、firstName と lastName を参照することができました。

これなら簡単ですね。

 

おわりに

今までできなかった MXBean を作成できるというのはとてもいいですね。特になにも準備しなくても jconsole もしくは JSR-174 に対応した管理ツールを使えばすぐに見られるわけですから。

そして、アノテーションを使えば、あっという間に複合型を含む MXBean も作れてしまう。この簡単さは感動ものですよ。

 

(Nov. 2005)

(改訂 Feb. 2007) PropertyNames アノテーションを ConstructorProperties アノテーションに変更