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();
    }
}

要将一个Bean中的属性和方法暴露成为一个JMX MBean中的属性和操作,你只要在配置文件中简单的配置 MBeanExporter 一个实例,并且按照如下方法将这个Bean传入:

<beans>
  
  <!-- 如果要暴露,这个Bean一定  可以延迟初始化。-->
  
  
  <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>

上述配置片段中,关系最大的是 exporter Bean。beans 属性使得 MBeanExporter 知道要将哪个Bean输出到JMX的 MBeanServer 上去。 缺省配置中,beans 里的 Map 中的条目的key被用作相应条目值所引用的Bean的 ObjectName。 可以按照 Section 20.4, “控制Bean的ObjectName” 描述的那样改变这种行为。

在这个配置里,testBean 就以 bean:name=testBean1 这个 ObjectName 暴露成了一个MBean。 默认情况下,Bean中所有 public 的属性被暴露为属性,所有的 public 方法(除了那些从 Object 类继承过来的之外)都被暴露为操作。

20.2.1. 创建MBeanServer

上述配置是假设了应用程序运行在这样的环境中,一个有且仅有一个在运作中的 MBeanServer 的环境。 这种情况下,Spring将试图定位该 MBeanServer,之后将你的Bean(如果有的话)注册到该服务器上。 在自带 MBeanServer 的容器(例如Tomcat或者IBM WebSphere)中,这种行为是很有用的。

然而,在一个孤立的环境,或者不提供 MBeanServer 的容器中,这种方法毫无用武之地。 要处理这类问题,你应该创建一个 MBeanServer 实例,也就是声明式地将 org.springframework.jmx.support.MBeanServerFactoryBean 实例添加到你的配置里。 通过设置 MBeanExporterserver 属性的值, 你也可以确保 MBeanExporter 使用了 MBeanServerFactoryBean 返回的特定的 MBeanServer。 例如:

<beans>

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

  
  <!--
    要触发输出,必须预先实例化这个Bean,一定  可以标志为延迟初始化。
  -->
  
  
  
  <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。为了使之正确工作,当然了,你必须确保你的类路径上存在一个JMX实现。

20.2.2. 重用原有的MBeanServer

如果不指定任何服务器,MBeanExporter 将自动检测一个运行中的 MBeanServer。 这在只有一个 MBeanServer 的情况下可以奏效,当存在多个 MBeanServer的时候, MBeanExporter 可能会选错服务器。这种情况下,应该使用 MBeanServeragentId来指定究竟使用哪个服务器。

<beans>
   <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean">
     
     <!-- 说明首先要查找一个服务器 -->
     
     <property name="locateExistingServerIfPossible" value="true"/>
     
     <!-- 根据给定的agentId查找对应的 MBeanServer 实例 -->
     
     <property name="agentId" value="<MBeanServer instance agentId>"/>
   </bean>
   
   <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
     <property name="server" ref="mbeanServer"/>
   ...
   </bean>
</beans>

在某些平台中,MBeanServer 有一个动态(或者未知)的要通过lookup方法获取的 agentId。 这时就应该用 factory-method

<beans>
   <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
     <property name="server">
       
       
       
       <!-- 自定义MBeanServerLocator -->
         
         
         
       <bean class="platform.package.MBeanServerLocator" factory-method="locateMBeanServer"/>
     </property>
     
     <!-- 其他Bean -->
     
   </bean>
</beans>

20.2.3. 延迟初始化的MBean

如果你在 MBeanExporter 上配置了一个Bean,而这个 MBeanExporter 又配置了延迟初始化,那么 MBeanExporter 遵循这种契约,避免初始化这个Bean。相反,它会在 MBeanServer 上注册一个代理,推延从容器获取这个Bean的时刻,直到在代理端发生对它的第一次调用。

20.2.4. MBean的自动注册

要通过 MBeanExporter 输出的任意的Bean,并已是有效的MBean,将会被注册到 MBeanServer上去,而无须Spring的干预。 通过设置属性 autodetect 的值为true,MBeanExporter 将会自动探测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就已经是一个有效的JMX MBean了。它将会被Spring自动注册。 缺省情况下,那些做自动JMX注册的Bean的 ObjectName 就是它的Bean名称。 标题为 Section 20.4, “控制Bean的ObjectName” 的章节里详细的描述了如何覆盖(overridden)这种行为。

20.2.5. 控制注册行为

考虑这样的场景,一个Spring MBeanExporter 试图用 ObjectName 'bean:name=testBean1'MBeanServer 注册一个MBean。 如果已经存在一个同样 ObjectNameMBean 实例,缺省行为是失败(并且抛出一个 InstanceAlreadyExistsException)。

MBean 时发生哪种行为。 Spring的JMX支持提供三种不同的注册行为,以此来控制注册进程发现一个 MBean 已经用同样的 ObjectName 注册的情况。下面的表格总结了这些注册行为:

Table 20.1. 注册行为

注册行为说明

REGISTRATION_FAIL_ON_EXISTING

这是缺省的注册行为。如果一个 MBean 实例已经用同样的 ObjectName 注册了,Spring将不会再次注册一个 MBean 实例,并且会抛出一个 InstanceAlreadyExistsException 异常,这不会影响到原有的 MBean实例。

REGISTRATION_IGNORE_EXISTING

如果一个 MBean 实例已经用同样的 ObjectName 注册了,正在注册的 MBean 实例将不会被注册。这种情况下,原有的 MBean 实例不会受到影响,也不会抛出异常。

REGISTRATION_REPLACE_EXISTING

如果一个 MBean 实例已经用同样的 ObjectName 注册了,原有 MBean 的注册关系将会被解除,新的 MBean 会以同样的 ObjectName 注册(新的 MBean 实际上取代了原始的实例)。

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 在后台委托给了负责定义bean管理接口的 org.springframework.jmx.export.assembler.MBeanInfoAssembler 的一个实现来管理暴露Bean的信息。缺省实现是 org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler,它仅定义了一个暴露所有public属性,public方法的管理接口(像你在前面例子看到的那样)。 Spring还提供了两个 MBeanInfoAssembler 接口的实现, 使你可以使用源码级元数据或者其他任意接口来控制管理接口的产生。

20.3.2. 使用源码级元数据

使用 MetadataMBeanInfoAssembler,你能够用源码级元数据给你的Bean定义管理接口。 org.springframework.jmx.export.metadata.JmxAttributeSource 封装了元数据的读取。 Sprin的JMX支持提供了这个接口的两个现成的实现。对应Commons Attributes是 org.springframework.jmx.export.metadata.AttributesJmxAttributeSource, 而对应于JDK 5.0注解的是 org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource。 为了使 MetadataMBeanInfoAssembler 正确运作,(由于没有缺省的配置实例)。 我们必须给它配置一个 JmxAttributeSource 接口的实例。下面例子中,我们将采取Commons Attributes的方法。

要标识一个Bean输出到JMX,你应该用 ManagedResource 属性注解那个Bean的类。 使用Commons Attributes元数据的这种方法的情况下,我们可以在 org.springframework.jmx.metadata 包里找到它。 每个你想要暴露为操作的方法,每个你想要暴露的属性,你都要将用 ManagedOperation 属性标识它。 当标识属性的时,为了产生只写或者只读的属性,你可以忽略注解对应的getter或者setter方法。

下面例子中,我们用Commons Attributes元数据标识了在前面例子看到的 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();
  }
}

你看到 JmxTestBeanManagedResource 属性标识了,且 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>

这里你看到了 MetadataMBeanInfoAssembler Bean被配置了一个 AttributesJmxAttributeSource 类的实例,并且通过assembler属性将之注入到 MBeanExporter 当中。为通过Spring暴露的MBean得到元数据驱动的管理接口的好处,要做的事情就是这么多了。

20.3.3. 使用JDK 5.0的注解

为了激活JDK 5.0的注解,用它来管理接口定义,Spring提供了一套相当于Commons Attribute注解和一个策略接口 JmxAttributeSource 的实现类 AnnotationsJmxAttributeSource,这个类可以让 MBeanInfoAssembler 读取这些注解。

下例用JDK 5.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();
  }
}

看到了吧,除了元数据定义的基本语法外,改动很小。这种方法在启动时相对慢些, 因为JDK 5.0的注解要转换为Commons Attributes使用的类。尽管如此,这也只是 一次性的开销,JDK 5.0注解给了编译时检查的额外好处。

<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"/>

    
    <!-- 这会使用注解元数据创建管理接口 -->
    <bean id="assembler"
          class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
        <property name="attributeSource" ref="jmxAttributeSource"/>
    </bean>

    
    
    <!-- 从注解中得到ObjectName  -->
    <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注解属性/注解类型
将类的所有实例标识为JMX受控资源ManagedResource@ManagedResourceClass 类
将方法标识为JMX操作ManagedOperation@ManagedOperationMethod方法
将getter或者setter标识为部分JMX属性ManagedAttribute@ManagedAttributeMethod (only getters and setters) 方法(仅getters和setters)
定义操作参数说明ManagedOperationParameter@ManagedOperationParameter@ManagedOperationParametersMethod 方法

源代码级的元数据类型可以使用以下配置参数:

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

参数描述应用于
ObjectNameMetadataNamingStrategy 用其来决定一个受控资源的 ObjectNameManagedResource
说明设置资源,属性,操作的说明ManagedResourceManagedAttributeManagedOperationManagedOperationParameter
currencyTimeLimit设置 currencyTimeLimit 描述符字段的值 currencyTimeLimitManagedResourceManagedAttribute
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接口

为了进一步简化配置,Spring引入了 AutodetectCapableMBeanInfoAssembler 接口, 它扩展了 MBeanInfoAssembler 接口,增加了自动探测MBean资源的支持。 如果你用一个 AutodetectCapableMBeanInfoAssembler 实例配置了 MBeanExporter,那么它就可以对要暴露给JMX的所有Bean的进行表决。

AutodetectCapableMBeanInfo 现成的唯一实现是 MetadataMBeanInfoAssembler,它“表决”将所有标识了 ManagedResource 属性的Bean包含在内。 这种情况的缺省方法是用bean的名称作为 ObjectName,这样,我们就得到了这样一份配置:

<beans>

  <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    
    
    <!-- 这里,注意怎样才不显示配置 'beans' -->
    <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>

  <!-- (对于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>
  
  <!-- (对于基于注解的JDK5+元数据) -->
  <!--
  <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, 然而,由于 JmxTestBeanManagedResource 属性做了标识, MetadataMBeanInfoAssembler 探测到且表决将要包含它,因此它仍会被注册。 这种方法的唯一问题是,现在的 JmxTestBean 有了业务含义。 这个问题可以通过 Section 20.4, “控制Bean的ObjectName” 定义那样改变 ObjectName 创建的默认行为来解决。

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

除了 MetadataMBeanInfoAssembler,Spring还包含了 InterfaceBasedMBeanInfoAssembler,它可以根据一组接口定义的方法限定要暴露的方法和属性。

虽然使用接口和简单的命名规则是暴露MBean的标准方式,但是 InterfaceBasedMBeanInfoAssembler 扩展这个功能,去除了命名约定,使得你不但可以使用多个接口,还可以省去实现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使用不同接口时就不想这样了。 这时,你可以给 InterfaceBasedMBeanInfoAssembler 通过 interfaceMappings 属性传递一个 Properties实例。每个条目的键值是Bean的名字,条目的值一个以逗号(;)隔开,使用于该Bean的的接口名称列表。

如果没有通过 managedInterfacesinterfaceMappings 属性指定管理接口,InterfaceBasedMBeanInfoAssembler 将通过反射,使用该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的方法暴露,就要使用 MethodNameMBeanInfoAssemblermethodMappings 属性来将Bean名字映射到方法名称列表上了。

20.4. 控制Bean的ObjectName

对于每个Bean的注册,MBeanExporter 在后台委派给了 ObjectNamingStrategy 的一个实现来获取 ObjectName。 缺省实现是 KeyNamingStrategy,它将默认使用 beansMap 的键值作为 ObjectName。 此外,KeyNamingStrategy 可以将该键值与 Properties 文件中的一个条目对应,以此解析 ObjectName。 除了 KeyNamingStrategy 之外,Spring还提供了另外两个 ObjectNamingStrategy 的实现: 基于bean的JVM标识构建 ObjectNameIdentityNamingStrategy; 利用源代码级元数据获取 ObjectNameMetadataNamingStrategy

20.4.1. 从Properties读取Properties

你可以配置你自己的 KeyNamingStrategy 实例,配置它从一个 Properties 实例读取 ObjectName,而不是用Bean的键值。 KeyNamingStrategy 首先会用bean的键值试图从 Properties 中定位一个条目。 假若没有找不到对应的条目,或者 Properties 实例为 null,最后才使用Bean的键值作为 ObjectName

以下代码显示了关于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>

这个 KeyNamingStrategy 实例配置了一个 Properties 实例,而这个 Properties 实例由mapping属性和mappingLocations属性所指定的路径定位到的 Properties 文件合成。 在这个配置里,由于 Properties 实例有个键值对应该Bean的键值,因此 testBeanObjectName 将被指定为 bean:name=testBean1

假若没有任何条目可以从 Properties 实例找到,Bean的键值将会作为 ObjectName

20.4.2. 使用MetadataNamingStrategy

MetadataNamingStrategy 用每个Bean上 ManagedResource 属性的 objectName 属性来构建 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>

假若没有 objectNameManagedResource里提供,那么一个 ObjectName 将以以下格式创建: [fully-qualified-package-name]:type=[short-classname],name=[bean-name]。 例如,对于以下的Bean,产生的 ObjectName 将是 com.foo:type=MyClass,name=myBean

<bean id="myBean" class="com.foo.MyClass"/>

20.4.3. <context:mbean-export/>元素

如果你至少在使用Java 5,那么就可以使用一个方便的 MBeanExporter 子类, AnnotationMBeanExporter。 当定义一个这个子类的实例时,由于他将始终使用基于注解的标准Java元数据(自动探测也将始终被激活),namingStrategyattributeSource 的配置也不再需要了。

<context:mbean-export/>

必要时,你可以提供某个MBean服务器的引用,同时 defaultDomain 属性 (AnnotationMBeanExporter的一个属性)接受产生的MBean ObjectNames域名的替代值。 这被用于替代前面章节 MetadataNamingStrategy 提及的完整限定包名。

<context:mbean-export server="myMBeanServer" default-domain="myDomain"/>
.

20.5. JSR-160连接器

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

20.5.1. 服务器端连接器

使Spring JMX构建,启动和暴露一个JSR-160 JMXConnectorServer,要使用以下配置:

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

ConnectorServerFactoryBean 默认创建一个 JMXConnectorServer, 并将其绑定到 "service:jmx:jmxmp://localhost:9875"。 因此该 serverConnector Bean在本机9875端口通过JMXMP协议将本地 MBeanServer 暴露给客户。 要留意JSR160规范已将JMXMP协议标记为可选的,如今,主流的开源JMX实现MX4J和J2SE 5.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 上。 以下例子展示了当创建一个JMX连接器时,你可以传给 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注册服务。如果没有使用Spring,通过使用以下配置片段,你也可以很容易地启动一个注册服务。

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

20.5.2. 客户端连接器

要构建一个 MBeanServerConnection 到一个远程的JSR-160 MBeanServer, 使用以下所示的 MBeanServerConnectionFactoryBean

<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的实现,这是JSR-160规范(IIOP和JRMP)和(可选的JMXMP)要求的。 通过使用其他提供商的或者其他的JMX实现(例如 MX4J),你就可以享受其他协议, 如SOAP,Hessian,基于HTTP或SSL的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>

上述例子中,使用了MX4J 3.0.0,更多的信息请查看MX4J官方文档。

20.6. 通过代理访问MBean

Spring JMX允许你创建代理,将调用重新路由到本地或者远程 MBeanServer 上注册的MBean。 这些代理提供了一个标准Java接口,你可以通过它与MBean交互。以下代码展示了如何为本地 MBeanServer 配置一个MBean代理:

<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>

你可以看到已经为以 ObjectName:bean:name=testBean 注册的MBean创建了一个代理, 该代理实现的所有接口都由 proxyInterfaces 和映射规则掌控。 也就是将这些接口上方法,属性映射为MBean上操作与属性的规则,它们与 InterfaceBasedMBeanInfoAssembler 使用的是同一套规则。

MBeanProxyFactoryBean 能创建一个任何MBean的代理,可以通过 MBeanServerConnection 来访问。 默认将定位,使用本地的 MBeanServer。但是,为了适应指向远程MBean的代理,你可以重载这种行为,提供一个指向远程 MBeanServerMBeanServerConnection

<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>

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

20.7. 通知

Spring提供的JMX对JMX通知包含了全面的支持。

20.7.1. 为通知注册监听器

Spring的JMX支持使得用任意数量MBean注册任意数量的 NotificationListeners 监听器(包括由Spring的 MBeanExporter 输出和其他机制注册的MBean)都非常容易。 通过例子,考虑当目标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 都能得到通知。 ConsoleLoggingNotificationListener 就可以采取任何它认为合适的行为来响应 Notification

你也可以直接使用Bean名作为输出的Bean和监听器直接的链接。

<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="testBean">
          <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>

如果有人想为所有通过 MBeanExporter 输出的Bean注册单个 NotificationListener实例,可以使用通配符'*'(没有引号)作为 notificationListenerMappings 属性映射中一个实体的键值;如下:

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

如果想做相反的事情(也就是,为一个MBean注册多个不同的监听器),那么他就要使用 notificationListeners 列表属性来替代(优先于 notificationListenerMappings 属性)。 这时就要配置多个 NotificationListenerBean 实例,而不仅仅是一个了…… 一个 NotificationListenerBean 不但封装了一个或者多个 NotificationListener 和已注册到一个 MBeanServerObjectName,它也封装了许多其他属性,例如一个 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>
                        <value>bean:name=testBean1</value>
                    </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 发生时,我们想得到一个回传对象, 且想通过提供一个 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>
                        <!-- handles notifications from two distinct MBeans -->
                        <value>bean:name=testBean1</value>
                        <value>bean:name=testBean2</value>
                    </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>
  
  
  <!-- 实现了NotificationListenerNotificationFilter接口 -->
  
  <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不但提供了注册接收通知的支持,也提供了对发布通知的支持。

[Note]Note

要注意的是,本章节仅仅与通过 MBeanExporter 暴露的,被Spring管理的Bean相关。 任何现存的,用户定义的MBean应当使用标准JMX API来做通知发布。

Spring的JMX通知发布支持中的关键接口是 NotificationPublisher(定义于 org.springframework.jmx.export.notification 包中)。 任意要通过 MBeanExporter 实例输出为MBean的Bean都可以实现 NotificationPublisherAware 接口来获得对 NotificationPublisher 实例的访问。 NotificationPublisherAware 仅仅提供通过一个简单的setter方法给实现了这个接口的Bean注入一个 NotificationPublisher 实例,那些Bean就因此可以发布 Notification 了。

就如 NotificationPublisher 类的Javadoc描述的一样,通过 NotificationPublisher 机制发布事件的受控Bean是 需要对任何通知的监听器或者其他诸如此类的监听器的状态管理负责的。Spring的JMX支持将处理与JMX架构相关的所有问题。 作为一个应用程序开发者,他所需要做的只是实现 NotificationPublisherAware 接口,然后利用注入的 NotificationPublisher 实例发布事件。要注意,受控Bean注册到一个 MBeanServer 后,NotificationPublisher 才被设置。

使用 NotificationPublisher 实例的方法很直观,人们只要构建一个 Notification 实例(或者一个合适的 Notification 子类的实例),接着填充与将要发布的事件相关的数据到通知里,然后传入 Notification,调用NotificationPublisher实例的方法 sendNotification(Notification) 就可以了。

让我们来看一个简单的例子,在这个场景里,输出 JmxTestBean 实例在每次 add(int, int) 操作调用时都会发布 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;

    // 清晰起见,忽略了其他getter和setter

    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;
    }
}

NotificationPublisher 接口和一套使之运作的机制是Spring JMX支持的优良特性之一。 它带来的代价确实是使你的类与Spring,JMX紧耦合了;与以往一样,我们的建议也是很实际的……如果你需要 NotificationPublisher 提供的功能,并且接受与Spring,JMX的紧耦合,那么就行动吧。

20.8. 更多资源

本章节包含了更多关于JMX的资源的链接。