当前位置: 代码迷 >> Web前端 >> 替 Web 应用建立基于 JMX 的管理系统
  详细解决方案

替 Web 应用建立基于 JMX 的管理系统

热度:592   发布时间:2012-07-23 09:42:19.0
为 Web 应用建立基于 JMX 的管理系统

?

以下转自:http://www.ibm.com/developerworks/cn/java/l-jmx2/index.html

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 的管理系统,我们需要做的事情有哪些呢?

1. 针对每一个需要管理的资源创建一个 MBean 的实例,这是 JMX 框架所要求的 , 有两种类型的实例可供选择,一种是直接管理资源的 MBean,一种通过调用资源实例进行管理的 MBean。

2. 编写一个 MBean 描述文件,并描述每一个 MBean,选择基于 XML 的 MBean 描述文件是一个不错的决定。

3. 通过读 MBean 描述文件,生成 MBeanInfo,从而生成一个个 MBean。

4. 将需要进行管理的 MBean 注册到 MBean Server 当中。

5. 编写客户端代码,选择 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 的管理框架绝对是一个非常明智的选择。

在下才疏学浅,难免错漏之处,还望有识之士,不吝赐教在下,在下感激于心。


关于作者

盛戈歆,广州拓微科技有限公司上海分公司高级程序员,对Java的各种技术非常痴迷,参与多个大型的Java项目,并设计实现其的管理系统。Email:?shenggexin@topwaver.com。联系电话:021-64366810-170

  相关解决方案