CLOVER🍀

That was when it all began.

MBeanを作成してみる

前はJMX Remoteを使用して情報取得をしてみたり、TomcatにValveを組み込んだりして遊んでいました。で、それでJMXの基礎はある程度やった気になっていましたが…よくよく考えると、MBeanを作成することをやっていませんでした。

というわけで、今回はMBeanの作成にトライです。

今回の記事は、以下のページを参考に、一部の内容をScalaで書き換えたものです。まあ、クラス名とかは変わっていますけどね。
http://www.javainthebox.net/laboratory/J2SE1.5/MonitoringAndManagement/JMX/JMX1.html

上記サイトに習い、StandardMBeanとDynamicMBeanを作成してみました。

まずは、StandardMBean。
MyStandardMBean.scala

trait MyStandardMBean {
  def getCount: Int
  def execute(): Unit
}

class MyStandard(val name: String) extends MyStandardMBean {
  var count: Int = _

  def getCount: Int = count
  def execute(): Unit = {
    count += 1
    printf("Execute[%s], count[%d]\n", name, count)
  }
}

StandardMBeanを作成するには、[対象クラス名]MBeanでインタフェースを作成し、実際の対象クラスがそのインターフェースを実装します。Scalaの場合、直接Javaのインターフェースにあたるものはないので、抽象メソッドのみを宣言したトレイトを使用します。まあ、コンパイルするとインターフェースになりますからね。

今回は、管理対象となるクラス名が「MyStandard」なので、作成したインターフェースの名前は「MyStandardMBean」です。これを守らないと、MBeanServer登録時に例外が発生することになります…。他のサイトの情報とか見ていると、名前をけっこう適当に決めていたような雰囲気もあったので、あんまり気にしないでいるとかなりハマることになりました…。

続いて、DynamicMBean。
MyDynamicMBean.scala

import scala.reflect.BeanProperty

import javax.management.{MBeanParameterInfo, MBeanOperationInfo}
import javax.management.modelmbean.DescriptorSupport
import javax.management.modelmbean.ModelMBeanAttributeInfo
import javax.management.modelmbean.ModelMBeanConstructorInfo
import javax.management.modelmbean.ModelMBean
import javax.management.modelmbean.ModelMBeanInfo
import javax.management.modelmbean.ModelMBeanOperationInfo
import javax.management.modelmbean.ModelMBeanInfoSupport
import javax.management.modelmbean.RequiredModelMBean

object MyDynamicMBean {
  def modelMBean(name: String): ModelMBean = {
    val modelMBean = new RequiredModelMBean(mbeanInfo)
    modelMBean.setManagedResource(apply(name), "ObjectReference")
    modelMBean
  }

  def apply(name: String): MyDynamicMBean = new MyDynamicMBean(name)

  def mbeanInfo: ModelMBeanInfo = {
    val attributes = Array(new ModelMBeanAttributeInfo("Count",
                                                       "int",
                                                       "Count Attribute",
                                                       true,
                                                       true,
                                                       false,
                                                       new DescriptorSupport(
                                                         "name=Count",
                                                         "descriptorType=attribute",
                                                         "displayName=Count",
                                                         "type=int",
                                                         "setMethod=setCount",
                                                         "getMethod=getCount"
                                                       )))

    val constructors = Array(new ModelMBeanConstructorInfo("MyDynamicMBean",
                                                           "MyDynamicMBean Constructor",
                                                           Array(new MBeanParameterInfo("name",
                                                                                        classOf[String].getName,
                                                                                        null))))

    val operations = Array(new ModelMBeanOperationInfo("execute",
                                                       "Execute Method",
                                                       null,
                                                       "void",
                                                       MBeanOperationInfo.ACTION),
                           new ModelMBeanOperationInfo("getCount",
                                                       "getCount Mehtod",
                                                       null,
                                                       "int",
                                                       MBeanOperationInfo.INFO),
                           new ModelMBeanOperationInfo("setCount",
                                                       "setCount Method",
                                                       Array(new MBeanParameterInfo("count",
                                                                                   Integer.TYPE.getName,
                                                                                   null)),
                                                       "int",
                                                       MBeanOperationInfo.ACTION))

    new ModelMBeanInfoSupport(classOf[MyDynamicMBean].getName,
                              "DynamicMBean Example",
                              attributes,
                              constructors,
                              operations,
                              null)
  }
}

class MyDynamicMBean(val name: String) {
  @BeanProperty
  var count: Int = _

  def execute(): Unit = {
    count += 1
    printf("Execute[%s], count[%d]\n", name, count)
  }
}

管理対象となるのは、あくまでクラスで宣言してあるMyDynamicMBeanクラスです。コンパニオンオブジェクトでは、DynamicMBeanとして使用するための準備をいろいろしています。

StarndardMBeanの時にはクラス自体を登録すればそれで終了だったのですが、DynamicMBeanの場合はどんなAttributeがあって、どんなOperationができて…というのを1つ1つ登録していく必要があります。かなりプリミティブですね。相当面倒でしたよ、ホント。

さらに、これになかなか気付かずかなりの時間ハマっていたのですが、Attributeを定義する時にはgetMethod/setMethodを規定したDescriptorSupportの設定と、そのメソッドをOperationに登録しておかないと意図しない動きになります。

これは、RequiredModelMBeanクラスを使用しているからなのかはわかりませんけどね。

Attributeの操作内容をOperationでも定義しておかないと、MBeanが実体のメソッドを認識できず、使用できないAttributeとなってしまいます。また、DescriptorSupportで情報の登録を行っていないと、使用することはできますが実際のメソッド呼び出しは行われなくなってしまいます。雰囲気的には、使って欲しかったgetter/setterとは違う領域を勝手に作っていた感じでした。

ちなみに、RequiredModelMBean#setManagedResourceで実体のオブジェクトを格納するのですが、Javadocによると第2引数は「ObjectReference」のみ有効らしいです。の割には、定数化されてないのよね…なんとも中途半端な。

    val modelMBean = new RequiredModelMBean(mbeanInfo)
    modelMBean.setManagedResource(apply(name), "ObjectReference")

では、MBeanServerへの登録を含むメインクラスです。
MBeanExample.scala

import java.lang.management.ManagementFactory
import javax.management.{MBeanServer, MBeanServerFactory, ObjectName}

object MBeanExample {
  val DOMAIN: String = "MyMBean"

  lazy val server: MBeanServer = ManagementFactory.getPlatformMBeanServer
  //val server: MBeanServer = MBeanServerFactory.createMBeanServer()

  def main(args: Array[String]): Unit = {
    try {
      registerMBeans()
      println("Starting Jmx Monitor...")
      readLine("Press EnterKey, Exit")
    } catch {
      case e: Exception => e.printStackTrace 
    }
  }

  private def scope[A, B](target: A)(body: A => B): B = body(target)

  def registerMBeans(): Unit = {
    scope(new MyStandard("MyStandardMBeanInstance")) {
      mbean =>
        registerMBean(mbean, "%s:type=myStandardMBean".format(DOMAIN))
    }

    scope(MyDynamicMBean.modelMBean("MyDynamicMBeanInstance")) {
      mbean =>
        registerMBean(mbean, "%s:type=myDynamicMBean".format(DOMAIN))
    }
  }

  def registerMBean(mbean: Any, name: String): Unit = {
    val objectName = new ObjectName(name)

    if (server.isRegistered(objectName)) {
      printf("*** Unregister Mbean[%s] ***\n", objectName)
      server.unregisterMBean(objectName)
    }

    printf("Register MBean[%s]\n", objectName)
    server.registerMBean(mbean, objectName)
  }
}

MBeanServerは、JConsoleで簡単に確認できるようにするためプラットフォームMBeanServerを使用しました。あと、Enterキーを押されるまで待ち続けるようにしています。

あと、sbtで確認していたので何度もこのプログラムをrunしてしまうとMBeanServerに何度もMBeanを登録してしまうため、同じObjectNameを持つMBeanは1度削除してから登録するようにしています。

では、実行してみます。

> run
[info] Updating {file:/xxxxx/mbean-example/}default-d93821...
[info] Done updating.
[info] Compiling 3 Scala sources to /xxxxx/mbean-example/target/scala-2.9.1.final/classes...
[info] Running MBeanExample 
Register MBean[MyMBean:type=myStandardMBean]
Register MBean[MyMBean:type=myDynamicMBean]
Starting Jmx Monitor...
Press EnterKey, Exit

という感じで起動するので、JConsoleで確認します。

この手順で起動した場合、JConsoleではsbtのプロセスを確認してくださいね。

StandardMBeanの登録情報。

DynamicMBeanのAttribute情報。

MBeanの作成はまだNotificationとかMXBeanとかがあって、まだまだ知らないことがいっぱいありますわ〜。