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的不同方面。 在后续章节 第 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中,可以使用下列源码级的元数据类型:

表 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 方法


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

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

参数 描述 适用于
ObjectName 由类 MetadataNamingStrategy 使用,决定一个管理资源的 ObjectName ManagedResource
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 的缺省行为,按照 第 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的名字映射到方法名字的列表上。