什么是Jmx
全称Java Management Extensions,可以将之理解为管理java资源的一个服务。当你运行一个java程序时,你可以通过它来掌握运行期间一些资源的情况,还可以在运行期间动态修改某些资源的值。
Jmx架构
至于Jmx的结构,可以参考下面这张图:
看起来挺复杂,不过没关系,一点一点来。
整体分三层:
- 最下层是资源层,也就是你要管理的资源,一般就是一个普通的java bean,包含你要管理的属性,以及你要进行的操作。只不过Jmx规定这些资源类要叫做XXXMBean,反正是要让Jmx认出来。
- 中间是代理层,你对于下面资源的操作都要通过代理层进行。这个很常见,许多java应用或者框架的结构都是这样,通过统一的代理进行操作,结构清晰,便于管理。
- 最上层是管理层,其实叫做管理层不太合适,因为这一层并不是直接管理资源,上文已经说过要通过代理层访问,因此这一层只不过给我们提供了连接代理层的方式。为了可以通过多种方式访问资源,如web端,控制台,或者通过编程API自己编写客户端,就需要对于不同的方式提供不同的适配器或连接器,如上图所示。
一个简单的demo
说了那么多,不如自己写一个简单的demo,理解更深刻。
首先要创建你要管理的资源,Jmx中一般是一个java bean,但是为了与一般的bean做区分,Jmx中的资源要实现以MBean为后缀的接口。几行代码,创建一个简单的MBean资源,如下:
public interface HelloWorldMBean {
String getHelloWorld();
void setHelloWorld(String hello);
}
public class HelloWorld implements HelloWorldMBean {
private String helloWorld;
@Override
public String getHelloWorld() {
return helloWorld;
}
@Override
public void setHelloWorld(String helloWorld) {
this.helloWorld = helloWorld;
}
}
上面是一个以MBean为后缀的接口,下面是其实现类,也就是我们所说的Jmx中的资源。其实就跟一个普通的java bean差不多,除了命名需要遵守特定的规则,其他没什么不同(至于为什么需要这样命名,后文讨论)。此外,如果你想查询属性,则要设置get方法;需要修改属性,需要设置set方法。(此部分内容也在后文讨论)。
资源写好了,按照架构图,应该给它找个代理。仍旧几行代码,如下:
// get a default MBean server
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
// the name of MBean
ObjectName mbeanName = new ObjectName("hello:name=HelloWorld");
// instance of helloWorldMBean and register
HelloWorldMBean helloWorldMBean = new HelloWorld();
// register MBean to server
server.registerMBean(helloWorldMBean, mbeanName);
配合着注释,简单解释就是,创建了一个供MBean注册的server,然后创建一个资源类的实例,将其注册上去。
不过有意思的是,代码中有个ObjectName,你可以把它当作你注册MBean时的代号。很简单,有许多资源要往你的server上注册,你要区分不同的MBean,就得有个代号,就是这个ObjectName。
创建ObjectName时,需要注意你传递的字符串需要遵从特定的格式,简单来说就是 {域名}:{key1}={value1},{key2}={value2}…{keyn}={valuen}。其实这个规则比较复杂,后文再讨论。
注册完了,如何使用呢? 很简单的几行代码,如下:
// get attribute
String helloWorld = (String) server.getAttribute(mbeanName, "HelloWorld");
System.out.println(helloWorld);//print null
// set attribute
server.setAttribute(mbeanName, new Attribute("HelloWorld", "hey"));
// get attribute after changing
helloWorld = (String) server.getAttribute(mbeanName, "HelloWorld");
System.out.println(helloWorld);//print hey
可以发现,将MBean注册到server之后,你只需通过ObjectName和属性名,就能修改和查询MBean中的属性值。(前提是你的MBean中有相应的set和get方法)
简单的demo这样就没了。。。好像没什么用啊, 如果只是更改bean的属性,那么我们直接调用set和get方法不是也可以吗?接下来我们试下用浏览器,通过web端,来获取和修改资源。
通过web控制的demo
我们基于上文简单的demo进行一下改造,不过在此之前要下载一个jar包,因为通过web控制所需的jar包并不在jdk自带的jar中,连接http://www.oracle.com/technetwork/java/javasebusiness/downloads/java-archive-downloads-java-plat-419418.html, 选那个jmx-1_2_1-ri.zip就好,解压后把jmxtools那个jar加到classpath就可以了。
接下来,写代码。MBean不用改,在代理层的代码下面加上以下几行代码:
// create a html adapter so as to control jmx by web page
ObjectName htmlAdapterName = new ObjectName("hello:name=htmlAdapter,port=8082");
// create a html adapter server, register and start
HtmlAdaptorServer adaptorServer = new HtmlAdaptorServer();
server.registerMBean(adaptorServer, htmlAdapterName);
adaptorServer.start();
将MBean注册到server之后,我们要在本地启动一个服务器以供来自web的连接。这个HtmlAdapterServer就是架构图中,供web端访问的适配器,同样需要把它注册到代理层的server中,然后调用start()方法,供web端访问的服务就开启了。
你只需要打开浏览器,输入localhost:8082,你就能看到当前你的jvm中注册的资源。仔细找找,你会发现刚刚自己写的MBean也在其中,点击这个连接,在MBean的视图中,你能看到对应属性,是否可读写,当前值。同时自己可以修改该属性。有点意思哈~
这里有个问题,你会发现,当你将ObjectName中的port属性改为其他端口时,仍旧只能访问8082端口,其他端口无效。所以也就意味着这个HtmlAdapterServer的端口值不是在这里设置的。这个问题我们后文再讲。
通过客户端控制的demo
上文中通过web控制的demo,可以作为查询、调控的界面,通过人工去控制。可是有些情况下,需要通过程序自动触发,比如你需要每隔十分钟更改一下资源值,这个时候通过人工控制就不现实了。
接下来我们写一个通过客户端控制的demo。
同样,我们的MBean不用改,仍是要改代理层的server,基于上文那个简单的demo,在将资源MBean注册到server后,添加如下几行代码
// register a port
LocateRegistry.createRegistry(9999);
// initial a url and get a jmx connector server instance
JMXServiceURL url = new JMXServiceURL(
"service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi");
JMXConnectorServer connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server);
System.out.println("before connector server start....");
connectorServer.start();
System.out.println("connector server has started......");
这里我们创建了一个JMXConnectorServer,其实架构图中也已经画出了,是通过客户端访问的连接器。
本地注册一个端口,然后创建一个url传给这个connector,调用start()方法,这样就在本地启动了一个以供客户端连接的服务器。
我们发现这个url的格式有点奇怪,其实这也是Jmx规定的,比如以`service:jmx` 作为开头,默认实现rmi方式等,这个后文再讲。
服务器启动起来了,我们再建立一个自己的客户端。可以新建一个程序,几行代码,如下:
// new jmx url
JMXServiceURL url = new JMXServiceURL(
"service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi");
// connect
JMXConnector connector = JMXConnectorFactory.connect(url);
// get a connection instance
MBeanServerConnection connection = connector.getMBeanServerConnection();
//objectName must be same as which was register on server
ObjectName objectName = new ObjectName("hello:name=HelloWorld");
// get attribute before change
String helloWorld = (String) connection.getAttribute(objectName, "HelloWorld");
System.out.println("before" + helloWorld);
// set attribute
connection.setAttribute(objectName, new Attribute("HelloWorld", "client"));
helloWorld = (String) connection.getAttribute(objectName, "HelloWorld");
System.out.println("after : " + helloWorld);
结合注释,很容易看懂。通过相同的url,连接到已经创建好的服务器,之后的查询和修改资源的过程跟之前一样,不再多说。
本章先简单介绍几个简单的demo,也遗留下来很多问题,接下来的文章继续解答。