How tomcat works——20 基于JMX的管理

概述

第 19 章讨论了 Manger 应用程序,演示了如何使用实现了 ContainerServlet 接口的ManagerServlet 类来访问 Catalina 的内部对象。本章演示了如何使用JMX技术进而更方便、老练地管理 Tomcat。对于不熟悉 JMX 的读者,本章开头先进行了简单的JMX介绍。另外本章介绍了Catalina中使用的“Commons Modeler”建模库,它可以很简单的创建管理beans
——用于管理其它的对象。另外还提供了例子方便更容易地理解JMX 在 Tomcat 中的使用。

20.1 JMX简介

目前为止,ContainerServlet 接口已经足够好地对Catalina 内部类进行访问获取、管理,那为什么还要关注 JMX呢?答案是 JMX 提供了更大的灵活性。很多基于服务的应用程序,
如 Tomcat,JBoss,JONAS,Geronimo 以及其它应用,都使用了JMX 这一很酷的技术来进行它们的资源管理。

JMX 目前的版本是 1.21,它定义了一个公开的管理 Java 对象的标准。例如,Tomcat4/5使用 JMX 的管理程序来使得servlet容器中各种对象(如server服务器、host主机、context上下文、valve阀门,等)更方便的被管理。Tomcat的开发人员甚至努力编写一个作为Admin的应用程序来管理应用。

一个可以使用 JMX 管理器来管理的 Java 对象称为 JMX 管理资源。事实上,一个 JMX 管理资源也可以是一个应用程序、一个实现或一个服务、设备、用户等。一JMX 管理资源通常是用Java 写的或者提供的一个 Java 包装。

要想让一个 Java 对象称为 JMX 管理资源,必须创建另一个名为MBean 的对象。org.apache.catalina.mbeans 包包括一些 MBeans。如ConnectorMBean, StandardEngineMBean, StandardHostMBean,StandardContextMBean 就是MBean的例子。从他们的名字我们可以猜到ConnectMBean 用于管理连接器,StandardContextMBean 用于管理org.apache.catalina.core.StandardContext 对象等等。当然,我们也可以编写MBean 来管理多个 Java 对象。

MBean 将 Java 对象的属性和方法暴露给管理应用程序。管理应用程序本身不能直接访问 Java 对象。因此可以选择任意的属性和方法让管理应用程序访问。

一旦我们有了一个 MBean 类,我们需要实例化它,并将其注册到一个 MBean服务器的对象中(MBean server)。MBean 服务器是应用程序中所有的 MBean 的中心枢纽登记处。管理应用程序通过 MBean 服务器访问 MBeans。将 JMX 和 Servlet 应用程序做个类比,管理应用程序相当于一个 web 浏览器。MBean服务器相当于一个 Servlet 容器,它为客户端提供管理资源的访问。而 MBeans相当于 Servlet 或者 JSP 页面。就像是 web 浏览器从来不直接接触 Servlet/JSP页面,而是通过容器访问。管理应用程序也不会直接访问 MBeans,而是通过 MBean服务器来进行。

一共有四种 MBean:标准 standard,动态 dynamic, 打开 open, 和模型 model。其中,标准 MBean 是里面最容易编写的,但是他的灵活性也最小。另外三种更灵活,我们将会特别关注模型MBeans,因为Catalina就是使用了这种类型的MBean。在后边有对模型 MBeans 的讨论,我们会省略介绍动态和打开二种,因为它们与本章内容关系不大。感兴趣的用户可以阅读 JMX1.21 规范了解更多的细节。

JMX规范的检测级别定义了编写JMX可管理资源的标准,即如何编写MBean。代理级别为创建代理提供了一个规范。 一个代理封装了一个MBean服务器处理MBean的服务。 他们管理的代理和MBean通常驻留在相同的Java虚拟机。 由于JMX规范中附带了参考实现,因此我们不需要编写自己的MBean服务器。参考实现提供了创建默认MBean服务器的方法。

扫描二维码关注公众号,回复: 3283694 查看本文章

注意:
可从这里下载规范和参考实现http://java.sun.com/products/JavaManagement/download.html。 MX4J,是一JMX的开源版本,可从http://mx4j.sourceforge.net获得。

20.2 JMX API

参考实现组成了Java标准库中的javax.management包以及其它JMX编程相关的特殊领域的包。本节会讨论 API 中的一些重要类型。

20.2.1 MBeanServer接口

javax.management.MBeanServer 是一个表示 MBean 服务器的接口。要创建一个MBean 服务器,可以使用 javax.management.MBeanServerFactory 中的方法即可,例如 createMBean() 方法。

要在 MBean 服务器注册 MBean,调用 MBean 服务器对象的 registerMBean()方法即可。下面是 registerMBean 方法的签名:

public ObjectInstance registerMBean(java.lang.Object object,ObjectName name) throws InstanceAlreadyExistsException,MBeanRegistrationException, NotCompliantMBeanException

registerMBean()方法需要传递一个 MBean 实例以及实例的对象名(ObjectName)。ObjectName 类似于 HashMap 中的键值,它必须是唯一的。registerMBean 返回一个 ObjectInstance 对象。javax.management.ObjectInstance 封装了 MBean 的对象名以及它的类名。

要检索 MBean 对象或对象集是否匹配一个模式,MBeanServer 接口提供了两个方法:queryNames()和queryMBeans()。queryNames()方法返回一个包括所有对象名匹配模式的 MBean 的 java.util.Set 集合。下面是 queryName()方法的签名:

public java.util.Set queryNames(ObjectName  name , QueryExp  query )

参数 query 指定筛选条件。如果 name 的值为 null 或者没有指定域和键的值,就返回所有注册 MBean 的ObjectName。如果 query 是 null,没有过滤器使用。

queryMBeans()方法跟 queryName()相似,但是它返回包含所有选择的ObjectInstance 对象的 java.util.Set 集合。queryMBeans()方法的签名如下:

public java.util.Set queryMBeans(ObjectName name, QueryExp query)

一旦你获得了需要的 MBean,就可以操作暴露的属性和方法。可以使用 MBeanServer 接口中的 invoke()方法来调用注册 MBean 中的任何方法。MBeanServer 接口的 getAttribute() 和 setAttribute()方法用于获得和设置注册MBean 的属性。

20.2.2 ObjectName类

MBean 服务器用于注册 MBean。对于在 MBean 服务器上的 MBean,它们都有唯一的对象名,就像是 HashMap 中的对象都有唯一的键值一样。

对象名由 javax.management.ObjectName 类表示。一个对象名由两部分组成:域(domain)和一个键值集合对。域是一个字符串,可以是空字符处。在一个对象名种,域之后的是冒号以及一个或多个键值对。键是一个非空字符串,不能包含下面的符号: 等于号(=), 逗号(,), 冒号(:),星号(*), 和问号(?)。相同的键在一个对象名种只可发生一次。键和值之间用等号隔开,两个键值对之间用逗号隔开。例如,下面是一个合法的对象名:

myDomain:type=Car,color=blue

ObjectName 也可以表示在 MBean 服务器上查找 MBean 的属性模式。一个ObjectName 是一个它的域和键值对表示的通配符模式。一个 ObjectName 模式可以含零个或多个键。

20.3 标准MBeans

标准 MBean 是最简单的 MBean。要是用标准 MBean 管理一个 Java 对象,需要做以下工作:

•创建一个接口,名为你的类名加上MBean后缀。例如,如果要管理的 Java 类是 Car,接口名应为CarMBean。
•修改 Java 类,让它实现你创建的接口。
•创建一个代理,该代理必须包括一个 MBean 服务器。
•为你的 MBean 创建一个 ObjectName。
•初始化 MBean 服务器。
•把MBean注册到MBean 服务器。

标准 MBean 更易于变现,但是使用它们的话需要修改你自己的类。在一些项目中修改类是可以的,在另一些项目中(特别是当类的数量很多时),这是不可接受的。其它类型的 MBean 允许在不修改类的情况下管理对象。

考虑下面的类如何变成 JMX 管理的,作为一个标准 MBean 的例子。

package ex20.pyrmont.standardmbeantest;

public class Car {
    private String color = "red";

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public void drive() {
        System.out.println("Baby you can drive my car.");
    }
}

修改的第一步是要实现 CarMBean 接口,新的 car 类如 Listing20.1 所示:

Listing 20.1: The modified Car class

package ex20.pyrmont.standardmbeantest;

public class Car implements CarMBean {
    private String color = "red";

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public void drive() {
        System.out.println("Baby you can drive my car.");
    }
}

接下来创建 CarMBean 接口如 Listing20.2

Listing 20.2: The CarMBean interface

package ex20.pyrmont.standardmbeantest;

public interface CarMBean {
    public String getColor();
    public void setColor(String color);
    public void drive();
}

基本上,在接口中需要声明所有你需要暴露的方法。在该例子中,CarMBean 接口列出了 Car 类中的所有接口。如果不想让管理程序使用 driver()方法,则从CarMBean 接口中移除 driver() 方法即可。

最后,Listing20.3的StandardAgent展示了如何创建一个标准MBean来管理Car类的对象。

Listing 20.3: The StandardAgent class

package ex20.pyrmont.standardmbeantest;

import javax.management.Attribute;
import javax.management.ObjectName;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;

public class StandardAgent {

    private MBeanServer mBeanServer = null;

    public StandardAgent() {
        mBeanServer = MBeanServerFactory.createMBeanServer();
    }

    public MBeanServer getMBeanServer() {
        return mBeanServer;
    }

    public ObjectName createObjectName(String name) {
        ObjectName objectName = null;
        try {
            objectName = new ObjectName(name);
        } catch (Exception e) {
        }
        return objectName;
    }

    private void createStandardBean(ObjectName objectName,
            String managedResourceClassName) {
        try {
            mBeanServer.createMBean(managedResourceClassName, objectName);
        } catch (Exception e) {
        }
    }

    public static void main(String[] args) {
        StandardAgent agent = new StandardAgent();
        MBeanServer mBeanServer = agent.getMBeanServer();
        String domain = mBeanServer.getDefaultDomain();
        String managedResourceClassName = "ex20.pyrmont.standardmbeantest.Car";
        ObjectName objectName = agent.createObjectName(domain + ":type="
                + managedResourceClassName);
        agent.createStandardBean(objectName, managedResourceClassName);

        // manage MBean
        try {
            Attribute colorAttribute = new Attribute("Color", "blue");
            mBeanServer.setAttribute(objectName, colorAttribute);
            System.out.println(mBeanServer.getAttribute(objectName, "Color"));
            mBeanServer.invoke(objectName, "drive", null, null);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

StandardAgent类是一个代理,它创建一个MBean服务器,用它注册一个CarMBean。首先要做的是要初始化 mBeanServer,在 StandardAgent 类的构造函数中,使用了MBeanServerFactory类的createMBeanServer()方法来构造一个MBean服务器:

public StandardAgent() {
    mBeanServer = MBeanServerFactory.createMBeanServer();
}

createMBeanServer()方法返回一个实现了 JMX 的 MBeanServer 对象。高级的 JMX程序员可能想自己实现 MBeanServer,但是本书并不关注该问题。

StandardAgent 的 createObjectName()根据传递给它的 String 参数返回一个ObjectName 对象。StandardAgent 的 createObjectName()方法调用了 MBeanServer的 createMBean()方法。createMBean 接受要管理的资源的类名以及唯一的用于区别它们的 ObjectName 对象。createMBean 还将创建的 MBean 对象注册到MBeanServer 上。因为标准 MBean 遵循传统的命名。不需要createMBean 方法提供 MBean 类型名。如果管理的资源类名为 Car,它的 MBean 就是 CarMBean。

StandardAgent 的 main()方法首先创建了 StandardAgent实例,然后调用getMBeanServer() 方法获得一个 MBeanServer 实例:

StandardAgent agent = new StandardAgent();
MBeanServer mBeanServer = agent.getMBeanServer();

然后为 CarMBean 创建了一个 ObjectName。MBeanServer 的默认域用作ObjectName 的域。一个名为 type 的键 key 被添加到域上,type 的值是管理资源的完全限定名:

String domain = mBeanServer.getDefaultDomain();
String managedResourceClassName ="ex20.pyrmont.standardmbeantest.Car";
ObjectName objectName = agent.createObjectName(domain + ":type=" +
managedResourceClassName);

然后 main()方法调用 createStandardBean()方法,传递一个 objectName 以及管理资源的类名:

agent.createStandardBean(objectName, managedResourceClassName);

接下来通过 CarMBean 实例管理 Car 对象。它创建了一个名为colorAttribute 的 Attribute 对象表示 Color 属性并将其值设置为 blue。然后调用 setAttribute()方法,参数为 objectName 和 colorAttribute。然后使用MBeanServer 上的 invoke()方法来调用 driver()方法:

// manage MBean
try {
    Attribute colorAttribute = new Attribute("Color","blue");
    mBeanServer.setAttribute(objectName, colorAttribute);
    System.out.println(mBeanServer.getAttribute(objectName,"Color"));
    mBeanServer.invoke(objectName,"drive",null,null);
}

如果运行 StandardAgent 类,会得到以下输出:

blue
Baby you can drive my car.

你可能会疑问为什么需要 JMX 来管理 Java 对象?在 StandardAgent 类里可以直接访问 Car 对象。的确是这样,但是这里的关键是你可以选择要暴露哪些属性和方法让管理程序来使用。另外可以在本章的“应用Demo”一节中看到MBeanServer 扮演着管理对象和管理程序之间的中间层的角色。

20.4 模型MBeans

模型 MBeans 提供了更多的灵活性。它编写起来更难,但是你不需要在修改自己的 Java 类。在不适合修改类的时候使用模型 MBean 是一种更好的选择。

在使用标准 MBean 的时候需要要管理的资源必须实现一个自己编写的接口。跟标准 MBean 不同,使用模型 MBean 的时,不需要编写任何接口。而是使用javax.management.modelmbean.ModelMBean 接口来表示模型 MBean。我们只需要实现这个接口即可,而 javax.management.modelmbean.RequiredModelMBean 类提供了该接口的默认实现。我们可以初始化 RequiredModelMBean 类或其子类。

注意:ModelMBean 接口的其它实现也可以,例如在 Commons Modeler 库中,有对该接口的实现,而不是继承 RequiredModelMBean。

要编写模型MBean的最大挑战是告诉我们的模型MBean对象哪些属性和方法要暴露给代理。可以通过创建 javax.management.modelmbean.ModelMBeanInfo 对象实现这一点。ModelMBeanInfo 对象用于描述暴露给代理的构造器、属性、操作以及事件监听器。创建一个 ModelMBeanInfo 对象是一个乏味的工作,但是一旦我们做好了一个,我们只需要将其跟 ModelMBean 对象关联即可。

使用 RequiredModelMBean 作为我们的 ModelMBean 的实现,有两种方法可以将ModelMBean 关联到 ModelMBeanInfo:

1.传递一个ModelMBeanInfo对象给RequiredModelMBean的构造函数
2.传递一个 ModelMBeanInfo 对象给 RequiredModelMBean 对象的setModelMBeanInfo() 方法。

在创建一个 ModelMBean 之后,必须使用 ModelMBean 接口的 setManagedResource()方法将其关联到管理资源。该方法签名如下:

public void setManagedResource(java.lang.Object managedResource,
java.lang.String managedResourceType) throws MBeanException,
RuntimeOperationsException, InstanceNotFoundException,InvalidTargetObjectTypeException

managedResourceType 参数的值可以是如下之一,ObjectReference, Handle,IOR, EJBHandle, or RMIReference。目前只支持 ObjectReference。

然后,当然还是需要创建一个 ObjectName 对象并经模型 MBean 注册到 MBean 服务器。

本节提供了一个例子来说明模型 MBean 的使用,使用了跟上一个例子相同的 Car对象。在看该实例之前先学习用于描述管理资源的属性和操作的 ModelMBeanInfo接口。

20.4.1 MBeanInfo 和ModelMBeanInfo 介绍

javax.management.mbean.ModelMBeanInfo 接口描述了要暴露给 MOdelMBean 的构造函数、属性、操作和监听器。
构造函数使用javax.management.modelmbean.ModelMBeanConstructorInfo 表示;
属性使用javax.management.modelmbean.ModelMBeanAttributeInfo 表示;
操作用javax.management.modelmbean.ModelMBeanOperationInfo 表示;
监听器用javax.management.modelmbean.ModelMBeanNotificationInfo 表示。
在本章中,我们只关注操作和属性。

JMX 提供了 ModelMBeanInfo 的默认实现:javax.management.modelmbean.ModelMBeanInfoSupport 类。下面是ModelMBeanInfoSupport 类的构造函数的签名,它在例子中会使用到:

public ModelMBeanInfoSupport(java.lang.String className,
java.lang.String description, ModelMBeanAttributeInfo[] attributes,
ModelMBeanConstructorInfo[] constructors,ModelMBeanOperationInfo[] operations,
ModelMBeanNotificationInfo[] notifications)

可以使用 ModelMBeanAttributeInfo 的构造函数构建一个ModelMBeanAttributeInfo 对象:

public ModelMBeanAttributeInfo(java.lang.String name,
java.lang.String type, java.lang.String description,boolean isReadable, boolean isWritable,boolean isIs, Descriptor descriptor)throws RuntimeOperationsException

下面是它的参数列表说明:
•name:属性名
•type:属性的类型或者类名
•description:属性的描述
•isReadable.:如果该属性有相应的 get 方法为真,否则为 false
•isWritable:如果该属性有响应的 set 方法为真,否则为 false
•isIs:如果该属性有 is getter 为真,否则为 false。
•descriptor: Descriptor 的实例,包括该 Attribute 的元数据。如果它是 null,就创建一个默认的descriptor。

可以使用如下 ModelMBeanOperationInfo 构造函数来创建一个该类的对象:

public ModelMBeanOperationInfo(java.lang.String name,java.lang.String description, MBeanParameterInfo[] signature,java.lang.String type, int impact, Descriptor)throws RuntimeOperationsException

下面是参数列表说明:
•name:方法名
•description:该操作的描述
•signature:对象数组描述该方法的参数。
•type:返回值类型
•impact:该方法的作用:如下值之一 INFO, ACTION, ACTION_INFO, UNKNOWN.
•descriptor:包含该 MBeanOperationInfo 对象的元数据的 Deccriptor

20.4.2 ModelMBean实例

这个例子说明了如何使用模型 MBean 来管理Car 对象,如 Listing20.4 所示。

Listing 20.4: The Car class

package ex20.pyrmont.modelmbeantest1;

public class Car {
    private String color = "red";

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public void drive() {
        System.out.println("Baby you can drive my car.");
    }
}

对于模型 MBean,不需要像标准 MBean 那样编写接口。我们只需简单的初始化RequiredMBean 类。Listing20.5 提供了 ModelAgent 类,它创建了 MBean 并管理Car 对象。

Listing 20.5: The ModelAgent class

package ex20.pyrmont.modelmbeantest1;

import javax.management.Attribute;
import javax.management.Descriptor;
import javax.management.MalformedObjectNameException;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.ObjectName;
import javax.management.modelmbean.DescriptorSupport;
import javax.management.modelmbean.ModelMBean;
import javax.management.modelmbean.ModelMBeanAttributeInfo;
import javax.management.modelmbean.ModelMBeanInfo;
import javax.management.modelmbean.ModelMBeanInfoSupport;
import javax.management.modelmbean.ModelMBeanOperationInfo;
import javax.management.modelmbean.RequiredModelMBean;

public class ModelAgent {

    private String MANAGED_CLASS_NAME = "ex20.pyrmont.modelmbeantest1.Car";
    private MBeanServer mBeanServer = null;

    public ModelAgent() {
        mBeanServer = MBeanServerFactory.createMBeanServer();
    }

    public MBeanServer getMBeanServer() {
        return mBeanServer;
    }

    private ObjectName createObjectName(String name) {
        ObjectName objectName = null;
        try {
            objectName = new ObjectName(name);
        } catch (MalformedObjectNameException e) {
            e.printStackTrace();
        }
        return objectName;
    }

    private ModelMBean createMBean(ObjectName objectName, String mbeanName) {
        ModelMBeanInfo mBeanInfo = createModelMBeanInfo(objectName, mbeanName);
        RequiredModelMBean modelMBean = null;
        try {
            modelMBean = new RequiredModelMBean(mBeanInfo);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return modelMBean;
    }

    private ModelMBeanInfo createModelMBeanInfo(ObjectName inMbeanObjectName,
            String inMbeanName) {
        ModelMBeanInfo mBeanInfo = null;
        ModelMBeanAttributeInfo[] attributes = new ModelMBeanAttributeInfo[1];
        ModelMBeanOperationInfo[] operations = new ModelMBeanOperationInfo[3];
        try {
            attributes[0] = new ModelMBeanAttributeInfo("Color",
                    "java.lang.String", "the color.", true, true, false, null);
            operations[0] = new ModelMBeanOperationInfo("drive",
                    "the drive method", null, "void",
                    MBeanOperationInfo.ACTION, null);
            operations[1] = new ModelMBeanOperationInfo("getColor",
                    "get color attribute", null, "java.lang.String",
                    MBeanOperationInfo.ACTION, null);

            Descriptor setColorDesc = new DescriptorSupport(new String[] {
                    "name=setColor", "descriptorType=operation",
                    "class=" + MANAGED_CLASS_NAME, "role=operation" });
            MBeanParameterInfo[] setColorParams = new MBeanParameterInfo[] { (new MBeanParameterInfo(
                    "new color", "java.lang.String", "new Color value")) };
            operations[2] = new ModelMBeanOperationInfo("setColor",
                    "set Color attribute", setColorParams, "void",
                    MBeanOperationInfo.ACTION, setColorDesc);

            mBeanInfo = new ModelMBeanInfoSupport(MANAGED_CLASS_NAME, null,
                    attributes, null, operations, null);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return mBeanInfo;
    }

    public static void main(String[] args) {
        ModelAgent agent = new ModelAgent();
        MBeanServer mBeanServer = agent.getMBeanServer();
        Car car = new Car();
        String domain = mBeanServer.getDefaultDomain();
        ObjectName objectName = agent.createObjectName(domain + ":type=MyCar");
        String mBeanName = "myMBean";
        ModelMBean modelMBean = agent.createMBean(objectName, mBeanName);
        try {
            modelMBean.setManagedResource(car, "ObjectReference");
            mBeanServer.registerMBean(modelMBean, objectName);
        } catch (Exception e) {
        }

        // manage the bean
        try {
            Attribute attribute = new Attribute("Color", "green");
            mBeanServer.setAttribute(objectName, attribute);

            String color = (String) mBeanServer.getAttribute(objectName,
                    "Color");
            System.out.println("Color:" + color);

            attribute = new Attribute("Color", "blue");
            mBeanServer.setAttribute(objectName, attribute);
            color = (String) mBeanServer.getAttribute(objectName, "Color");
            System.out.println("Color:" + color);
            mBeanServer.invoke(objectName, "drive", null, null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

如大家所见,编写 MBean 需要做大量的工作,尤其是声明暴露的属性和操作的时候。接下来一节将会介绍使用“Commons Modeler”库来帮助大家更快的编写模型 MBean。

20.5 Commons Modeler库

Commons Modeler库是 Apache 软件基金会 Jakarta 项目下的子项目。它提供了更简便的方法来编写模型 MBean。它最大的帮助在于使用它你不需要创建自己的ModelMBeanInfo 对象。

回想前面的创建 RequiredModelMBean 例子的方法,需要创建 ModelMBeanInfo对象并传递给RequiredModelMBean 的构造函数:

ModelMBeanInfo mBeanInfo = createModelMBeanInfo(objectName,mbeanName);
RequiredModelMBean modelMBean = null;
try {
    modelMBean = new RequiredModelMBean(mBeanInfo);
}
...

ModelMBeanInfo 对象用于描述暴露的属性和操作,而编写createModelMBeanInfo 是一项乏味的工作,我们必须列出所有的属性和操作然后将其传递给 createModelMBeanInfo。

使用该模型库,则不需要再使用 createModelMBeanInfo 对象。对模型 MBean 的描述被封装到 org.apache.catalina.modeler.ManagedBean 对象中。不需要再编写代码来暴露属性和操作。只需要编写一个 XMl 文档列出我们想创建的 MBean、对于每个 MBean,需要写出 MBean 类和管理资源的完全限定名,以及暴露的方法和属性。然后使用 org.apache.commons.modeler.Registry 对象读取 XML 文档,就可以创建一个包括所有 XML 文档中描述的所有 ManagedBean 实例的 MBeanServer实例。

然后可以调用 ManagedBean 实例的 createMBean()方法来创建一个模型 MBean。其它的工作就是平常需要做的了。需要创建 ObjectName 实例并将 MBean 注册到MBean 服务器。接下来看看描述文件是如何工作的,然后讨论其中最重要的类:Modeler, Registry, ManagedBean, 以及 BaseModelMBean。

注意:Tomcat4 使用的是旧版本,有些方法现在已不赞成继续使用。

20.5.1 MBean描述符

一个 MBean 描述符是用于描述 MBean 服务器管理的模型 MBean 的 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

<mbeans-descriptors>
...
</mbeans-descriptors>

在 mbeans-descriptors 标签内的元素师 mbean 元素,每一个表示一个模型 MBean。Mbean 元素包括表示属性、操作、构造器、监听器的元素。接下的子节中会讨论Tomcat 的 MBean 描述符中使用到的三种元素:

mbean

Mbean 元素描述模型 Mbean,它包括构造相应的 ModelMBeanInfo 对象的信息,mbean 元素如下定义

<!ELEMENT mbean (descriptor?, attribute*, constructor*, notification*,operation*)>

Mbean 可以有选择性的 descriptor 元素,0 或多个 attribute 元素,0 或多个constructor 元素,0 或多个 notification 元素,0 或多个 opertion 元素。

Mbean 元素可以有如下属性:
• className:实现的完全限定名。如果该属性没有,会使用org.apache.commons.modeler.BaseModelMBean
• description:对该模型 MBean 的描述
• domain:该managed bean创建的模型MBean要注册的服务器的域
• group:选择性的“grouping classification”可用于选择相似的 MBean 的组
• name:用于唯一确认 MBean 的 ID,一般使用服务器组件的基类名
• type.:管理资源类的完全限定名

attribute

使用 attribute 元素描述 MBean 的 JavaBean 属性。Attribute 元素可以有选择性的 decriptor 元素和如下属性:
• description:该属性的描述
• displayName:属性的显示名称
• getMethod:该属性的 get 方法
• is.:一个 Boolean 值,表示该属性是一个有 getter 方法的 boolean 类型。默认的该值为 false
• name:该 JavaBean的属性名
• readable: boolean 值,用于表示管理程序是否对该属性可读。默认的该值为 true
• setMethod:表示该 attribute 元素的 setter 方法
• type:该属性的完全限定 Java 类名
• writeable:boolean 值,用于表示该管理程序是否对该属性进行写操作,默认为 true

operation

Operation 元素描述暴露给管理程序的 public 方法,它可以有 0 或多个参数子元素,有如下属性:
• description:操作的描述
• impact:该方法的作用,可选值为 ACTION (write like), ACTION-INFO (write+read like), INFO(read like), or UNKNOWN
• name:该 public 方法的名字
• returnType:返回值类型

parameter

Parameter 元素用于描述传递给构造函数或操作的参数,它可以有如下属性:
• description:对该参数的描述
• name:参数名
• type:参数类型

20.5.2 Mbean元素例子

Catalina 中有很多模型 MBean,在 org.apache.catalina.mbeans 包中的mbean-descriptors.xml 中声明。Listing20.6 提供了 Tomcat4 中对StandardServer 的 Mbean 的声明。

Listing 20.6: The declaration of the StandardServer MBean

<mbean name="StandardServer"
className="org.apache.catalina.mbeans.StandardServerMBean"
description="Standard Server Component"
domain="Catalina"
group="Server"
type="org.apache.catalina.core.StandardServer">

<attribute name="debug"
description="The debugging detail level for this component"
type="int"/>

<attribute name="managedResource"
description="The managed resource this MBean is associated with"
type="java.lang.Object"/>

<attribute name="port"
description="TCP port for shutdown messages"
type="int"/>

<attribute name="shutdown"
description="Shutdown password"
type="java.lang.String"/>

<operation name="store"
description="Save current state to server.xml file"
impact="ACTION"
returnType="void">
</operation>

</mbean>

Listing20.6 中的 mbean 元素声明了一个模型 MBean 用于标识 StandardServer。该 MBean 用 org.apache.catalina.mbeans.StandardServerMBean 表示,管理一个 org.apache.catalina.core.StandardServer 类型的对象。域是 Catalina 而组是 Server。该模型 MBean 暴露了4个属性:debug,managedResource,port 及 shutdown,这4个属性都是 attribute 元素位于 mbean 元素里面。MBean 还暴露了 store()方法,用 operation 元素表示。

20.5.3 编写自己的模型MBean

当使用常用模型的时候,在 mbean 元素的 className 属性中定义模型 MBean 的类型。默认的常用模型使用 org.apache.commons.modeler.BaseModelMBean 类,但是有些情况我们可能想要继承 BaseModelMBean:

1.想要覆盖管理资源的属性和方法
2.想要给管理资源添加为定义的属性和方法

Catalina 在 org.apache.catalina.mbeans 包中提供了 BaseModelMBean 类的一些子类,后面我们会对它们进行简单的介绍。

20.5.4 Registry

该 API 重点在于org.apache.commons.modeler.Registry 类,这里是可以使用该类做的事情:
• 获得一个 javax.management.MBeanServer 实例(不需要调用javax.management.MBeanServerFactor 的 createMBeanServer 方法)
•使用 loadRegistry 方法读取描述文件
•创建一个可用于创建模型 MBean 的 ManagedBean 对象

20.5.5 ManagedBean

ManagedBean 替代 javax.management.MBeanInfo 来表示一个模型 MBean。

20.5.6 BaseModelMBean

org.apache.commons.modeler.BaseModelMBean 类实现了javax.management.modelmbean.ModelMBean 接口。使用这个类,则不需要再使用javax.management.modelmbean.RequiredModelMBean 类。

该类还有一点很有用是该类用一个 resources 属性来表示管理资源:

protected java.lang.Object resource;

20.5.7 使用Modeler API

管理的 Car 类如 Listing20.7 所示:

Listing 20.7: The Car class

package ex20.pyrmont.modelmbeantest2;

public class Car {

    public Car() {
        System.out.println("Car constructor");
    }

    private String color = "red";

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public void drive() {
        System.out.println("Baby you can drive my car.");
    }
}

使用常用模型,不需要再在属性和操作上面进行硬编码,而是简单地编写XML 文档即可。在这个例子中,用于描述它的文档 car-mbean-descriptor.xml 如Listing20.8 所示。

Listing 20.8: The car-mbean-descriptor.xml file

<?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="myMBean"
    className="javax.management.modelmbean.RequiredModelMBean"
    description="The ModelMBean that manages our Car object"
    type="ex20.pyrmont.modelmbeantest.Car">

    <attribute name="Color"
      description="The car color"
      type="java.lang.String"/>

    <operation name="drive"
      description="drive method"
      impact="ACTION"
      returnType="void">
      <parameter name="driver" description="the driver parameter"
        type="java.lang.String"/>
    </operation>

  </mbean>

</mbeans-descriptors>

接下来需要如 Listing20.9 所示的代理类:

Listing 20.9: The ModelAgent Class

package ex20.pyrmont.modelmbeantest2;

import java.io.InputStream;
import java.net.URL;
import javax.management.Attribute;
import javax.management.MalformedObjectNameException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.modelmbean.ModelMBean;

import org.apache.commons.modeler.ManagedBean;
import org.apache.commons.modeler.Registry;

public class ModelAgent {
    private Registry registry;
    private MBeanServer mBeanServer;

    public ModelAgent() {
        registry = createRegistry();
        try {
            mBeanServer = Registry.getServer();
        } catch (Throwable t) {
            t.printStackTrace(System.out);
            System.exit(1);
        }
    }

    public MBeanServer getMBeanServer() {
        return mBeanServer;
    }

    public Registry createRegistry() {
        Registry registry = null;
        try {
            URL url = ModelAgent.class
                    .getResource("/ex20/pyrmont/modelmbeantest2/car-mbean-descriptor.xml");
            InputStream stream = url.openStream();
            Registry.loadRegistry(stream);
            stream.close();
            registry = Registry.getRegistry();
        } catch (Throwable t) {
            System.out.println(t.toString());
        }
        return (registry);
    }

    public ModelMBean createModelMBean(String mBeanName) throws Exception {
        ManagedBean managed = registry.findManagedBean(mBeanName);
        if (managed == null) {
            System.out.println("ManagedBean null");
            return null;
        }
        ModelMBean mbean = managed.createMBean();
        ObjectName objectName = createObjectName();
        return mbean;
    }

    private ObjectName createObjectName() {
        ObjectName objectName = null;
        String domain = mBeanServer.getDefaultDomain();
        try {
            objectName = new ObjectName(domain + ":type=MyCar");
        } catch (MalformedObjectNameException e) {
            e.printStackTrace();
        }
        return objectName;
    }

    public static void main(String[] args) {
        ModelAgent agent = new ModelAgent();
        MBeanServer mBeanServer = agent.getMBeanServer();
        Car car = new Car();
        System.out.println("Creating ObjectName");
        ObjectName objectName = agent.createObjectName();
        try {
            ModelMBean modelMBean = agent.createModelMBean("myMBean");
            modelMBean.setManagedResource(car, "ObjectReference");
            mBeanServer.registerMBean(modelMBean, objectName);
        } catch (Exception e) {
            System.out.println(e.toString());
        }
        // manage the bean
        try {
            Attribute attribute = new Attribute("Color", "green");
            mBeanServer.setAttribute(objectName, attribute);
            String color = (String) mBeanServer.getAttribute(objectName,
                    "Color");
            System.out.println("Color:" + color);

            attribute = new Attribute("Color", "blue");
            mBeanServer.setAttribute(objectName, attribute);
            color = (String) mBeanServer.getAttribute(objectName, "Color");
            System.out.println("Color:" + color);
            mBeanServer.invoke(objectName, "drive", null, null);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

可见使用该模型库编写的代理类比较简短。

20.6 Catalina中的MBeans

如在本章开头提到的,Catalina 在 org.apache.catalina.mbeans 包中提供了一些 MBean 类。这些 MBean 类直接或间接的继承org.apache.commons.modeler.BaseModelMBean 类。本节主要讨论 Tomcat4 中 3个最重要的MBean类:ClassNameMBean, StandardServerMBean和 MBeanFactory。如果理解了这3个类,就不难理解其它的 MBean 类。另外本节还将介绍org.apache.catalina.mbeans 包中的 MBeanUtil 类。

20.6.1 ClassNameMBean

org.apache.catalina.mbeans.ClassNameMBean 继承了org.apache.commons.modeler.BaseModelMBean。它提供了代表管理资源的类名的只写属性 className。该类如 Listing20.10 所示。

Listing 20.10: The ClassNameMBean class

package org.apache.catalina.mbeans;

import javax.management.MBeanException;
import javax.management.RuntimeOperationsException;
import org.apache.commons.modeler.BaseModelMBean;

public class ClassNameMBean extends BaseModelMBean {
    public ClassNameMBean() throws MBeanException, RuntimeOperationsException {
        super();
    }

    public String getClassName() {
        return (this.resource.getClass().getName());
    }
}

ClassNameMBean 是 BaseModelMBean 类的一个子类,它提供了一个在管理资源中不可变的属性。mbeans-descriptors.xml 文件中的多个 mbean 元素使用这个类作为模型 MBean 的类型。

20.6.2 StandardServerMBean

StandardServerMBean 继承了 org.apache.commons.modeler.BaseModelMBean 类,用于管理 org.apache.catalina.core.StandardServer。如 Listing20.11 的StandardServerMBean 类是一个模型 MBean 的例子,它覆盖了管理资源中的store()方法。当管理程序调用 store()方法时,会执行StandardServerMBean 类中的而不是StandardServer 中的 store()方法。

Listing 20.11: The StandardServerMBean class

package org.apache.catalina.mbeans;

import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanServer;
import javax.management.RuntimeOperationsException;
import org.apache.catalina.Server;
import org.apache.catalina.ServerFactory;
import org.apache.catalina.core.StandardServer;
import org.apache.commons.modeler.BaseModelMBean;

public class StandardServerMBean extends BaseModelMBean {

    private static MBeanServer mserver = MBeanUtils.createServer();

    public StandardServerMBean() throws MBeanException, RuntimeOperationsException {
        super();
    }

    public synchronized void stored throws InstanceNotFoundException,MBeanException, RuntimeOperationsException {

        Server server = ServerFactory.getServer();
        if (server instanceof StandardServer) {
            try {
                ((StandardServer) server).store();
            }catch (Exception e) {
                throw new MBeanException(e, "Error updating conf/server.xml");
        }
      }
    }
}

StandardServerMBean 是 BaseModelMBean 的子类,是模型MBean的例子,它覆盖了管理资源中的方法。

20.6.3 MBeanFactory

MBeanFactory 表示用于创建模型 MBean 的工厂对象,它管理 Catalina 中各种资源。MBeanFactory 类还提供了用于删除这些 MBeans。

作为一个例子,我们看看如Listing20.12 所示的createStandardContext()方法。
.
Listing 20.12: The createStandardContext method

public String createStandardContext(String parent, String path,
                                        String docBase)
        throws Exception {

        // Create a new StandardContext instance
        StandardContext context = new StandardContext();    
        path = getPathStr(path);
        context.setPath(path);
        context.setDocBase(docBase);
        ContextConfig contextConfig = new ContextConfig();
        context.addLifecycleListener(contextConfig);

        // Add the new instance to its parent component
        ObjectName pname = new ObjectName(parent);
        Server server = ServerFactory.getServer();
        Service service = server.findService(pname.getKeyProperty("service"));
        Engine engine = (Engine) service.getContainer();
        Host host = (Host) engine.findChild(pname.getKeyProperty("host"));

        // Add context to the host
        host.addChild(context);

        // Return the corresponding MBean name
        ManagedBean managed = registry.findManagedBean("StandardContext");

        ObjectName oname =
            MBeanUtils.createObjectName(managed.getDomain(), context);
        return (oname.toString());

    }

20.6.4 MBeanUtil

org.apache.catalina.mbeans.MBeanUtil 类是一个工具类,它提供了静态方法来创建 Mbean 和管理 Catalina 对象、删除 MBean 的静态方法以及创建对象名的静态方法。例如,如 Listing20.13 所示的 createMBean 方法创建一个org.apache.catalina.Server 对象的模型 MBean。

Listing 20.13: The createMBean method that creates a model MBean that manages a Server object.

public static ModelMBean createMBean(Server server)
        throws Exception {

        String mname = createManagedName(server);
        ManagedBean managed = registry.findManagedBean(mname);
        if (managed == null) {
            Exception e = new Exception("ManagedBean is not found with "+mname);
            throw new MBeanException(e);
        }
        String domain = managed.getDomain();
        if (domain == null)
            domain = mserver.getDefaultDomain();
        ModelMBean mbean = managed.createMBean(server);
        ObjectName oname = createObjectName(domain, server);
        mserver.registerMBean(mbean, oname);
        return (mbean);

    }

20.7 Catalina中创建MBean

现在熟悉了 Catalina 中的模型 MBean,接下来看一下是如何创建了这些 MBean来管理应用程序的。

Tomcat 的配置文件 server.xml 在 Server 元素中定义了 Listener 元素:

<Server port="8005" shutdown="SHUTDOWN" debug="0">
    <Listener c1assName="org.apache.catalina.mbeans.ServerLifecycleListener" debug="0"/>
...

它将给 org.apache.catalina.core.StandardServer 添加一个org.apache.catalina.mbeans.ServerLifecycleListener 类型的监听器,当StandardServer 实例启动时,会触发一个 START_EVENT 事件,如StandardServer 类中定义的那样:

public void start() throws LifecycleException {
    ...
    lifecycle.fireLifecycleEvent(START_EVENT, null);
    ...
}

StandardServer 对象停止的时候,会触发 STOP_EVENT 事件,如 stop 方法中定义的那样:

public void stop() throws LifecycleException {
    ...
    lifecycle.fireLifecycleEvent(STOP_EVENT, null);
    ...
}

这些事件会导致 ServerLifecycleListener 中的 lifecycleEvent 方法被执行。Listing20.14 展示了 lifecycleEvent 方法。

Listing 20.14: The lifecycleEvent method of the ServerLifecycleListener class

public void lifecycleEvent(LifecycleEvent event) {
        Lifecycle lifecycle = event.getLifecycle();
        if (Lifecycle.START_EVENT.equals(event.getType())) {

            if (lifecycle instanceof Server) {

                // Loading additional MBean descriptors
                loadMBeanDescriptors();
                createMBeans();

            }

            /*
            // Ignore events from StandardContext objects to avoid
            // reregistering the context
            if (lifecycle instanceof StandardContext)
                return;
            createMBeans();
            */

        } else if (Lifecycle.STOP_EVENT.equals(event.getType())) {

            if (lifecycle instanceof Server) {
                destroyMBeans();
            }

        } else if (Context.RELOAD_EVENT.equals(event.getType())) {

            // Give context a new handle to the MBean server if the
            // context has been reloaded since reloading causes the
            // context to lose its previous handle to the server
            if (lifecycle instanceof StandardContext) {
                // If the context is privileged, give a reference to it
                // in a servlet context attribute
                StandardContext context = (StandardContext)lifecycle;
                if (context.getPrivileged()) {
                    context.getServletContext().setAttribute
                        (Globals.MBEAN_REGISTRY_ATTR,
                         MBeanUtils.createRegistry());
                    context.getServletContext().setAttribute
                        (Globals.MBEAN_SERVER_ATTR,
                         MBeanUtils.createServer());
                }
            }

        }

    }

createMBeans()是创建 Catalina 中所有 MBeans 方法。该方法首先创建一个MBeanFactory。

Listing 20.15: The createMBeans method in ServerLifecycleListener

 protected void createMBeans() {

        try {

            MBeanFactory factory = new MBeanFactory();
            createMBeans(factory);
            createMBeans(ServerFactory.getServer());

        } catch (MBeanException t) {

            Exception e = t.getTargetException();
            if (e == null)
                e = t;
            log("createMBeans: MBeanException", e);

        } catch (Throwable t) {

            log("createMBeans: Throwable", t);

        }

    }

第一个 createMBeans() 方法使用 MBeanUtil 类给 MBeanFactory 创建一个ObjectName 并将其向 MBean 服务器注册。

第二个createMBeans()方法有一个org.apache.catalina.Server对象并为其创建模型 MBean。阅读如 Listing20.16 所示的 createMBeans()方法的代码很有趣。

Listing 20.16: The createMBeans method that creates an MBean for a Server object

protected void createMBeans(Server server) throws Exception {

        // Create the MBean for the Server itself
        if (debug >= 2)
            log("Creating MBean for Server " + server);
        MBeanUtils.createMBean(server);
        if (server instanceof StandardServer) {
            ((StandardServer) server).addPropertyChangeListener(this);
        }

        // Create the MBeans for the global NamingResources (if any)
        NamingResources resources = server.getGlobalNamingResources();
        if (resources != null) {
            createMBeans(resources);
        }

        // Create the MBeans for each child Service
        Service services[] = server.findServices();
        for (int i = 0; i < services.length; i++) {
            // FIXME - Warp object hierarchy not currently supported
            if (services[i].getContainer().getClass().getName().equals
                ("org.apache.catalina.connector.warp.WarpEngine")) {
                if (debug >= 1) {
                    log("Skipping MBean for Service " + services[i]);
                }
                continue;
            }
            createMBeans(services[i]);
        }

    }

注意如 Listing20.16 所示的 createMBeans ()方法使用 for 循环来迭代StandardServer 实例中的所有 Service 对象:

createMBeans(services[i]);

该方法为服务(Service)创建一个 MBean 实例并调用 createMBeans()方法来为该服务所有的连接器和引擎创建 MBean 对象。用于创建 Service 的 MBean 的createMBeans()方法如 Listing20.17 所示:

Listing 20.17: The createMBeans method that creates a Service MBean

protected void createMBeans(Service service) throws Exception {

        // Create the MBean for the Service itself
        if (debug >= 2)
            log("Creating MBean for Service " + service);
        MBeanUtils.createMBean(service);
        if (service instanceof StandardService) {
            ((StandardService) service).addPropertyChangeListener(this);
        }

        // Create the MBeans for the corresponding Connectors
        Connector connectors[] = service.findConnectors();
        for (int j = 0; j < connectors.length; j++) {
            createMBeans(connectors[j]);
        }

        // Create the MBean for the associated Engine and friends
        Engine engine = (Engine) service.getContainer();
        if (engine != null) {
            createMBeans(engine);
        }

    }

createMBeans (engine)调用为主机创建 MBeans 的 createMBeans()方法:

protected void createMBeans(Engine engine) throws Exception {

        // Create the MBean for the Engine itself
        if (debug >= 2) {
            log("Creating MBean for Engine " + engine);
        }
        MBeanUtils.createMBean(engine);
        engine.addContainerListener(this);
        if (engine instanceof StandardEngine) {
            ((StandardEngine) engine).addPropertyChangeListener(this);
        }

        // Create the MBeans for the associated nested components
        Logger eLogger = engine.getLogger();
        if (eLogger != null) {
            if (debug >= 2)
                log("Creating MBean for Logger " + eLogger);
            MBeanUtils.createMBean(eLogger);
        }
        Realm eRealm = engine.getRealm();
        if (eRealm != null) {
            if (debug >= 2)
                log("Creating MBean for Realm " + eRealm);
            MBeanUtils.createMBean(eRealm);
        }

        // Create the MBeans for the associated Valves
        if (engine instanceof StandardEngine) {
            Valve eValves[] = ((StandardEngine)engine).getValves();
            for (int j = 0; j < eValves.length; j++) {
                if (debug >= 2)
                    log("Creating MBean for Valve " + eValves[j]);
                MBeanUtils.createMBean(eValves[j]);
            }
        }

        // Create the MBeans for each child Host
        Container hosts[] = engine.findChildren();
        for (int j = 0; j < hosts.length; j++) {
            createMBeans((Host) hosts[j]);
        }

        // Create the MBeans for DefaultContext
        DefaultContext dcontext = engine.getDefaultContext();
        if (dcontext != null) {
            dcontext.setParent(engine);
            createMBeans(dcontext);
        }

    }

createMBeans(host)方法又创建 ContextMBean,如下:

protected void createMBeans(Host host) throws Exception {
    ...
    MBeanUtils.createMBean(host);
    ...
    Container contexts[] = host.findChildren();
    for (int k = 0; k < contexts.length; k++) {
        createMBeans((Context) contexts[k]);
    }
    ...
}

createMBeans(context)如下所示:

protected void createMBeans(Context context) throws Exception {
        // Create the MBean for the Context itself
        if (debug >= 4)
            log("Creating MBean for Context " + context);
        MBeanUtils.createMBean(context);
        context.addContainerListener(this);
        if (context instanceof StandardContext) {
            ((StandardContext) context).addPropertyChangeListener(this);
            ((StandardContext) context).addLifecycleListener(this);
        }

        // If the context is privileged, give a reference to it
        // in a servlet context attribute
        if (context.getPrivileged()) {
            context.getServletContext().setAttribute
                (Globals.MBEAN_REGISTRY_ATTR,
                 MBeanUtils.createRegistry());
            context.getServletContext().setAttribute
                (Globals.MBEAN_SERVER_ATTR, 
                 MBeanUtils.createServer());
        }

        // Create the MBeans for the associated nested components
        Loader cLoader = context.getLoader();
        if (cLoader != null) {
            if (debug >= 4)
                log("Creating MBean for Loader " + cLoader);
            MBeanUtils.createMBean(cLoader);
        }
        Logger hLogger = context.getParent().getLogger();
        Logger cLogger = context.getLogger();
        if ((cLogger != null) && (cLogger != hLogger)) {
            if (debug >= 4)
                log("Creating MBean for Logger " + cLogger);
            MBeanUtils.createMBean(cLogger);
        }
        Manager cManager = context.getManager();
        if (cManager != null) {
            if (debug >= 4)
                log("Creating MBean for Manager " + cManager);
            MBeanUtils.createMBean(cManager);
        }
        Realm hRealm = context.getParent().getRealm();
        Realm cRealm = context.getRealm();
        if ((cRealm != null) && (cRealm != hRealm)) {
            if (debug >= 4)
                log("Creating MBean for Realm " + cRealm);
            MBeanUtils.createMBean(cRealm);
        }

        // Create the MBeans for the associated Valves
        if (context instanceof StandardContext) {
            Valve cValves[] = ((StandardContext)context).getValves();
            for (int l = 0; l < cValves.length; l++) {
                if (debug >= 4)
                    log("Creating MBean for Valve " + cValves[l]);
                MBeanUtils.createMBean(cValves[l]);
            }

        }        

        // Create the MBeans for the NamingResources (if any)
        NamingResources resources = context.getNamingResources();
        createMBeans(resources);

    }

如果上下文的privileged属性为真,会给该web应用程序创建和存储两个属性。属性的键为 Globals.MBEAN_REGISTRY_ATTR 和 Globals.MBEAN_SERVER_ATTR。下面是 org.apache.catalina.Globals 类中的代码片段:

/**
* The servlet context attribute under which the managed bean Registry
* will be stored for privileged contexts (if enabled).
*/
public static final String MBEAN_REGISTRY_ATTR = "org.apache.catalina.Registry";

/**
* The servlet context attribute under which the MBeanServer will be
* stored for privileged contexts (if enabled).
*/
public static final String MBEAN_SERVER_ATTR ="org.apache.catalina.MBeanServer";

MBeanUtils.createRegistry()返回一个 Registry 实例。MBeanUtils.createServer()方法返回一个 javax.management.MBeanServer 实例,所有的 Catalina 的 MBean 都在上面注册。

换句话说,可以获得 privileged 属性为真的 web 应用的 Reigstry 和MBeanServer 的实例。下面一节将会讨论如何使用 JMX 管理应用程序来管理Tomcat。

20.8 应用Demo

这里的应用Demo是一个用于管理 Tomcat 的应用程序。它很简单,但是足够我们认识怎么使用 MBeans 暴露的 Catalina。可以使用它列出 Catalina 中所有的ObjectName 实例,列出当前运行的所有上下文以及删除它们。

首先,我们需要为该应用程序创建一个描述文件如 Listing20.18 所示。必须将该文件放在%CATALINA_HOME%/webapps 目录下。

Listing 20.18: The myadmin.xml file

<Context path="/myadmin" docBase="../server/webapps/myadmin" debug="8" privileged="true"    reloadable="true">
</Context>

需要关心的一件事是:确保 Context 元素的 privileged 属性为真。docBase 属性应设置成该应用程序所在的目录地址。

该应用程序是一个 Servlet,如 Listing20.19 所示。

Listing 20.19: The MyAdminServlet class

package myadmin;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.Set;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.modeler.Registry;


public class MyAdminServlet extends HttpServlet {
    private Registry registry;
    private MBeanServer mBeanServer;

    public void init() throws ServletException {
        registry = (Registry) getServletContext().getAttribute(
                "org.apache.catalina.Registry");
        if (registry == null) {
            System.out.println("Registry not available");
            return;
        }
        mBeanServer = (MBeanServer) getServletContext().getAttribute(
                "org.apache.catalina.MBeanServer");
        if (mBeanServer == null) {
            System.out.println("MBeanServer not available");
            return;
        }
    }

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        if (registry == null || mBeanServer == null) {
            out.println("Registry or MBeanServer not found");
            return;
        }
        out.println("<html><head></head><body>");
        String action = request.getParameter("action");
        if ("listAllManagedBeans".equals(action)) {
            listAllManagedBeans(out);
        } else if ("listAllContexts".equals(action)) {
            listAllContexts(out);
        } else if ("removeContext".equals(action)) {
            String contextObjectName = request
                    .getParameter("contextObjectName");
            removeContext(contextObjectName, out);
        } else {
            out.println("Invalid command");
        }
        out.println("</body></html>");
    }

    private void listAllManagedBeans(PrintWriter out) {
        String[] managedBeanNames = registry.findManagedBeans();
        for (int i = 0; i < managedBeanNames.length; i++) {
            out.print(managedBeanNames[i] + "<br/>");
        }
    }

    private void listAllContexts(PrintWriter out) {
        try {
            ObjectName objName = new ObjectName("Catalina:type=Context,*");
            Set set = mBeanServer.queryNames(objName, null);
            Iterator it = set.iterator();
            while (it.hasNext()) {
                ObjectName obj = (ObjectName) it.next();
                out.print(obj
                        + " <a href=?action=removeContext&contextObjectName="
                        + URLEncoder.encode(obj.toString(), "UTF-8")
                        + ">remove</a><br/>");
            }
        } catch (Exception e) {
            out.print(e.toString());
        }
    }

    private void removeContext(String contextObjectName, PrintWriter out) {
        try {
            ObjectName mBeanFactoryObjectName = new ObjectName(
                    "Catalina:type=MBeanFactory");
            if (mBeanFactoryObjectName != null) {
                String operation = "removeContext";
                String[] params = new String[1];
                params[0] = contextObjectName;
                String signature[] = { "java.lang.String" };
                try {
                    mBeanServer.invoke(mBeanFactoryObjectName, operation,
                            params, signature);
                    out.println("context removed");
                } catch (Exception e) {
                    out.print(e.toString());
                }
            }
        } catch (Exception e) {
        }
    }
}

最后,需要如 Listing20.20 所示的应用部署文件。

Listing 20.20: The web.xml file

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
    <servlet>
        <servlet-name>myAdmin</servlet-name>
        <servlet-class>myadmin.MyAdminServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>myAdmin</servlet-name>
        <url-pattern>/myAdmin</url-pattern>
    </servlet-mapping>
</web-app>

要列出所有的 ObjectName 实例,可以使用如下 URL:

http://localhost:8080/myadmin/myAdmin?action=listAllMBeans

我们可以看到一系列的 MBean 对象,下面是前六个:

MemoryUserDatabase
DigestAuthenticator
BasicAuthenticator
UserDatabaseRealm
SystemErrLogger
Group

可以使用如下 URL 列出所有上下文容器:

http://localhost:8080/myadmin/myAdmin?action=listAllContexts

可以看到所有的运行中的应用,可以点击 remove 超链接删除它们。

20.9 小结

在本章中我们学习了如何使用 JMX 来管理 Tomcat。本章介绍了2种类型的 MBeans(共有4种) 并展示了一个简单使用 MBeans 来管理 Catalina 的应用程序。


排版格式更好的电子书:

https://yuedu.baidu.com/ebook/ac92f0d35122aaea998fcc22bcd126fff7055d60

猜你喜欢

转载自blog.csdn.net/LoveJavaYDJ/article/details/71278953
今日推荐