引言
本文简述JMX在应用中的作用和Jetty中的JMX架构。
JMX简介
JMX(Java Management Extensions)是一个为应用程序植入管理功能的框架。JMX 是一套标准的代理和服务,实际上,用户可以在任何 Java 应用程序中使用这些代理和服务实现管理。有篇文章是这么比喻的,感觉更加贴切:
在一个系统中常常会有一些配置信息,比如服务的 IP 地址,端口号什么的,那 么如何来写这些代码呢?程序初哥一般是写死在程序里,到要改变时就去改程序,然后再编译发布;程序熟手则一般把这些信息写在一个配置文件里(JAVA一般都是*.properties文件),到要改变时只要改配置文件,但还是重新启动系统,以便读取配置文件里的新值;程序好手则会写一个段代码,把配置值缓存起来,系统在读值的时候,先看看配置文件有没有更动。如有更改则重读一遍,否则从缓存里读取值;程序高手则懂得取物为我所用,用 JMX!把配置属性集中在一个类,然后写一个叫 MBean的玩意,再配置一下就轻松搞定了。而且JMX自动提供了一个WEB页面来给你来改变这些配置信息。
JMX 是一份规范,SUN 依据这个规范在 JDK(1.3、1.4、5.0)提供了 JMX 接 口,SUN自己也实现了一份。JDK5.0 的 jre\lib\rt.jar 已经包含了 jmxri.jar、jmxremote.jar、rmissl.jar三个包的代码。如果你用到jmxtools.jar、jmxremote_optional.jar的类,则需要将这两个类加入到 classpath 或 Eclipse 的项目库引用中。
JMX架构
参照上图,JMX体系结构分为下面三层:
1、设备层
主要定义了信息模型。在JMX中,各种管理对象以管理构件的形式存在,需要管理时,向MBean服务器进行注册。该层还定义了通知机制以及一些辅助元数据类。
2、代理层
主要定义了各种服务以及通信模型。该层的核心是一个MBean服务器,所有的管理构件都需要向它注册,才能被管理。注册在MBean服务器上管理构件并不直接和远程应用程序进行通信,它们通过协议适配器和连接器进行通信。而协议适配器和连接器也以管理构件的形式向MBean服务器注册才能提供相应的服务。一个代理要被管理,它必须提供至少一个协议适配器或者连接器。面临多种管理应用时,代理可以包含各种不同的协议适配器和连接器。
MBean服务器将Mbean管理起来了,因此就可以针对于此拓展一些有用的代理服务,比较常用的有:
1)动态类装载--通过管理小程序服务可以获得并实例化新的类,还可以使位于网络上的类库本地化;
2)监视服务--监视管理构件的属性值变化,并将这些变化通知给所有的监听者;
3)时间服务--定时发送一个消息或作为一个调度器使用;
4)关系服务--jetty中就有实际的应用。
3、分布式服务层
主要定义了能对代理层进行操作的管理接口和构件,这样管理者就可以操作代理。而可以提供的管理有上图所描述的那些:
1)为管理应用程序提供一个接口,以便它通过一个连接器能透明和代理层或者JMX管理资源进行交互;
2)通过各种协议的映射(如SNMP、HTML等),提供了一个JMX代理和所有可管理组件的视图;
3)分布管理信息,以便构造一个分布式系统,也就是将高层管理平台的管理信息向其下众多的JMX代理发布;
4)收集多个JMX 代理端的管理信息并根据管理终端用户的需要筛选用户感兴趣的信息并形成逻辑视图送给相应的终端用户。
Jetty中的JMX
Jetty拓展了Sun的JMX实现,主要体现在了资源加载方面,感觉像是Webx3的SpringExt对Spring的拓展。
1、主要组件的类图
结构比较简单,需要注意的是,图中红色的类是jetty拓展了sun的jmx框架的所在。
ObjectMbean:隶属设备层,继承自动态Mbean,实现了运行过程中Mbean暴露的属性和方法通过配置文件的方式加载的功能。
ConnectorServer:隶属代理层,封装了JMXConnectorServer,实现了其初始化的操作。
MbeanContainer:隶属代理层,提供代理服务:关系服务,不过不清楚维护的这种MBean之间的关系实际在Jetty中的应用场景在哪?
2、时序图
阶段一:addBean
这阶段完成了Mbean的加载,但是作为动态Mbean,其MbeanInfo并没有得到初始化,将由下一阶段来完成。
阶段二:register
红色的部分为初始化MbeanInfo,即Jetty拓展JMX的所在,自定义的MBeanInfo通过配置文件加载,配置文件如下所示:
[org/eclipse/jetty/server/jmx]
[Server-mbean.propertied]
3、弱引用
注意到很多地方用到了弱引用规避内存泄露。由于JMX的引入仅仅是提供了拓展的管理功能,如果持有Bean的Map弱引用了应用中的实例Bean,将可以有效地避免了应用中的Bean无法被回收的问题。
public class MBeanContainer extends AbstractLifeCycle implements Container.Listener, Dumpable
{
private final static Logger LOG = Log.getLogger(MBeanContainer.class.getName());
private final MBeanServer _server;
private final WeakHashMap<Object, ObjectName> _beans = new WeakHashMap<Object, ObjectName>();
private final HashMap<String, Integer> _unique = new HashMap<String, Integer>();
private final WeakHashMap<ObjectName,List<Container.Relationship>> _relations = new WeakHashMap<ObjectName,List<Container.Relationship>>();
private String _domain = null;
连接JMX
根据连接器和协议适配器的配置,连接JMX的方式也是多种多样的,当然,你可以在分布服务层做个改进,是的连接更加通用。Jetty默认提供的是RMI连接器。
1、RMI连接
阶段一:环境准备
取消start.ini对jmx.xml的注释和jmx.xml中ConnectorServer的注释。
阶段二:连接JMX
启动app,使用java自带的客户端jconsole连接 service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi。
2、HTTP连接
阶段一:环境准备
如果对于Jetty而言,你需要替换其连接器和适配器,注册了个HtmlApaptorServer在MBeanServer之上。代码如此:
MBeanServer server = MBeanServerFactory.createMBeanServer();
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
ObjectName adapterName = new ObjectName("HelloAgent:name=htmladapter,port=8082");
HtmlAdaptorServer adapter = new HtmlAdaptorServer();
server.registerMBean(adapter, adapterName);
adapter.start();
阶段二:连接JMX
打开网页:http://localhost:8082/ 。
3、调用接口连接JMX,管理其Mbean
通过调用接口可以实现其管理功能,比如:定时任务,消息提醒。
public class Client {
public static void main(String[] args) throws Exception {
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi");
JMXConnector jmxc = JMXConnectorFactory.connect(url, null);
MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
// MBean的总数
System.out.println("MBean count = " + mbsc.getMBeanCount());
// 对name属性的操作
mbsc.setAttribute(mbeanName, new Attribute("Name", "PANDA"));
System.out.println("Name = " + mbsc.getAttribute(mbeanName, "Name"));
// 得到proxy代理后直接调用的方式
HelloMBean proxy = (HelloMBean)MBeanServerInvocationHandler.newProxyInstance(mbsc, mbeanName, HelloMBean.class, false);
proxy.printHello();
proxy.printHello("Hello JMX");
// 远程调用的方式
mbsc.invoke(mbeanName, "printHello", null, null);
mbsc.invoke(mbeanName, "printHello", new Object[] { "hello jmx"}, new String[] { String.class.getName() });
// 得mbean的信息
MBeanInfo info = mbsc.getMBeanInfo(mbeanName);
System.out.println("Hello Class: " + info.getClassName());
System.out.println("Hello Attriber:" + info.getAttributes()[0].getName());
System.out.println("Hello Operation:" + info.getOperations()[0].getName());
// 注销
mbsc.unregisterMBean(mbeanName);
// 关闭 MBeanServer 连接
jmxc.close();
}