从零开始玩转JMX(1):简介和 Standard MBean

简介

基本术语

  • MBean:是Managed Bean的简称,可以翻译为“管理构件”。在JMX中MBean代表一个被管理的资源实例,通过MBean中暴露的方法和属性,外界可以获取被管理的资源的状态和操纵MBean的行为。事实上,MBean就是一个Java Object,同JavaBean模型一样,外界使用自醒和反射来获取Object的值和调用Object的方法,只是MBean更为复杂和高级一些。MBean通过公共方法以及遵从特定的设计模式封装了属性和操作,以便暴露给管理应用程序。例如,一个只读属性在管理构件中只有Get方法,既有Get又有Set方法表示是一个可读写的属性。一共有四种类型的MBean: Standard MBean, Dynamic MBean, Open MBean, Model MBean。
  • MBeanServer:MBean生存在一个MBeanServer中。MBeanServer管理这些MBean,并且代理外界对它们的访问。并且MBeanServer提供了一种注册机制,是的外界可以通过名字来得到相应的MBean实例。
  • JMX Agent:Agent只是一个Java进程,它包括这个MBeanServer和一系列附加的MbeanService。当然这些Service也是通过MBean的形式来发布。
  • Protocol Adapters and Connectors:MBeanServer依赖于Protocol Adapters和Connectors来和运行该代理的Java虚拟机之外的管理应用程序进行通信。Protocol Adapters通过特定的协议提供了一张注册在MBeanServer的MBean的视图。例如,一个HTML Adapter可以将所有注册过的MBean显示在Web 页面上。不同的协议,提供不同的视图。Connectors还必须提供管理应用一方的接口以使代理和管理应用程序进行通信,即针对不同的协议,Connectors必须提供同样的远程接口来封装通信过程。当远程应用程序使用这个接口时,就可以通过网络透明的和代理进行交互,而忽略协议本身。Adapters和Connectors使MBean服务器与管理应用程序能进行通信。因此,一个代理要被管理,它必须提供至少一个Protocol Adapter或者Connector。面临多种管理应用时,代理可以包含各种不同的Protocol Adapters和Connectors。当前已经实现和将要实现的Protocol Adapters和Connectors包括: RMI Connector, SNMP Adapter, IIOP Adapter, HTML Adapter, HTTP Connector.

Adapter 和Connector的区别在于:Adapter是使用某种Internet协议来与JMX Agent获得联系,Agent端会有一个对象 (Adapter)来处理有关协议的细节。比如SNMP Adapter和HTTP Adapter。而Connector则是使用类似RPC的方式来访问Agent,在Agent端和客户端都必须有这样一个对象来处理相应的请求与应答。比如RMI Connector。

JMX Agent可以带有任意多个Adapter,因此可以使用多种不同的方式访问Agent。

JMX基本构架

JMX分为三层,分别负责处理不同的事务。它们分别是:

  • Instrumentation 层
    Instrumentation层主要包括了一系列的接口定义和描述如何开发MBean的规范。通常JMX所管理的资源有一个或多个MBean组成,因此这个资源可以是任何由Java语言开发的组件,或是一个JavaWrapper包装的其他语言开发的资源。
  • Agent 层
    Agent 用来管理相应的资源,并且为远端用户提供访问的接口。Agent层构建在Intrumentation层之上,并且使用并管理 Instrumentation层内部描述的组件。Agent层主要定义了各种服务以及通信模型。该层的核心是一MBeanServer,所有的MBean都要向它注册,才能被管理。注册在MBeanServer上的MBean并不直接和远程应用程序进行通信,他们通过协议适配器(Adapter)和连接器(Connector)进行通信。通常Agent由一个MBeanServer和多个系统服务组成。JMX Agent并不关心它所管理的资源是什么。
  • Distributed 层
    Distributed层关心Agent如何被远端用户访问的细节。它定义了一系列用来访问Agent的接口和组件,包括Adapter和Connector的描述。

如果一个Java对象可以由一个遵循JMX规范的管理器应用管理,那么这个Java对象就可以由JMX管理资源。要使一个Java对象可管理,则必须创建相应的MBean对象,并通过这些MBean对象管理相应的Java对象。当拥有MBean类后,需要将其实例化并注册到MBeanServer上。


详述

这里采用的是JDK7,JDK7中已经包含了jmx,但是如果用到HtmlAdaptorServer类(后面会看到)还需要用到jmxtools.jar, 可以去这里下载,有两个包:jmx-1_2_1-ri.zip; jmx_remote-1_0_1_03-ri.zip。jmx-1_2_1-ri.zip解压后lib中有jmxri.jar和jmxtools.jar,将jmxtool.jar拷贝出来放入classpath中即可(jmxri.jar在JDK5+已经包被包含了)。

Standard MBean

Standard MBean的设计和实现是最简单的,它们的管理接口通过方法名来描述。Standard MBean的实现依靠一组命名规则,称之为设计模式。这些命名规则定义了属性和操作。
检查Standard MBean接口和应用设计模式的过程被称为内省(Introspection)。JMX代理通过内省来查看每一个注册在MBeanServer上的MBean的方法和超类,看它是否遵从一定设计模式,决定它是否代表了一个MBean,并辨认出它的属性和操作。
Standard MBean是JMX管理构件中最简单的一种,只需要开发一个MBean接口(为了实现Standard MBean,必须遵循一套继承规范。必须每一个MBean定义一个接口,而且这个接口的名字必须是其被管理的资源的对象类的名称后面加上”MBean”),一个实现MBean接口的类,并且把它们注册到MBeanServer中就可以了。

package com.test.jmx;

public interface HelloMBean {
    public String getName();
    public void setName(String name);
    public void printHello();
    public void printHello(String whoName);
}

接下来是真正的资源对象,因为命名规范的限制,因此对象名称必须为Hello.

package com.test.jmx;

public class Hello implements HelloMBean {
    private String name;
    @Override
    public String getName() {
        return name;
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void printHello() {
        System.out.println("Hello world, "+ name);
    }

    @Override
    public void printHello(String whoName) {
        System.out.println("Hello, "+whoName);
    }
}

接下去创建一个Agent类:

package com.test.jmx;

import com.sun.jdmk.comm.HtmlAdaptorServer;

import javax.management.*;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class HelloAgent {
    public static void main(String[] args) throws MalformedObjectNameException,
            NotCompliantMBeanException, InstanceAlreadyExistsException,
            MBeanRegistrationException, IOException {
        // 下面这种方式不能再JConsole中使用
//      MBeanServer server = MBeanServerFactory.createMBeanServer();
// 首先建立一个MBeanServer,MBeanServer用来管理我们的MBean,通常是通过MBeanServer来获取我们MBean的信息,间接
// 调用MBean的方法,然后生产我们的资源的一个对象。
        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();

        String domainName = "MyMBean";

//为MBean(下面的new Hello())创建ObjectName实例
        ObjectName helloName = new ObjectName(domainName+":name=HelloWorld");
// 将new Hello()这个对象注册到MBeanServer上去
        mbs.registerMBean(new Hello(),helloName);

// Distributed Layer, 提供了一个HtmlAdaptor。支持Http访问协议,并且有一个不错的HTML界面,这里的Hello就是用这个作为远端管理的界面
// 事实上HtmlAdaptor是一个简单的HttpServer,它将Http请求转换为JMX Agent的请求
        ObjectName adapterName = new ObjectName(domainName+":name=htmladapter,port=8082");
        HtmlAdaptorServer adapter = new HtmlAdaptorServer();
        adapter.start();
        mbs.registerMBean(adapter,adapterName);

        int rmiPort = 1099;
        Registry registry = LocateRegistry.createRegistry(rmiPort);

        JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:"+rmiPort+"/"+domainName);
        JMXConnectorServer jmxConnector = JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs);
        jmxConnector.start();
    }
}

编译运行,在浏览器中输入localhost:8082,这样我们就可以对程序进行管理,如图:

可以看到我们注册的MyMBean域下的”name=HelloWorld”,可以点击进去,然后可以修改属性Name和执行2个printHello方法,可以在控制台看到效果。具体不贴图赘述,机智的小伙伴一试就知道怎么玩转了。

上面代码中还通过RMI(JMXServiceURL, JMXConnectorServer )注册URL来提供客户端连接,可以通过JConsole作为客户端来管理MBean. 打开JConsole工具(%JAVA_HOME%/bin/jconsole.exe),如图在远程进程中输入rmi地址“service:jmx:rmi:///jndi/rmi://localhost:1099/MyMBean”:

点击“连接”之后就出现:

这样就可以像HTML一样管理MBean了。

注意上面的代码中:

Registry registry = LocateRegistry.createRegistry(rmiPort);

可以在某一特定端口创建名字夫妇,从而用户无需再手工启动rmiregistry,如果不加入这句代码,就会出现Connection Refused的异常:

Exception in thread "main" java.io.IOException: Cannot bind to URL [rmi://localhost:1099/MyMBean]: javax.naming.ServiceUnavailableException [Root exception is java.rmi.ConnectException: Connection refused to host: localhost; nested exception is: 
    java.net.ConnectException: Connection refused: connect]
    at javax.management.remote.rmi.RMIConnectorServer.newIOException(Unknown Source)
    at javax.management.remote.rmi.RMIConnectorServer.start(Unknown Source)
    at com.test.jmx.HelloAgent.main(HelloAgent.java:44)
Caused by: javax.naming.ServiceUnavailableException [Root exception is java.rmi.ConnectException: Connection refused to host: localhost; nested exception is: 
    java.net.ConnectException: Connection refused: connect]
    at com.sun.jndi.rmi.registry.RegistryContext.bind(Unknown Source)
    at com.sun.jndi.toolkit.url.GenericURLContext.bind(Unknown Source)
    at javax.naming.InitialContext.bind(Unknown Source)
    at javax.management.remote.rmi.RMIConnectorServer.bind(Unknown Source)
    ... 2 more
Caused by: java.rmi.ConnectException: Connection refused to host: localhost; nested exception is: 
    java.net.ConnectException: Connection refused: connect
    at sun.rmi.transport.tcp.TCPEndpoint.newSocket(Unknown Source)
    at sun.rmi.transport.tcp.TCPChannel.createConnection(Unknown Source)
    at sun.rmi.transport.tcp.TCPChannel.newConnection(Unknown Source)
    at sun.rmi.server.UnicastRef.newCall(Unknown Source)
    at sun.rmi.registry.RegistryImpl_Stub.bind(Unknown Source)
    ... 6 more
Caused by: java.net.ConnectException: Connection refused: connect
    at java.net.DualStackPlainSocketImpl.connect0(Native Method)
    at java.net.DualStackPlainSocketImpl.socketConnect(Unknown Source)
    at java.net.AbstractPlainSocketImpl.doConnect(Unknown Source)
    at java.net.AbstractPlainSocketImpl.connectToAddress(Unknown Source)
    at java.net.AbstractPlainSocketImpl.connect(Unknown Source)
    at java.net.PlainSocketImpl.connect(Unknown Source)
    at java.net.SocksSocketImpl.connect(Unknown Source)
    at java.net.Socket.connect(Unknown Source)
    at java.net.Socket.connect(Unknown Source)
    at java.net.Socket.<init>(Unknown Source)
    at java.net.Socket.<init>(Unknown Source)
    at sun.rmi.transport.proxy.RMIDirectSocketFactory.createSocket(Unknown Source)
    at sun.rmi.transport.proxy.RMIMasterSocketFactory.createSocket(Unknown Source)
    ... 11 more

当然,这也就其他的解决办法:运行 %JAVA_HOME%/bin/rmiregistry.exe 1099就有和那行代码一样的效果。

我们不仅可以通过JConsole作为客户端采用rmi的方式来进行管理,我们同样可以采用自定义程序作为客户端来连接JMXConnectorServer管理MBean.

package com.test.jmx;

import java.io.IOException;
import java.util.Iterator;
import java.util.Set;

import javax.management.Attribute;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanServerConnection;
import javax.management.MBeanServerInvocationHandler;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

public class Client {

    public static void main(String[] args) throws IOException,
            MalformedObjectNameException, InstanceNotFoundException,
            AttributeNotFoundException, InvalidAttributeValueException,
            MBeanException, ReflectionException, IntrospectionException {
        String domainName = "MyMBean";
        int rmiPort = 1099;
        JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:"+rmiPort+"/"+domainName);
        // 可以类比HelloAgent.java中的那句:
        // JMXConnectorServer jmxConnector = JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs);
        JMXConnector jmxc = JMXConnectorFactory.connect(url);
        MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();

        //print domains
        System.out.println("Domains:------------------");
        String domains[] = mbsc.getDomains();
        for(int i=0;i<domains.length;i++){
            System.out.println("\tDomain["+i+"] = "+domains[i]);
        }
        //MBean count
        System.out.println("MBean count = "+mbsc.getMBeanCount());
        //process attribute
        ObjectName mBeanName = new ObjectName(domainName+":name=HelloWorld");
        mbsc.setAttribute(mBeanName, new Attribute("Name","zzh"));//注意这里是Name而不是name
        System.out.println("Name = "+mbsc.getAttribute(mBeanName, "Name"));

        //接下去是执行Hello中的printHello方法,分别通过代理和rmi的方式执行
        //via proxy
        HelloMBean proxy = MBeanServerInvocationHandler.newProxyInstance(mbsc, mBeanName, HelloMBean.class, false);
        proxy.printHello();
        proxy.printHello("jizhi boy");
        //via rmi
        mbsc.invoke(mBeanName, "printHello", null, null);
        mbsc.invoke(mBeanName, "printHello", new String[]{"jizhi gril"}, new String[]{String.class.getName()});

        //get mbean information
        MBeanInfo info = mbsc.getMBeanInfo(mBeanName);
        System.out.println("Hello Class: "+info.getClassName());
        for(int i=0;i<info.getAttributes().length;i++){
            System.out.println("Hello Attribute:"+info.getAttributes()[i].getName());
        }
        for(int i=0;i<info.getOperations().length;i++){
            System.out.println("Hello Operation:"+info.getOperations()[i].getName());
        }

        //ObjectName of MBean
        System.out.println("all ObjectName:--------------");
        Set<ObjectInstance> set = mbsc.queryMBeans(null, null);
        for(Iterator<ObjectInstance> it = set.iterator();it.hasNext();){
            ObjectInstance oi = it.next();
            System.out.println("\t"+oi.getObjectName());
        }
        jmxc.close();
    }
}

运行结果:

Domains:------------------
    Domain[0] = MyMBean
    Domain[1] = java.nio
    Domain[2] = JMImplementation
    Domain[3] = com.sun.management
    Domain[4] = java.lang
    Domain[5] = java.util.logging
MBean count = 21
Name = zzh
Hello Class: com.test.jmx.Hello
Hello Attribute:Name
Hello Operation:printHello
Hello Operation:printHello
all ObjectName:--------------
    java.lang:type=OperatingSystem
    java.lang:type=Compilation
    java.lang:type=MemoryPool,name=PS Old Gen
    java.lang:type=Memory
    JMImplementation:type=MBeanServerDelegate
    java.lang:type=MemoryPool,name=PS Perm Gen
    java.lang:type=Runtime
    MyMBean:name=htmladapter,port=8082
    java.nio:type=BufferPool,name=direct
    java.lang:type=GarbageCollector,name=PS MarkSweep
    java.nio:type=BufferPool,name=mapped
    java.lang:type=Threading
    com.sun.management:type=HotSpotDiagnostic
    java.lang:type=GarbageCollector,name=PS Scavenge
    MyMBean:name=HelloWorld
    java.lang:type=ClassLoading
    java.lang:type=MemoryPool,name=PS Survivor Space
    java.lang:type=MemoryManager,name=CodeCacheManager
    java.lang:type=MemoryPool,name=Code Cache
    java.util.logging:type=Logging
    java.lang:type=MemoryPool,name=PS Eden Space

这是客户端的运行结果,由于在客户端调用了服务端的方法,可以在服务端看到打印结果:

Hello world, zzh
Hello, jizhi boy
Hello world, zzh
Hello, jizhi gril

上面代码涉及到辅助原数据的概念:辅助元数据类用来描述管理构件。辅助元数据类不仅被用来内省标准管理构件,也被动态管理构件用来进行自我描述。这些类根据属性、操作、构建器和通告描述了管理接口。JMX代理通过这些元数据类管理所有管理构件,而不管这些管理构件的类型。部分辅助元类如下:

  1. MBeanInfo–包含了属性、操作、构建器和通知的信息。
  2. MBeanFeatureInfo–为下面类的超类。
  3. MBeanAttributeInfo–用来描述管理构件中的属性。
  4. MBeanConstructorInfo–用来描述管理构件中的构建器。
  5. MBeanOperationInfo–用来描述管理构件中的操作。
  6. MBeanParameterInfo–用来描述管理构件操作或构建器的参数。
  7. MBeanNotificationInfo–用来描述管理构件发出的通知。

有关Notication, Dynamic MBean, Model MBean以及Apache Common Modeler由于篇幅限制将后面文章中讲述。


参考资料

  1. JMX整理
  2. JMX简介
  3. http://blog.csdn.net/DryKillLogic/article/category/762777
  4. 用Apache的commons-modeler来辅助开发JMX 

本系列:



相关文章

发表评论

Comment form

(*) 表示必填项

还没有评论。

跳到底部
返回顶部