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。 在 第 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. 复用现有的MBeanServer

如果服务器没有指定,MBeanExporter会尝试自动检测运行中的MBeanServer。 这在大多数只有一个MBeanServer实例的环境中可以奏效,但当存在多个实例时,可能会选错服务器。 在这种情况下,应该用MBeanServer agentId来说明究竟用哪个实例:

<beans>
   <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean">
     <!-- indicate to first look for a server -->
     <property name="locateExistingServerIfPossible" value="true"/>
     <!-- search the MbeanServer instance with the given agentId -->
     <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通过查询方法来获得有动态/未知的agentId, 这时应该用factory-method

<beans>
   <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
     <property name="server">
       <!-- Custom MBeanServerLocator -->
       <bean class="platform.package.MBeanServerLocator" factory-method="locateMBeanServer"/>
     </property>
   ...
   </bean>
</beans>

20.2.3. MBean的惰性初始化

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

20.2.4. 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 来设置。 在 第 20.4 节 “控制bean的 ObjectName 章节里,更详细的描述了如何覆盖(override)这种行为。

20.2.5. 控制注册行为

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

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

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