20.3. 控制Bean的管理接口

Spring Framework

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的方方面面。 第 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中,可用使用以下源代码级的元数据类型:

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

用途 Commons Attributes属性 JDK 5.0注解 属性/注解类型
将类的所有实例标识为JMX受控资源 ManagedResource @ManagedResource Class 类
将方法标识为JMX操作 ManagedOperation @ManagedOperation Method方法
将getter或者setter标识为部分JMX属性 ManagedAttribute @ManagedAttribute Method (only getters and setters) 方法(仅getters和setters)
定义操作参数说明 ManagedOperationParameter @ManagedOperationParameter@ManagedOperationParameters Method 方法

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

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

参数 描述 应用于
ObjectName MetadataNamingStrategy 用其来决定一个受控资源的 ObjectName ManagedResource
说明 设置资源,属性,操作的说明 ManagedResourceManagedAttributeManagedOperationManagedOperationParameter
currencyTimeLimit 设置 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接口

为了进一步简化配置,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 有了业务含义。 这个问题可以通过 第 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名字映射到方法名称列表上了。