不积跬步,无以至千里

JMX


看tomcat源码的时候,看到一个MBean的概念,从而了解到了JMX. JMX,全称Java Management Extensions,是管理java的一种扩展.它提供了一个观察、改变正在运行的java程序变量的一个途径.

JMX能做什么

首先看一个示例程序,从网上找到的示例:

public interface UserMBean {
    String getUsername();
    void setUsername(String username);
    String getPassword();
    void setPassword(String password);
    int add(int x, int y);
}
public class User implements UserMBean {
    private String username;
    private String password;
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    @Override
    public String getPassword() {
        return password;
    }
    @Override
    public void setPassword(String password) {
        this.password = password;
    }
    public int add(int x, int y) {
        return x + y;
    }
}
import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.lang.management.ManagementFactory;
public class MBeanTest {
    public static void main(String[] args) {
        try {
            MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
            ObjectName objectName = new ObjectName("jmxdemo:type=User");
            User user = new User();
            beanServer.registerMBean(user, objectName);
            String oldusername = null;
            String oldpassword = null;
            while (true) {
                if (oldusername != user.getUsername() || oldpassword != user.getPassword()) {
                    System.out.println(user.getUsername() + ',' + user.getPassword());
                    oldusername = user.getUsername();
                    oldpassword = user.getPassword();
                }
                Thread.sleep(1000);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行MBeanTest的main方法后,在控制台通过jconsole命令启动java控制台监控

选中我们的测试类:MBeanTest连接,在MBeans标签下,找到我们的jmxdemo,可以看到User下的两个属性和一个方法.此时修改username 在main方法中,当User对象的属性发生变化的时候会在控制台打印新的属性值,修改后在控制台可以看到我们经过jconsole修改的属性已经生效.

JMX是什么

看下Apache的官方说明:

The JMX technology provides the tools for building distributed, Web-based, modular and 
dynamic solutions for managing and monitoring devices, applications, and service-driven networks. By design, 
this standard is suitable for adapting legacy systems, implementing new management and monitoring solutions, 
and plugging into those of the future. 

JMX为建立分布式,基于web技术,模块化动态管理和监控设备,应用,基于服务的网络提供了一个工具.根据设计,这个标准可以适配旧系统,也可以为新的系统提供一个增强的管理和监控功能.

JMX的架构分为三层

基础层

最底层的是MBean,也就是我们需要管理的类.MBean有三种:StandardMBean,DynamicMBean,OpenBean

  1. StandardMBean 大多数的MBean通过类名以MBean结尾来指定当前类为StandardMBean,如上边的UserMBean,然后通过代理来和MBean做交互:
public interface CacheControlMBean {
    //如果是需要做远程调用的,最好是在方法中抛出IOException,以免在做代理的时候,这个异常被UndeclaredThrowableException包裹
    int getSize() throws IOException;
    void setSize(int size) throws IOException;
    int getUsage() throws IOException;
    int dropOldest(int n) throws IOException;
}
public void main() {
    MBeanServer mbs = ...;
    Integer sizeI = (Integer) mbs.getAttribute(objectName, "Size");
    int size = sizeI.intValue();
    if (size > desiredSize) {
        mbs.invoke(objectName,
                "dropOldest",
                new Integer[]{new Integer(size - desiredSize)},
                new String[]{"int"});
    }
    MBeanServer mbs = ...;
    CacheControlMBean cacheControl = (CacheControlMBean)
            MBeanServerInvocationHandler.newProxyInstance(mbs,
                    objectName,
                    CacheControlMBean.class,
                    false);
    int size = cacheControl.getSize();
    if (size > desiredSize)
        cacheControl.dropOldest(size - desiredSize);
}
  1. Dynamic MBeans

Dynamic MBeans用于在编译阶段未生成的接口或类,dynamic MBean必须实现javax.management.DynamicMBean接口,所有的属性,方法都在运行时定义

  1. OpenBean MBean中的属性只能使用基本的java类型,在jdk1.6之前,如果需要使用CompositeData来表示这个属性是OpenType类型,具体可以参考JMX的官方文档CompositeData.在JDK1.6中,引入了MXBean的概念,它会自动把CompositeType转换为指定的类型,使得MBean的属性可以是任何java类型. 要标示一个类为MXBean有以下方式:
//public并且类名以MXBean结尾
public interface WhatsitMXBean {}
//添加@MXBean注解
@MXBean
public interface Whatsit1Interface {}
@MXBean(true)
public interface Whatsit2Interface {}

尽管这样,在声明这个复杂类的时候,还是需要标示这个类是CompositeType,以便能够正常做转换.java文档中提供了4种方式:

Static {@code from} method:
public class NamedNumber {
    public int getNumber() {return number;}
    public String getName() {return name;}
    private NamedNumber(int number, String name) {
        this.number = number;
        this.name = name;
    }
    public static NamedNumber from(CompositeData cd) {
        return new NamedNumber((Integer) cd.get("number"),
                               (String) cd.get("name"));
    }
    private final int number;
    private final String name;
}

Public constructor with @ConstructorProperties annotation:
public class NamedNumber {
    public int getNumber() {return number;}
    public String getName() {return name;}
    @ConstructorProperties({"number", "name"})
    public NamedNumber(int number, String name) {
        this.number = number;
        this.name = name;
    }
    private final int number;
    private final String name;
}

Setter for every getter:
public class NamedNumber {
    public int getNumber() {return number;}
    public void setNumber(int number) {this.number = number;}
    public String getName() {return name;}
    public void setName(String name) {this.name = name;}
    public NamedNumber() {}
    private int number;
    private String name;
}

Interface with only getters:
public interface NamedNumber {
    public int getNumber();
    public String getName();
}

适配层

MBeanServer,主要是提供对资源的注册和管理,和远程做通信

接入层

提供远程访问的入口,接口是javax.management.remote.JMXConnector Java提供了几种方式来做接入层

  1. 直接在服务器端启动jconsole查看,也就是示例代码中展示的.
  2. 通过页面来访问 修改MBeanTest如下:
public class MBeanTest {
    public static void main(String[] args) {
        try {
// MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
// ObjectName objectName = new ObjectName("jmxdemo:type=User");
// User user = new User();
// beanServer.registerMBean(user, objectName);
            MBeanServer server = ManagementFactory.getPlatformMBeanServer();
            ObjectName objectName = new ObjectName("jmxdemo:type=User");
            User user = new User();
            server.registerMBean(user, objectName);
            ObjectName adapterName = new ObjectName("UserAgent:name=htmladapter,port=8082");
            HtmlAdaptorServer adapter = new HtmlAdaptorServer();
            server.registerMBean(adapter, adapterName);
            adapter.start();
            String oldusername = null;
            String oldpassword = null;
            while (true) {
                if (oldusername != user.getUsername() || oldpassword != user.getPassword()) {
                    System.out.println(user.getUsername() + ',' + user.getPassword());
                    oldusername = user.getUsername();
                    oldpassword = user.getPassword();
                }
                Thread.sleep(1000);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

通过浏览器访问:http://localhost:8082.
需要说明的是jdk中并未包含HtmlAdaptorServer类,需要到apache网站下载jmx-1_2_1-ri.zip解压后,将lib文件夹下的两个jar包加入到classpath中.

  1. 自己做二次开发 再次修改MBeanTest如下:
public class MBeanTest {
    public static void main(String[] args) {
        try {
            MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
            ObjectName objectName = new ObjectName("jmxdemo:type=User");
            User user = new User();
            beanServer.registerMBean(user, objectName);

            //这个步骤很重要,注册一个端口,绑定url后用于客户端通过rmi方式连接JMXConnectorServer
            LocateRegistry.createRegistry(9999);
            //URL路径的结尾可以随意指定,但如果需要用Jconsole来进行连接,则必须使用jmxrmi
            JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi");
            JMXConnectorServer jcs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, beanServer);
            jcs.start();
            
            String oldusername = null;
            String oldpassword = null;

            while (true) {
                if (oldusername != user.getUsername() || oldpassword != user.getPassword()) {
                    System.out.println(user.getUsername() + ',' + user.getPassword());
                    oldusername = user.getUsername();
                    oldpassword = user.getPassword();
                }
                Thread.sleep(1000);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

同时新建一个client类:

public class ClientTest {
    public static void main(String[] args) {
        try {
            JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi");
            JMXConnector jmxc = JMXConnectorFactory.connect(url, null);
            MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
            ObjectName mbeanName = new ObjectName("jmxdemo:type=User");
            
            //设置指定Mbean的特定属性值  //这里的setAttribute、getAttribute操作只能针对bean的属性
            //例如对getName或者setName进行操作,只能使用Name,需要去除方法的前缀
            mbsc.setAttribute(mbeanName, new Attribute("Password", "pass"));
            mbsc.setAttribute(mbeanName, new Attribute("Username", "user"));
            String password = (String) mbsc.getAttribute(mbeanName, "Password");
            String username = (String) mbsc.getAttribute(mbeanName, "Username");
            System.out.println("password=" + password + ";username=" + username);

            UserMBean proxy = MBeanServerInvocationHandler.newProxyInstance(mbsc, mbeanName, UserMBean.class, false);
            proxy.test();//在server端(MBeanTest)调用test方法            
            mbsc.invoke(mbeanName, "test", null, null);//在server端(MBeanTest)调用test方法
            //invoke调用bean的方法,只针对非设置属性的方法,不能对get和set方法做调用
            //mbsc.invoke(mbeanName, "getUsername", null, null);//会报错
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

在执行Client的main方法前,在User类中添加方法public void test(){System.out.println("test!!!");},同时UserMBean中添加接口public void test();

当然如果没有client类,也可以通过jconsole使用远程访问:

为什么要用

JMX有两大功能,一个是提供了对存变量的修改,一个是对内存中java变量的查看. 首先看怎样对内存中的变量做动态修改.目前实现动态修改内存中的变量有多种方式,如每隔特定的时间去某个服务中心拉取变量的值,或者服务中心主动推送值发生变化的变量,并且目前这种方式都可以通过中间件的方式实现,对业务流程无入侵.但这种方式需要对接入系统的框架做适配. 通过JMX则对系统使用的框架没有要求. Tomcat就是使用了JMX来实现类实例的控制,它通过JmxEnabled和LifecycleMBeanBase来对加载的类做统一的控制,同时LifecycleMBeanBase也管控了Tomcat加载的所有的类的实例.

以上总结了JMX是什么,怎么用,以及什么时候用的问题,感觉还是挺不错的.目前了解到JBOSS是通过JMX来实现的,后边有需要用到的场景的话可以再详细了解下.

参考

Java Management Extensions
JMX超详细解读