1. 简介

本文将介绍如何从Shell脚本访问MBean,以及最常用的工具实现方式。

需要明确的是,JMX基于RMI协议。 协议处理本身由Java完成,但我们可以将其封装在Shell脚本中实现命令行调用。这种方案特别适合自动化运维场景。

尽管实现简单,但多数JMX工具已被废弃或不可用。本文先分析几个现成工具,再手写一个定制方案。

2. 编写简单MBean

为测试工具,先创建一个简单计算器MBean。首先定义接口:

public interface CalculatorMBean {

    Integer sum();

    Integer getA();

    void setA(Integer a);

    Integer getB();

    void setB(Integer b);
}

然后实现类:

public class Calculator implements CalculatorMBean {

    private Integer a = 0;
    private Integer b = 0;

    // getters and setters

    @Override
    public Integer sum() {
        return a + b;
    }
}

接下来创建CLI应用注册MBean。 关键代码是通过MBeanServerregisterMBean()方法注册Calculator实例,并构造ObjectName

public class JmxCalculatorMain {

    public static void main(String[] args) throws Exception {
        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
        server.registerMBean(
          new Calculator(), new ObjectName("com.baeldung.jxmshell:type=basic,name=calculator")
        );

        // ...
    }
}

ObjectName需遵循官方规范:使用包名作为域(domain),配合key=value键值对。其中type是必填键(虽然我们这里用不到),但用于将MBean分组管理。

最后添加阻塞逻辑防止应用退出:

try (Scanner scanner = new Scanner(System.in)) {
    System.out.println("<press enter to terminate>");
    scanner.nextLine();
}

简化演示,我们使用端口11234,并通过JVM参数关闭认证和SSL:

-Dcom.sun.management.jmxremote.port=11234
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false

现在可以测试工具了。

3. 使用Jmxterm实现Shell调用

Jmxterm是替代JConsole的交互式CLI工具。通过输入重定向,可将其改造为非交互式脚本。先下载jmxterm-1.0.4-uber.jar放到/tmp目录。

3.1. 封装Jmxterm脚本

脚本核心功能:执行MBean操作、获取/设置属性值。 定义默认变量简化使用:

#!/bin/sh

jar='/tmp/jmxterm-1.0.4-uber.jar'
address='localhost:11234'

mbean='com.baeldung.jxmshell:name=calculator,type=basic'
operation='sum'
command="info -b $mbean"

⚠️ 注意:address需在JmxCalculatorMain运行后才可访问。operation变量保存MBean方法名或属性名,command变量存储最终命令。默认info命令显示MBean的可用操作和属性,-b指定目标MBean。

通过参数解析处理--run/--set/--get选项:

while test $# -gt 0
do
    case "$1" in
    --run)
        shift; operation=$1
        command="run -b ${mbean} ${operation}"
    ;;
    --set)
        shift; operation="$1"
        shift; attribute_value="$1"
    
        command="set -b ${mbean} ${operation} ${attribute_value}"
    ;;
    --get)
        shift; operation="$1"

        command="get -b ${mbean} ${operation}"
    ;;
    esac
    shift
done

--run执行MBean方法,--set设置属性值,--get获取属性值。

最后通过管道传递命令给Jmxterm,-v silent关闭冗余输出,-n禁用交互提示:

echo $command | java -jar $jar -l $address -n -v silent 

3.2. 操作MBean

赋予脚本执行权限后,无参数运行查看默认行为:

./jmxterm.sh

输出MBean信息:

# attributes
  %0   - A (java.lang.Integer, rw)
  %1   - B (java.lang.Integer, rw)
# operations
  %0   - java.lang.Integer sum()

✅ 注意:setter方法被自动识别为属性(移除了set前缀)。

调用setA()设置属性:

./jmxterm.sh --set A 1

成功时无输出。通过getA()验证当前值:

./jmxterm.sh --get A

输出:

A = 1;

设置B=2并调用sum()

./jmxter.sh --set B 2
./jmxter.sh --run sum

结果:

3

此方案适合简单MBean操作。

4. 使用cmdline-jmxclient命令行调用

下一个工具是cmdline-jmxclient。使用0.10.3版本,功能类似Jmxterm但无交互模式。

直接看命令行示例:设置属性值并执行操作:

jar=cmdline-jmxclient-0.10.3.jar
address=localhost:11234
mbean=com.baeldung.jxmshell:name=calculator,type=basic
java -jar $jar - $address $mbean A=1
java -jar $jar - $address $mbean B=1
java -jar $jar - $address $mbean sum

因无需认证,用-代替认证参数。

输出示例:

11/11/2022 22:10:15 -0300 org.archive.jmx.Client sum: 2

主要差异:

  • 输出格式不同
  • 包体更小(约20KB vs Jmxterm的6MB+)

5. 编写定制方案

现有工具已过时,正好借此机会深入理解MBean底层原理。 从封装javax.management包开始:

public class JmxConnectionWrapper {

    private final Map<String, MBeanAttributeInfo> attributeMap;
    private final MBeanServerConnection connection;
    private final ObjectName objectName;

    public JmxConnectionWrapper(String url, String beanName) throws Exception {
        objectName = new ObjectName(beanName);

        connection = JMXConnectorFactory.connect(new JMXServiceURL(url))
          .getMBeanServerConnection();

        MBeanInfo bean = connection.getMBeanInfo(objectName);
        MBeanAttributeInfo[] attributes = bean.getAttributes();

        attributeMap = Stream.of(attributes)
          .collect(Collectors.toMap(MBeanAttributeInfo::getName, Function.identity()));
    }

    // ...
}

构造函数接收JMX URL和MBean名称,保存ObjectName引用后通过JMXConnectorFactory建立连接。获取MBeanInfo后提取属性信息并转为Map存储。此Map用于后续区分属性和操作。

添加辅助方法:获取/设置属性值。当传入新值时先设置再返回当前值:

public Object attributeValue(String name, String value) throws Exception {
    if (value != null)
        connection.setAttribute(objectName, new Attribute(name, Integer.valueOf(value)));

    return connection.getAttribute(objectName, name);
}

因已知MBean只含Integer属性,直接用Integer.valueOf()构造值。健壮方案应通过attributeMap获取属性类型。

调用无参操作时,向invoke()传递空数组:

public Object invoke(String operation) throws Exception {
    Object[] params = new Object[] {};
    String[] signature = new String[] {};
    return connection.invoke(objectName, operation, params, signature);
}

用于调用非属性相关的方法。

5.1. 实现CLI部分

创建CLI应用操作MBean:

public class JmxInvoker {

    public static void main(String... args) throws Exception {
        String attributeValue = null;
        if (args.length > 3) {
            attributeValue = args[3];
        }

        String result = execute(args[0], args[1], args[2], attributeValue);
        System.out.println(result);
    }

    public static String execute(
      String url, String mBeanName, String operation, String attributeValue) {
        JmxConnectionWrapper connection = new JmxConnectionWrapper(url, mBeanName);

        // ...
    }
}

main()方法将参数传递给execute(),根据操作类型决定调用方式:

if (connection.hasAttribute(operation)) {
    Object value = connection.attributeValue(operation, attributeValue);
    return operation + "=" + value;
} else {
    Object result = connection.invoke(operation);
    return operation + "(): " + result;
}

当操作名匹配属性时调用attributeValue(),否则调用invoke()

5.2. 命令行调用

假设应用已打包为JAR并置于指定位置,定义默认值:

jar=/tmp/jmx-invoker.jar
address='service:jmx:rmi:///jndi/rmi://localhost:11234/jmxrmi'
invoker='com.baeldung.jmxshell.custom.JmxInvoker'
mbean='com.baeldung.jxmshell:name=calculator,type=basic'

执行命令设置属性并调用sum()

$ java -cp $jar $invoker $address $mbean A 1
A=1

$ java -cp $jar $invoker $address $mbean B 1
B=1
$ java -cp $jar $invoker $address $mbean sum
sum(): 2

定制化JmxInvoker可完全掌控MBean解析逻辑。

6. 总结

本文介绍了管理MBean的多种工具及其Shell脚本调用方式,并实现了定制工具。更多脚本示例见代码仓库。

源代码可在GitHub获取。


原始标题:Calling JMX MBean Method From a Shell Script | Baeldung