# 创建自定义 JMX 客户端

> 原文： [https://docs.oracle.com/javase/tutorial/jmx/remote/custom.html](https://docs.oracle.com/javase/tutorial/jmx/remote/custom.html)

此路径中的前面课程向您展示了如何创建 JMX 技术 MBean 和 MXBeans，以及如何使用 JMX 代理注册它们。但是，前面的所有示例都使用了现有的 JMX 客户端 JConsole。本课将演示如何创建自己的自定义 JMX 客户端。

自定义 JMX 客户端的示例 [`Client`](../examples/Client.java) 包含在 [`jmx_examples.zip`](../examples/jmx_examples.zip) 中。此 JMX 客户端与前面课程中看到的相同 MBean，MXBean 和 JMX 代理进行交互。由于`Client`类的大小，它将在以下部分中以块的形式进行检查。

## 导入 JMX 远程 API 类

为了能够创建与从 JMX 客户端远程运行的 JMX 代理的连接，您需要使用 [`javax.management.remote`](https://docs.oracle.com/javase/8/docs/api/javax/management/remote/package-summary.html) 中的类。

```
package com.example;
...

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

public class Client {
...

```

`Client`类将创建 [`JMXConnector`](https://docs.oracle.com/javase/8/docs/api/javax/management/remote/JMXConnector.html) 实例，为此需要 [`JMXConnectorFactory`](https://docs.oracle.com/javase/8/docs/api/javax/management/remote/JMXConnectorFactory.html) 和 [`JMXServiceURL`](https://docs.oracle.com/javase/8/docs/api/javax/management/remote/JMXServiceURL.html) 。

## 创建通知侦听器

JMX 客户端需要一个通知处理程序，以侦听和处理可能由在 JMX 代理的 MBean 服务器中注册的 MBean 发送的任何通知。 JMX 客户端的通知处理程序是 [`NotificationListener`](https://docs.oracle.com/javase/8/docs/api/javax/management/NotificationListener.html) 接口的实例，如下所示。

```
... 

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`](https://docs.oracle.com/javase/8/docs/api/javax/management/AttributeChangeNotification.html) 类型的通知时，它将通过调用`AttributeChangeNotification`获取已更改的 MBean 属性的名称和类型，以及其旧值和新值。 ]方法`getAttributeName`，`getAttributeType`，`getNewValue`和`getOldValue`。

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

```

ClientListener listener = new ClientListener();

```

## 创建 RMI 连接器客户端

`Client`类创建一个 RMI 连接器客户端，该客户端配置为连接到启动 JMX 代理程序`Main`时将启动的 RMI 连接器服务器。这将允许 JMX 客户端与 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`定义名为`url`的 [`JMXServiceURL`](https://docs.oracle.com/javase/8/docs/api/javax/management/remote/JMXServiceURL.html) ，它表示连接器客户端期望找到连接器服务器的位置。此 URL 允许连接器客户端从运行在本地主机的端口 9999 上的 RMI 注册表中检索 RMI 连接器服务器存根`jmxrmi`，并连接到 RMI 连接器服务器。

通过这样识别的 RMI 注册表，可以创建连接器客户端。连接器客户端`jmxc`是由 [`JMXConnectorFactory`](https://docs.oracle.com/javase/8/docs/api/javax/management/remote/JMXConnectorFactory.html) 的`connect()`方法创建的接口 [`JMXConnector`](https://docs.oracle.com/javase/8/docs/api/javax/management/remote/JMXConnector.html) 的实例。 `connect()`方法在调用时传递参数`url`和空环境贴图。

## 连接到远程 MBean 服务器

在 RMI 连接到位后，JMX 客户端必须连接到远程 MBean 服务器，以便它可以与远程 JMX 代理程序在其中注册的各种 MBean 进行交互。

```
...

MBeanServerConnection mbsc = 
    jmxc.getMBeanServerConnection();

...     

```

然后通过调用`JMXConnector`实例`jmxc`的`getMBeanServerConnection()`方法创建名为 mbsc 的 [`MBeanServerConnection`](https://docs.oracle.com/javase/8/docs/api/javax/management/MBeanServerConnection.html) 实例。

连接器客户端现在连接到由 JMX 代理创建的 MBean 服务器，并且可以注册 MBean 并对它们执行操作，连接对两端保持完全透明。

首先，客户端定义一些简单的操作来发现有关代理的 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);
}

...

```

客户端调用`MBeanServerConnection`的各种方法，以获取运行不同 MBean 的域，MBean 服务器中注册的 MBean 数，以及它发现的每个 MBean 的对象名。

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

客户端通过创建 MBean **代理**，通过 MBean 服务器连接访问 MBean 服务器中的`Hello` MBean。此 MBean 代理是客户端的本地代理，并模拟远程 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 接口访问 MBean，允许您在代理上进行调用，而不必编写冗长的代码来访问远程 MBean。这里通过调用 [`javax.management.JMX`](https://docs.oracle.com/javase/8/docs/api/javax/management/JMX.html) 类中的方法`newMBeanProxy()`创建`Hello`的 MBean 代理，传递 MBean 的`MBeanServerConnection`，对象名，MBean 接口的类名和`true`，表示代理必须表现为 [`NotificationBroadcaster`](https://docs.oracle.com/javase/8/docs/api/javax/management/NotificationBroadcaster.html) 。 JMX 客户端现在可以执行`Hello`定义的操作，就好像它们是本地注册的 MBean 的操作一样。 JMX 客户端还添加了通知侦听器并更改了 MBean 的`CacheSize`属性，以使其发送通知。

## 通过代理对远程 MXBeans 执行操作

您可以使用与创建 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`](https://docs.oracle.com/javase/8/docs/api/javax/management/JMX.html#newMXBeanProxy-javax.management.MBeanServerConnection-javax.management.ObjectName-java.lang.Class-) 而不是`newMBeanProxy`。 MXBean 代理`mxbeanProxy`允许客户端调用`QueueSample` MXBean 的操作，就好像它们是本地注册的 MXBean 的操作一样。

## 关闭连接

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

```
jmxc.close();

```

通过调用 [`JMXConnector.close`](https://docs.oracle.com/javase/8/docs/api/javax/management/remote/JMXConnector.html#close--) 方法关闭连接。

### 运行自定义 JMX 客户端示例

此示例需要 Java SE 平台的第 6 版。要使用自定义 JMX 客户端 [`Client`](../examples/Client.java) 远程监视`Main` JMX 代理，请按照下列步骤操作：

1.  如果尚未这样做，请将 [`jmx_examples.zip`](../examples/jmx_examples.zip) 保存到`work_dir`目录中。
2.  在终端窗口中使用以下命令解压缩示例类包。

    ```
    unzip jmx_examples.zip

    ```

3.  编译`work_dir`目录中的示例 Java 类。

    ```
    javac com/example/*.java

    ```

4.  Start the `Main` application, specifying the properties that expose `Main` for remote management:

    ```
    java -Dcom.sun.management.jmxremote.port=9999 \
         -Dcom.sun.management.jmxremote.authenticate=false \
         -Dcom.sun.management.jmxremote.ssl=false \ 
         com.example.Main

    ```

    生成`Main`正在等待发生某事的确认。

5.  Start the `Client` application in a different terminal window:

    ```
    java com.example.Client

    ```

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

6.  Press Enter.

    显示由`Main`启动的 MBean 服务器中注册的所有 MBean 的域。

7.  Press Enter again.

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

8.  Press Enter again.

    `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`。
9.  Press Enter again.

    `QueueSampler` MXBean 的操作由`Client`调用，结果如下：

    *   显示`QueueSample`值`date`，`head`和`size`。
    *   调用`clearQueue`操作。
10.  Press Enter again.

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