本篇文章内容主要来源于官方文档《Trail: Java Management Extensions (JMX)》,目的是体验一波JMX。


什么是JMX?

JMX(Java Management Extensions)是一个为监控应用资源而制定的一套规范。JMX规范使用Java语言定义了一套体系、提供了接口和服务。JMX的体系结构如下所示:
831179-20160924113748309-1785034710.png

总共分为了三层:

  • 设备层(Instrumentation),由许多提供属性、操作的MBean组成,这些MBean就是对外暴露资源用的对象。
  • 代理层(Agent),设备层的MBean需要注册到代理层,用户可以在代理层对MBean进行查找、注册、删除等操作
  • 适配层(Adapter),允许管理工具层使用不同的网络协议来操作代理层
  • 管理工具层(Client),直接面向用户的GUI工具

设备层

设备层由众多MBean组成。MBean是代表某些资源的Java对象,遵循一定的规范,能够将属性、操作暴露出去。通常一个MBean需要支持以下功能:

  1. 能被访问的属性
  2. 能够做一些更新操作
  3. 能够发出通知事件

比如要定义一个标准的Bean(还有其他种类的 MBean ),需要做以下两个步骤:

  • 创建接口SomethingMBean
  • 实现上述接口的类必须叫做Something

比如现在创建一个接口, WebServerConfigMBean

  1. public interface WebServerConfigMBean {
  2. String getApplicationName();
  3. void setCache(int size);
  4. int getCache();
  5. }

实现WebServerConfigMBean的类名必须叫做 WebServerConfig ,否则会报一个
javax.management.NotCompliantMBeanException 异常,说我们这个类既不是 XMBeanStandard MBean 、 也不是Dynamic MBean

  1. public class WebServerConfig implements WebServerConfigMBean {
  2. private String applicationName;
  3. private int cache;
  4. public WebServerConfig(String applicationName, int cache) {
  5. this.applicationName = applicationName;
  6. this.cache = cache;
  7. }
  8. @Override
  9. public String getApplicationName() {
  10. return this.applicationName;
  11. }
  12. @Override
  13. public void setCache(int size) {
  14. this.cache = size;
  15. }
  16. @Override
  17. public int getCache() {
  18. return this.cache;
  19. }
  20. }

代理层

代理层总的来说就是用来管理 MBean 的,并让外部的应用程序可以控制这些MBean。
主要包含 MBean Server 、管理 MBean 的服务、一个连接器或协议适配器。

远程管理层

远程管理需要使用协议获取到所有MBean的资源信息,而连接器主要用来控制资源。

实战1

定义MBean

MBean的种类很多,后面会逐一介绍,这里主要先讲最简单的 Standard MBeanStandard Bean 需要定义一个接口。注意,只要是 **MBean ,每个接口都要带有 MBean** 的后缀sayHello()add() 是暴露给外部的可调用的方法, getName() 是必要的返回 MBean 名称的方法, get/setCacheSize() 是暴露给外部查看/修改资源的方法。

  1. public interface HelloMBean {
  2. public void sayHello();
  3. public int add(int x, int y);
  4. public String getName();
  5. public int getCacheSize();
  6. public void setCacheSize(int size);
  7. }

这里给出 HelloMBean 的实现,注意这里不需要带 MBean 后缀了:

public class Hello implements HelloMBean {
    private static final String name = "Reginald";
    private static final int
            DEFAULT_CACHE_SIZE = 200;
    private int cacheSize = DEFAULT_CACHE_SIZE;
    @Override
    public void sayHello() {
        System.out.println("hello, world");
    }
    @Override
    public int add(int x, int y) {
        return x + y;
    }
    @Override
    public String getName() {
        return name;
    }
    @Override
    public int getCacheSize() {
        return this.cacheSize;
    }
    @Override
    public void setCacheSize(int size) {
        this.cacheSize = size;
        System.out.println("Cache size now " + this.cacheSize);
    }
}

创建JmxAgent管理资源

一开始是通过 ManagementFactory 获取 MBeanServer ,如果这样获取不到就会尝试通过 MBeanServerFactory#createMBeanServer() 来获取 MBeanServer

public static void main( String[] args ) throws Exception {
    MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
    ObjectName name = new ObjectName("com.example:type=Hello");
    Hello mbean = new Hello();
    mbs.registerMBean(mbean, name);
    System.out.println("Waiting forever...");
    Thread.sleep(Long.MAX_VALUE);
}

此外这里还要注意,每个被注册的 MBean 必须带有名字,即 ObjectName ,且名字必须遵守 域名:键值对1[,键值对2...] 这样的规范来。之后通过 registerMBean 来注册对应的 MBean 。最后,外部的资源管理器就能查看 cacheSize 、修改 cacheSize 、执行一些方法了~

通过远程管理器管理资源

打开 jconsole (或者装了 MBean插件JVisual ),进入我们启动的这个这个程序,选择最后一栏 MBean
image.png

Name属性

image.png

CacheSize属性

image.png
如果把 CacheSize 从“200” 改为 “300”,那么控制台会输出 “Cache size now 300 ”

add方法

相当于在 Hello 这个对象上调用 add() 方法,值是外部传入的!输出结果会返回回来,如果有返回值会以弹窗的形式返回;没有返回值则近提示调用成功。
image.png

sayHello方法

因为没有参数,所以直接点击按钮调用。因为无返回值,所以仅提示调用成功。
image.png

实战2:增加MBean的消息通知

在实战1的基础上,增加通知机制,即有变化通知订阅者~
首先对 MBean 进行改造:

public class Hello extends NotificationBroadcasterSupport implements HelloMBean {
    // ...省略
    // 当修改cacheSize就触发通知,告诉所有订阅者
    public synchronized void setCacheSize(int size) {
        int oldSize = this.cacheSize;
        this.cacheSize = size;
        System.out.println("Cache size now " + this.cacheSize);
        Notification n = new AttributeChangeNotification(this,
                sequenceNumber++, System.currentTimeMillis(),
                "CacheSize changed", "CacheSize", "int",
                oldSize, this.cacheSize);
        sendNotification(n);
    }
    // 用来获取消息列表的
    @Override
    public MBeanNotificationInfo[] getNotificationInfo() {
        String[] types = new String[]{
                AttributeChangeNotification.ATTRIBUTE_CHANGE
        };
        String name = AttributeChangeNotification.class.getName();
        String description = "An attribute of this MBean has changed";
        Descriptor descriptor = new ImmutableDescriptor(new String[]{"size"}, new Object[]{this.cacheSize});
        MBeanNotificationInfo info =
                new MBeanNotificationInfo(types, name, description, descriptor);
        return new MBeanNotificationInfo[]{info};
    }
    // 新增消息序列
    private long sequenceNumber = 1;
}

当在通知栏目点击订阅后,再修改 CacheSize 就会触发消息通知:
image.png

实战3:自定义JMX Client

之前都是通过 JConsole 来管理 MBean 对象,这回要自定义一个 Client 来管理 MBean 对象。
第一步,创建一个 JMXServiceURL ,这货用来创建一个URL,这个URL的结构拆分如下所示:

  1. 该URL必须要以 service:jmx 开头,
  2. 使用RMI注册器? :rmi:///jndi/rmi://:9999/jmxrmi (总之这部分是要用到RMI技术的)
    JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://:9999/jmxrmi");
    
    第二步,使用上面的 url 创建JMX连接,即 JMXConnector 。这一步看文档说是和 ConnectorServer 连接, ConnectorServer 是啥呢?:
    JMXConnector jmxc = JMXConnectorFactory.connect(url, null);
    
    第三步,通过 JMXConnector 获取到 MBeanServerConnectionMBeanServerConnection 应该就是负责 MBean 的管理。
    MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
    
    第四步,通过 MBeanServerConnection 获取 MBean 的所有 domain (后面省略一些没啥意思的操作):
    String domains[] = mbsc.getDomains();
    Arrays.sort(domains);
    for (String domain : domains) {
     System.out.println("\tDomain = " + domain);
    }
    

第五步,通过 MBeanServerConnection 获取 MBean 的数量:

echo("\nMBean count = " +  mbsc.getMBeanCount());
echo("\nQuery MBeanServer MBeans:");
Set<ObjectName> names = new TreeSet<ObjectName>(mbsc.queryNames(null, null));
for (ObjectName name : names) {
    echo("\tObjectName = " + name);
}

第六步,通过代理控制 MBean 对象:

ObjectName mbeanName = new ObjectName("com.example:type=Hello");
HelloMBean mbeanProxy = JMX.newMBeanProxy(mbsc, mbeanName,HelloMBean.class, true);

第七步,给该 MBean 添加监听事件:

// 自定义的监听器
ClientListener listener = new ClientListener();
// 给指定的Bean注册监听器
mbsc.addNotificationListener(mbeanName, listener, null, null);

第八步,修改 MBean 触发消息通知:

mbeanProxy.setCacheSize(150);

总结

官方提供的测试程序《jmx_examples.zip》

本篇文章只是体验一番JMX服务,这里对实战3里的 JMXConnectorProxy 还有 URL 方式很懵逼。通过本篇文章,能大致窥探到一些例如Tomcat的admin端操作的原理~