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。 关键代码是通过MBeanServer
的registerMBean()
方法注册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获取。