第二章 JMX 版本的 “Hello World”
(想象你是个音乐发烧友)你决定买一套音响设备,于是先到音响店,挑了一个自己喜欢的型号带回家。接下来,你是哪种类型的人呢?第一种:仔细的打开所有的包装、检查零件清单,然后顺着组装手册一步一步的组装;第二种:打开所有包装、然后自己开始琢磨着所有的连接线开始组装。本章是为第二种类型的人写的,如果你是第一种类型的人,请确信你是否读了第一章,它描述了JMX 框架和基本架构(本章会重点描述)。
本章的目的是为了让你熟悉由Sun 提高的JMX 参考实现( Reference Implementation :RI )。学习完本章,你将会学会管理你的第一个资源,创建一个JMX 代理,并通过浏览器和代理通信。换句话说,你将新建一个MBean,使用MBean Server ,通过Sun 提供的参考实现里的HTML 适配器来管理你的MBean 。
本书的剩余部分假定你已经有JDK1.3 (最低版本)安装在你的机器上了,并且你正确的设置了环境变量。如果需要,你可以从 http://www.javasoft.com 上下载一个JDK。
2.1 开始吧
在开始继续之前,让我们简单的回顾一下JMX 的架构,然后建立一个开发环境。
第一章详细的描述了JMX 的架构,并且讨论了它是怎样提供一个管理解决方案的。然而为了确保你掌握了第一章介绍的知识,我们先来回顾一下吧。JMX 架构设计为一个包含三部分或三层的Java 框架,它们一块工作从而提供一个java 的管理解决方案。表2.1 列出了JMX 架构的三层。
表 2.1 JMX 架构的三层组件
层 | 描述 |
分布层( Distributed layer ) | 包含使管理系统和 JMX 代理通信的组件 |
代理层( Agent layer ) | 包括代理和 MBean 服务器 |
指令层( Instrumentation layer ) | 包括代表可管理资源的 MBean |
图 2.1 说明三层怎样协同工作
图 2.1 三层协同工作
本章你将会同三层中的各各层的组件打交道。在指令层,将会使用一个MBean ,MBean 是一个Java 对象,它封装了一个资源并将它暴露给管理程序。在代理层,将会使用一个JMX 代理,实际上,你会写一个你自己的代理,它包含你的MBean 。最后,在分布层,将会使用HTML 适配器,它是一个java 对象,它允许管理应用将HTML 作为一个通信协议来和你的代理通信。管理应用是任何对侵入、配置、操纵可管理资源感兴趣的应用。
如果你没有Sun 的JMX RI ,可以从http://www.javasoft.com 上面下载,你可以在Products and APIs 的下载区域找到它。下载了zip 文件后,解压到磁盘,会产生一个JMX 的父目录,其中包含以下目录:
§ contrib— — 包括Sun 不声明支持的分发。比如,Sun 在其中提供了一个RMI 连接器,或者说适配器,允许管理应用通过RMI 和JMX 代理通信。
§ jmx— 包括JMX RI/ 例子和文档
在本书的剩下部分,你需要将你的java 源文件放在JMXBook 目录下,然后可以使用一个批处理来设置path 和classpath ,以便于从JMXBook 下编译和运行例子。批处理文件包含以下内容:
set CLASSPATH=c:/JMXBook;
C:/jmx-1_0_1-ribin/jmx/lib/jmxri.jar;
C:/jmx-1_0_1-ribin/jmx/lib/jmxtools.jar;
C:/jmx-1_0_1-ri-bin/contrib/remoting/jar/jmx_remoting.jar;
set PATH=c:/jdk1.3/bin;
批处理文件用于在Windows 环境下设置JMX 环境来编译和运行例子,如果你使用Unix ,你需要相应的修改脚本。如你在脚本所见,我们使用JDK1.3 ,但是每一个例子都可以运行在任何J2SE 版本。当工作在命令行时,作进一步工作前,你需要先运行这个脚本。运行设置脚本后,可以输入java javax.management.ObjectName 来测试CLASSPATH 设置是否正确,如果你得到一个错误,告诉你此类中没有main 方法,那么恭喜你,你设对了。
使用ant: 对于熟悉ant 的读者,在附录B 中有一个ant 构建的设置,在哪里还有ant xml 构建文档以及相关的环境设置信息。
译者注:个人理解JMX RI 现在应该由JMDK 替代了,所以请下载JMDK 就可以使用本书的例子了。JMDK 下载:java.sun.com/products/jdmk/index.jsp 。下载opendmk-1.0-b02-bin-dual-01-Oct-2007_19-17-46.jar ,然后加压,按照README 的操作,在命令行下运行:java -jar opendmk-1.0-b02-bin-dual-01-Oct-2007_19-17-46.jar ,就可以得到jdmkrt.jar、jdmktk.jar 、jmxremote_optional.jar 三个jar ,将他们放在你classpth 中。
2.2 管理你的第一个资源
到现在为止,我们已经简单的回顾了JMX 的主要组件,并且你已经有一个开发环境了。接下来准备构建你的第一个MBean 吧。本章只是对MBena 的一个简介,更复杂的例子将会在以后的章节中描述。
第一个例子,你将会构建一个简单的HelloWorldMBean 。HelloWorldMBean 暴露一个简单的Java String 对象作为被管理的资源,它仅是是一个成员变量。我们使用这个例子作为一个工具,向你介绍使用JMX 的方方面面,包括MBean 、MBeanServer 、HtmlAdapter 。需要强调的是,可管理资源是任何通过Mbean 可侵入和可管理的资源。(本章中,我们不需要担心Mbean 的编码标准。还记得第一章曾经介绍过吗?本书会介绍三种类型的Mbean :标准Mbean 、动态MBean 和模型MBean 。但是本章只会创建一个标准MBean ,MBean 的具体开发规则会在以后的章节中心详细描述)图2.2 显示了HelloWorldMBean 的UML 图。
图 2.2 HelloWorld
下一节开始讨论图中描述的接口和实现。
开发HelloWorldMBean 第一步是写以哦Java 接口,包含三个方法:getter 、setter 和一个打印HelloWorldMBean 的greeting 的方法。通常,你可能不需要为如此简单的HelloWorld 例子定义一个接口,但是你将会在第4章和第5 章看到,JMX 使用接口来描述Mbean 暴露的属性和操作。
回忆一下,getter 方法是类中以getMember() 命名的方法,setter 是以setMember() 命名的方法。将标准Mbean接口中的方法想象成具体类实现的一个描述,你只需要简单的通过名字理解它的意图就可以了。Getter 和setter定义了使用Mbean 的对象的成员变量的访问方式。通过成员变量的getter 方法,可以允许读操作,setter 方法云讯写操作。如同你看到的下面的接口,这个MBean 十分简单。
package jmxbook.ch2;
public interface HelloWorldMBean{
public void setGreeting(String greeting);
public String getGreeting();
public void printGreeting ();
}
注意package 语句,本章的所有例子都在包jmxbook.ch2 下,其他章节的例子也都在对于包下(例如第三章在mxbook.ch3 下)
HelloWorldMBean 接口定义了一个getter 方法 (getGreeting()) 和一个setter 方法(setGreeting()) 以及printGreeting () 方法。随后你可以使用printGreeting() 方法打印Mbean 的greeting 的值。2.1 展示了接口
//Listing 2.1 HelloWorld
package jmxbook.ch2;
//#1
public class HelloWorld implements HelloWorldMBean{
private String greeting = null;
//#2
public HelloWorld (){
this.greeting = "Hello World! I am a Standard MBean";
}
//#2
public HelloWorld (String greeting){
this.greeting = greeting;
}
public void setGreeting(String greeting){
this.greeting = greeting;
}
public String getGreeting(){
return greeting;
}
public void printGreeting (){
System.out.println(greeting);
}
}
( 注解)<#1 实现HelloWorldMBean 接口 >
( 注解)<#2 定义两个公共构造函数>
如此就新建了你的第一个MBean ,为了测试它,需要建立一个JMX 代理来容纳这个MBean ,下一节讨论HelloAgent类的创建。创建了代理后就可以使用Mbean 了。
迄今为止Mbean 已经有了,我们需要把它注册在一个代理中使它可用。所以我们新建一个HelloAgent 类,它是一个简单的JMX 代理。
正如第一章说的那样,JMX 代理是JMX 代理层的一个组件,它是Mbean 容器。本书第三部分会详细描述JMX 代理。
HelloAgent 会做三件事:
§ 1. 创建一个Mbean 实例来放MBean
§ 2. 创建一个 HtmlAdapter 来处理从HTML 客户端来的连接
§ 3. 注册一个HelloWorldMBean 的实例
如你将要看到的,HelloAgent 可能是你写的最简单的代理,但是它很有用。这样反应了使用JMX 的一个最主要的点:它非常有用但是却很简单。刚刚已经开发了HelloWorldMBean ,那么现在可以写一个简单的代理来管理这个MBean ,代理使用Sun 提供的HtmlAdapter 。
使用任一个浏览器,适配器使得你可以和代理交互,以查看注册在它里面的MBean 和它们的属性。图2.3 显示了这个交互。
Figure 2.3 Using a Web browser to contact the HTML adapter present in the MBean server
适配器可以让你:
§ 1. 查看可读的MBean 属性
§ 2. 更新可写的MBean 属性
§ 3. 调用一些方法
不仅仅如此,适配器同时向你提供了动态创建和注册额外的Mbean 的一个简便方法。最基本的,适配器提供了一个简单的管理工具来操纵MBean 。HTML 适配器返回一个协议(HTML ),使得你的浏览器成为一个有用的管理应用。但是我们还是别太超前了,首先来创建JMX 代理吧。
Listing 2.2 是HelloAgent 类,先别担心看不懂这些代码,在本书的第三部会详细的介绍JMX 代理。现在,你只需要简单的理解就可以了,简而言之,listing 2.2 做了如下工作:
1 1. 创建了一个MBeanServer 和一个HtmlAdapter
2 2. 注册了MBean (因此你才管理MBean )
3 3. 给了Mbean 一个唯一标识符
4. 注册HTML adapter 然后运行它
//Listing 2.2 HelloAgent .java
package jmxbook.ch2;
import javax.management.*;
import com.sun.jdmk.comm.*;
public class HelloAgent {
private MBeanServer server = null;
public HelloAgent() {
server = MBeanServerFactory.createMBeanServer("HelloAgent");//#1
HtmlAdaptorServer adapter = new HtmlAdaptorServer();//#2
HelloWorld hw = new HelloWorld();//#3
ObjectName adapterName = null;
ObjectName helloWorldName = null;
try {
//#4
helloWorldName = new ObjectName("HelloAgent:name=helloWorld1");
server.registerMBean(hw, helloWorldName);
//#5
adapterName = new ObjectName("HelloAgent:name=htmladapter,port=9092");
adapter.setPort(9092);
server.registerMBean(adapter, adapterName);
adapter.start();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String args[]) {
System.out.println("HelloAgent is running");
HelloAgent agent = new HelloAgent();
}
}
( 注解 )<#1,#2 创建MBeanServer 和 HtmlAdapter >
( 注解 )<#3 创建 HelloWorldMBean 实例 >
( 注解 )<#4 创建 ObjectName 实例 ; 注册 HelloWorldMBean >
( 注解 )<#5 注册并运行 HtmlAdapterMBean >
注意import语句JMX框架的所有类都在javax.management 包下,包com.sun.jdmk.comm 是Sun提供的JMX RI,这个包作为不声明支持包,即对于JMX开发虽然它很有用,但是它不是JMX规范的一部分。用来实例化HTML adapter 的类就在这个包中。
创建 MBean Server 和 HTML 适配器
#1 HelloAgent 类包含一个main方法,它可以使HelloAgent 作为一个独立的进程运行。Main方法只是简单调用HelloAgent 的构造方法,因此让我们从这儿开始分析代码吧。HelloAgent构造方法的第一步是新建了一个MBean Server。
还记得第一章说过的吗?MBeanServer是一个Java对象,它用来容纳和操纵JMX MBean。MBean Server是一个标准的JMX类,它是JMX代理的核心。代理使用javax.management.MBeanServerFactory 得到一个MBeanServer 实例,MBeanServerFactory是一个实现为工厂模式的JMX类,它提供MBeanServer的具体实例。当你需要一个MBeanServer实例时,就可以使用工厂对象来新建或是请求一个新实例。工厂可以管理很多MBeanServer实例,调用它就会返回一个已存在的或是新建的实例(如HelloAgent中那样 )。
注意createMBeanServer()的参数“HelloAgent ”,它标识了这个代理的域名。域名是一个唯一的标识符,用于标识一组MBean,它唯一的标识了这个MBeanServer 以区别域其他的MBeanServer 。每一个MBeanServer都有一个域名,使得你可以一种有特定意义的方式来分组MBean。如果你再次使用相同的域名参数调用createMBeanServer()方法,那么它将会只 简 单的返回刚才创建的那个MBeanServer实例。(关于MBeanServer的,在第8章将会学到更多。现在你只需要知道MBeanServer扮演了注册表的角色,通过它可以存储、查找、操纵MBean)
#2 下一步是为管理应用创建一种方式来和HelloAgent交互。回想一下,代理通过构造协议适配器和连接器来向管理应用暴露自己。适配器和连接器是JMX为啥如此有用的一个主要原因。适配器和连接器是Java对象,它使得应用可以使用特定的协议和JMX代理交互(在本书的后面,你将会学习到更多关于他们的信息,并创建更复杂的例子)。本章只会用到HTML适配器,只需要调用它的默认构造方法就可以新建一个适配器,如你在HelloAgent类中看到的那样。
注册和管理 MBean
#4 一旦适配器创建了,你需要在MBeanServer里注册它。这说明适配器(连接器)也是MBean,因此组成适配器和连接器的java类被写成符合JMX规范的某一种类型的MBean。因为它们也是Mbean,所以在运行期像其他的Mbean一样可以被管理。但是在注册一个Mbean之前,你需要确保你可以标识它和找到它,这就是javax.management.ObjectName的功能了。
现在为止,代理已经创建了MBeanServer并注册了HTML适配器。接下来需要知道MBeanServer是怎样对注册在它里面的对象保持追踪了。回看一下在MBeanServer上注册HelloWorldMBean的代码(#4)。当注册Mbean时,需要使它和那些已经注册的MBean区别开来。
要做到这点,必须新建一个javax.management.ObjectName 实例。ObjectName是一个JMX类用来为MBean提供命名服务,用于唯一的标识每一个注册在它里面的MBean。每个ObjectName包括两部分:
§ 一 个域名—域名一般是MBean想注册的那个MBeanServer 的域名。如果不是这样,就意味着想把这个MBean和其他的MBean隔离开来。
§ 一 个 名值对属性列表—名值对属性用于唯一标识MBean,以及提供MBean的信息。对象名也许是用户首先看到的关于MBean的信息。可以是名值对属性提供诸如名字、端口、位置此类的信息。
例子中 HelloWorldMBean 的ObjectName 如下所示
"HelloAgent :name=helloWorld1"
这样你就有一个ObjectName 实例,一旦注册就可以标识和查找到这个MBean了。
#5 如前所述, 代理为适配器新建了一个 ObjectName 并且把它注册在MBeanServer 里。因为它们是MBean,所以每个适配器都可以选择向管理应用暴露一些可以配置的属性。
尽管适配器已近创建和注册了,管理应用仍然不可以和代理交互。要使客户端可以使用适配器,必须调用它的start()方法。Start()方法告诉适配器MBean开始在默认端口9092监听HTTP连接。现在HelloAgent 已经准备奥接受客户段的请求了。
在前一节中我们简要的描述了ObjectName 。为了有一个全面的了解,让我们回到ObjectName 列上。就如你注意的那样,在创建ObjectName时必须有一个特定的结构。
图 2.4显示了 ObjectName 的结构:
图 2.4 ObjectName 的结构
域名
域名提供了代理和其他代理联系的上下文。例如,一个代理可能创建用来在一个特定的机器上容纳MBean管理资源,这种情况下,域名可能是机器的hostname。一个域名不一定非得如hostname那样有明确含义的,但一般来说,你应该尽量提供一些有意的名字。如此,你就可以更容易的了解ObjectName 和MBean更多的信息。
图2.4中,域名甚至可以不指定,如果左边是空的,MBean Server将会提供一个默认的域名。对MBeanServerFactory类也是同样的,如果使用不带域名参数的createMBeanServer(),工厂类将返回一个使用默认域名的MBeanServer 。
差不多现在你已经注意到MbeanServer对象和Mbean是关联在一个域中的(通过ObjectName)。但是实际上,某个域的MBean可以在另一个有不同域名的MBeanServer上注册。这种情况也是可接受的,因为域名并没有对注册在一个MBeanServer上的Mbean附加任何规则和约束。
对象名的名值对属性列表是一系列以逗号分隔的属性值,它们提供了唯一标识MBeanServer中MBean的机制。属性不要求是MBean的精确属性,唯一要求是当它和ObjectName实例比较时必须是唯一的。对每一个ObjectName,你必须指定最少一个属性值以使它和MbeanServer中的其他MBean可唯一区分。
ObjectName提供三个使用String参数的构造方法。在HelloAgent如下新建ObjectName :
helloWorldName = new ObjectName ( "HelloAgent :name=helloWorld1" );
这个 ObjectName使用name=helloWorld1 名值对来唯一标识HelloWorldMBean。如果你注册其他的MBean,你不可以再使用这个名值对,你需要其他的属性。
注册 object name 冲突
你可以认为注册表像一个复杂Hashtable ,你将对象放在表中,使用一个key 关联它。 为了注册MBean,HelloAgent 类调用MBeanServer.registerMBean() 方法。如果ObjectName不是唯一的,MBeanServer将会抛出一个javax.management.InstanceAlreadyExistsException异常,来说明已经有一个相同ObjectName的MBean被注册了。MBeanServer不会比较ObjectName关联的对象时候相等,它只比较关联它们的ObjectName。
2.4 运行代理
我们检查一下到现在为止你做了那些事。首先你创建MBean,包含在HelloWorld类中,它向外暴露一个属性:greeting 作为被管理的资源。接着创建了HelloAgent 类,它是一个简单的JMX代理。代理将会容纳MBean并提供管理MBean的方式。剩下的工作就是:编译、运行、和代理交互了。
为了运行代理,你需要编译源码然后运行HelloAgent 。为了编译源码,在确认你的环境(CLASSPATH和其他)设置正确后,然后执行如下的命令:
javac jmxbook/ch2/*.java
2.4.2 运行代理
下面的命令会运行 HelloAgent :
java jmxbook.ch2.HelloAgent
执行这个命令后,你的代理已经启动了。 命令行将不会返回,因为HelloAgent 进程没有推出。你会看到:“HelloAgent is running,表示代理已经开始运行了。
连接代理,你需要一个HTML 客户端,任何浏览器都可以作为一个客户端。例如,HelloAgent 的HTML 适配器默认在端口9092 监听连接。如果你的环境上这个端口不可以用,那么回到源码适配器构造方法下面一行,修改成可用的端口:
adapter.setPort( [port value ] );
确保端口可用,本书的剩余部分我们一直在端口9092 上使用HTML 适配器。
一旦已经有一个可用的端口了,打开你的浏览器,输入http://localhost:9092 (如果你指定了不同的端口,请使用它替换9092 ).
运行在代理上的HTML 适配器现在指定的端口监听HTTP 请求,当你的浏览器连接时,适配器将返回HTML 作为响应。现在你可以管理代理,凭借HTML 和代理来进行交互。
继续之前,你可以恭喜自己了,你已经成功的创建了你的第一个MBean 和代理,下一节你将会使用HTML 适配器和你的代理通信了。
2.5 使用 HTML 适配器
现在HTML 适配器已经OK ,它运行在包含MBean 的代理上,是时候连上它,看看它都为你提供了些什么东东。HTML适配器提供了通过HTML 客户端(任何浏览器)侵入JMX 代理的方式,它包括三个主要页面:
§ 代理视图 — 代理视图是你看见的第一个页面,它上面有这个代理容纳的MBean 的总结,从这个页面你能过滤MBean 列表以提供更为精确的视图。
§ MBean 视图 — 这个页面提供了一个特定MBean 的详细信息。通过这个页面,你可以设置、取得MBean 的属性以及调用MBean 的方法。
§ 管理视图 — 这个页面使得你可以在代理中注册一个新的MBean 。
浏览器连接代理后,代理视图就出现了(图2.5 ). 代理视图是一个从代理的HTML 适配器得到的HTML 页面,它向你展示了所有注册在这个代理上的MBean ,以MBean 的ObjectName 的值显示。从这个页面,你可以迁移到MBean 视图或管理视图。在进入其他页面之前,注意页面顶部的“Filter by object Name ”栏,现在显示*:* ,它告诉代理返回所有的注册在代理中的MBean 列表。页面上MBean 数目显示了每次查询返回的MBean 的总数。
Figure 2.5 代理视图
过滤器允许使用对象名的一部分或是全部来过滤MBean 列表。比如说,你输入HelloAgent :name=helloWorld1 ,列表将只显示HelloWorldMBean. 可以使用下述的规则来输入对象名:
表 2.2 代理视图过滤器规则
MBean filter rule | Example |
使用 * 作为字母和数字的多个字符的通配符,可以作为名值对的通配符 | *:name=helloWorld1,* |
使用作为 ? 单个字符的通配符 | ??Agent:name=helloWorld1 |
如果不指定域名,过滤器将使用默认域名,你必须至少指定一个名值对(或使用 * ) | *:* |
域名的一部分是合法,但是不可以指定名值对的部分 ( 对 key 和 value均如此 ) | ??Agent:name=helloWorld1 |
所有的关键字都精确匹配或使用通配符 | ??Agent:* |
名值对可以以任意顺序指定 |
试着自己使用过滤器,这样有助于你掌握ObjectName 的格式。试完之后,将filter 恢复为*:* ,你就可以看到所有的MBean 列表了。
注意MBean 列表中有一个在2.4 节没有注册过的MBean :MBeanServerDelegate ,它是一个由MBeanServer 创建的MBean ,用来处理特定的任务--- 特别地,是用来为MBeanServer 发送通知用的。MBeanServer 将这个MBean注册在一个独立的域中,以区别其他注册的MBean 。
所有的MBean 都是可见的,单击MBeanServerDelegate 的连接,会跳转到这个MBean 的视图。
MBean 视图是另一个从HTML 适配器接受到HTML 页面,它显示你点击的MBean 的信息。视图显示了被选择的MBean的所有的包含表2.3 中的信息。
表 2.3 MBean 视图的元素
MBean detail | Description or example |
类名 | MBean 的类名 , 比如 HelloWorld |
对象名 | MBean 的 ObjectName , 比如 HelloAgent: name=helloWorld1 |
描述 | MBean 的描述。对于标准 MBean ,由 MBeanServer 创建这些描述 |
属性表 | MBean 暴露的属性列表,包括属性的类型、侵入和值,如果有的话。属性表运行你更改可写的属性。 |
暴露的操作 | MBean 暴露的操作列表,你可以触发这些属性 |
重新载入周期 | 告诉 MBeanServer 是否需要重新实例化 MBean 以及周期 |
解除注册按钮 | 告诉 MBeanServer 解除对这个 Mbean 的注册 |
Figure 2.6 MBeanServerDelegate MBean 的视图
Figure 2.6 MBean 视图
看一下图2.6 的MBean 属性表,注意Access 列,当前都是RO ,代表着只读(Read Only ),其他可能的值有:WO ( 只写:Write Only) 和 RW ( 可读写:Read/Write) 。如你所猜想的那样,RO 意味着MBean 的接口为此属性值提供了getter 方法,WO 意味只提供了setter 方法,RW 意味这提供了getter 和setter 方法。
HTML 适配器使用反射检查接口的方法名,它去除了每一个方法前的get 和set ,使用剩下的部分作为属性名,余下的方法(那些不以set 或get 开头的方法)被放进MBean 视图的操作部分(即:List of MBean operations )
回看图 2.6 ,你可以看到MBeanServerDelegate 只暴露了可读属性,这些属性描述了正在使用的JMX RI 以及它实现的是那个版本的JMX 规范。
让我们回到代理视图,点击代理视图,这次选择HelloWorldMBean ,如图2.7 看到的那样:
Figure 2.7 H elloWorldMBean 视图
除了两个重要的区别外,HelloWorldMBean 视图和MBeanServerDelegateMBean 是一样的。HelloWorldMBean 暴露只暴露了一个属性:greeting ,(并且它有getter 和setter ),意味着这个属性是可读可写的,所以视图的Value 列是可以输入值的。点击Apply 按钮来提交你做的任何改变,之后页面会刷新,文本域显示的当前值反应了你刚才做的改变。
现在看MBean 的操作(Operations ),你只看到一个可用的操作。MBean 视图为每一个操作都构建了一个按钮,按钮的名字就是方法名。对于HelloWorldMBean ,你可以看到一个printGreeting 的按钮,这是HelloWorldMBean 接口剩下的方法(除去getter 和setter )。按钮前是void ,它是这个方法的返回值。如果方法有输入,你将会看到每个输入对应一个输入文本框。
HTML 适配器只为输入参数提供几种特定的类型,只支持String 、基本类型、基本类型的外包类。
当你点击printGreeting 按钮后,可以看到发生了两件事,第一: 页面迁移到了另一个页面,上面显示了方法执行成功并且没有返回值;第二:看看代理的输出,你可以看到你刚才输入的Greeting 属性的值被打印了出来。
恭喜你已经成功的管理了HelloWorld 。还剩下一个视图:管理视图。返回代理视图,然后点击Admin 按钮,就可以进入HelloAgent 的管理视图了。
使用上两节的HTML 页面,你能配置和查询注册在代理中的MBean 。然而,如果你想不写任何代码就向代理中添加一个MBean ,该怎么办呢?Admin 视图使得你可以侵入MBeanServer 以移除或添加MBean 。在页面上,你可以指定一个ObjectName 值和一个MBean 的Java 类,MBeanServer 将根据你的输入构建并注册一个相应的MBean 。Admin视图显示了4 个文本域(图2.8 ):
§ Domain — HTML 适配器使用了代理的默认域。当前显示HelloAgent ,这是对象名的第一部分。
§ Keys — 需要符合[name=value ],* 的要求 . 这个框框表示对象名的名值对部分。
§ Java Class — 表示你想创建的MBean 的类的全限定名。
§ Class Loader — 是可选的。 你可以为MBeanServer 在试图加载前面输入的Java 类时,指定一个特定的类加载器。
Figure 2.8 The Admin View presented by the HTML adapter
为了更进一步理解这个页面,让我们创建更多的MBean 。在下一节你将会看到,在一个MBeanServer 中,你可以有同一种类型的许多MBean ,只要他们的对象名不同。
2.5.4 在 HelloAgent 是注册 / 解除注册 MBean
让我们通过Admin 视图加载另一个HelloWorldMBean 的实例。域名使用默认的HelloAgent ,在Keys 中输入name=helloWorld2 ,在Java Class 中输入jmxbook.ch2.HelloWorld ,它实现了HelloWorldMBean 接口。
在Java Class 中指定的任何类都必须对代理的MBeanServer 是可见的。对HelloAgent ,这就要求输入的类必须在CLASSPATH 中。
ClassLoader 空着 , 告诉 HelloAgentMBeanServer 使用默认的类加载器。代理将使用输入的值构建ObjectName ,像这样:
HelloAgent :name=helloWorld2
当这些都就绪后,你已经准备好了新建一个HelloWorld 实例了。在Action选项的下拉框中有三个选项:
§ Create — 告诉 MBeanServer使用无参构造方法新建一个MBean
§ Unregister — 只在你指定的ObjectName 在代理中存在对应的MBean 时有效
§ Constructors — 向你显示MBean的构造函数列表
选择Create选项,点击Send Request按钮,你将会看到一个成功的消息,告诉你已经成功的新建并注册了一个新的MBean。返回Agent视图,确认一下MBean列表。
使用有参数的构造方法
还记得HelloWorldMBean 的实现类HelloWorld 有两个构造方法吗?一个默认构造方法和一个有参数的构造方法,参数代表Greeting属性的初始值。让我们使用第二个构造方法再注册一个HelloWorldHelloWorld 的实例吧。
1. 返 回Admin视图,输入合适的值来创建一个新的HelloWorldMBean 。确保输入一个唯一的值(例如name=helloWorld3)。
2. 这 次选择从Action列表选择Constructors,然后点击Send Request按钮,你会看到构造方法列表(有两个),其中一个显示了文本框用来输入它的一个参数 。
3. 输 Greeting属性的值,然后点击和上面的构造方法关联的Create按钮。如果对象名和类名你都输入对了,你将会再次看到“creation successful”消息。
4. 返 回代理视图,确认MBean列表,这次有三个HelloWorldMBean 实例了。
2.6 使用 MBean 通知
通 过前面几节,你已经创建和注册了自己的MBean,对使用JMX来开始工作你已经具备了足够的知识。你已经学习了怎样创建一个标准MBean,怎样把它添加进JMX代理,怎样通过HTML适配器管理它。但是,你仍然错过了一个关键的部分:通知
JMX通知是Java对象,通过它可以从MBean和代理向那些注册了接收通知的对象发送信息(如图2.9)。对接受事件感兴趣的对象是通知监听器,他们实现了javax.management.NotificationListener interface接口。
图 2.9 从 MBean 向一个注册的监听器发生通知
通知是JMX很重要的一部分,因为它们允许事件的传输。JMX事件可以是任何事情,从MBean的属性变更到在MBeanServer注册一个新MBean,都可以是JMX事件。
为了给你一个通知的简单介绍,本节将会将通知添加进HelloWorldMBean。在第6章我们将深入学习通知机制。
2.6.1 在 HelloWorldMBean 中添加通知的代码
HelloWorldMBean 发送通知,它得允许其他的对象注册监听器来接受通知。JMX支持两种机制来为MBean提供监听器以注册来接受通知:
§ 实现 javax.management.NotificationBroadcaster 接口
§ 继承 javax.management.NotificationBroadcasterSupport 类 ( 它实现类了NotificationBroadcaster 接口 )
使用接口的好处是你的类可以继承其他的类,而使用继承的好处是你不需要为 接口写额外的代码。如果你的MBean不需要继承其他的类,那么可以使用继承以使用它里面的实现。HelloWorld 不需要继承其他的类,所以就可以自由的使用继承。如列表2.3所示:
Listing 2.3 HelloWorld .java
package jmxbook.ch2;
import java.io.*;
import javax.management.*;
//#1
public class HelloWorld extends NotificationBroadcasterSupport implements HelloWorldMBean {
//#2
public HelloWorld () {
this.greeting = "Hello World! I am a Standard MBean ";
}
//#2
public HelloWorld (String greeting) {
this.greeting = greeting;
}
public void setGreeting(String greeting){
this.greeting = greeting;
Notification notification = new Notification("jmxbook.ch2.helloWorld.test", this, -1,System.currentTimeMillis(), greeting);//#3
sendNotification(notification);//#4
}
public String getGreeting(){
return greeting;
}
public void printGreeting (){
System.out.println(greeting );
}
private String greeting;
}
( 注解 )<#1 继承 NotificationBroadcasterSupport 类 >
( 注解 )<#2 定义两个构造方法 >
( 注解 )<#3 创建 javax.management.Notification 对象 >
( 注解 )<#4 发送通知 >
#1 通过继承NotificationBroadcasterSupport 类改变了HelloWorld 类的声明,NotificationBroadcasterSupport 类向它的子类MBean提供了注册监听器和发送通知的方法,它实现了javax.management.NotificationBroadcaster 接口。
#2 本例只是发送一个简单的通知,在这步使用构造方法新建了一个通知:
public Notification(java.lang.String type, java.lang.Object source,long sequenceNumber,long timeStamp,java.lang.String message)
构造方法的参数如表 2.4 所示:
表 2.4 Notification 构造方法参数
Parameter | Description |
java.lang.String type | 点分格式的 String 来标识通知,用于通知的简要描述 |
java.lang.Object source | 产生通知的 MBean , 可以是 MBean 对象的引用或是它的ObjectName |
long sequenceNumber | 在一个通知序列中表示本通知 |
long timestamp | 创建通知 的时间戳 |
java.lang.String message | 通知源的消息 |
#3 通过继承NotificationBroadcasterSupport ,你不仅间接实现了NotificationBroadcaster 接口,还继承了sendNotification() 方法,当MBean需要发送通知时就可以调用这个方法,父类会将它发送给所有对应的监听者。对应的监听者是那些已经在MBean上注册并且它的过滤器接受特定类型通知的一些监听器。
前面的例子使用NotificationBroadcasterSupport 类 ,它提供了NotificationBroadcaster 接口子接口(NotificationEmitter)的一个实现 ,接口 NotificationBroadcaster如下:
public interface NotificationBroadcaster {
public void addNotificationListener(NotificationListener listener,NotificationFilter filter,Object handback) throws IllegalArgumentException;
public MBeanNotificationInfo [] getNotificationInfo() ;
public void removeNotificationListener(NotificationListener listener) throws ListenerNotFoundException;
}
实现这个接口的MBean通过 addNotificationListener() 方法为其他的对象提供了注册机制,这个方法接受一个NotificationListener 对象、一个NotificationFilter 对象、和一个handback 对象作为参数。
NotificationListener 参数是一个实现了NotificationListener 接口的对象,它指定一个handleNotification()方法。当通知被发送个一个监听器时这个方法会被回调。
NotificationFilter 参数是一个可选参数,它允许MBean来过滤发送哪些消息给监听器。handback 参数在每次通知发送时都会被发送回客户端。
注意通知注册和传输机制和java事件模型的相似性。
为了发送通知,还需要一些对象来接受它们。本例将HelloAgent 作为通知监听器,只需要对代码最很少改动。首先,HelloAgent需要实现NotificationListener 接口,它还是会新建和注册HTML适配器和HelloWorldMBean,创建完MBean后,它就可以为HelloWorldMBean注册一个通知监听器来接受通知。HelloAgent的代码变更部分在列表2.4中以粗体标注:
//Listing 2.4 HelloAgent .java
package jmxbook.ch2;
import javax.management.*;
import com.sun.jdmk.comm.HtmlAdaptorServer;
//#1
public class HelloAgent implements NotificationListener {
private MBeanServer mbs = null;
public HelloAgent () {
mbs = MBeanServerFactory.createMBeanServer("HelloAgent ");
HtmlAdaptorServer adapter = new HtmlAdaptorServer();
HelloWorld hw = new HelloWorld();
ObjectName adapterName = null;
ObjectName helloWorldName = null;
try{
adapterName = new ObjectName ("HelloAgent:name=htmladapter,port=9092");
mbs.registerMBean(adapter, adapterName);
adapter.start();
helloWorldName = new ObjectName ("HelloAgent:name=helloWorld1");
mbs.registerMBean(hw, helloWorldName);
<strong>hw.addNotificationListener(this, null, null);</strong>//#2
}catch(Exception e) {
e.printStackTrace();
}
}//constructor
//#3
<strong>public void handleNotification(Notification notif, Object handback){
System.out.println("Receiving notification ...");
System.out.println(notif.getType());
System.out.println(notif.getMessage());
}</strong>
public static void main(String args[]) {
HelloAgent agent = new HelloAgent();
}
}//class
( 注解 )<#1 实现 NotificationListener 接口 >
( 注解 )<#2 注册接受通知 >
( 注解 )<#3 实现接口方法
#1 HelloAgent 代理实现NotificationListener 接口,接口中只有一个方法handleNotification(),在通知传地到时它被调用。
#2 注册MBean之后,HelloAgent 将自己作为一个listener 注册在了HelloWorldMBean上,这是通过将自己作为NotificationListener 参数传递给MBean的addNotificationListener()(继承自父类NotificationBroadcasterSupport )来完成的
#3 前面提到过,对象要想接收到通知,必须实现NotificationListener 接口,它只有一个方法handleNotification( )。本例中只是简单的打印了一些信息。
运行结果
要测试本例,首先需要编译源码然后执行HelloAgent(参考2.5节)。一旦代理开始运行了,就可以打开浏览器,输入http://localhost:9092 ,进入Agent页面,为测试通知,按照以下步骤进行:
1. 迁移至 MBean 视图,点击 HelloWorldMBean
2. 当 HelloWorldMBean 的 greeting 改变时会发送一个通知,因此输入一个新值,然后点击 Apply
3. 查看 HelloAgent 控制台,你会看到如下输出:
Receiving notification ...
jmxbook.ch2.helloWorld.test
I have changed my greeting
输出包括你的打印信息 ” Receiving notification … ” ,以及通知类型和通知包含的消息。
2.7 总结
本章让手把手教你学习了几个JMX的例子。其中你开发了一个可管理的资源,创建并运行了一个简单的JMX代理,我们讨论了怎么注册MBean,怎样确保它的对象名唯一,怎么创建MBeanServer等。
另外你还是使用了Sun提供JMX RI中的HTML适配器,通过HelloWorld例子,你应该明白了从JMX的角度来看MBean的开发是很如此简单,MBean只通过简单的几行代码来暴露自己的资源。
最后,为了使JMX的介绍更全面,我们写了一个JMX通知的例子,在第6章我们会详细讨论通知,哪儿你会明白为啥通知是管理系统中的一个基本部分。
第三章,你将会开发一个JMX代理,然后在本书的其余部分,你会加强它的功能,它将会被用于其他的很多章节。