Chapter 20. JMX

20.1. 介绍

Spring的JMX支持提供了一些特性,使你能够简单透明地将你的Spring应用程序集成到一个JMX基础设施中去。

确切的讲,Spring的JMX支持提供了四种核心特性:

  • 自动将任一Spring bean注册为JMX MBean

  • 使用灵活的机制来控制bean的管理接口

  • 通过远程的JSR-160连接器对外声明式暴露MBean

  • 对本地和远程MBean资源的简单代理

这些特性被设计成不管是Spring还是JMX的接口和类都和你的应用程序组件不耦合。实际上,为了利用Spring的JMX特性,大部分应用程序的类都不必去关心Spring或JMX。

20.2. 输出bean到JMX

在Spring的JMX框架中,核心类是 MBeanExporter。这个类负责获取Spring的bean,并用一个JMX MBeanServer 类来注册它们。举个例子,考虑下面的类:

package org.springframework.jmx;

public class JmxTestBean implements IJmxTestBean {

    private String name;
    private int age;
    private boolean isSuperman;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    
    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public int add(int x, int y) {
        return x + y;
    }

    public void dontExposeMe() {
        throw new RuntimeException();
    }
}

你只需要在配置文件里简单地配置一个 MBeanExporter 的实例,并以如下所示的方法将这个bean传入,就可以将这个bean的属性和方法作为一个MBean的属性和操作暴露出去。

<beans>

  <!-- this bean must not be lazily initialized if the exporting is to happen -->
  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
    <property name="beans">
      <map>
        <entry key="bean:name=testBean1" value-ref="testBean"/>
      </map>
    </property>
  </bean>

  <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
  </bean>

</beans>

上面的配置片段中,与bean定义相关的是 exporter bean,属性 beans 是告诉类 MBeanExporter 必须将哪些bean输出到JMX的 MBeanServer 去。 缺省配置中,在 beans 中,Map 用到的每个条目的key被用做相应条目值所引用的bean的 ObjectName。 在 Section 20.4, “控制bean的 ObjectName ” 中描述的情况下,可以改变这个行为。

在这项配置中,testBean 这个bean在 ObjectName值为 bean:name=testBean1 的情况下作为MBean暴露出去的。缺省情况下,这个bean所有的 public 的属性都作为对应MBean的属性, 这个bean所有的 public 的方法(除了那些继承自类 Object 的方法)都作为对应MBean的操作暴露出去的。

20.2.1. 创建一个MBeanServer

上述配置是假定应用程序运行在一个(仅有一个)MBeanServer 运行的环境中的。 这种情况下,Spring会试着查找运行中的 MBeanServer 并用这个server(如果有的话)来注册自己的bean。 在一个拥有自己的 MBeanServer 的容器中(如Tomcat或IBM WebSphere),这种行为是非常有用。

然而,在一个单一的环境,或运行在一个没有提供任何 MBeanServer 的容器里的情况下,这种方法毫无用处。 要处理这类问题,你可以在配置文件里添加一个类 org.springframework.jmx.support.MBeanServerFactoryBean 的实例来声明创建一个 MBeanServer 的实例。你也可以通过设置类 MBeanExporterserver 属性的值来确保使用一个特殊的 MBeanServer, 这个 MBeanServer 值是由一个 MBeanServerFactoryBean 返回的。

<beans>

  <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean"/>

  <!--
    this bean needs to be eagerly pre-instantiated in order for the exporting to occur;
    this means that it must not be marked as lazily initialized
  -->
  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="bean:name=testBean1" value-ref="testBean"/>
      </map>
    </property>
    <property name="server" ref="mbeanServer"/>
  </bean>

  <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
  </bean>

</beans>

这里,通过 MBeanServerFactoryBean 创建一个 MBeanServer 的实例,并通过属性server将这个实例提供给MBeanExporter。 在提供你自己的MBeanServer实例的时候,MBeanExporter 将不会去查找运行中的MBeanServer,而是使用这个提供的MBeanServer 实例。为了让它正确的工作,必须在你的类路径上有一个JMX的实现。

20.2.2. MBean的惰性初始化

如果你用 MBeanExporter 来配置的一个bean,同时也配置了惰性初始化,那么 MBeanExporter 不会 破坏这个约定,将避免初始化相应的bean。 而是会给 MBeanServer 注册一个代理,推迟从容器中获取这个bean,直到在代理侧发生对它的第一次调用。

20.2.3. MBean的自动注册

所有通过 MBeanExporter 输出,并已经是有效MBean的bean,会在没有Spring干涉的情况下向 MBeanServer 注册。 通过设置 autodetect 属性为 trueMBeanExporter能自动的发现MBean:

<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
  <property name="autodetect" value="true"/>
</bean>

<bean name="spring:mbean=true" class="org.springframework.jmx.export.TestDynamicMBean"/>

这里,被称做 spring:mbean=true 的bean已经是一个有效的MBean了,将由Spring自动的注册。 缺省情况下,为JMX注册而自动发现的bean有它们自己的名字,用 ObjectName 来设置。 在 Section 20.4, “控制bean的 ObjectName ” 章节里,更详细的描述了如何覆盖(override)这种行为。

20.2.4. 控制注册行为

考虑这样一个场景,一个Spring的 MBeanExporter 试着用 ObjectName 'bean:name=testBean1' 往一个 MBeanServer 里注册一个MBean。 如果之前已经有一个 MBean 实例在同一个 ObjectName 下注册了,则缺省的行为是失败(并抛出一个 InstanceAlreadyExistsException 异常)。

当向 MBeanServer 注册一个 MBean 的时候,可以控制发生哪些行为。 Spring对JMX的支持三种不同的注册行为,当注册进程找到一个已经在同一个 ObjectName 下注册过的MBean时,以此来控制注册行为。这些注册行为总结在下面的表中:

Table 20.1. 注册行为

注册行为解释

REGISTRATION_FAIL_ON_EXISTING

这是缺省的注册行为。如果一个 MBean 实例已经在同一个 ObjectName 下进行注册了,则正在注册中的这个 MBean 将不会注册,同时抛出 InstanceAlreadyExistsException 异常,已经存在的 MBean 不会受到影响。

REGISTRATION_IGNORE_EXISTING

如果一个 MBean 实例已经在同一个 ObjectName 下进行注册了,正在注册的 MBean 被注册,已经存在的 MBean 不受影响,不会有 Exception 抛出。

当多个应用程序想在一个共享 MBeanServer 中共享一个公共 MBean 时,这个行为很有用。

REGISTRATION_REPLACE_EXISTING

如果一个 MBean 实例已经在同一个 ObjectName 下进行注册了,现存的已经注册过的MBean将被注销掉,新的 MBean 将代替原来的进行注册(新的 MBean 有效的替换之前的那个实例)。

上述各值(分别是 REGISTRATION_FAIL_ON_EXISTINGREGISTRATION_IGNORE_EXISTINGREGISTRATION_REPLACE_EXISTING)作为常数定义在类 MBeanRegistrationSupport 中(类 MBeanExporter 继承自这个超类)。如果你想改变缺省注册行为,只需要在你的 MBeanExporter 的定义中简单的把属性 registrationBehaviorName 设置成这些值中的一个就可以了。

下面的例子说明了如何从缺省的注册行为改变为 REGISTRATION_REPLACE_EXISTING 行为。

<beans>

    <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
        <property name="beans">
            <map>
                <entry key="bean:name=testBean1" value-ref="testBean"/>
            </map>
        </property>
        <property name="registrationBehaviorName" value="REGISTRATION_REPLACE_EXISTING"/>
    </bean>

    <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
        <property name="name" value="TEST"/>
        <property name="age" value="100"/>
    </bean>

</beans>

20.3. 控制bean的管理接口

在上一个例子中,并没有对bean的管理接口进行控制;每个bean的 所有public属性和方法分别作为JMX的属性和操作来输出。 为了更细粒度的对那些输出bean的属性和方法进行控制,这些属性和方法实际是作为JMX的属性和操作输出的,Spring JMX提供了一个全面的可扩展的机制来控制你那些bean的管理接口。

20.3.1. MBeanInfoAssembler 接口

在后台,MBeanExporter 委派接口 org.springframework.jmx.export.assembler.MBeanInfoAssembler 的一个实现,这个接口负责定义每个输出bean的管理接口。 类 org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler 是它的缺省实现,简单的定义了一个管理接口,输出所有public的属性和方法(如你在前面的例子见到的那样)。 Spring为接口 MBeanInfoAssembler 提供了两个另外的实现,允许你用源代码级元数据或任意接口来控制产生管理接口。

20.3.2. 使用源码级元数据

MetadataMBeanInfoAssembler 使你可以用源码级元数据来定义bean的管理接口。 元数据的读取由接口 org.springframework.jmx.export.metadata.JmxAttributeSource 封装。 Spring JMX提供了这个接口的两种实现,即开即用: 类 org.springframework.jmx.export.metadata.AttributesJmxAttributeSource针对公用属性(Commons Attributes), 而类 org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource 针对JDK5.0注解。 为了功能正常,类 MetadataMBeanInfoAssembler必须 和接口 JmxAttributeSource 的一个实现一起配置(这里 没有 缺省)。 在接下来的例子中,我们会用到公用属性元数据的方法。

要对一个输出到JMX的bean作标记,应该用属性 ManagedResource 来注解这个bean类。 在使用公用属性元数据方法的情况下,要能在包 org.springframework.jmx.metadata 中 找到它。 你希望作为操作输出的每个方法必须用属性 ManagedOperation 打上标记, 你希望输出的每个属性必须用属性ManagedAttribute打上标记。 在标记属性的时候,可以忽略getter的注解,或忽略分别创建一个只写或只读属性的setter。

下例展示了用公用属性元数据对前文见过的类JmxTestBean进行标记的情况:

package org.springframework.jmx;

/**
 * @@org.springframework.jmx.export.metadata.ManagedResource
 *  (description="My Managed Bean", objectName="spring:bean=test",
 *  log=true, logFile="jmx.log", currencyTimeLimit=15, persistPolicy="OnUpdate",
 *  persistPeriod=200, persistLocation="foo", persistName="bar")
 */
public class JmxTestBean implements IJmxTestBean {

  private String name;

  private int age;


  /**
   * @@org.springframework.jmx.export.metadata.ManagedAttribute
   *   (description="The Age Attribute", currencyTimeLimit=15)
   */
  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }

  /**
   * @@org.springframework.jmx.export.metadata.ManagedAttribute
   *  (description="The Name Attribute",  currencyTimeLimit=20,
   *   defaultValue="bar", persistPolicy="OnUpdate")
   */
  public void setName(String name) {
    this.name = name;
  }

  /**
   * @@org.springframework.jmx.export.metadata.ManagedAttribute
   *   (defaultValue="foo", persistPeriod=300)
   */
  public String getName() {
    return name;
  }

  /**
   * @@org.springframework.jmx.export.metadata.ManagedOperation
   *  (description="Add Two Numbers Together")
   */
  public int add(int x, int y) {
    return x + y;
  }

  public void dontExposeMe() {
    throw new RuntimeException();
  }
}

这里你可以看到,用属性 ManagedResource 来标记类 JmxTestBean, 这个 ManagedResource 是用一系列属性来配置的。 这些属性用于配置由 MBeanExporter 产生的MBean的不同方面。 在后续章节 Section 20.3.4, “源代码级的元数据类型” 中,将有更详细的说明。

你将会注意到属性 agename 是用属性 ManagedAttribute注解的, 但是在属性 age 这种情况下,只标记了getter。 这将使得这两个属性作为属性包括到管理接口中去,但属性 age 将是只读的。

最后,你会注意到方法 add(int, int) 是用 ManagedOperation 标记的, 而方法 dontExposeMe() 却不是。 这使得管理接口在使用 MetadataMBeanInfoAssembler 的时候只包含一个操作:add(int, int)

下列代码显示了如何用 MetadataMBeanInfoAssembler 配置 MBeanExporter

<beans>

  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="bean:name=testBean1" value-ref="testBean"/>
      </map>
    </property>
    <property name="assembler" ref="assembler"/>
  </bean>

  <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
  </bean>

  <bean id="attributeSource"
        class="org.springframework.jmx.export.metadata.AttributesJmxAttributeSource">
    <property name="attributes">
      <bean class="org.springframework.metadata.commons.CommonsAttributes"/>
    </property>
  </bean>

  <bean id="assembler" class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
    <property name="attributeSource" ref="attributeSource"/>
  </bean>

</beans>

这里你可以看到,用类 AttributesJmxAttributeSource 的一个实例来配置一个MetadataMBeanInfoAssembler bean,并通过装配属性将它传递给 MBeanExporter。如果Spring输出MBean是利用元数据驱动管理接口,则所有这些都是必需的。

20.3.3. 使用JDK 5.0注解

为了激活JDK5.0注解,用它来进行管理接口定义,Spring提供了一套相当于Commons Attribut属性类的注解和一个策略接口 JmxAttributeSource 的实现类 AnnotationsJmxAttributeSource, 这个类允许 MBeanInfoAssembler 来读这些注解。

下例是一个用JDK5.0注解定义管理接口的bean:

package org.springframework.jmx;

import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedAttribute;

@ManagedResource(objectName="bean:name=testBean4", description="My Managed Bean", log=true,
    logFile="jmx.log", currencyTimeLimit=15, persistPolicy="OnUpdate", persistPeriod=200,
    persistLocation="foo", persistName="bar")
public class AnnotationTestBean implements IJmxTestBean {

  private String name;
  private int age;

  @ManagedAttribute(description="The Age Attribute", currencyTimeLimit=15)
  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }

  @ManagedAttribute(description="The Name Attribute",
      currencyTimeLimit=20,
      defaultValue="bar",
      persistPolicy="OnUpdate")
  public void setName(String name) {
    this.name = name;
  }

  @ManagedAttribute(defaultValue="foo", persistPeriod=300)
  public String getName() {
    return name;
  }

  @ManagedOperation(description="Add two numbers")
  @ManagedOperationParameters({
    @ManagedOperationParameter(name = "x", description = "The first number"),
    @ManagedOperationParameter(name = "y", description = "The second number")})
  public int add(int x, int y) {
    return x + y;
  }

  public void dontExposeMe() {
    throw new RuntimeException();
  }
}

如你所见,跟元数据定义的基本语法相比,改变很少。这个方法在后台启动的时候稍微有点慢,因为JDK5.0注解被转成了Commons Attributes使用的类。 但是,这仅只是一次性的消耗,JDK5.0注解对编译期的检查更有好处。

与上述被注解的类有关的XML配置如下所示:

<beans>
    <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
        <property name="assembler" ref="assembler"/>
        <property name="namingStrategy" ref="namingStrategy"/>
        <property name="autodetect" value="true"/>
    </bean>

    <bean id="jmxAttributeSource"
          class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>

    <!-- will create management interface using annotation metadata -->
    <bean id="assembler"
          class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
        <property name="attributeSource" ref="jmxAttributeSource"/>
    </bean>

    <!-- will pick up ObjectName from annotation -->
    <bean id="namingStrategy"
          class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
        <property name="attributeSource" ref="jmxAttributeSource"/>
    </bean>

    <bean id="testBean" class="org.springframework.jmx.AnnotationTestBean">
        <property name="name" value="TEST"/>
        <property name="age" value="100"/>
    </bean>
</beans>

20.3.4. 源代码级的元数据类型

在Spring JMX中,可以使用下列源码级的元数据类型:

Table 20.2. 源代码级的元数据类型

目的Commons Attributes属性JDK 5.0 注解属性 / 注解类型
Class 所有的实例标记为由JMX管理的资源ManagedResource@ManagedResource
把方法标记为JMX的操作ManagedOperation@ManagedOperation方法
把getter或setter标记为JMX的半个属性ManagedAttribute@ManagedAttribute方法(仅 getters 和 setters)
定义描述操作参数ManagedOperationParameter@ManagedOperationParameter@ManagedOperationParameters@ManagedOperationParameter@ManagedOperationParameters方法

接下来的配置参数可以用于这些源码级的元数据类型:

Table 20.3. 源码级的元数据参数

参数描述适用于
ObjectName 由类 MetadataNamingStrategy 使用,决定一个管理资源的 ObjectNameManagedResource
description 设置资源、属性或操作友好的描述 ManagedResourceManagedAttributeManagedOperationManagedOperationParameter
currencyTimeLimit 描述符字段,用于设置 currencyTimeLimit 的值 ManagedResourceManagedAttribute
defaultValue 描述符字段,用于设置 defaultValue 的值 ManagedAttribute
log 描述符字段,用于设置 log 的值 ManagedResource
logFile 描述符字段,用于设置 logFile 的值 ManagedResource
persistPolicy 描述符字段,用于设置 persistPolicy 的值 ManagedResource
persistPeriod 描述符字段,用于设置 persistPeriod 的值 ManagedResource
persistLocation 描述符字段,用于设置 persistLocation 的值 ManagedResource
persistName 描述符字段,用于设置 persistName 的值 ManagedResource
name设置一个操作参数的显示名字ManagedOperationParameter
index设置操作参数的索引ManagedOperationParameter

20.3.5. 接口AutodetectCapableMBeanInfoAssembler

为了进一步简化配置,Srping引入了接口 AutodetectCapableMBeanInfoAssembler , 它扩展接口 MBeanInfoAssembler,增加了对自动检测MBean资源的支持。 如果你用 AutodetectCapableMBeanInfoAssembler 的一个实例来配置 MBeanExporter,则允许对将要暴露给JMX的所有bean进行“表决”。

即开既用,MetadataMBeanInfoAssembler 是接口 AutodetectCapableMBeanInfo 唯一的实现, 它“表决”将所有被属性 ManagedResource 标记过的bean包含在内。 这种情况下,缺省的方法是用bean的名字作为 ObjectName,在配置中结果如下:

<beans>

  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <!-- notice how no 'beans' are explicitly configured here -->
    <property name="autodetect" value="true"/>
    <property name="assembler" ref="assembler"/>
  </bean>

  <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
  </bean>

  <!-- (for Commons Attributes-based metadata) -->
  <bean id="attributeSource"
        class="org.springframework.jmx.export.metadata.AttributesJmxAttributeSource">
    <property name="attributes">
      <bean class="org.springframework.metadata.commons.CommonsAttributes"/>
    </property>
  </bean>
  
  <!-- (for Java5+ annotations-based metadata) -->
				
  <!--
  <bean id="attributeSource"
        class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>
  -->

  <bean id="assembler" class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
    <property name="attributeSource" ref="attributeSource"/>
  </bean>

</beans>

注意,在这个配置中,没有传给 MBeanExporter 任何bean;但是,JmxTestBean将仍被注册, 因为属性 ManagedResource 为它做了标记,并且 MetadataMBeanInfoAssembler 发现了这一点,“表决”包括了它。 这种方法唯一的问题是,JmxTestBean 的名字有商业含义。 要解决这个问题,你可以修改创建ObjectName 的缺省行为,按照 Section 20.4, “控制bean的 ObjectName ” 章节中所讲的那样去定义。

20.3.6. 用Java接口定义管理接口

除了 MetadataMBeanInfoAssembler,Spring还有 InterfaceBasedMBeanInfoAssembler, 它允许你在一系列方法的基础上约束将要输出的方法和属性,这一系列方法是由一组接口来定义的。

虽然输出MBeans的标准机制是使用接口和一个简单的命名策略,InterfaceBasedMBeanInfoAssembler 去掉了命名约定的需要而扩展了这一功能,允许你使用一个以上的接口,并且去掉了为了bean去实现MBean接口的需要。

考虑这个接口,用它为前面见过的类 JmxTestBean 定义一个管理接口:

public interface IJmxTestBean {

  public int add(int x, int y);

  public long myOperation();

  public int getAge();

  public void setAge(int age);

  public void setName(String name);

  public String getName();
}

这个接口定义了方法和属性,它们将作为JMX MBean的操作和属性输出。下面的代码展示了如何配置Spring JMX,用这个接口作为管理接口的定义:

<beans>

  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="bean:name=testBean5" value-ref="testBean"/>
      </map>
    </property>
    <property name="assembler">
      <bean class="org.springframework.jmx.export.assembler.InterfaceBasedMBeanInfoAssembler">
        <property name="managedInterfaces">
          <value>org.springframework.jmx.IJmxTestBean</value>
        </property>
      </bean>
    </property>
  </bean>

  <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
  </bean>

</beans>

你可以看到,在为任一bean构造管理接口时,InterfaceBasedMBeanInfoAssembler 被配成使用接口 IJmxTestBean。由 InterfaceBasedMBeanInfoAssembler 处理的bean是 需要实现那些用于生成JMX管理接口的接口的,明白这一点非常重要。

在上面的例子中,接口 IJmxTestBean 用于构造所有bean的所有管理接口。 许多情况下,并不想这样,你可能想对不同的bean用不同的接口。 这种情况下,你可以通过属性 interfaceMappings 传一个 Properties 的实例给 InterfaceBasedMBeanInfoAssembler, 在这里,每个实体的键都是bean的名字,每个实体的值就是用逗号隔开的用于那个bean的接口的名字列表。

如果既没有通过属性 managedInterfaces 又没有通过属性 interfaceMappings 指定管理接口,那么 InterfaceBasedMBeanInfoAssembler 将反射到bean上,使用所有被该bean实现的接口来创建管理接口。

20.3.7. 使用MethodNameBasedMBeanInfoAssembler

MethodNameBasedMBeanInfoAssembler 允许你指定一个将作为属性和操作输出到JMX的方法的名字列表。下面的代码展示了一个这种情况的配置样例:

<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="bean:name=testBean5" value-ref="testBean"/>
      </map>
    </property>
    <property name="assembler">
      <bean class="org.springframework.jmx.export.assembler.MethodNameBasedMBeanInfoAssembler">
        <property name="managedMethods">
          <value>add,myOperation,getName,setName,getAge</value>
        </property>
      </bean>
    </property>
</bean>

可以看到,方法 addmyOperation 将作为JMX的操作输出,getName()setName(String)getAge() 将作为JMX的半个属性输出。 在上面的代码中,方法映射用于那些输出给JMX的bean。 要在一个bean接一个bean的基础上控制方法的暴露,使用 MethodNameMBeanInfoAssembler 的属性 methodMappings 把bean的名字映射到方法名字的列表上。

20.4. 控制bean的 ObjectName

在后台,MBeanExporter 委派 ObjectNamingStrategy 的一个实现去获取正在注册的每个bean的ObjectName。 缺省的实现是 KeyNamingStrategy,它缺省用 beans Map 的键作为 ObjectName。 此外,KeyNamingStrategy 能把beans Map 的键映射为一个 Properties 文件中的实体,以此来决定 ObjectName。 除了 KeyNamingStrategy 之外,Spring提供了另外两个 ObjectNamingStrategy 的实现: IdentityNamingStrategy 构造一个 ObjectName, 这是基于JVM识别的bean;MetadataNamingStrategy 是用源代码级元数据获取 ObjectName

20.4.1. 从Properties中读取ObjectName

可以配置你自己 KeyNamingStrategy 实例,配置它从一个 Properties 的实例中读取 ObjectName,而不是用bean的键去读。 KeyNamingStrategy 会试着用与bean键相应的键在 Properties 中查找一个实体。 如果没有发现任何实体或是 Properties 实例为 null,就用这个bean的键。

下面代码展示了一个 KeyNamingStrategy 配置的例子:

<beans>

  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="testBean" value-ref="testBean"/>
      </map>
    </property>
    <property name="namingStrategy" ref="namingStrategy"/>
  </bean>

  <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
  </bean>

  <bean id="namingStrategy" class="org.springframework.jmx.export.naming.KeyNamingStrategy">
    <property name="mappings">
      <props>
        <prop key="testBean">bean:name=testBean1</prop>
      </props>
    </property>
    <property name="mappingLocations">
      <value>names1.properties,names2.properties</value>
    </property>
  </bean

</beans>

用一个 Properties 的实例来配置一个 KeyNamingStrategy 的实例, 这个 Properties 的实例是由映射属性定义的 Properties 实例和由映射属性定义的路径中的属性文件的内容合并起来的。 这个配置中,给bean testBeanObjectName 值为 bean:name=testBean1 , 因为这个实体在 Properties 的实例中,这个实例有一个与bean的键相对应的键。

如果在 Properties 实例中没有找到实体,则bean的键名将用作 ObjectName 的值。

20.4.2. 使用 MetadataNamingStrategy

MetadataNamingStrategy 使用每个bean属性 ManagedResourceObjectName 属性来创建 ObjectName。 下列代码展示了 MetadataNamingStrategy 的配置:

<beans>

  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="testBean" value-ref="testBean"/>
      </map>
    </property>
    <property name="namingStrategy" ref="namingStrategy"/>
  </bean>

  <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
  </bean>

  <bean id="namingStrategy" class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
    <property name="attributeSource" ref="attributeSource"/>
  </bean>

  <bean id="attributeSource"
      class="org.springframework.jmx.export.metadata.AttributesJmxAttributeSource"/>

</beans>

20.5. 用JSR-160连接器输出bean

对远程访问,Spring JMX模块在包 org.springframework.jmx.support 中提供了两种 FactoryBean 实现来创建服务器端和客户端连接器。

20.5.1. 服务器端连接器

要创建一个Spring JMX,启动暴露一个JSR-60 JMXConnectorServer,用下面的配置:

<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean"/>

缺省情况下,ConnectorServerFactoryBean 创建一个与 "service:jmx:jmxmp://localhost:9875" 绑定的 JMXConnectorServer。 bean serverConnector 因此通过本机端口为9875上的JMXMP协议把本地的 MBeanServer暴露给客户端。 注意在JSR 160规范中,JMXMP是标记为可选的: 当前,主要的开源JMX实现,MX4J和由J2SE5.0提供的那个都 支持JMXMP。

要指定其他的URL并用 MBeanServer 来注册 JMXConnectorServer, 分别用 serviceUrlObjectName 属性:

<bean id="serverConnector"
      class="org.springframework.jmx.support.ConnectorServerFactoryBean">
  <property name="objectName" value="connector:name=rmi"/>
  <property name="serviceUrl" 
            value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/myconnector"/>
</bean>

如果设置属性 ObjectName,Spring会在这个 ObjectName 下用MBeanServer自动注册连接器。 下面的例子展示了一整套参数,在创建一个JMXConnector时,你可以把它们传递给 ConnectorServerFactoryBean

<bean id="serverConnector"
      class="org.springframework.jmx.support.ConnectorServerFactoryBean">
  <property name="objectName" value="connector:name=iiop"/>
  <property name="serviceUrl" 
               value="service:jmx:iiop://localhost/jndi/iiop://localhost:900/myconnector"/>
  <property name="threaded" value="true"/>
  <property name="daemon" value="true"/>
  <property name="environment">
    <map>
      <entry key="someKey" value="someValue"/>
    </map>
  </property>
</bean>

注意,在使用基于RMI的连接器时,你需要启动查找服务(tnameserv或rmiregistry)来完成名字注册。 如果你用Spring通过RMI输出远程服务,那么Spring将已经创建了一个RMI注册。 如果没有,你可以用下面的配置很容易就启动一个注册:

<bean id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean">
  <property name="port" value="1099"/>
</bean>

20.5.2. 客户端连接器

要创建一个 MBeanServerConnection 到远程, JSR-160用 MBeanServerConnectionFactoryBean 激活了 MBeanServer , 如下面所示:

<bean id="clientConnector" class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
  <property name="serviceUrl" value="service:jmx:rmi://localhost:9875"/>
</bean>

20.5.3. 基于Burlap/Hessian/SOAP的JMX

JSR-160允许扩展客户端和服务器端之间的通信方式。上面的例子使用了强制的基于RMI的实现和JRMP(可选的), 这是由JSR-160规范(IIOP和JRMP)所要求的。通过使用其他的提供商或JMX实现(如 MX4J), 你可以从基于简单HTTP或SSL的诸如SOAP、Hessian、Burlap协议中获益。

<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean">
  <property name="objectName" value="connector:name=burlap"/>
  <property name="serviceUrl" value="service:jmx:burlap://localhost:9874"/>
</bean>

在上面的例子中,使用了MX4J3.0.0,更多的信息参见官方的MX4J文档。

20.6. 通过代理访问MBeans

Spring JMX允许你创建代理,这个代理改变到注册到本地或远程 MBeanServer 的MBean的调用。 这些代理提供里一个标准的Java接口,通过它,你可以和MBean相合。 下面的代码展示了如何为一个运行在本地 MBeanServer 的配置一个代理:

bean id="proxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean">
    <property name="objectName" value="bean:name=testBean"/>
    <property name="proxyInterface" value="org.springframework.jmx.IJmxTestBean"/>
</bean>

可以看到在 ObjectNamebean:name=testBean 下为注册的MBean创建了一个代理。 代理要实现的接口由属性 proxyInterfaces 和将这些接口上的方法和属性映射到MBean的操作和属性上的规则来控制,这些规则和 InterfaceBasedMBeanInfoAssembler 使用的规则是一样的。

MBeanProxyFactoryBean 能创建一个到任何MBean的代理,可以通过一个 MBeanServerConnection来访问。 缺省情况下,查找和使用本地 MBeanServer,但是你可以重写(override)它, 提供一个指向远程MBeanServerMBeanServerConnection到指向远程MBean的代理。

<bean id="clientConnector"
      class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
  <property name="serviceUrl" value="service:jmx:rmi://remotehost:9875"/>
</bean>

<bean id="proxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean">
  <property name="objectName" value="bean:name=testBean"/>
  <property name="proxyInterface" value="org.springframework.jmx.IJmxTestBean"/>
  <property name="server" ref="clientConnector"/>
</bean>

你可以看到,我们创建了一个 MBeanServerConnection,它指向一个远程的, 使用了 MBeanServerConnectionFactoryBean 的机器。然后通过属性 server 将这个 MBeanServerConnection 传给 MBeanProxyFactoryBean 。 创建的代理将通过这个 MBeanServerConnection 转发所有到 MBeanServer的调用。

20.7. 通知

Spring的JMX提供的内容包括了对JMX通知的全面的支持。

20.7.1. 为通知注册监听器

Spring的JMX支持使得用任意数量的MBean(这包括由Spring的 MBeanExporter 输出的MBean和通过其他机制注册的MBean)注册任意数量的 NotificationListeners非常容易。 例子最能阐明影响 NotificationListeners的注册有多么简单。 考虑一个场景,任何时候一个目标MBean的属性改变了,每个都会得到通知(通过一个Notification)。

package com.example;

import javax.management.AttributeChangeNotification;
import javax.management.Notification;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;

public class ConsoleLoggingNotificationListener
               implements NotificationListener, NotificationFilter {

    public void handleNotification(Notification notification, Object handback) {
        System.out.println(notification);
        System.out.println(handback);
    }

    public boolean isNotificationEnabled(Notification notification) {
        return AttributeChangeNotification.class.isAssignableFrom(notification.getClass());
    }
}
<beans>

  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="bean:name=testBean1" value-ref="testBean"/>
      </map>
    </property>
    <property name="notificationListenerMappings">
        <map>
            <entry key="bean:name=testBean1">
                <bean class="com.example.ConsoleLoggingNotificationListener"/>
            </entry>
        </map>
    </property>
  </bean>

  <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
  </bean>

</beans>

有了上面的配置,每次来自目标MBean(bean:name=testBean1)的一个JMX的 Notification 都会被广播, 通过属性 notificationListenerMappings 注册的作为监听器的 ConsoleLoggingNotificationListener bean将得到通知。 然后 ConsoleLoggingNotificationListener bean可以采取任何它认为合适的行动来响应这个 Notification

如果想给所有的正在输出的已经装入MBeanExporter的bean注册单个 NotificationListener实例,可以用特殊的通配符'*'(没有引号) 作为属性映射notificationListenerMappings的一个实体的键,看这段代码……

    <property name="notificationListenerMappings">
        <map>
            <entry key="*">
                <bean class="com.example.ConsoleLoggingNotificationListener"/>
            </entry>
        </map>
    </property>

如果想与上面相反(即,为一个MBean注册多个不同的监听器),那么就必须使用 notificationListeners 这个列表属性来代替(优先于属性notificationListenerMappings)。 这次,要配置多个 NotificationListenerBean 实例,而不是简单的为单个MBean配置一个 NotificationListener……一个 NotificationListenerBean 封装了一个 NotificationListenerObjectName(或ObjectNames) 这样,它就在一个 MBeanServer 里进行注册。 NotificationListenerBean 也封装了许多其他的属性, 如NotificationFilter,可以用于高级JMX通知场景的任意用于的回传对象等。

使用 NotificationListenerBean 实例的时的配置跟之前的配置并没有很大的不同:

<beans>

  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="bean:name=testBean1" value-ref="testBean"/>
      </map>
    </property>
    <property name="notificationListeners">
        <list>
            <bean class="org.springframework.jmx.export.NotificationListenerBean">
                <constructor-arg>
                    <bean class="com.example.ConsoleLoggingNotificationListener"/>
                </constructor-arg>
                <property name="mappedObjectNames">
                    <list>
                        <bean class="javax.management.ObjectName">
                            <constructor-arg value="bean:name=testBean1"/>
                        </bean>
                    </list>
                </property>
            </bean>
        </list>
    </property>
  </bean>

  <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
  </bean>

</beans>

上面的例子跟第一个通知的例子差不多。假设每次产生一个 Notification 我们都想得到一个回传对象(handback object), 此外,我们想通过提供一个 NotificationFilter 来过滤掉无关的Notifications。 (要全面的讨论什么是一个回传对象,NotificationFilter 实际上是什么,请参考JMX规范(1.2)相应的章节 'The JMX Notification Model'。)

<beans>

  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
      <map>
        <entry key="bean:name=testBean1" value-ref="testBean1"/>
        <entry key="bean:name=testBean2" value-ref="testBean2"/>
      </map>
    </property>
    <property name="notificationListeners">
        <list>
            <bean class="org.springframework.jmx.export.NotificationListenerBean">
                <constructor-arg ref="customerNotificationListener"/>
                <property name="mappedObjectNames">
                    <list>
                        <!-- let's handle notifications from two distinct MBeans -->
                        <bean class="javax.management.ObjectName">
                            <constructor-arg value="bean:name=testBean1"/>
                        </bean>
                        <bean class="javax.management.ObjectName">
                            <constructor-arg value="bean:name=testBean2"/>
                        </bean>
                    </list>
                </property>
                <property name="handback">
                    <bean class="java.lang.String">
                        <constructor-arg value="This could be anything..."/>
                    </bean>
                </property>
                <property name="notificationFilter" ref="customerNotificationListener"/>
            </bean>
        </list>
    </property>
  </bean>
  
  <!-- implements both the 'NotificationListener' and 'NotificationFilter' interfaces -->
  <bean id="customerNotificationListener" class="com.example.ConsoleLoggingNotificationListener"/>

  <bean id="testBean1" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
  </bean>

  <bean id="testBean2" class="org.springframework.jmx.JmxTestBean">
    <property name="name" value="ANOTHER TEST"/>
    <property name="age" value="200"/>
  </bean>

</beans>

20.7.2. 发布通知

Spring不只提供接受 Notifications 的注册,还提供发布 Notifications

[Note]Note

请注意这一章只是与Spring的那些通过 MBeanExporter 所谓MBean输出的管理bean有关系;任何现存的,用户定义的MBean必须使用标准的JMX API来发布通知。

在Spring的JMX通知发布支持中,关键的是 NotificationPublisher 接口 (在包 org.springframework.jmx.export.notification 中定义的)。 任一将要通过 MBeanExporter 作为MBean输出的bean实例都可以实现对应的 NotificationPublisherAware接口来获取对接口 NotificationPublisher 的访问。 NotificationPublisherAware 接口只提供了一个 NotificationPublisher 的实例通过简单的setter方法实现bean,这样bean就可以用于发布 Notifications

如同在Javadoc中对类 NotificationPublisher 的描述,通过 NotificationPublisher 机制发布事件的管理bean 负责 任何通知监听器以及诸如此类……的状态管理。Spring的JMX支持谨慎的处理所有的JMX架构问题。应用程序开发者所需要的 就是实现接口 NotificationPublisherAware,用提供的接口 NotificationPublisher 开始发布事件。 注意,在已经用一个 MBeanServer 将管理bean注册之后, 再设置NotificationPublisher

NotificationPublisher 的实例非常简单的…… 仅仅创建一个 Notification 实例 (或一个适当的 Notification 子类的实例),带有与事件相关联数据的通知将被发布, 然后在 NotificationPublisher 实例上调用sendNotification(Notification), 在Notification中传递它。

让我们来看一个简单的例子……在下面的场景里,每当操作 add(int, int) 被调用时, 输出的 JmxTestBean 实例将发布一个 NotificationEvent

package org.springframework.jmx;
			
import org.springframework.jmx.export.notification.NotificationPublisherAware;
import org.springframework.jmx.export.notification.NotificationPublisher;
import javax.management.Notification;

public class JmxTestBean implements IJmxTestBean, NotificationPublisherAware {

    private String name;
    private int age;
    private boolean isSuperman;
    private NotificationPublisher publisher;

    // other getters and setters omitted for clarity

    public int add(int x, int y) {
        int answer = x + y;
        this.publisher.sendNotification(new Notification("add", this, 0));
        return answer;
    }

    public void dontExposeMe() {
        throw new RuntimeException();
    }
    
    public void setNotificationPublisher(NotificationPublisher notificationPublisher) {
        this.publisher = notificationPublisher;
    }
}

Spring的JMX提供的接口 NotificationPublisher 和让其工作的辅助装置是一个非常好的特性。 的确,伴随而来的是你的类与Spring和JMX的耦合开销,跟平时一样,这里的建议也是比较实际的, 如果你需要 NotificationPublisher 提供的功能,并且你可以接受与Spring和JMX的耦合,那么就去做。

20.8. 更多资源

这部分包含了更多的关于JMX的资源链接:

Sponsored by