本篇文章内容主要来源于官方文档《Trail: Java Management Extensions (JMX)》,目的是体验一波JMX。
什么是JMX?
JMX(Java Management Extensions)是一个为监控应用资源而制定的一套规范。JMX规范使用Java语言定义了一套体系、提供了接口和服务。JMX的体系结构如下所示:
总共分为了三层:
- 设备层(Instrumentation),由许多提供属性、操作的MBean组成,这些MBean就是对外暴露资源用的对象。
- 代理层(Agent),设备层的MBean需要注册到代理层,用户可以在代理层对MBean进行查找、注册、删除等操作
- 适配层(Adapter),允许管理工具层使用不同的网络协议来操作代理层
- 管理工具层(Client),直接面向用户的GUI工具
设备层
设备层由众多MBean组成。MBean是代表某些资源的Java对象,遵循一定的规范,能够将属性、操作暴露出去。通常一个MBean需要支持以下功能:
- 能被访问的属性
- 能够做一些更新操作
- 能够发出通知事件
比如要定义一个标准的Bean(还有其他种类的 MBean
),需要做以下两个步骤:
- 创建接口SomethingMBean
- 实现上述接口的类必须叫做Something
比如现在创建一个接口, WebServerConfigMBean
public interface WebServerConfigMBean {
String getApplicationName();
void setCache(int size);
int getCache();
}
实现WebServerConfigMBean的类名必须叫做 WebServerConfig
,否则会报一个javax.management.NotCompliantMBeanException
异常,说我们这个类既不是 XMBean
、 Standard MBean
、 也不是Dynamic MBean
public class WebServerConfig implements WebServerConfigMBean {
private String applicationName;
private int cache;
public WebServerConfig(String applicationName, int cache) {
this.applicationName = applicationName;
this.cache = cache;
}
@Override
public String getApplicationName() {
return this.applicationName;
}
@Override
public void setCache(int size) {
this.cache = size;
}
@Override
public int getCache() {
return this.cache;
}
}
代理层
代理层总的来说就是用来管理 MBean
的,并让外部的应用程序可以控制这些MBean。
主要包含 MBean Server
、管理 MBean
的服务、一个连接器或协议适配器。
远程管理层
远程管理需要使用协议获取到所有MBean的资源信息,而连接器主要用来控制资源。
实战1
定义MBean
MBean的种类很多,后面会逐一介绍,这里主要先讲最简单的 Standard MBean
。 Standard Bean
需要定义一个接口。注意,只要是 **MBean
,每个接口都要带有 MBean
** 的后缀! sayHello()
和 add()
是暴露给外部的可调用的方法, getName()
是必要的返回 MBean
名称的方法, get/setCacheSize()
是暴露给外部查看/修改资源的方法。
public interface HelloMBean {
public void sayHello();
public int add(int x, int y);
public String getName();
public int getCacheSize();
public void setCacheSize(int size);
}
这里给出 HelloMBean
的实现,注意这里不需要带 MBean
后缀了:
public class Hello implements HelloMBean {
private static final String name = "Reginald";
private static final int
DEFAULT_CACHE_SIZE = 200;
private int cacheSize = DEFAULT_CACHE_SIZE;
@Override
public void sayHello() {
System.out.println("hello, world");
}
@Override
public int add(int x, int y) {
return x + y;
}
@Override
public String getName() {
return name;
}
@Override
public int getCacheSize() {
return this.cacheSize;
}
@Override
public void setCacheSize(int size) {
this.cacheSize = size;
System.out.println("Cache size now " + this.cacheSize);
}
}
创建JmxAgent管理资源
一开始是通过 ManagementFactory
获取 MBeanServer
,如果这样获取不到就会尝试通过 MBeanServerFactory#createMBeanServer()
来获取 MBeanServer
:
public static void main( String[] args ) throws Exception {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName name = new ObjectName("com.example:type=Hello");
Hello mbean = new Hello();
mbs.registerMBean(mbean, name);
System.out.println("Waiting forever...");
Thread.sleep(Long.MAX_VALUE);
}
此外这里还要注意,每个被注册的 MBean
必须带有名字,即 ObjectName
,且名字必须遵守 域名:键值对1[,键值对2...]
这样的规范来。之后通过 registerMBean
来注册对应的 MBean
。最后,外部的资源管理器就能查看 cacheSize
、修改 cacheSize
、执行一些方法了~
通过远程管理器管理资源
打开 jconsole
(或者装了 MBean插件
的 JVisual
),进入我们启动的这个这个程序,选择最后一栏 MBean
Name属性
CacheSize属性
如果把 CacheSize
从“200” 改为 “300”,那么控制台会输出 “Cache size now 300 ”
add方法
相当于在 Hello
这个对象上调用 add()
方法,值是外部传入的!输出结果会返回回来,如果有返回值会以弹窗的形式返回;没有返回值则近提示调用成功。
sayHello方法
因为没有参数,所以直接点击按钮调用。因为无返回值,所以仅提示调用成功。
实战2:增加MBean的消息通知
在实战1的基础上,增加通知机制,即有变化通知订阅者~
首先对 MBean
进行改造:
public class Hello extends NotificationBroadcasterSupport implements HelloMBean {
// ...省略
// 当修改cacheSize就触发通知,告诉所有订阅者
public synchronized void setCacheSize(int size) {
int oldSize = this.cacheSize;
this.cacheSize = size;
System.out.println("Cache size now " + this.cacheSize);
Notification n = new AttributeChangeNotification(this,
sequenceNumber++, System.currentTimeMillis(),
"CacheSize changed", "CacheSize", "int",
oldSize, this.cacheSize);
sendNotification(n);
}
// 用来获取消息列表的
@Override
public MBeanNotificationInfo[] getNotificationInfo() {
String[] types = new String[]{
AttributeChangeNotification.ATTRIBUTE_CHANGE
};
String name = AttributeChangeNotification.class.getName();
String description = "An attribute of this MBean has changed";
Descriptor descriptor = new ImmutableDescriptor(new String[]{"size"}, new Object[]{this.cacheSize});
MBeanNotificationInfo info =
new MBeanNotificationInfo(types, name, description, descriptor);
return new MBeanNotificationInfo[]{info};
}
// 新增消息序列
private long sequenceNumber = 1;
}
当在通知栏目点击订阅后,再修改 CacheSize
就会触发消息通知:
实战3:自定义JMX Client
之前都是通过 JConsole
来管理 MBean
对象,这回要自定义一个 Client
来管理 MBean
对象。
第一步,创建一个 JMXServiceURL
,这货用来创建一个URL,这个URL的结构拆分如下所示:
- 该URL必须要以
service:jmx
开头, - 使用RMI注册器?
:rmi:///jndi/rmi://:9999/jmxrmi
(总之这部分是要用到RMI技术的)
第二步,使用上面的JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://:9999/jmxrmi");
url
创建JMX连接,即JMXConnector
。这一步看文档说是和ConnectorServer
连接,ConnectorServer
是啥呢?:
第三步,通过JMXConnector jmxc = JMXConnectorFactory.connect(url, null);
JMXConnector
获取到MBeanServerConnection
。MBeanServerConnection
应该就是负责MBean
的管理。
第四步,通过MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
MBeanServerConnection
获取MBean
的所有domain
(后面省略一些没啥意思的操作):String domains[] = mbsc.getDomains(); Arrays.sort(domains); for (String domain : domains) { System.out.println("\tDomain = " + domain); }
第五步,通过 MBeanServerConnection
获取 MBean
的数量:
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);
}
第六步,通过代理控制 MBean
对象:
ObjectName mbeanName = new ObjectName("com.example:type=Hello");
HelloMBean mbeanProxy = JMX.newMBeanProxy(mbsc, mbeanName,HelloMBean.class, true);
第七步,给该 MBean
添加监听事件:
// 自定义的监听器
ClientListener listener = new ClientListener();
// 给指定的Bean注册监听器
mbsc.addNotificationListener(mbeanName, listener, null, null);
第八步,修改 MBean
触发消息通知:
mbeanProxy.setCacheSize(150);
总结
官方提供的测试程序《jmx_examples.zip》
本篇文章只是体验一番JMX服务,这里对实战3里的 JMXConnector
、 Proxy
还有 URL
方式很懵逼。通过本篇文章,能大致窥探到一些例如Tomcat的admin端操作的原理~