Seasar2でJMX調査中

JMXはJDK5からJavaSEについてくるようになって、JDK6, JDK7とどんどん進化しているようで下位互換は保障されるか微妙だが根っこの基盤は固まっている感じ。とはいえ、最新のJMX2.0(JSR255)の進みが異常に遅く思えるのは私だけか。誰も関心がなさそうだし。この2,3年でJSR255がEA2からEA4に上がっただけ。でもボチボチJDK7もまとめに入ってるのでしっかりやらな、といった感じなのかな。
で、specificationもサクっと目をとおした。JSR3,160,255,262、とまぁ本当にはじめの1歩だったので斜め読みした。あんまMustとかShouldとか表記規則はないんだね。
で、最新JDK6ではMBeanも@MXBeanなどアノテーションで簡単に管理下に置けます。でも実際にはアノテーションはれなかったり(リリース済の資産だったり、別フレームワークだったりなど)するので何かしらMBean管理下する仕組みは必要そうです。基本中の基本ということの動作確認ということでSeasar2コンポーネントで試してみる。

動的にMBean化するにはDynamicMBeanをつかうわけだがその中でもModelMBean(RequiredMBean)を使ったやりかたになります。MBeanには属性、メソッドをJC
oncolseから操作できることからわかるとおりここらへんの情報を動的に準備する必要があり、流れとしては

  1. メソッド情報作成
  2. 属性情報作成
  3. MBeanメソッド情報作成
  4. MBeanメタ情報生成
  5. ReuiredMBean作成

になります。動かしたソースはこんな感じで、MBean対象はおなじみGreeting。

public class GreetingImpl implements Greeting {
	private String greeting = "Hello World!";
	public GreetingImpl(){}
	public String greet() {
		return greeting;
	}
	@Override
	public void sayHello() {
		System.out.println(greeting);
	}
	@Override
	public String getGreeting() {
		return greeting;
	}
	@Override
	public void setGreeting(String greeting) {
		this.greeting = greeting;
	}
}

で、うごかすメインはこれ。

    public GreetingMain() {	    
        mbs = ManagementFactory.getPlatformMBeanServer();

        S2Container container = S2ContainerFactory.create(PATH);
        Greeting greeting = (Greeting) container.getComponent("greeting");
        ComponentDef cd = container.getComponentDef("greeting");
        Class targetComponentClass = cd.getComponentClass();
        try {
        	   ModelMBean model = createModelMBean(greeting, targetComponentClass);        
        	   ObjectName helloName = null;
            helloName = new ObjectName("S2JMXAgent:name=hello");
            mbs.registerMBean(model, helloName);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

コンポーネントとって登録してます。createModelMBeanで実際に作成します。

    public static ModelMBean createModelMBean(Object resource, Class targetComponentClass)
        throws JMException, InvalidTargetObjectTypeException {

        //?メソッド情報作成
        final List<Method> operations = prepareMethodInfo(resource, targetComponentClass);	        
        //?属性情報作成
        final ModelMBeanAttributeInfo[] attrs = prepareAttributeInfo(resource);	        
        //?MBeanメソッド情報作成
        final ModelMBeanOperationInfo[] ops = prepareMBeanOperationInfo(operations);	        
        //?MBeanメタ情報生成
        ModelMBeanInfo mmbi = new ModelMBeanInfoSupport(resource.getClass().getName(),
                              resource.getClass().getName(),
                              attrs,
                              null,  
                              ops,
                              null);
        
        //
        ModelMBean mmb = new RequiredModelMBean(mmbi);
        mmb.setManagedResource(resource, "ObjectReference");
        
        return mmb;   
    }

?はこんな感じ

    private static List<Method> prepareMethodInfo(Object resource, Class targetComponentClass) {
        final List<Method> operations = new ArrayList<Method>();
        final Method[] methods = targetComponentClass.getMethods();
	        
        for (Method method : methods) {
            if (method.getDeclaringClass().equals(Object.class)) continue;	            
            operations.add(method);
        }
        return operations;
    }

とまぁメソッドをListにつめてるだけだなんですが、その中でObject.classのメソッド(toStringとか)はずしてます。さらに実際のコンポーネントのオブジェクトからresource.getClass().getMethod()ってやってしまうとエンハンスされたメソッドまでとれちゃうのでComponentDefからtargetクラスの情報をもとにメソッドを抽出します
で、次、属性情報。

    private static ModelMBeanAttributeInfo[] prepareAttributeInfo(Object resource) throws IntrospectionException {
        List<ModelMBeanAttributeInfo> attrinfo = new ArrayList<ModelMBeanAttributeInfo>();
        BeanDesc bd = BeanDescFactory.getBeanDesc(resource.getClass());
        
        int size = bd.getPropertyDescSize();
        for (int i = 0; i < size; i++) {		
        PropertyDesc pd = bd.getPropertyDesc(i);
            attrinfo.add(makeAttribute(pd));
        }

        final ModelMBeanAttributeInfo[] attrs =
            attrinfo.toArray(new ModelMBeanAttributeInfo[attrinfo.size()]);
        return attrs;
    }

    private static ModelMBeanAttributeInfo makeAttribute(PropertyDesc pd) throws IntrospectionException {
        final String attrName = pd.getPropertyName();
        final List<String> descriptors = new ArrayList<String>();

        descriptors.add("name=" + attrName);
        descriptors.add("descriptorType=attribute");
        Method getter = pd.getReadMethod();
        Method setter = pd.getWriteMethod();		
        if (getter!=null) {
            descriptors.add("getMethod=" + getter.getName());
        }
        if (setter!=null) {
            descriptors.add("setMethod=" + setter.getName());
        }

        final Descriptor attrD = new DescriptorSupport(descriptors.toArray(new String[descriptors.size()]));
        return new ModelMBeanAttributeInfo(attrName, attrName, getter, setter, attrD);
    }

です。BeanDescに頼って簡単にプロパティを抽出。正確にはこれだけじゃダメで、MBean的にはSetterだけのやGetterだけの属性に対しての操作も可能なのです。ここではあくまでさわりの動作確認ということで。descriptorの情報を細かくセットすることでJConsoleなどでもわかりやすく表示できるようになる。
で、次はJMX都合の作法になっちゃうが操作情報のセットを行います。

    private static ModelMBeanOperationInfo[] prepareMBeanOperationInfo(List<Method> operations) {
        int opcount = operations.size();
        ModelMBeanOperationInfo[] ops = new ModelMBeanOperationInfo[opcount];
        for (int i=0;i<opcount;i++){
            final Method m = operations.get(i);
            ops[i] = new ModelMBeanOperationInfo(m.getName(),m);
        }
        return ops;
    }

あとは

    //MBeanメタ情報生成
    ModelMBeanInfo mmbi = new ModelMBeanInfoSupport(resource.getClass().getName(),
                              resource.getClass().getName(),
                              attrs,
                              null,  
                              ops,
                              null);
        
    ModelMBean mmb = new RequiredModelMBean(mmbi);
    mmb.setManagedResource(resource, "ObjectReference");

今まで作った情報をもとにメタつくってModelMBeanを生成する。これを一番最初に書いたMBeanServerに登録すればOK.ちなみにdiconはおなじみgreeting.dicon

<components>
    <include path="aop.dicon"/>
    <component name="greeting" 
        class="com.waki.jmx.sample.GreetingImpl">
        <aspect>aop.traceInterceptor</aspect>
    </component>
</components>

です。
あとは実行してThreadなりSystem.inなりでとめるなりしておいてJConsoleでみてみる。
f:id:wkzk:20090215130755j:image
で接続して
f:id:wkzk:20090215130749j:image
でsayHello()操作をよびだすとMessageBoxで正常にできたむねのメッセージが表示され、コンソールに

2009-02-15 12:57:11,890 [RMI TCP Connection(4)-61.215.143.71] DEBUG org.seasar.framework.aop.interceptors.TraceInterceptor - BEGIN com.waki.jmx.sample.GreetingImpl#sayHello()
Hello World!
2009-02-15 12:57:11,890 [RMI TCP Connection(4)-61.215.143.71] DEBUG org.seasar.framework.aop.interceptors.TraceInterceptor - END com.waki.jmx.sample.GreetingImpl#sayHello() : null

とちゃんとaspectされた動きました。
結構簡単に成功。楽しかったのでもうちょっとやってみよう。ちゃんとやるには先輩であるJBossRMIClassLoaderやSpringみるとClassloasderについて考慮が必要みたい、フムフム、これはおいおい。

万能なわけじゃないけどJMXやBTrace,VisualVMなどテスト時、本番入った後の運用、保守でちょっとした役に立ちそうなものがそろってきているのでwebアプリケーションフレームワークばかりじゃなくこっちのほうも注目されていってほしいと節に思うのでした。(つづく)