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,这段代码是标准的,关键部分是我们为MBean选择的实现类和名称。我们将通过调用MBeanServer上的registerMBean()、传递Calculator的实例并实例化ObjectName来完成此操作。
澄清一下,ObjectName接收一个任意字符串。但是,为了遵循约定,我们将包名作为域,以及“键=值”对列表:
public class JmxCalculatorMain {
public static void main(String[] args) throws Exception {
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
server.registerMBean(new Calculator(), new ObjectName("cn.tuyucheng.taketoday.jxmshell:type=basic,name=calculator"));
// ...
}
}
此外,唯一需要的键是“type”,这在我们的场景中无关紧要,但这会将我们的MBean与域中的其他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. 从Shell脚本使用Jmxterm
Jmxterm是一种交互式CLI工具,可作为使用JConsole监视MBean的替代方法。但是,通过一些输入操作,我们可以创建一个脚本以非交互方式使用它。让我们首先下载jmxterm-1.0.4-uber.jar并将其放入/tmp文件夹。
3.1 在脚本中包装Jmxterm
我们将在脚本中使用Jmxterm jar的一些功能:执行MBean操作并获取或设置属性值。为了简化它,我们将为它的jar位置、MBean address和name、operation和最终command定义默认值。
因此,让我们创建一个名为jmxterm.sh的文件:
#!/bin/sh
jar='/tmp/jmxterm-1.0.4-uber.jar'
address='localhost:11234'
mbean='cn.tuyucheng.taketoday.jxmshell:name=calculator,type=basic'
operation='sum'
command="info -b $mbean"
我们应该注意,只有在运行JmxCalculatorMain后才能访问该address。我们将在operation变量中保存来自MBean的方法名称或属性。同时,command变量保存发送到Jmxterm的最终命令。作为默认值,info命令显示可用的操作和属性,-b指定要显示的MBean。
然后,我们将进行一些参数解析,通过构建command变量来处理–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,我们可以获得它的当前值。
最后,我们echo我们的命令,将它与我们正在运行的应用程序的address一起通过管道传输到Jmxterm jar。-v silent选项关闭冗长,而-n告诉Jmxterm不要打印用户提示。因此,此选项很有用,因为我们没有以交互方式使用它:
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()
我们可以注意到set从我们的setter名称中删除,它们出现在attributes部分。
所以,让我们调用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=cn.tuyucheng.taketoday.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
值得注意的是,由于我们的MBean不需要身份验证,因此我们传递“-”而不是身份验证参数。
以下是运行这些命令的输出:
11/11/2022 22:10:15 -0300 org.archive.jmx.Client sum: 2
与此工具的主要区别在于它的输出格式。此外,它更轻巧,大小约为20KB,而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()));
}
// ...
}
首先,我们的构造函数接收一个要连接的URL和一个MBean名称,我们保留一个ObjectName的引用以在将来的调用中使用,然后开始与JMXConnectorFactory的连接。然后,我们得到一个MBeanInfo引用,从中提取属性。最后,我们将MBeanAttributeInfo[]转换为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中的信息来确定正确的类型。
同样,因为我们知道我们的MBean只包含无参数操作,所以我们在connection上调用invoke()并为参数和签名传递空数组:
public Object invoke(String operation) throws Exception {
Object[] params = new Object[] {};
String[] signature = new String[] {};
return connection.invoke(objectName, operation, params, signature);
}
我们将使用它来调用MBean中与属性无关的任何方法。
5.1 编写CLI部分
最后,让我们创建一个CLI应用程序来从shell操作我们的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;
}
如果我们的MBean包含一个具有操作名称的属性,我们将调用attributeValue()。否则,我们调用invoke()。
5.2 从命令行调用它
假设我们将应用程序打包在一个jar中,并将其放在jar变量指定的位置,让我们定义我们的默认值:
jar=/tmp/jmx-invoker.jar
address='service:jmx:rmi:///jndi/rmi://localhost:11234/jmxrmi'
invoker='cn.tuyucheng.taketoday.jmxshell.custom.JmxInvoker'
mbean='cn.tuyucheng.taketoday.jxmshell:name=calculator,type=basic'
然后,我们运行以下命令来设置属性并在我们的MBean中执行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处理程序并在解析MBean时拥有完全控制权。
6. 总结
在本文中,我们看到了用于管理MBean的工具以及如何从shell脚本使用它们。然后,我们创建了一个自定义工具来处理它们。最后,代码仓库中提供了更多脚本示例。
与往常一样,本教程的完整源代码可在GitHub上获得。