创建自定义 JMXClient 端

本教程的先前类向您展示了如何创建 JMX 技术 MBean 和 MXBean,以及如何向 JMX 代理注册它们。但是,所有前面的示例都使用了现有的 JMXClient 端 JConsole。本课将演示如何创建自己的自定义 JMXClient 端。

自定义 JMXClient 端Client的示例包含在jmx_examples.zip中。该 JMXClient 端与上一课中所见的 MBean,MXBean 和 JMX 代理进行交互。由于Client类的大小,在以下部分中将按块对它进行检查。

导入 JMX 远程 API 类

为了能够创建到从 JMXClient 端远程运行的 JMX 代理的连接,您需要使用javax.management.remote中的类。

package com.example;
...

import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

public class Client {
...

Client类将创建JMXConnector个实例,为此将需要JMXConnectorFactoryJMXServiceURL

创建通知监听器

JMXClient 端需要一个通知处理程序,以侦听和处理 JMX 代理的 MBean 服务器中注册的 MBean 可能发送的任何通知。 JMXClient 端的通知处理程序是NotificationListenerinterface的实例,如下所示。

... 

public static class ClientListener implements NotificationListener {

    public void handleNotification(Notification notification,
            Object handback) {
        echo("\nReceived notification:");
        echo("\tClassName: " + notification.getClass().getName());
        echo("\tSource: " + notification.getSource());
        echo("\tType: " + notification.getType());
        echo("\tMessage: " + notification.getMessage());
        if (notification instanceof AttributeChangeNotification) {
            AttributeChangeNotification acn =
                (AttributeChangeNotification) notification;
            echo("\tAttributeName: " + acn.getAttributeName());
            echo("\tAttributeType: " + acn.getAttributeType());
            echo("\tNewValue: " + acn.getNewValue());
            echo("\tOldValue: " + acn.getOldValue());
        }
    }
}    
...

该通知侦听器确定接收到的所有通知的来源,并检索存储在通知中的信息。然后,它根据收到的通知类型对通知信息执行不同的操作。在这种情况下,当侦听器收到类型为AttributeChangeNotification的通知时,它将通过调用AttributeChangeNotification方法getAttributeNamegetAttributeTypegetNewValuegetOldValue来获取已更改的 MBean 属性的名称和类型以及其旧值和新值。

稍后在代码中创建一个新的ClientListener实例。

ClientListener listener = new ClientListener();

创建 RMI 连接器 Client 端

Client类创建一个 RMI 连接器 Client 端,该 Client 端配置为连接到您将在启动 JMX 代理Main时启动的 RMI 连接器服务器。这将允许 JMXClient 端与 JMX 代理进行交互,就像它们在同一台计算机上运行一样。

...
    
public static void main(String[] args) throws Exception {

echo("\nCreate an RMI connector client and " +
    "connect it to the RMI connector server");
JMXServiceURL url = 
    new JMXServiceURL("service:jmx:rmi:///jndi/rmi://:9999/jmxrmi");
JMXConnector jmxc = JMXConnectorFactory.connect(url, null);
...

如您所见,Client定义了一个名为urlJMXServiceURL,它表示连接器 Client 端希望在其中找到连接器服务器的位置。该 URL 允许连接器 Client 端从在 localhost 的端口 9999 上运行的 RMI 注册表中检索 RMI 连接器服务器存根jmxrmi,并连接到 RMI 连接器服务器。

通过这样识别 RMI 注册表,可以创建连接器 Client 端。连接器 Client 端jmxc是interfaceJMXConnector的实例,该interface是由JMXConnectorFactoryconnect()方法创建的。 connect()方法被传递给参数url和一个空环境 Map。

连接到远程 MBean 服务器

有了 RMI 连接后,JMXClient 端必须连接到远程 MBean 服务器,以便它可以与远程 JMX 代理在其中注册的各种 MBean 交互。

...
        
MBeanServerConnection mbsc = 
    jmxc.getMBeanServerConnection();
                
...

然后,通过调用JMXConnector实例jmxcgetMBeanServerConnection()方法来创建名为 mbsc 的MBeanServerConnection实例。

现在,连接器 Client 端已连接到 JMX 代理创建的 MBean 服务器,并且可以注册 MBean 并对其执行操作,而连接对两端完全透明。

首先,Client 端定义一些简单的操作来发现有关在代理的 MBean 服务器中找到的 MBean 的信息。

...
        
echo("\nDomains:");
String domains[] = mbsc.getDomains();
Arrays.sort(domains);
for (String domain : domains) {
    echo("\tDomain = " + domain);
}
        
...
        
echo("\nMBeanServer default domain = " + mbsc.getDefaultDomain());

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);
}
      
...

Client 端调用MBeanServerConnection的各种方法来获取运行不同 MBean 的域,在 MBean 服务器中注册的 MBean 的数量以及所发现的每个 MBean 的对象名称。

通过代理对远程 MBean 执行操作

Client 端通过创建 MBean proxy 通过 MBean 服务器连接访问 MBean 服务器中的Hello MBean。此 MBean 代理对于 Client 端而言是本地的,并且模拟远程 MBean。

...

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

echo("\nAdd notification listener...");
mbsc.addNotificationListener(mbeanName, listener, null, null);

echo("\nCacheSize = " + mbeanProxy.getCacheSize());

mbeanProxy.setCacheSize(150);

echo("\nWaiting for notification...");
sleep(2000);
echo("\nCacheSize = " + mbeanProxy.getCacheSize());
echo("\nInvoke sayHello() in Hello MBean...");
mbeanProxy.sayHello();

echo("\nInvoke add(2, 3) in Hello MBean...");
echo("\nadd(2, 3) = " + mbeanProxy.add(2, 3));

waitForEnterPressed();
        
...

MBean 代理使您可以通过 Java interface访问 MBean,从而使您可以在代理上进行调用,而不必编写冗 Long 的代码来访问远程 MBean。通过在javax.management.JMX类中调用newMBeanProxy()的方法来创建Hello的 MBean 代理,并将其传递给 MBean 的MBeanServerConnection,对象名,MBean interface的类名和true,以表示该代理必须表现为NotificationBroadcaster。现在,JMXClient 端可以执行Hello定义的操作,就好像它们是本地注册的 MBean 的操作一样。 JMXClient 端还添加了一个通知侦听器,并更改了 MBean 的CacheSize属性,以使其发送通知。

通过代理对远程 MXBean 执行操作

您可以使用与创建 MBean 代理完全相同的方式为 MXBean 创建代理。

...
        
ObjectName mxbeanName = new ObjectName ("com.example:type=QueueSampler");
QueueSamplerMXBean mxbeanProxy = JMX.newMXBeanProxy(mbsc, 
    mxbeanName,  QueueSamplerMXBean.class);
QueueSample queue1 = mxbeanProxy.getQueueSample();
echo("\nQueueSample.Date = " + queue1.getDate());
echo("QueueSample.Head = " + queue1.getHead());
echo("QueueSample.Size = " + queue1.getSize());
echo("\nInvoke clearQueue() in QueueSampler MXBean...");
mxbeanProxy.clearQueue();

QueueSample queue2 = mxbeanProxy.getQueueSample();
echo("\nQueueSample.Date = " +  queue2.getDate());
echo("QueueSample.Head = " + queue2.getHead());
echo("QueueSample.Size = " + queue2.getSize());

...

如上所示,要为 MXBean 创建代理,您要做的就是调用JMX.newMXBeanProxy而不是newMBeanProxy。 MXBean 代理mxbeanProxy允许 Client 端调用QueueSample MXBean 的操作,就好像它们是本地注册的 MXBean 的操作一样。

关闭连接

一旦 JMXClient 端获得了所需的所有信息并在远程 JMX 代理的 MBean 服务器中的 MBean 上执行了所有必需的操作,就必须关闭该连接。

jmxc.close();

调用JMXConnector.close方法将关闭连接。

运行自定义 JMXClient 端示例

此示例需要 Java SE 平台的版本 6.要使用自定义 JMXClient 端Client远程监视Main JMX 代理,请执行以下步骤:

  • 如果尚未这样做,请将jmx_examples.zip保存到work_dir目录中。

  • 通过在终端窗口中使用以下命令来解压缩 samples 类包。

unzip jmx_examples.zip
  • work_dir目录中编译示例 Java 类。
javac com/example/*.java
  • 启动Main应用程序,指定为远程 管理 公开Main的属性:
java -Dcom.sun.management.jmxremote.port=9999 \
     -Dcom.sun.management.jmxremote.authenticate=false \
     -Dcom.sun.management.jmxremote.ssl=false \ 
     com.example.Main

生成确认Main正在 await 发生的事情。

  • 在另一个终端窗口中启动Client应用程序:
java com.example.Client

显示确认已获得MBeanServerConnection的确认。

  • Press Enter.

显示在Main开头的 MBean 服务器中注册的所有 MBean 的域。

  • 再次按 Enter。

显示在 MBean 服务器中注册的 MBean 数量,以及所有这些 MBean 的对象名称。显示的 MBean 包括 Java VM 中运行的所有标准平台 MXBean,以及Main在 MBean 服务器中注册的Hello MBean 和QueueSampler MXBean。

  • 再次按 Enter。

Hello MBean 的操作由Client调用,结果如下:

  • 通知侦听器已添加到Client,以侦听来自Main的通知。

  • CacheSize属性的值从 200 更改为 150.

  • 在您启动Main的终端窗口中,显示CacheSize属性更改的确认。

  • 在您启动Client的终端窗口中,显示来自Main的通知,通知Client CacheSize属性更改。

  • Hello MBean 的sayHello操作被调用。

  • 在您启动Main的终端窗口中,显示消息“ Hello world”。

  • 调用Hello MBean 的add操作,并使用值 2 和 3 作为参数。结果以Client显示。

  • 再次按 Enter。

QueueSampler MXBean 的操作由Client调用,结果如下:

  • 显示QueueSampledateheadsize

  • clearQueue操作被调用。

  • 再次按 Enter。

Client关闭与 MBean 服务器的连接,并显示确认信息。