(转)JMX与commons-modeler(by quqi99)

本文描述了Jakarta项目之一commons-modeler,其当前版本是1.0版。它使用XML文件来配置组件的模型MBean元信息,还利用一种算法为复杂资源类提供MBean类的基本实现。
<!-- START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --> <!-- END RESERVED FOR FUTURE USE INCLUDE FILES-->

在开始阅读本文之前,我们必须统一对一个概念的认识(也许本人翻译的不标准,请包涵)。这个概念是instrumentation,它是一种转化,即把应用系统的组件通过某种规范化的包装转化成某个管理框架中的管理组件的过程或结果。

项目愿景

JMX是Java管理扩展(Java Management Extensions)的简称,用于为软件系统构造管理模块。JMX使用一个MBean来代表一个软件系统中需要管理的组件,它定义了四种MBean,其 中模型MBean简单、灵活而又功能强大。模型MBean有一种方法为很多不同的资源定义了标准MBean,从而没有必要为每个资源写单独的MBean实 现类。

然而模型MBean的这种能力是有代价的,程序员需要设置大量的元信息来告诉JMX如何为它们创建模型MBean,这些 元信息包括组件的属性、操作和其它信息。commons-modeler项目的目的就是降低程序员实现模型MBean的高昂代价,它使得创建模型 MBean不再工作量巨大,而是轻松愉快。




回页首

用户问题

JMX是很好的管理功能实现框架,然而用户需要一种更灵活、更有效的方式来定义组件的模型MBean。




回页首

简单分析

J2EE 已经成为越来越多的企业开发服务应用的战略性架构,更好地管理和监控这些服务应用的需求也日益迫切。JMX就是满足企业对J2EE应用系统的管理需求的标 准。其实JMX已经在许多基础性J2EE服务软件中发挥重要作用,比如WEBLOGIC SERVER,JBOSS SERVER和TOMCAT SERVER,而且已经成为这些软件产品的基础。人们越来越接受JMX,在J2EE1.4中JMX是一个必备的成分,而J2SE1.5中,JMX将成为一 个标准组件。

当JMX被广泛地用于J2EE架构中的同时,它也快速地渗透入基于J2EE架构的应用程序,轻松而直接地用来管理 J2EE架构的应用程序可以说是这门技术对应用开发者的主要好处。运营分布式计算的同时,很多IT部门正在为应用管理而焦头烂额。J2EE架构允许复杂的 应用结构和部署,如果没有一个标准的应用管理机制,对于IT部门来说是非常可怕的。现在应用开发者能利用JMX帮助IT部门很好地控制这些管理问题。

在基本的应用管理功能之外,JMX技术还有助于商业利益。很多任务关键型应用系统支持商业过程,监控和挖掘这些过程有助于关键业务决策。通过JMX技术,人们可以容易地按需获取任务关键型系统的关键信息,这些便利是以前的技术做不到的。

我们再也不能把系统的管理作为构建和发布应用系统的额外部分。有时开发者在开发系统时实现管理功能,他们经常使用一些特别 的技术,如控制台打印、日志文件等,虽然这些技术能解决及时之需,但不能满足企业对整个应用系统体系的复杂管理需求,这里,企业往往需要所有的应用系统采 用一种管理架构实现统一管理功能,而不是每个应用系统一个技术。

对于一个复杂的应用系统体系,我们很难预测所有管理需求,管理工具也许能帮我们一些,但是我们还得面对不完善和不完备的应用管理方案。面对众多的软件供应商、越来越多的定制J2EE应用系统,管理工具提供商的形势也日趋严峻。

因此我们需要一种基于标准的instrumentation,这样管理应用系统能使用它们进而管理各式各样的企业应用。许 多管理协议和标准陆续出台,它们之间有SNMP, DMI, CIM/WBEM, AMI等,每个都有一些亮点,但却没有一个是对这个问题的好方案,因为利用这些技术,应用开发者在做应用程序的instrumentation时非常困 难,在这方面JMX确实做得不错。




回页首

使用界面

4.1 JMX介绍

我们从架构、运行和例子等几个方面对JMX进行介绍,这些东西是理解 COMMONS-MODELER项目的前提。

4.1.1 JMX 架构

JMX 是一个管理方面的Java标准,它提供了一个远程或本地管理Java应用系统的模型。JMX 的目的之一是提供各种管理应用系统能访问的instrumentation。 JMX模型使用一套公共的应用管理组件,而且提供各种协议来访问它们。这样应用程序被转化一次就能被多种协议访问。

下图体现了JMX的架构,分层的体系保证了"转化一次,多种协议访问"的目的。


在 转化级(Instrumentation级),称为MBeans的管理组件利用应用程序组件(a.k.a被管理资源)提供应用相关的管理信息。这些 MBeans,与Java Beans类似,提供了管理被管理资源功能和信息的属性和操作,它们和被管理资源交互,由程序员基于应用系统的管理需求开发。

代理级(Agent级)与MBeans管理组件交互,提供对这些MBeans组件的管理功能,MBean Server是这级的主要部件,它掌管着所有的MBeans组件。代理级通常由一个代理组件来充当,它负责创建MBean Server、注册MBeans和创建连接器服务端等工作。

连接器级(Connector级)或称适配器级(Adapter级)由各种远程访问协议的适配器或连接器组成,提供管理 系统的远程访问能力,在JMX规范中,这一级是代理级的子级。连接器一般由连接器MBean,连接器服务(处于分布式服务级)和连接器客户端(运行在远程 管理软件端)三个部分组成;而适配器一般由适配器MBean和适配器服务两步分组成。典型的连接器是RMI连接器,典型的适配器是HTTP适配器和 SNMP适配器。

远程管理软件,依赖于JMX在连接器级支持的远程协议,通过SNMP或HTTP等远程访问协议能访问MBeans提供的管理功能。这样应用管理功能就能和企业中现有的传统管理软件集成使用。

MBean管理组件是一些Java类,JMX目前定义的MBean管理组件类型有四种:标准、动态、开放和模型 MBeans。 标准MBeans提供静态管理接口;动态MBeans提供一个能在运行时改变的管理接口,而且配备有元信息;开放MBeans 是一种动态MBeans,但是使用的JAVA类型受限,能提供其它管理应用系统的兼容性,在JMX1.0中没有完全定义清晰, JMX1.2中则是必备的;模型MBean是动态MBean的一种,为被管理资源的转化提供了通用的MBean 类实现模版,这样开发者只要指定被管理资源的信息就能创建一个MBean,而不需要编写MBean 类。

JMX还包括一个完整的通知模型,这样所有的MBeans能生成通知,而管理程序能向这些MBeans注册通知和异步地接收通知。

4.1.2 JMX运行模型


了 解了JMX的体系结构后,我们现在来看看JMX的运行模型。《JMX运行模型》图告诉我们JMX体系下管理软件和被管理应用系统关键的运行情况(关于图中 的增值代理逻辑,代理服务的概念可到JMX官方规范中去查询)。这个图显示我们的应用软件部署在J2EE应用服务器上(可以没有EJB,如 TOMCAT),程序开发者把需要管理的程序资源比如某个JAVA Bean转化成MBean,这个MBean在运行之前必须向JMX设施中的MBean Server注册,运行时会调用被管理资源完成管理任务,比如执行被管理资源的某个方法,访问被管理资源的某个属性等。管理软件必须通过MBean Server访问MBean,MBean Server则利用一个唯一的ObjectName对象来标识和访问注册了的MBean。在程序员为所有的被管理资源编写了MBean之后,必须在应用软 件的某个地方完成创建和注册MBean对象。本地管理软件可以和应用软件同属一个虚拟机,不必通过远程协议访问MBean Serve,这个例子有TOMCAT 4.1x种的ADMIN应用程序。远程管理软件可以通过SNMP适配器、HTTP适配器、各种连接器来和JMX代理通信,WebJMX项目就是高效使用 HTTP适配器编写远程管理软件的典型项目。

4.1.3 JMX例子

下面的例子是J2SDK环境下的例子,但同样具有本地管理软件,应用系统资源、MBean和JMX设施等部件。我们只写了标准MBean和模型MBean的实现,主要突出了模型MBean代码的复杂性,其它种类的MBean代码可以参看JMX参考实现的例子。

4.1.3.1 JMX设施

我们可以 使用任何JMX兼容的实现,可以是JMX1.x规范中的参考实现也可以是开源项目MX4J。这里使用JMX1.2规范中的参考实现,以降低大家在调式代码 时对库代码的怀疑,而且参考实现中附带了一个sun的HTTPAdaptor,能可视化地调用MBean,比起MX4J中提供的HTTPAdaptor来 说在可操作性上强多了。

4.1.3.2 应用系统资源

我们假设一个路由器类,它管理着许多能通过这个设备出去的IP地址,它提供增加IP地址和删除IP地址的操作。 下面是这个资源类的代码,我增加了一些System.out.println语句以便观察服务端的执行情况:

import java.util.ArrayList;
import java.util.List;
public class Router {	
	List ipList=new ArrayList();	
	public void addIP(String ip){		
		System.out.println("addIP:"+ip);	
		ipList.add(ip);	
	}	

	public void deleteIP(String ip){	
		System.out.println("deleteIP:"+ip);		
		ipList.remove(ip);	
	}	
	public  String[] getIPS(){	
		if (ipList.isEmpty()) return new String[0];		
		String[] x =(String[])ipList.toArray(new String[ipList.size()]);	
		for(int i =0; i<x.length;i++){	
			System.out.println(x[i]);		
		}	
		return x;	
	}
}


从代码可以看出,这个资源类有一个属性-IPS和两个操作-addIP和deleteIP。函数getIPS是属性IPS的getter,可以看出这个getter的不符合javabean的命名规范,我们看看MBean是如何处理的。

4.1.3.3 MBean

我们要把路由器资源类的功能转化成能被管理软件操作的功能,这样我们就要对路由器类进行管理转化,形成相应的MBean。

(1) 标准MBean

编写标准MBean,必须先定义一个接口,接口名字是资源的类名附加MBean

public interface StandardRouterMBean {
	String[] getIPS();	
	void deleteIP(String ip);	

	void addIP(String ip);
}


把资源类的代码复制一份,并以StandardRouter为新类名,而且让其实现StandardRouterMBean,其它不变:

public class StandardRouter implements StandardRouterMBean{
}

(2) 模型MBean

我们说过为转化一个资源为模型MBean不需要为它们写特别的MBean接口或MBean类,但是我们得为创建和注册一个模型MBean准备大量的关于资源的材料,这包括资源类将要被管理的属性,操作等。下面是转化Router资源类的代码:

public class	ModelMBeanUtils {
	public static void CreateModlerMBean(MBeanServer server) {	
		final  boolean READABLE = true;		
		final  boolean WRITABLE = true;	
		final  boolean BOOLEAN  = true;    
		// 构造 IPS 只读属性信息
		Descriptor descr1 = new DescriptorSupport();	
		descr1.setField("name", "IPS");	
		descr1.setField("descriptorType", "attribute");	
		descr1.setField("displayName", "IP addresses");	
		descr1.setField("getMethod", "getIPS");    		
		ModelMBeanAttributeInfo IPSInfo = 		
			new ModelMBeanAttributeInfo(	
			"IPS",                        // 属性名		
			String[].class.getName(),        //属性类型		
			"IP addresses.",        // 属性描述文字		
			READABLE, !WRITABLE, !BOOLEAN,  // 读写		
			descr1                         // 属性描述子	
		);	

		//构造 getIPS 属性获取操作信息		
		Descriptor descr6 = new DescriptorSupport();	
		descr6.setField("name", "getIPS");	
		descr6.setField("descriptorType", "operation");	
		descr6.setField("role", "operation");		
		MBeanParameterInfo [] deleteIPParams2 = null;	
		ModelMBeanOperationInfo getIPSInfo = 			
			new ModelMBeanOperationInfo(			
			"getIPS",                   // 操作名	
			"get all IP address.",    //描述		
			deleteIPParams2,                         // 参数		
			String[].class.getName(),      // 返回类型		
			MBeanOperationInfo.ACTION,      // 作用			
			descr6                        // 操作描述子		
		);		

		// 构造addIP操作信息		
		Descriptor descr2 = new DescriptorSupport();	
		descr2.setField("name", "deleteIP");	
		descr2.setField("descriptorType", "operation");		

		//构造参数信息		
		MBeanParameterInfo [] deleteIPParams = new MBeanParameterInfo [1];	
		deleteIPParams[0] = new MBeanParameterInfo("ip",String.class.getName(),"ip");


<!--    code sample is too wide -->		
               ModelMBeanOperationInfo deleteIPInfo = 		
			new ModelMBeanOperationInfo(	
				"deleteIP",                
				"delete a Ip from router.",	
				deleteIPParams,               
				"void",      // return type		
				MBeanOperationInfo.ACTION,    
				descr2                        			
			);	
			// 构造addIP操作信息		
			Descriptor descr3 = new DescriptorSupport();	
			descr3.setField("name", "addIP");	
			descr3.setField("descriptorType", "operation");		

			ModelMBeanOperationInfo addIPInfo = 		
				new ModelMBeanOperationInfo(		
					"addIP",              
					"add a Ip into router.",		
					deleteIPParams,               
					"void",      // return type		
					MBeanOperationInfo.ACTION,    	
					descr3      		
				);      
				//构造MBean信息		
				Descriptor descr4 = new DescriptorSupport();	
				descr4.setField("name", "Router");		
				descr4.setField("descriptorType", "mbean");	

				// create ModelMBeanInfo     	
				ModelMBeanInfo info = new ModelMBeanInfoSupport(	
					RequiredModelMBean.class.getName(),   // MBean类		
                                       <!--    code sample is too wide -->					
                                        "Router",                           // description		
                                       <!--    code sample is too wide -->					
                                       new ModelMBeanAttributeInfo[] {      // 所有的属性信息	
					IPSInfo			
				},		
				null,                                // 所有的构造函数信息	
				new ModelMBeanOperationInfo[] {      // 所有的操作信息	
					getIPSInfo,			 
					addIPInfo,			 
					deleteIPInfo		 
				},		  
				null,                                // 所有的通知信息		
				descr4                               //MBean 描述子		
                                <!--    code sample is too wide -->			
                        );		
			try {		 
				// create and configure model mbean		 
				RequiredModelMBean model = new RequiredModelMBean();       
                                <!--    code sample is too wide -->				//设置被管理的资源类对象		
				model.setManagedResource(new Router(), "ObjectReference");		
                                <!--    code sample is too wide -->				
                                model.setModelMBeanInfo(info);		
				server.registerMBean(model, 
				new ObjectName("jmx_work:name=ModelRouter"));      
			}catch (Exception e) {		
				e.printStackTrace();	
			}    	
		}
}


上面代码的大部分用来构造资源类的被管理信息,包括 一个属性,一个属性操作方法和两个操作;另外我们还构造了一个MBean信息,来组织前面的各种管理信息;接着我们创建一个 RequiredModelMBean对象,这个类是JMX规范指定的实现模型MBean的类。实际情况下创建一个MBean的代码比这还复杂,比如对参 数的描述,对构造函数的描述等。

4.1.3.4 为远程管理实现的Agent

这 个Agent主要完成创建MBean Server对象,创建并注册MBean对象,创建远程访问的适配器对象并启动适配器服务以便接收远程管理软件的请求。对于HttpAdaptor适配器 对象来说,远程管理软件可以用浏览器来充当。下面是为远程管理实现的JMX Agent代码:

import javax.management.*;
public class RemoteHttpAgent {	
	public static void main(String[] args) {	
		try {		
			// 创建MBean Server对象	
			MBeanServer server = 		
				MBeanServerFactory.createMBeanServer();	
			// 创建标准MBean的对象名,对象名是MBean Server标识MBean的方法		
			ObjectName name = 				
				new ObjectName("jmx_work:name=StandardRouter");		
			// 向MBean Server注册标准MBean		
			server.registerMBean(new StandardRouter(), name);		
			//创建 模型MBean并注册			
			ModelMBeanUtils.CreateModlerMBean(server);		
			//创建适配器对象	
			com.sun.jdmk.comm.HtmlAdaptorServer adaptor =		
				new com.sun.jdmk.comm.HtmlAdaptorServer();	
			//向Bean Server注册适配器对象		
			server.registerMBean(adaptor, 
				new ObjectName("adaptor:protocol=HTTP"));
         		//设置并启动HTTP适配器服务		
			adaptor.setPort(8080);		
			adaptor.start();		
		}catch (Exception e) {	
			e.printStackTrace();	
		}	
	}
}


在编译和运行这个Agent之后,你可以使用浏览器访问localhost:8080端口得到管理界面,在这个界面上你可以完成调用MBean的方法,访问和设置MBean,创建、注册和注销MBean等任务。这个界面的具体操作说明请参见JMX参考实现中的文档。

4.1.3.5 本地管理软件

本地管理软件要完成对路由器上的IP地址增加和删除的操作,使用MBeanServer的invoke、getAttriubte和setAttribute函数可以操作MBeanServer上注册的MBean,其代码如下:

import javax.management.*;
public class LocalAgent {	

	MBeanServer server= null;	
	public  LocalAgent(){		
		try {		
			// 创建MBean Server对象		
			server = MBeanServerFactory.createMBeanServer();	
			// 创建标准MBean的对象名,对象名是MBean Server标识MBean的方法		
			ObjectName name = 			
				new ObjectName("jmx_work:name=StandardRouter");		
			// 向MBean Server注册标准MBean		
			server.registerMBean(new StandardRouter(), name);			
                        <!--    code sample is too wide -->			//创建 模型MBean并注册			
			ModelMBeanUtils.CreateModlerMBean(server);	
		}catch (Exception e) {	
			e.printStackTrace();	
		}	
	}	

	public void printIPS(ObjectName on){	
	
		// Create the new object and associated MBean	
		String signature[] = new String[0];	
		Object params[] = new Object[0];		
		String[] ips;			
		try {			
			ips = (String[]) server.getAttribute(on, "IPS");	
			for(int i =0;i<ips.length;i++){			
				System.out.println("LocalAgent:printIPS:"+ips[i]);		
                          <!--    code sample is too wide -->			
                        }			
		}catch (Exception e) {			
			// TODO Auto-generated catch block		
			e.printStackTrace();		
		}		
	}

	public void addIP(ObjectName on,String ip){		
		// Create the new object and associated MBean	
		String signature[] = new String[]{"java.lang.String"};  //定义了参数的类型	
		Object params[] = new Object[]{ip};   //定义了参数的值		
		try {		
			server.invoke(on, "addIP",params, signature);	
		} catch (Exception e) {	
			// TODO Auto-generated catch block		
			e.printStackTrace();		
		}			
	}	

	public void deleteIP(ObjectName on,String ip){	
		// Create the new object and associated MBean	
		String signature[] = new String[]{"java.lang.String"};  //定义了参数的类型	
		Object params[] = new Object[]{ip};   //定义了参数的值		

		try {		
			server.invoke(on, "deleteIP",params, signature);	
		} catch (Exception e) {	
			// TODO Auto-generated catch block	
			e.printStackTrace();		
		}				
	}
	public static void main(String[] args) {	
		LocalAgent la= new LocalAgent();		
		try {		
			ObjectName on = new ObjectName("jmx_work:name=StandardRouter");		
                        <!--    code sample is too wide -->			
                        la.addIP(on, "10.1.34.123");		
			la.printIPS(on);			
			la.deleteIP(on, "10.1.34.123");		
			la.printIPS(on);		
		}catch (MalformedObjectNameException e) {	
			// TODO Auto-generated catch block		
			e.printStackTrace();	
		}catch (NullPointerException e) {	
			// TODO Auto-generated catch block	
			e.printStackTrace();		
		}	
	}
}

这段代码体现了通过MBeanServer接口访问 MBean对象的方法。MBeanServer接口主要提供下面三个函数供外界访问其管理的MBean对象(注意我们这里说得MBean对象函数或属性是 指创建MBean对象之前设置的那些构造信息当中的属性和函数,而不是MBean类中的函数和属性):

  1. 获取MBean对象某个属性的值: 接口函数为Object getAttribute(ObjectName, String),第一个参数是MBean对象注册时的ObjectName,第二个参数是属性名。外面的代码调用这个函数后,MBeanServer为调 用MBean对象的getAttribute方法,后者一般会用自己的invoke方法调用被管理资源对象的属性getter函数。
  2. 设置MBean对象某个属性的值: 接口函数是Void setAttribute(ObjectName, Attribute),第一个参数是MBean对象注册时的ObjectName,第二个参数是属性名和属性值组成的Attribute对象。外面的代码 调用这个函数后,MBeanServer为调用MBean对象的setAttribute方法,后者一般会用自己的invoke方法调用被管理资源对象的 属性setter函数。
  3. 调用MBean对象的某个函数: 接口函数是Object invoke(ObjectName,String, String[],String[]),第一个参数是MBean对象注册时的ObjectName,第二个参数是函数名,第三个参数是MBean对象函数 参数的值,第四个参数是MBean对象函数的类型。外面的代码调用这个函数后,MBeanServer为调用MBean对象的invoke方法,后者直接 调用被管理资源的相应方法。

除了这些关键的接口函数,MBeanServer接口中还有丰富的查询函数供代理和管理程序使用,最明显的接口函数有查询已经注册的所有MBean对象。

4.2 应用编程接口

commons- modeler项目需要commons-logging、commons-Digester、commons-beanutils、commons- collection几个项目的库,当然还需要JMX库,而且要求与JMX1.1兼容。commons-modeler环境下编写JMX应用系统的主要编 程接口包括一个配置文件和两个类。

4.2.1 xml配置文件schema描述

本节主要讲述了编写commons-modeler配置文件的语法,这个语法由xml schema形式化定义。

(1) 元素 mbeans-descriptors,这是ModelMbean配置文件的顶级元素,它可以包括多个mbean的配置


(2) 元素 mbeans-descriptors/mbean,它定义了一个ModelMBean


(3) 元素 mbeans-descriptors/mbean/attribute代表了ModelMBean的一个被管理的属性


(4) 元素 mbeans-descriptors/mbean/constructor代表一个构造函数


(5) 元素 mbeans-descriptors/mbean/notification代表一个通知


(6) 元素 mbeans-descriptors/mbean/notification/notification-type代表一个通知类型


(7) 元素 mbeans-descriptors/mbean/operation代表MBean的一个操作


(8) 元素 parameter代表一个参数


4.2.2 两个主要的类

commons- modeler的两个主要的类是Registry和ManagedBean。Registry是commons-modeler项目的入口,用设计模式术 语来说它还是单例模式和工厂方法模式的结合体,你可以从Registry类实例中可以获取Registry对象、MBeanServer对象、 ManagedBean对象。ManagedBean对象则代表配置中的一个ModelMBean配置,它可以创建一个ModelMBean对象、同时把 这个ModelMBean对象和应用系统中某个资源对象连接起来。

在这两个类的帮助下,commons-modeler环境下JMX的开发顺序如下:

  1. Registry.loadRegistry(java.io.InputStream stream)装载配置文件;
  2. Registry.getServer()获取MBeanServer对象;
  3. 向MBeanServer对象注册协议适配器或连接器,并启动访问服务;
  4. 调用Registry.getRegistry()方法获得Registry对象;
  5. 调用Registry对象的findManagedBean(String name)方法获得名字为参数name的ModelMBean对象配置的ManagedBean对象;
  6. 生成被管理资源的对象;
  7. 调用ManagedBean对象的createMBean(Obeject)方法创建ModelMBean对象;
  8. 利用ManagedBean对象的其他方法获取创建ObjectName对象的相关信息创建ObjectName对象;
  9. 向MBeanServer对象注册ModelMBean对象。

一般地,JMX代理的代码到第三步就结束,4-9步由应用系统的其它部分完成。

4.2.3 例子

为了让commons-modeler环境下编程比较直接JMX编程的效果更直观,我们仍然使用前面的应用资源对象类Router来构造commons-modeler环境下的例子。除了已编写的Router类,这个例子还包括一个配置文件和一个代理类。

4.2.3.1 xml配置文件

<?xml version="1.0"?>
<!DOCTYPE mbeans-descriptors PUBLIC 
"-//Apache Software Foundation//DTD Model MBeans Configuration File" 
"http://jakarta.apache.org/commons/dtds/mbeans-descriptors.dtd">
<mbeans-descriptors> 
	<mbean       name="ModelerRouter"   
					description="modeler Router MBean"  
					domain="jmx_work"               
					group="Modeler"                
					type="Router">   
	<attribute   name="IPS"       
					description="get All IP Adress"        
					type="java.lang.String[]"           
					writeable="false"         
					getMethod="getIPS"/>  
	<operation   name="addIP"   
					description="add a ip into the router" 
					impact="ACTION"         
					returnType="void">   
	<parameter	name="ip"        
					description="the Ip will be added."   
					type="java.lang.String"/>   
	</operation>  
	<operation   name="deleteIP"     
					description="remove a ip from the router"    
					impact="ACTION"       
					returnType="void">   
	<parameter	name="ip"   
					description="the Ip will be deleted."     
					type="java.lang.String"/>  
	</operation>  
	</mbean>  
</mbeans-descriptors>

4.2.3.2 代理

和前面相比,创建MBean的代码发生了变化,变得简单而且容易维护。下面是代码:

public class RemoteHttpAgent {	
	private  MBeanServer mserver;	
	private  Registry  mregistry;	
	private  HtmlAdaptorServer madaptor=null;	

	public RemoteHttpAgent(int port){	
		mserver=createServer();		
		mregistry=createRegistry();		
		createHttpAdaptor(port);	
	}

	private   Registry createRegistry() {	
		Registry registry=null;			
		try {			
			URL url = this.getClass().getResource("/mbean-descriptors.xml");	
                        <!--    code sample is too wide -->			
                        InputStream stream = url.openStream();		
			Registry.loadRegistry(stream);			
			stream.close();			
			registry = Registry.getRegistry();		
		}catch (Throwable t) {	
			t.printStackTrace(System.out);	
			System.exit(1);		
		}		
		return (registry);
	}	
	private MBeanServer createServer() {		
		if (mserver == null) {			
			try {				
				mserver = Registry.getServer();			 
			}catch (Throwable t) {	
				t.printStackTrace(System.out);		
				System.exit(1);			 
			}		  
		}		  
		return (mserver);	 
	}

	private void createHttpAdaptor(int port){	
		madaptor =	new HtmlAdaptorServer();	 
		//向Bean Server注册适配器对象	 
		try {	
			mserver.registerMBean(madaptor, 	
				new ObjectName("adaptor:protocol=HTTP"));	 
		}catch (Exception e) {
			e.printStackTrace();	
		}	
		madaptor.setPort(port);	  
		madaptor.start();	
	}	

	public static void main(String[] args){	
		RemoteHttpAgent rha = new RemoteHttpAgent(80);		
		try {		
			rha.createMBean("ModelerRouter");		
		}catch (Exception e) {	
			e.printStackTrace();
		}	
	}	

	public  void createMBean(String mname)	
	throws Exception {		
			ManagedBean managed = mregistry.findManagedBean(mname);		
			if (managed == null) {			
				Exception e = new Exception("ManagedBean is not found with "+mname);		
                                <!--    code sample is too wide -->				
                                throw new MBeanException(e);		
			}		
			String domain = managed.getDomain();		
			if (domain == null)			
				domain = mserver.getDefaultDomain();		
			Router r = new Router();		
			ModelMBean mbean = managed.createMBean(r);		
			ObjectName oname = new ObjectName(domain+":name=" + managed.getName());		
                        <!--    code sample is too wide -->			
                        mserver.registerMBean(mbean, oname);	
		}
	}

上面的代码把commons-modeler环境下 编写代理代码的顺序的三步都封装在了代理的构造函数中,而后面几步的代码都在createMBean函数中,与前面直接使用JMX API的代码比较起来,创建一个ModelMBean对象再也不需要写那么多的构造各种info的复杂、难以维护的代码了。在实际的使用环境中,比如 tomcat4.1x系列的admin应用,createMBean函数的参数可能是应用程序中实际的资源对象,我们要求createMBean函数能从 资源对象的相关信息获取它的ModelMBean对象配置从而创建针对这个资源对象的ModelMBean对象。

编译和运行这个代理之后,使用浏览器到本地的80端口就能访问这个代理及其MBean了。




回页首

内部剖析

为 什么Commons-modeler项目能大大简化ModelMBean对象的构造过程,下面的静态结构分析可以知道这个端倪,为什么需要 BaseModelMBean而不是规范中的缺省RequiredModelMBean作为所有ModelMBean对象的类,下面的运行结构分析解析了 这个谜底。最后为了支持JMX1.2,我们对Commons-modeler项目做了些修改,参见支持JMX1.2。

5.1 静态结构

下 面的《Commons-modeler项目类图》显示了Commons-modeler项目中主要类之间的关系,以及这些类和JMX规范中的类或接口的关 系。图中绿色的类来自于JMX规范API。Commons-modeler项目类主要分为两部分。一部分是ManagedBean和xxxInfo类,它 们代表了xml配置文件中的相应信息,其中xxxInfo类又关联着JMX规范中的ModelMBeanxxxInfo类,这样ManagedBean在 创建ModelMBean对象时可以根据配置中的信息构造相应的ModelMBeanxxxInfo对象,而BaseModelMBean则是缺省的 ModelMBean类,与规范中规定的RequiredModelMBean类并行。类图中的另一部分是主要的代理编程接口,主要有Registry和 其相关的类或接口。


5.2 运行结构分析

不 管是远程管理软件还是本地应用程序访问MBean都要通过MBeanServer的invoke方法调用(属性操作方法get/setAttribute 最终也得通过invoke调用相应的getter或settter) MBean的invoke方法、接着由MBean的invoke方法调用被管理资源的相应操作来实现对资源的管理。但是被管理资源的类往往比较复杂,它们 方法的参数、返回值或属性的类型对大多数管理软件来说是无能为力的。所以ModelMBean的一个MBean类管理多数资源类的目的就很难达 到,Commons-modeler项目引入了BaseModelMBean类,这个类使用了一种算法来解决这些问题:对于不太复杂的资源类来 说,BaseModelMBean类的invoke方法会直接调用这些资源类的相应方法,对于那些复杂的资源类来说,我们可以扩展 BaseModelMBean类,编写相应的方法来掩盖资源类的相应方法。针对这种接口转换的需求我们自然想到了Adapter模式,对于这个模式的具体 解释可以参见相关书籍,下面是本项目中这个模式的应用图解:


正 如上图所示,BaseModelMBean类的Invoke方法的先尝试调用自己的方法,如果自己的类没有实现这个方法,就会调用被管理资源对象的方法。 有了BaseModelMBean的Invoke的算法,当我们要实现一个管理复杂资源类的ModelMBean类时,我们可以扩展 BaseModelMBean类,实现一个简单参数、返回值类型的函数,这个函数将从简单参数构造被管理资源类相应函数需要的复杂参数,然后调用这个函 数,并转化复杂类型的返回值成为管理软件能支持的类型。关于这种实现可以参看tomcat 4.1x系列admin应用中的GroupMBean。

5.3 支持JMX1.2

要让commons-modeler支持jmx1.2参考实现,需要做如下约定和修改:

  1. 在配置文件中,当参数、属性或返回值的类型是数组(也就是[]形式)时,不能写成 type="java.lang.String[]"的样子,而应该写成type="[Ljava.lang.String;"的java表达方式。因为 JMX1.2加强了对类型的检查。java.lang.String[]这样的表达不是java内部的数组表达方式。
  2. 对commons-modeler中OperationInfo类的下列方法修改如下:
public ModelMBeanOperationInfo createOperationInfo() {  
	if (info != null)          
		return (info);       
	ParameterInfo params[] = getSignature();      
	MBeanParameterInfo parameters[] =        
		new MBeanParameterInfo[params.length];  
	for (int i = 0; i < params.length; i++) 
		parameters[i] = params[i].createParameterInfo();     
	int impact = ModelMBeanOperationInfo.UNKNOWN;   
   
	if ("ACTION".equals(getImpact()))        
		impact = ModelMBeanOperationInfo.ACTION;     
	else if ("ACTION_INFO".equals(getImpact()))      
		impact = ModelMBeanOperationInfo.ACTION_INFO;  
	else if ("INFO".equals(getImpact()))          
		impact = ModelMBeanOperationInfo.INFO;    
	info = new ModelMBeanOperationInfo          
		(getName(), getDescription(), parameters,  
		getReturnType(), impact);  

	Descriptor descriptor = info.getDescriptor();      
	descriptor.removeField("class");
	//       descriptor.setField("role", getRole());   
	descriptor.setField("role","operation" );    
	System.out.println(""+descriptor.toString());   
	info.setDescriptor(descriptor);     
	return (info);  
}


JMX1.X系列参考实现中,对于MBean某个属性的getter或setter操作其描述信息中的Role为getter或setter,而在1.2中这个值与其他操作一样是operation。




回页首

遗留问题

可以说,commons-modeler项目利用配置文件比较好地解决了创建ModelMBean对象之前构造相关信息的难题,但是这个配置文件是否能进 一步简化,因为好多信息还是可以直接中从被管理资源类中获取;另外当资源类的函数或属性的类型比较复杂时,能否自动创建BaseModelMBean类的 子类的框架,这样可以免去程序员的好多麻烦。




回页首

总结

利 用JMX规范的API接口直接编写MBean程序是比较复杂的,commons-modeler项目大大简化了这个过程。commons-modeler 环境下利用ModelMBean编写JMX程序可以总结为9步,前3步编写代理,后6步与应用程序密切配合,创建管理应用程序中资源对象的 ModelMBean并向MBeanServer注册供管理程序访问。在实现时,这个项目的配置文件和Adapter模式的使用大大增加了灵活性和易管理 性,这种思想是值得我们学习和借鉴的。

参考资料

本文描述了Jakarta项目之一commons-modeler,其当前版本是1.0版。它使用XML文件来配置组件的模型MBean元信息,还利用一种算法为复杂资源类提供MBean类的基本实现。
<!-- START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --> <!-- END RESERVED FOR FUTURE USE INCLUDE FILES-->

在开始阅读本文之前,我们必须统一对一个概念的认识(也许本人翻译的不标准,请包涵)。这个概念是instrumentation,它是一种转化,即把应用系统的组件通过某种规范化的包装转化成某个管理框架中的管理组件的过程或结果。

项目愿景

JMX是Java管理扩展(Java Management Extensions)的简称,用于为软件系统构造管理模块。JMX使用一个MBean来代表一个软件系统中需要管理的组件,它定义了四种MBean,其 中模型MBean简单、灵活而又功能强大。模型MBean有一种方法为很多不同的资源定义了标准MBean,从而没有必要为每个资源写单独的MBean实 现类。

然而模型MBean的这种能力是有代价的,程序员需要设置大量的元信息来告诉JMX如何为它们创建模型MBean,这些 元信息包括组件的属性、操作和其它信息。commons-modeler项目的目的就是降低程序员实现模型MBean的高昂代价,它使得创建模型 MBean不再工作量巨大,而是轻松愉快。




回页首

用户问题

JMX是很好的管理功能实现框架,然而用户需要一种更灵活、更有效的方式来定义组件的模型MBean。




回页首

简单分析

J2EE 已经成为越来越多的企业开发服务应用的战略性架构,更好地管理和监控这些服务应用的需求也日益迫切。JMX就是满足企业对J2EE应用系统的管理需求的标 准。其实JMX已经在许多基础性J2EE服务软件中发挥重要作用,比如WEBLOGIC SERVER,JBOSS SERVER和TOMCAT SERVER,而且已经成为这些软件产品的基础。人们越来越接受JMX,在J2EE1.4中JMX是一个必备的成分,而J2SE1.5中,JMX将成为一 个标准组件。

当JMX被广泛地用于J2EE架构中的同时,它也快速地渗透入基于J2EE架构的应用程序,轻松而直接地用来管理 J2EE架构的应用程序可以说是这门技术对应用开发者的主要好处。运营分布式计算的同时,很多IT部门正在为应用管理而焦头烂额。J2EE架构允许复杂的 应用结构和部署,如果没有一个标准的应用管理机制,对于IT部门来说是非常可怕的。现在应用开发者能利用JMX帮助IT部门很好地控制这些管理问题。

在基本的应用管理功能之外,JMX技术还有助于商业利益。很多任务关键型应用系统支持商业过程,监控和挖掘这些过程有助于关键业务决策。通过JMX技术,人们可以容易地按需获取任务关键型系统的关键信息,这些便利是以前的技术做不到的。

我们再也不能把系统的管理作为构建和发布应用系统的额外部分。有时开发者在开发系统时实现管理功能,他们经常使用一些特别 的技术,如控制台打印、日志文件等,虽然这些技术能解决及时之需,但不能满足企业对整个应用系统体系的复杂管理需求,这里,企业往往需要所有的应用系统采 用一种管理架构实现统一管理功能,而不是每个应用系统一个技术。

对于一个复杂的应用系统体系,我们很难预测所有管理需求,管理工具也许能帮我们一些,但是我们还得面对不完善和不完备的应用管理方案。面对众多的软件供应商、越来越多的定制J2EE应用系统,管理工具提供商的形势也日趋严峻。

因此我们需要一种基于标准的instrumentation,这样管理应用系统能使用它们进而管理各式各样的企业应用。许 多管理协议和标准陆续出台,它们之间有SNMP, DMI, CIM/WBEM, AMI等,每个都有一些亮点,但却没有一个是对这个问题的好方案,因为利用这些技术,应用开发者在做应用程序的instrumentation时非常困 难,在这方面JMX确实做得不错。




回页首

使用界面

4.1 JMX介绍

我们从架构、运行和例子等几个方面对JMX进行介绍,这些东西是理解 COMMONS-MODELER项目的前提。

4.1.1 JMX 架构

JMX 是一个管理方面的Java标准,它提供了一个远程或本地管理Java应用系统的模型。JMX 的目的之一是提供各种管理应用系统能访问的instrumentation。 JMX模型使用一套公共的应用管理组件,而且提供各种协议来访问它们。这样应用程序被转化一次就能被多种协议访问。

下图体现了JMX的架构,分层的体系保证了"转化一次,多种协议访问"的目的。


在 转化级(Instrumentation级),称为MBeans的管理组件利用应用程序组件(a.k.a被管理资源)提供应用相关的管理信息。这些 MBeans,与Java Beans类似,提供了管理被管理资源功能和信息的属性和操作,它们和被管理资源交互,由程序员基于应用系统的管理需求开发。

代理级(Agent级)与MBeans管理组件交互,提供对这些MBeans组件的管理功能,MBean Server是这级的主要部件,它掌管着所有的MBeans组件。代理级通常由一个代理组件来充当,它负责创建MBean Server、注册MBeans和创建连接器服务端等工作。

连接器级(Connector级)或称适配器级(Adapter级)由各种远程访问协议的适配器或连接器组成,提供管理 系统的远程访问能力,在JMX规范中,这一级是代理级的子级。连接器一般由连接器MBean,连接器服务(处于分布式服务级)和连接器客户端(运行在远程 管理软件端)三个部分组成;而适配器一般由适配器MBean和适配器服务两步分组成。典型的连接器是RMI连接器,典型的适配器是HTTP适配器和 SNMP适配器。

远程管理软件,依赖于JMX在连接器级支持的远程协议,通过SNMP或HTTP等远程访问协议能访问MBeans提供的管理功能。这样应用管理功能就能和企业中现有的传统管理软件集成使用。

MBean管理组件是一些Java类,JMX目前定义的MBean管理组件类型有四种:标准、动态、开放和模型 MBeans。 标准MBeans提供静态管理接口;动态MBeans提供一个能在运行时改变的管理接口,而且配备有元信息;开放MBeans 是一种动态MBeans,但是使用的JAVA类型受限,能提供其它管理应用系统的兼容性,在JMX1.0中没有完全定义清晰, JMX1.2中则是必备的;模型MBean是动态MBean的一种,为被管理资源的转化提供了通用的MBean 类实现模版,这样开发者只要指定被管理资源的信息就能创建一个MBean,而不需要编写MBean 类。

JMX还包括一个完整的通知模型,这样所有的MBeans能生成通知,而管理程序能向这些MBeans注册通知和异步地接收通知。

4.1.2 JMX运行模型


了 解了JMX的体系结构后,我们现在来看看JMX的运行模型。《JMX运行模型》图告诉我们JMX体系下管理软件和被管理应用系统关键的运行情况(关于图中 的增值代理逻辑,代理服务的概念可到JMX官方规范中去查询)。这个图显示我们的应用软件部署在J2EE应用服务器上(可以没有EJB,如 TOMCAT),程序开发者把需要管理的程序资源比如某个JAVA Bean转化成MBean,这个MBean在运行之前必须向JMX设施中的MBean Server注册,运行时会调用被管理资源完成管理任务,比如执行被管理资源的某个方法,访问被管理资源的某个属性等。管理软件必须通过MBean Server访问MBean,MBean Server则利用一个唯一的ObjectName对象来标识和访问注册了的MBean。在程序员为所有的被管理资源编写了MBean之后,必须在应用软 件的某个地方完成创建和注册MBean对象。本地管理软件可以和应用软件同属一个虚拟机,不必通过远程协议访问MBean Serve,这个例子有TOMCAT 4.1x种的ADMIN应用程序。远程管理软件可以通过SNMP适配器、HTTP适配器、各种连接器来和JMX代理通信,WebJMX项目就是高效使用 HTTP适配器编写远程管理软件的典型项目。

4.1.3 JMX例子

下面的例子是J2SDK环境下的例子,但同样具有本地管理软件,应用系统资源、MBean和JMX设施等部件。我们只写了标准MBean和模型MBean的实现,主要突出了模型MBean代码的复杂性,其它种类的MBean代码可以参看JMX参考实现的例子。

4.1.3.1 JMX设施

我们可以 使用任何JMX兼容的实现,可以是JMX1.x规范中的参考实现也可以是开源项目MX4J。这里使用JMX1.2规范中的参考实现,以降低大家在调式代码 时对库代码的怀疑,而且参考实现中附带了一个sun的HTTPAdaptor,能可视化地调用MBean,比起MX4J中提供的HTTPAdaptor来 说在可操作性上强多了。

4.1.3.2 应用系统资源

我们假设一个路由器类,它管理着许多能通过这个设备出去的IP地址,它提供增加IP地址和删除IP地址的操作。 下面是这个资源类的代码,我增加了一些System.out.println语句以便观察服务端的执行情况:

import java.util.ArrayList;
import java.util.List;
public class Router {	
	List ipList=new ArrayList();	
	public void addIP(String ip){		
		System.out.println("addIP:"+ip);	
		ipList.add(ip);	
	}	

	public void deleteIP(String ip){	
		System.out.println("deleteIP:"+ip);		
		ipList.remove(ip);	
	}	
	public  String[] getIPS(){	
		if (ipList.isEmpty()) return new String[0];		
		String[] x =(String[])ipList.toArray(new String[ipList.size()]);	
		for(int i =0; i<x.length;i++){	
			System.out.println(x[i]);		
		}	
		return x;	
	}
}


从代码可以看出,这个资源类有一个属性-IPS和两个操作-addIP和deleteIP。函数getIPS是属性IPS的getter,可以看出这个getter的不符合javabean的命名规范,我们看看MBean是如何处理的。

4.1.3.3 MBean

我们要把路由器资源类的功能转化成能被管理软件操作的功能,这样我们就要对路由器类进行管理转化,形成相应的MBean。

(1) 标准MBean

编写标准MBean,必须先定义一个接口,接口名字是资源的类名附加MBean

public interface StandardRouterMBean {
	String[] getIPS();	
	void deleteIP(String ip);	

	void addIP(String ip);
}


把资源类的代码复制一份,并以StandardRouter为新类名,而且让其实现StandardRouterMBean,其它不变:

public class StandardRouter implements StandardRouterMBean{
}

(2) 模型MBean

我们说过为转化一个资源为模型MBean不需要为它们写特别的MBean接口或MBean类,但是我们得为创建和注册一个模型MBean准备大量的关于资源的材料,这包括资源类将要被管理的属性,操作等。下面是转化Router资源类的代码:

public class	ModelMBeanUtils {
	public static void CreateModlerMBean(MBeanServer server) {	
		final  boolean READABLE = true;		
		final  boolean WRITABLE = true;	
		final  boolean BOOLEAN  = true;    
		// 构造 IPS 只读属性信息
		Descriptor descr1 = new DescriptorSupport();	
		descr1.setField("name", "IPS");	
		descr1.setField("descriptorType", "attribute");	
		descr1.setField("displayName", "IP addresses");	
		descr1.setField("getMethod", "getIPS");    		
		ModelMBeanAttributeInfo IPSInfo = 		
			new ModelMBeanAttributeInfo(	
			"IPS",                        // 属性名		
			String[].class.getName(),        //属性类型		
			"IP addresses.",        // 属性描述文字		
			READABLE, !WRITABLE, !BOOLEAN,  // 读写		
			descr1                         // 属性描述子	
		);	

		//构造 getIPS 属性获取操作信息		
		Descriptor descr6 = new DescriptorSupport();	
		descr6.setField("name", "getIPS");	
		descr6.setField("descriptorType", "operation");	
		descr6.setField("role", "operation");		
		MBeanParameterInfo [] deleteIPParams2 = null;	
		ModelMBeanOperationInfo getIPSInfo = 			
			new ModelMBeanOperationInfo(			
			"getIPS",                   // 操作名	
			"get all IP address.",    //描述		
			deleteIPParams2,                         // 参数		
			String[].class.getName(),      // 返回类型		
			MBeanOperationInfo.ACTION,      // 作用			
			descr6                        // 操作描述子		
		);		

		// 构造addIP操作信息		
		Descriptor descr2 = new DescriptorSupport();	
		descr2.setField("name", "deleteIP");	
		descr2.setField("descriptorType", "operation");		

		//构造参数信息		
		MBeanParameterInfo [] deleteIPParams = new MBeanParameterInfo [1];	
		deleteIPParams[0] = new MBeanParameterInfo("ip",String.class.getName(),"ip");


<!--    code sample is too wide -->		
               ModelMBeanOperationInfo deleteIPInfo = 		
			new ModelMBeanOperationInfo(	
				"deleteIP",                
				"delete a Ip from router.",	
				deleteIPParams,               
				"void",      // return type		
				MBeanOperationInfo.ACTION,    
				descr2                        			
			);	
			// 构造addIP操作信息		
			Descriptor descr3 = new DescriptorSupport();	
			descr3.setField("name", "addIP");	
			descr3.setField("descriptorType", "operation");		

			ModelMBeanOperationInfo addIPInfo = 		
				new ModelMBeanOperationInfo(		
					"addIP",              
					"add a Ip into router.",		
					deleteIPParams,               
					"void",      // return type		
					MBeanOperationInfo.ACTION,    	
					descr3      		
				);      
				//构造MBean信息		
				Descriptor descr4 = new DescriptorSupport();	
				descr4.setField("name", "Router");		
				descr4.setField("descriptorType", "mbean");	

				// create ModelMBeanInfo     	
				ModelMBeanInfo info = new ModelMBeanInfoSupport(	
					RequiredModelMBean.class.getName(),   // MBean类		
                                       <!--    code sample is too wide -->					
                                        "Router",                           // description		
                                       <!--    code sample is too wide -->					
                                       new ModelMBeanAttributeInfo[] {      // 所有的属性信息	
					IPSInfo			
				},		
				null,                                // 所有的构造函数信息	
				new ModelMBeanOperationInfo[] {      // 所有的操作信息	
					getIPSInfo,			 
					addIPInfo,			 
					deleteIPInfo		 
				},		  
				null,                                // 所有的通知信息		
				descr4                               //MBean 描述子		
                                <!--    code sample is too wide -->			
                        );		
			try {		 
				// create and configure model mbean		 
				RequiredModelMBean model = new RequiredModelMBean();       
                                <!--    code sample is too wide -->				//设置被管理的资源类对象		
				model.setManagedResource(new Router(), "ObjectReference");		
                                <!--    code sample is too wide -->				
                                model.setModelMBeanInfo(info);		
				server.registerMBean(model, 
				new ObjectName("jmx_work:name=ModelRouter"));      
			}catch (Exception e) {		
				e.printStackTrace();	
			}    	
		}
}


上面代码的大部分用来构造资源类的被管理信息,包括 一个属性,一个属性操作方法和两个操作;另外我们还构造了一个MBean信息,来组织前面的各种管理信息;接着我们创建一个 RequiredModelMBean对象,这个类是JMX规范指定的实现模型MBean的类。实际情况下创建一个MBean的代码比这还复杂,比如对参 数的描述,对构造函数的描述等。

4.1.3.4 为远程管理实现的Agent

这 个Agent主要完成创建MBean Server对象,创建并注册MBean对象,创建远程访问的适配器对象并启动适配器服务以便接收远程管理软件的请求。对于HttpAdaptor适配器 对象来说,远程管理软件可以用浏览器来充当。下面是为远程管理实现的JMX Agent代码:

import javax.management.*;
public class RemoteHttpAgent {	
	public static void main(String[] args) {	
		try {		
			// 创建MBean Server对象	
			MBeanServer server = 		
				MBeanServerFactory.createMBeanServer();	
			// 创建标准MBean的对象名,对象名是MBean Server标识MBean的方法		
			ObjectName name = 				
				new ObjectName("jmx_work:name=StandardRouter");		
			// 向MBean Server注册标准MBean		
			server.registerMBean(new StandardRouter(), name);		
			//创建 模型MBean并注册			
			ModelMBeanUtils.CreateModlerMBean(server);		
			//创建适配器对象	
			com.sun.jdmk.comm.HtmlAdaptorServer adaptor =		
				new com.sun.jdmk.comm.HtmlAdaptorServer();	
			//向Bean Server注册适配器对象		
			server.registerMBean(adaptor, 
				new ObjectName("adaptor:protocol=HTTP"));
         		//设置并启动HTTP适配器服务		
			adaptor.setPort(8080);		
			adaptor.start();		
		}catch (Exception e) {	
			e.printStackTrace();	
		}	
	}
}


在编译和运行这个Agent之后,你可以使用浏览器访问localhost:8080端口得到管理界面,在这个界面上你可以完成调用MBean的方法,访问和设置MBean,创建、注册和注销MBean等任务。这个界面的具体操作说明请参见JMX参考实现中的文档。

4.1.3.5 本地管理软件

本地管理软件要完成对路由器上的IP地址增加和删除的操作,使用MBeanServer的invoke、getAttriubte和setAttribute函数可以操作MBeanServer上注册的MBean,其代码如下:

import javax.management.*;
public class LocalAgent {	

	MBeanServer server= null;	
	public  LocalAgent(){		
		try {		
			// 创建MBean Server对象		
			server = MBeanServerFactory.createMBeanServer();	
			// 创建标准MBean的对象名,对象名是MBean Server标识MBean的方法		
			ObjectName name = 			
				new ObjectName("jmx_work:name=StandardRouter");		
			// 向MBean Server注册标准MBean		
			server.registerMBean(new StandardRouter(), name);			
                        <!--    code sample is too wide -->			//创建 模型MBean并注册			
			ModelMBeanUtils.CreateModlerMBean(server);	
		}catch (Exception e) {	
			e.printStackTrace();	
		}	
	}	

	public void printIPS(ObjectName on){	
	
		// Create the new object and associated MBean	
		String signature[] = new String[0];	
		Object params[] = new Object[0];		
		String[] ips;			
		try {			
			ips = (String[]) server.getAttribute(on, "IPS");	
			for(int i =0;i<ips.length;i++){			
				System.out.println("LocalAgent:printIPS:"+ips[i]);		
                          <!--    code sample is too wide -->			
                        }			
		}catch (Exception e) {			
			// TODO Auto-generated catch block		
			e.printStackTrace();		
		}		
	}

	public void addIP(ObjectName on,String ip){		
		// Create the new object and associated MBean	
		String signature[] = new String[]{"java.lang.String"};  //定义了参数的类型	
		Object params[] = new Object[]{ip};   //定义了参数的值		
		try {		
			server.invoke(on, "addIP",params, signature);	
		} catch (Exception e) {	
			// TODO Auto-generated catch block		
			e.printStackTrace();		
		}			
	}	

	public void deleteIP(ObjectName on,String ip){	
		// Create the new object and associated MBean	
		String signature[] = new String[]{"java.lang.String"};  //定义了参数的类型	
		Object params[] = new Object[]{ip};   //定义了参数的值		

		try {		
			server.invoke(on, "deleteIP",params, signature);	
		} catch (Exception e) {	
			// TODO Auto-generated catch block	
			e.printStackTrace();		
		}				
	}
	public static void main(String[] args) {	
		LocalAgent la= new LocalAgent();		
		try {		
			ObjectName on = new ObjectName("jmx_work:name=StandardRouter");		
                        <!--    code sample is too wide -->			
                        la.addIP(on, "10.1.34.123");		
			la.printIPS(on);			
			la.deleteIP(on, "10.1.34.123");		
			la.printIPS(on);		
		}catch (MalformedObjectNameException e) {	
			// TODO Auto-generated catch block		
			e.printStackTrace();	
		}catch (NullPointerException e) {	
			// TODO Auto-generated catch block	
			e.printStackTrace();		
		}	
	}
}

这段代码体现了通过MBeanServer接口访问 MBean对象的方法。MBeanServer接口主要提供下面三个函数供外界访问其管理的MBean对象(注意我们这里说得MBean对象函数或属性是 指创建MBean对象之前设置的那些构造信息当中的属性和函数,而不是MBean类中的函数和属性):

  1. 获取MBean对象某个属性的值: 接口函数为Object getAttribute(ObjectName, String),第一个参数是MBean对象注册时的ObjectName,第二个参数是属性名。外面的代码调用这个函数后,MBeanServer为调 用MBean对象的getAttribute方法,后者一般会用自己的invoke方法调用被管理资源对象的属性getter函数。
  2. 设置MBean对象某个属性的值: 接口函数是Void setAttribute(ObjectName, Attribute),第一个参数是MBean对象注册时的ObjectName,第二个参数是属性名和属性值组成的Attribute对象。外面的代码 调用这个函数后,MBeanServer为调用MBean对象的setAttribute方法,后者一般会用自己的invoke方法调用被管理资源对象的 属性setter函数。
  3. 调用MBean对象的某个函数: 接口函数是Object invoke(ObjectName,String, String[],String[]),第一个参数是MBean对象注册时的ObjectName,第二个参数是函数名,第三个参数是MBean对象函数 参数的值,第四个参数是MBean对象函数的类型。外面的代码调用这个函数后,MBeanServer为调用MBean对象的invoke方法,后者直接 调用被管理资源的相应方法。

除了这些关键的接口函数,MBeanServer接口中还有丰富的查询函数供代理和管理程序使用,最明显的接口函数有查询已经注册的所有MBean对象。

4.2 应用编程接口

commons- modeler项目需要commons-logging、commons-Digester、commons-beanutils、commons- collection几个项目的库,当然还需要JMX库,而且要求与JMX1.1兼容。commons-modeler环境下编写JMX应用系统的主要编 程接口包括一个配置文件和两个类。

4.2.1 xml配置文件schema描述

本节主要讲述了编写commons-modeler配置文件的语法,这个语法由xml schema形式化定义。

(1) 元素 mbeans-descriptors,这是ModelMbean配置文件的顶级元素,它可以包括多个mbean的配置


(2) 元素 mbeans-descriptors/mbean,它定义了一个ModelMBean


(3) 元素 mbeans-descriptors/mbean/attribute代表了ModelMBean的一个被管理的属性


(4) 元素 mbeans-descriptors/mbean/constructor代表一个构造函数


(5) 元素 mbeans-descriptors/mbean/notification代表一个通知


(6) 元素 mbeans-descriptors/mbean/notification/notification-type代表一个通知类型


(7) 元素 mbeans-descriptors/mbean/operation代表MBean的一个操作


(8) 元素 parameter代表一个参数


4.2.2 两个主要的类

commons- modeler的两个主要的类是Registry和ManagedBean。Registry是commons-modeler项目的入口,用设计模式术 语来说它还是单例模式和工厂方法模式的结合体,你可以从Registry类实例中可以获取Registry对象、MBeanServer对象、 ManagedBean对象。ManagedBean对象则代表配置中的一个ModelMBean配置,它可以创建一个ModelMBean对象、同时把 这个ModelMBean对象和应用系统中某个资源对象连接起来。

在这两个类的帮助下,commons-modeler环境下JMX的开发顺序如下:

  1. Registry.loadRegistry(java.io.InputStream stream)装载配置文件;
  2. Registry.getServer()获取MBeanServer对象;
  3. 向MBeanServer对象注册协议适配器或连接器,并启动访问服务;
  4. 调用Registry.getRegistry()方法获得Registry对象;
  5. 调用Registry对象的findManagedBean(String name)方法获得名字为参数name的ModelMBean对象配置的ManagedBean对象;
  6. 生成被管理资源的对象;
  7. 调用ManagedBean对象的createMBean(Obeject)方法创建ModelMBean对象;
  8. 利用ManagedBean对象的其他方法获取创建ObjectName对象的相关信息创建ObjectName对象;
  9. 向MBeanServer对象注册ModelMBean对象。

一般地,JMX代理的代码到第三步就结束,4-9步由应用系统的其它部分完成。

4.2.3 例子

为了让commons-modeler环境下编程比较直接JMX编程的效果更直观,我们仍然使用前面的应用资源对象类Router来构造commons-modeler环境下的例子。除了已编写的Router类,这个例子还包括一个配置文件和一个代理类。

4.2.3.1 xml配置文件

<?xml version="1.0"?>
<!DOCTYPE mbeans-descriptors PUBLIC 
"-//Apache Software Foundation//DTD Model MBeans Configuration File" 
"http://jakarta.apache.org/commons/dtds/mbeans-descriptors.dtd">
<mbeans-descriptors> 
	<mbean       name="ModelerRouter"   
					description="modeler Router MBean"  
					domain="jmx_work"               
					group="Modeler"                
					type="Router">   
	<attribute   name="IPS"       
					description="get All IP Adress"        
					type="java.lang.String[]"           
					writeable="false"         
					getMethod="getIPS"/>  
	<operation   name="addIP"   
					description="add a ip into the router" 
					impact="ACTION"         
					returnType="void">   
	<parameter	name="ip"        
					description="the Ip will be added."   
					type="java.lang.String"/>   
	</operation>  
	<operation   name="deleteIP"     
					description="remove a ip from the router"    
					impact="ACTION"       
					returnType="void">   
	<parameter	name="ip"   
					description="the Ip will be deleted."     
					type="java.lang.String"/>  
	</operation>  
	</mbean>  
</mbeans-descriptors>

4.2.3.2 代理

和前面相比,创建MBean的代码发生了变化,变得简单而且容易维护。下面是代码:

public class RemoteHttpAgent {	
	private  MBeanServer mserver;	
	private  Registry  mregistry;	
	private  HtmlAdaptorServer madaptor=null;	

	public RemoteHttpAgent(int port){	
		mserver=createServer();		
		mregistry=createRegistry();		
		createHttpAdaptor(port);	
	}

	private   Registry createRegistry() {	
		Registry registry=null;			
		try {			
			URL url = this.getClass().getResource("/mbean-descriptors.xml");	
                        <!--    code sample is too wide -->			
                        InputStream stream = url.openStream();		
			Registry.loadRegistry(stream);			
			stream.close();			
			registry = Registry.getRegistry();		
		}catch (Throwable t) {	
			t.printStackTrace(System.out);	
			System.exit(1);		
		}		
		return (registry);
	}	
	private MBeanServer createServer() {		
		if (mserver == null) {			
			try {				
				mserver = Registry.getServer();			 
			}catch (Throwable t) {	
				t.printStackTrace(System.out);		
				System.exit(1);			 
			}		  
		}		  
		return (mserver);	 
	}

	private void createHttpAdaptor(int port){	
		madaptor =	new HtmlAdaptorServer();	 
		//向Bean Server注册适配器对象	 
		try {	
			mserver.registerMBean(madaptor, 	
				new ObjectName("adaptor:protocol=HTTP"));	 
		}catch (Exception e) {
			e.printStackTrace();	
		}	
		madaptor.setPort(port);	  
		madaptor.start();	
	}	

	public static void main(String[] args){	
		RemoteHttpAgent rha = new RemoteHttpAgent(80);		
		try {		
			rha.createMBean("ModelerRouter");		
		}catch (Exception e) {	
			e.printStackTrace();
		}	
	}	

	public  void createMBean(String mname)	
	throws Exception {		
			ManagedBean managed = mregistry.findManagedBean(mname);		
			if (managed == null) {			
				Exception e = new Exception("ManagedBean is not found with "+mname);		
                                <!--    code sample is too wide -->				
                                throw new MBeanException(e);		
			}		
			String domain = managed.getDomain();		
			if (domain == null)			
				domain = mserver.getDefaultDomain();		
			Router r = new Router();		
			ModelMBean mbean = managed.createMBean(r);		
			ObjectName oname = new ObjectName(domain+":name=" + managed.getName());		
                        <!--    code sample is too wide -->			
                        mserver.registerMBean(mbean, oname);	
		}
	}

上面的代码把commons-modeler环境下 编写代理代码的顺序的三步都封装在了代理的构造函数中,而后面几步的代码都在createMBean函数中,与前面直接使用JMX API的代码比较起来,创建一个ModelMBean对象再也不需要写那么多的构造各种info的复杂、难以维护的代码了。在实际的使用环境中,比如 tomcat4.1x系列的admin应用,createMBean函数的参数可能是应用程序中实际的资源对象,我们要求createMBean函数能从 资源对象的相关信息获取它的ModelMBean对象配置从而创建针对这个资源对象的ModelMBean对象。

编译和运行这个代理之后,使用浏览器到本地的80端口就能访问这个代理及其MBean了。




回页首

内部剖析

为 什么Commons-modeler项目能大大简化ModelMBean对象的构造过程,下面的静态结构分析可以知道这个端倪,为什么需要 BaseModelMBean而不是规范中的缺省RequiredModelMBean作为所有ModelMBean对象的类,下面的运行结构分析解析了 这个谜底。最后为了支持JMX1.2,我们对Commons-modeler项目做了些修改,参见支持JMX1.2。

5.1 静态结构

下 面的《Commons-modeler项目类图》显示了Commons-modeler项目中主要类之间的关系,以及这些类和JMX规范中的类或接口的关 系。图中绿色的类来自于JMX规范API。Commons-modeler项目类主要分为两部分。一部分是ManagedBean和xxxInfo类,它 们代表了xml配置文件中的相应信息,其中xxxInfo类又关联着JMX规范中的ModelMBeanxxxInfo类,这样ManagedBean在 创建ModelMBean对象时可以根据配置中的信息构造相应的ModelMBeanxxxInfo对象,而BaseModelMBean则是缺省的 ModelMBean类,与规范中规定的RequiredModelMBean类并行。类图中的另一部分是主要的代理编程接口,主要有Registry和 其相关的类或接口。


5.2 运行结构分析

不 管是远程管理软件还是本地应用程序访问MBean都要通过MBeanServer的invoke方法调用(属性操作方法get/setAttribute 最终也得通过invoke调用相应的getter或settter) MBean的invoke方法、接着由MBean的invoke方法调用被管理资源的相应操作来实现对资源的管理。但是被管理资源的类往往比较复杂,它们 方法的参数、返回值或属性的类型对大多数管理软件来说是无能为力的。所以ModelMBean的一个MBean类管理多数资源类的目的就很难达 到,Commons-modeler项目引入了BaseModelMBean类,这个类使用了一种算法来解决这些问题:对于不太复杂的资源类来 说,BaseModelMBean类的invoke方法会直接调用这些资源类的相应方法,对于那些复杂的资源类来说,我们可以扩展 BaseModelMBean类,编写相应的方法来掩盖资源类的相应方法。针对这种接口转换的需求我们自然想到了Adapter模式,对于这个模式的具体 解释可以参见相关书籍,下面是本项目中这个模式的应用图解:


正 如上图所示,BaseModelMBean类的Invoke方法的先尝试调用自己的方法,如果自己的类没有实现这个方法,就会调用被管理资源对象的方法。 有了BaseModelMBean的Invoke的算法,当我们要实现一个管理复杂资源类的ModelMBean类时,我们可以扩展 BaseModelMBean类,实现一个简单参数、返回值类型的函数,这个函数将从简单参数构造被管理资源类相应函数需要的复杂参数,然后调用这个函 数,并转化复杂类型的返回值成为管理软件能支持的类型。关于这种实现可以参看tomcat 4.1x系列admin应用中的GroupMBean。

5.3 支持JMX1.2

要让commons-modeler支持jmx1.2参考实现,需要做如下约定和修改:

  1. 在配置文件中,当参数、属性或返回值的类型是数组(也就是[]形式)时,不能写成 type="java.lang.String[]"的样子,而应该写成type="[Ljava.lang.String;"的java表达方式。因为 JMX1.2加强了对类型的检查。java.lang.String[]这样的表达不是java内部的数组表达方式。
  2. 对commons-modeler中OperationInfo类的下列方法修改如下:
public ModelMBeanOperationInfo createOperationInfo() {  
	if (info != null)          
		return (info);       
	ParameterInfo params[] = getSignature();      
	MBeanParameterInfo parameters[] =        
		new MBeanParameterInfo[params.length];  
	for (int i = 0; i < params.length; i++) 
		parameters[i] = params[i].createParameterInfo();     
	int impact = ModelMBeanOperationInfo.UNKNOWN;   
   
	if ("ACTION".equals(getImpact()))        
		impact = ModelMBeanOperationInfo.ACTION;     
	else if ("ACTION_INFO".equals(getImpact()))      
		impact = ModelMBeanOperationInfo.ACTION_INFO;  
	else if ("INFO".equals(getImpact()))          
		impact = ModelMBeanOperationInfo.INFO;    
	info = new ModelMBeanOperationInfo          
		(getName(), getDescription(), parameters,  
		getReturnType(), impact);  

	Descriptor descriptor = info.getDescriptor();      
	descriptor.removeField("class");
	//       descriptor.setField("role", getRole());   
	descriptor.setField("role","operation" );    
	System.out.println(""+descriptor.toString());   
	info.setDescriptor(descriptor);     
	return (info);  
}


JMX1.X系列参考实现中,对于MBean某个属性的getter或setter操作其描述信息中的Role为getter或setter,而在1.2中这个值与其他操作一样是operation。




回页首

遗留问题

可以说,commons-modeler项目利用配置文件比较好地解决了创建ModelMBean对象之前构造相关信息的难题,但是这个配置文件是否能进 一步简化,因为好多信息还是可以直接中从被管理资源类中获取;另外当资源类的函数或属性的类型比较复杂时,能否自动创建BaseModelMBean类的 子类的框架,这样可以免去程序员的好多麻烦。




回页首

总结

利 用JMX规范的API接口直接编写MBean程序是比较复杂的,commons-modeler项目大大简化了这个过程。commons-modeler 环境下利用ModelMBean编写JMX程序可以总结为9步,前3步编写代理,后6步与应用程序密切配合,创建管理应用程序中资源对象的 ModelMBean并向MBeanServer注册供管理程序访问。在实现时,这个项目的配置文件和Adapter模式的使用大大增加了灵活性和易管理 性,这种思想是值得我们学习和借鉴的。

参考资料

猜你喜欢

转载自search13.iteye.com/blog/1688504