web Web应用系统总算开发了,接下来该如何让客户(Web应用系统的管理员)轻松管理我的一堆配置文件,或者如何实现动态修改系统运行属性,同时又让客户不需要过多的了解配置文件的内容就能够实现这些管理呢?这是许多刚刚结束Web应用系统前期开发的系统分析人员需要面临的问题。又或者说我想对早已完成Web应用系统进行有效的资源管理,希望再添加管理功能的同时,对原有的代码不需要做过多的修改,换句话说就是管理系统与被管理的应用系统做到很好的隔离。JMX的管理框架(图1)为你很好的解决了这些问题。
JMX(Java Management Extensions)是来管理网络,设备,应用程序等资源,它描述了一个可扩展的管理体系结构,并且提供了JMX API和一些欲定义的java管理服务。在撰写本文时,JMX规范最新版本为v1.2(http://jcp.org/aboutJava/communityprocess/final/jsr003/index3.html),JMX参考实现的最新版本为v1.2.1(http://java.sun.com/products/JavaManagement/)。JMX推出后,一些大型的项目就立即采用了基于JMX的实现框架,例如Jakarta tomcat和JBoss,这从分说明JMX的可行性和良好的特性。
对于Web应用的管理往往是比较麻烦的,例如客户手动的修改配置文件,开启数据库监控程序等等,如果要动态修改数据库访问方案或者监控用户数,动态修改日志级别会更加麻烦,并且可能把系统的结构弄得凌乱,造成结构不良的恶果,更别说可扩展性了。JMX的分层结构以及高度的组件化,通过将各种资源封装成MBean的方式,让我们可以很低成本的实现对现有Web应用的扩展性很强的管理方案。
本文以Tomcat作为Web服务器为例,详细的介绍如何使用JMX建立对Web应用的管理。对于JMX的概念性的东西、体系结构以及使用规范已经有不少的相关文档,为了能够更好的理解本文,在阅读本文时请先参阅这些文档,本文的笔墨将着重在应用和实现上。下图(图2)为JMX的基本框架图(见JMX规范),,目的是给大家理解本文提供方便。
一、创建Web应用的管理系统
对Web应用构建一个基于JMX的管理系统,我们需要做的事情有哪些呢?
针对每一个需要管理的资源创建一个MBean的实例,这是JMX框架所要求的,有两种类型的实例可供选择,一种是直接管理资源的MBean,一种通过调用资源实例进行管理的MBean。
编写一个MBean描述文件,并描述每一个MBean,选择基于XML的MBean描述文件是一个不错的决定。
通过读MBean描述文件,生成MBeanInfo,从而生成一个个MBean。
将需要进行管理的MBean注册到MBean Server当中。
编写客户端代码,选择Web的方式进行客户端的编码比较Web应用的风格,也比较容易实现。
那么一个基于JMX的Web应用的管理框架已经成形,图3是它的基本结构图,虚线部分为基于JMX的管理系统。接下来我们按照步骤实现整个管理系统。
二、获得MBeanServer的实例
有两种方案获得MBeanServer的实例。
1、通过获得Web服务器的MBeanServer的实例,这样做的好处是通过该MBeanServer对本身,甚至可以实现对Web服务器的自身的一些管理。Tomcat的管理框架也是建立JMX的基础上,它使用的JMX的实现是MX4J,这是一个非常优秀的JMX开源项目,在tomcat4.1.27中,MBeanServer的实例存放与属性名为"org.apache.catalina.MBeanServer"的application变量(Web应用中变量的几种范围:page,request,session,application)中,因此servlet中获得MBeanServer实例的办法:
server = (MBeanServer) getServletContext().getAttribute("org.apache.catalina.MBeanServer");
如果通过这种方式,你获得的server为null,这说明你必须还要完成下面的工作,使你能够有权限获得系统的MBeanServer,tomcat才会将MBeanServer的实例存放在web应用程序下属性名"org.apache.catalina.MBeanServer"的系统变量中。
找到tomcat下conf目录,修改server.xml文件。修改Web应用的context元素,添加上privileged="true"这一项属性即可,例如:
<Context path="/myapp" docBase="c:/web/" debug="9" privileged="true" reloadable="true" crossContext="true"/>
2、通过JMX API中MBeanServerFactory类的createMbeanServer()的方法创建MBeanServer的实例,这样做得好处的使JMX的实现与Web服务器无关,使代码的移植性更强。在创建完MBeanServer以后,为了让能够在管理系统中很方便的获得该MBeanServer的引用,可将其置入application变量中(推荐),或者使用singleton设计模式的方法创建和获得。
使用JMX API创建MBean Server的代码如下:
MBeanServer server = MBeanServerFactory.createMBeanServer();
究竟采取何种方案获得MBeanServer并不十分重要,可以考虑实现的方便进行选择。
三、创建MBean
为了能够管理Web应用的资源,首先要使资源能够被管理,按照JMX规范的要求,我们将资源封装成MBean,实际上也就是为Web应用添加可管理性。
获得MBeanServer的实例以后,就可以编写自己的MBean了,根据JMX的规范,MBean有标准的和动态的两种主要类型,这里就不赘述了,具体可以参看JMX的规范(http://)。有两种用于特殊用途的动态 MBeans:模型MBean和开放MBean。模型 MBean (Modle MBean)提供了"现成的"MBean 实现,您可以使用它来快速地利用任何 JMX 可管理资源。它是预制的、通用的和动态的 MBean 类,并且提供了参考实现,已经包含了所有必要缺省行为的实现 - 允许您在运行时添加或覆盖需要定制的那些实现。这使得基于 Java 的、非工具化的资源能够在运行时提供保证兼容的 MBean 虚包,使它们能够通过 JMX 体系结构进行管理。
在Web应用中,资源是多元化,有运行实例,静态文件,甚至是设备或者是硬件状态,那么我们把这些资源可以分为两类,一些资源需要进行动态的管理监控(如登陆用户数,数据库连接监控,即时日志等),一些则是静态资源,只需要进行静态的管理(系统配置文件,日志级别等),要管理这些不同类型的资源,这就要求MBean一方面能够直接调用运行实例提供的接口进,另一方面又希望MBean自身能够提供静态资源的管理,模型MBean能够很好的满足这样的要求,并且对于新增需要管理的资源,提供了很好的灵活性和可扩展性,因此推荐模型MBean作为Web应用首选。当然具体采用何种类型的MBean,需要结合不同的应用,或者多种类型的MBean相结合使用。顺便提一点,采用不同类型的MBean,对于管理客户端是透明的,也就是说客户端使用一致的访问方法来访问MBean的属性和方法,这也是JMX的一个优点。
为了更好的观察如何实现对静态资源和动态资源的管理,本文编写了两个MBean,分别实现对数据库连接属性实现静态和动态的管理。静态管理的MBean的目的是实现对数据库配置文件修改,这样当Web应用重启后,会使用新的数据库配置,我们采用MBean的提供对资源的直接操作进行实现。动态管理的MBean目的是对已经实例化的数据库连接对象的属性进行修改,这样在不重新启动Web应用的情况下,改变数据库连接。
1、实现管理配置文件的MBean。
JDBCConfig是一个Model MBean,它的作用是对config.properties文件封装成MBean,该MBean包括四个属性及其相关的getter和setter和一个save方法,其中save方法的作用是属性存入配置文件,从而实现了通过MBean直接对配置文件进行操作。这个例子中的MBean继承了BaseModelMBean类,该类实现了javax.management.ModelMBean接口,我们可以在commons-modeler.jar中找到BaseModelMBean类,commons-modeler.jar是tomcat4.x中使用的一个开放源码的包。
import javax.management.MBeanException; import javax.management.RuntimeOperationsException; import org.apache.commons.modeler.BaseModelMBean; import java.io.*; import java.util.Properties; public class JDBCConfigMBean extends BaseModelMBean { private String driver; private String username; private String password; private String dburl; private static final String CONFIG_FILEPATH = "c:\\myweb\\conf\\config.properties"; /////////////////////////////////////////////////////////////////// //constructor /////////////////////////////////////////////////////////////////// public JDBCConfig() throws MBeanException, RuntimeOperationsException { super(); init(); } private void init() { InputStream in = null; try { Properties prop = new Properties(); in = new BufferedInputStream( new FileInputStream(CONFIG_FILEPATH)); prop.load(in); driver = prop.getProperty("driver"); dburl = prop.getProperty("dburl"); username = prop.getProperty("username"); password = prop.getProperty("password"); in.close(); } catch (Exception e) { try { if (in != null) in.close(); } catch (IOException e1) { e1.printStackTrace(); } e.printStackTrace(); } } /////////////////////////////////////////////////////////////////// //getter and setter /////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////// //public method /////////////////////////////////////////////////////////////////// public String save() { OutputStream out = null; try { out = new BufferedOutputStream(new FileOutputStream(CONFIG_FILEPATH)); Properties prop = new Properties(); prop.setProperty("driver", driver); prop.setProperty("dburl", dburl); prop.setProperty("username", username); prop.setProperty("password", password); prop.store(out, "---jdbc config---"); out.close(); return "save success!"; } catch (Exception e) { try { if (out != null) out.close(); } catch (IOException e1) { e1.printStackTrace(); } e.printStackTrace(); return "save error!"; } } }
2、实现修改运行实例的属性。
上面的例子是对静态资源的修改,如果需要对正在运行的类实例进行动态修改,Model MBean同样提供了很好的方法,在javax.management.ModelMBean的API中有个setManagedResourced方法,这个方法的作用是将正在运行的类实例置入Model MBean,实际上所要做的也就这么多。当需要对改变实例属性的时候,只需要调用MBean的setAttribute的方法即可,MBean将会通过反射调用该实例对应的属性设置方法,就是这么简单。但是许多人喜欢直接从代码了解过程,因此我将如何实现动态修改实例的属性代码也写出来。同样以JDBC为例,这里假设系统已经有了一个使用JDBC的数据库连接,现在需要改变JDBC的属性,并建立新的连接,这一切都在不重启Web应用系统的情况下完成。
第一段代码是Web应用使用的数据库连接类,它提供一个获得数据库连接,和测试连接属性的两个方法,以及相关的属性。当Web应用启动时,该类将会实例化,我们暂且把这个实例叫资源实例。package com.myApp.db; import java.sql.Connection; import java.sql.SQLException; public class DBAccess { private String driver; private String username; private String password; private String dburl; public Connection getConnection() { ...... } public static boolean testConnection(String driver,String username, String password,String dburl) { } /////////////////////////////////////////////////////////////////// //getter and setter /////////////////////////////////////////////////////////////////// }
第二段代码是一个Model MBean类,和前面提到的第一个Model MBean一样,继承了BaseModelMBean,不同的是它将拥有Web应用运行时期资源实例的引用,因为所有的属性和方法都在所引用的资源实例中,父类BaseModelMBean完成了通过反射调用该资源实例对应的属性设置方法,所以这个类的编写相当简单,我们可以在里面完成其他一些相关的方法。package com.myApp.db; import org.apache.commons.modeler.BaseModelMBean; import javax.management.MBeanException; import javax.management.RuntimeOperationsException; public class ResInstanceMBean extends BaseModelMBean { public ResInstanceMBean () throws MBeanException, RuntimeOperationsException { super(); } /** other operations **/ ..... }
第三段代码是如何实现管理数据库连接类的运行实例。/**Web应用运行初期创建的数据库连接**/ DBAccess dbAccess = new DBAccess(); dbAccess.setDriver(); dbAccess.setPassword(); dbAccess.setUsername(); dbAccess.setDburl(); ...... /** 创建并注册管理数据库的MBean //创建mbean ResInstanceMBean mbean= new ResInstanceMBean(); //设置MBeanInfo,这是必须的 mbean.setModelMBeanInfo(createMBeanInfo()); //设置MBean所管理的资源实例,instance为数据库连接的实例, //"objectReference"是必须的,否则将无法将资源实例设置到MBean中,记住就行了 DBAccess instance = getDBAccess(); mbean.setManagedResource(instance, "objectReference"); //注册mbean到MBean Server中 MBeanServer serv = getMBeanServer(); ObjectName oname = createObjectName(mbean); serv.registerMBean(mbean, oname);
四、创建MBean描述文件
在上面第三段代码中,我们可以看到,要将MBean注册到MBean Server中必须先创建MBeanInfo,MBean的setModelMBeanInfo()用来将MBeanInfo设置到MBean中。为了能够灵活的获得MBean的信息,从而将MBean注册到MBeanServer,在O'Reilly出版的"java enterprise的最佳实践"里提到,采用XML文件对MBean描述是一种非常不错的选择方案,并且提供了一个XML描述范例,因此本文也推荐在管理Web应用也采用使用MBean描述文件的方法。实际上无论tomcat4.X,还是JBOSS,都采用使用MBean描述文件的方式创建MBean,下面提供了一个Tomcat4.x里面的MBean描述文件方案,并用该方案描述了上述提到的两个数据库连接管理的MBean。Tomcat提供了读取该描述文件的办法,具体可以参看Tomcat提供的帮助文档--如何使用MBean descriptor ( "http://jakarta.apache.org/tomcat/tomcat-4.1-doc/mbeans-descriptor-howto.html")。<mbean-list> <mbean name="JDBCConfigMBean" className="com.myApp.jmx.JDBCConfigMBean" description="the object to access database" domain="myapp"> <attribute name="driver" description="Jdbc driver name" type="java.lang.String" writeable="false"/> <attribute name="dburl" description="database url" type="java.lang.String"/> <attribute name="username" description="Database user name" type="java.lang.String"/> <attribute name="password" description="vthe user name's password" type="java.lang.String"/> <operation name="save" description="save the configuration" impact="ACTION" returnType="java.lang.String"> </operation> </mbean> <mbean name="DBAccess" className="com.myApp.jmx.ResInstanceMBean" description="the object to access database" domain="myapp" type="com.myApp.db.DBAccess"> <attribute name="driver" description="Jdbc driver name" type="java.lang.String" writeable="false"/> <attribute name="dburl" description="database url" type="java.lang.String"/> <attribute name="username" description="Database user name" type="java.lang.String"/> <attribute name="password" description="vthe user name's password" type="java.lang.String"/> <operation name="testConnection" description="test configure attribute" impact="ACTION" returnType="java.lang.String"> <parameter name="driver" description="Jdbc driver name for test" type="java.lang.String"/> <parameter name="username" description="Database user name for test" type="java.lang.String"/> <parameter name="password" description="the user name's password for test" type="java.lang.String"/> <parameter name="dburl" description="database url for test" type="java.lang.String"/> </operation> </mbean> </mbean-list>五、注册MBean
在对MBean注册前,必须得到MBean的描述信息,并且保存在MBeanInfo的实例中,否则是无法将MBean注册到MBean Server当中的,通过MBean描述文件,获得各种类型MBean的描述信息是一件非常简单的事情,而这些正是创建MBean所需要的,这样做的优点在于不需要通过编写代码,只需要修改描述文件,就可以添加新的MBean,注册的代码实际上我们之前的代码已经列出。在MBean注册时必须指定对应的ObjectName,ObjectName相当于MBean在MBean Server中的唯一名字,它的格式为:"domain:key1=value1,key2=value2...",可根据系统的要求定义一套命名的规则。//注册mbean到MBean Server中 MBeanServer serv = getMBeanServer(); ObjectName oname = createObjectName(mbean); serv.registerMBean(mbean, oname);六、编写管理框架的客户端
我们已经完成了服务器端MBean的注册工作,接下来是如何让用户能够使用这些MBean管理资源。虽然JMX的参考实现中提供了HTMLAdapter,使用户能够通过浏览器使用MBean。但是提供的界面并不是那么友好可亲,一向苛刻的客户对这绝对不会满意的。因此,编写一些简洁的访问MBean页面还是有必要的。如何通过java访问MBean,可以参阅JMX的资料,这些资料非常多。
根据上面的介绍,如果要增加对Web应用的管理功能或管理系统,基于JMX的管理框架绝对是一个非常明智的选择。