31. JMX

31.1 Introduction

Spring 中的 JMX 支持为您提供了轻松,透明地将 Spring 应用程序集成到 JMX 基础结构中的功能。

JMX?

本章不是 JMX 的介绍……它不会试图解释为什么可能要使用 JMX 的动机(或者实际上是 JMX 代表的字母)。如果您不熟悉 JMX,请在本章末尾查看第 31.8 节“其他资源”

具体来说,Spring 的 JMX 支持提供了四个核心功能:

  • 将* any * Spring bean 自动注册为 JMX MBean

  • 一种灵活的机制,用于控制 bean 的 Management 界面

  • 通过远程 JSR-160 连接器以声明方式公开 MBean

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

这些功能旨在在不将应用程序组件耦合到 Spring 或 JMX 接口和类的情况下起作用。确实,在大多数情况下,您的应用程序类无需了解 Spring 或 JMX 即可利用 Spring JMX 功能。

31.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 的属性和方法公开为 MBean 的属性和操作,您只需在配置文件中配置MBeanExporter类的实例,然后按如下所示传入 bean:

<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中每个条目的键用作相应条目值所引用的 Bean 的ObjectName。可以按照第 31.4 节“控制 bean 的对象名”中所述更改此行为。

通过这种配置,testBean bean 作为ObjectName bean:name=testBean1下的 MBean 公开。默认情况下,bean 的所有* public 属性都作为属性公开,而所有 public *方法(从Object类继承的方法除外)都作为操作公开。

Note

MBeanExporterLifecycle bean(请参见名为“启动和关闭回调”的部分),默认情况下,MBean 在应用程序生命周期中尽可能晚地导出。通过设置autoStartup标志,可以配置发生导出的phase或禁用自动注册。

31.2.1 创建 MBeanServer

上面的配置假设应用程序正在一个(并且只有一个)MBeanServer已运行的环境中运行。在这种情况下,Spring 将尝试找到正在运行的MBeanServer并将您的 bean 注册到该服务器(如果有)。当您的应用程序在具有自己的MBeanServer的容器(例如 Tomcat 或 IBM WebSphere)中运行时,此行为很有用。

但是,此方法在独立环境中或在不提供MBeanServer的容器中运行时无用。为了解决这个问题,您可以通过在配置中添加org.springframework.jmx.support.MBeanServerFactoryBean类的实例来声明性地创建MBeanServer实例。您还可以通过将MBeanExporterserver属性的值设置为MBeanServerFactoryBean返回的MBeanServer值来确保使用特定的MBeanServer;例如:

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

MBeanServer的实例是由MBeanServerFactoryBean创建的,并通过 server 属性提供给MBeanExporter。当您提供自己的MBeanServer实例时,MBeanExporter将不会尝试查找正在运行的MBeanServer,而是将使用提供的MBeanServer实例。为了使其正常工作,您必须(当然)必须在 Classpath 上具有 JMX 实现。

31.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 for 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>

    <!-- other beans here -->

</beans>

31.2.3 延迟初始化的 MBean

如果您使用也配置为延迟初始化的MBeanExporter配置 Bean,则MBeanExporter不会违反此 Contract,并且将避免实例化该 Bean。相反,它将在MBeanServer处注册一个代理,并将从容器中获取 Bean,直到对该代理进行第一次调用为止。

31.2.4 MBean 的自动注册

通过MBeanExporter导出并且已经是有效 MBean 的所有 bean 都将直接在MBeanServer上注册,而无需 Spring 的进一步干预。通过将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 已经是有效的 JMX MBean,它将由 Spring 自动注册。缺省情况下,自动检测到 JMX 注册的 bean 的 Bean 名称用作ObjectName。如第 31.4 节“控制 bean 的对象名”中所述,可以覆盖此行为。

31.2.5 控制注册行为

考虑以下情形:Spring MBeanExporter尝试使用ObjectName 'bean:name=testBean1'MBeanServer注册MBean。如果MBean实例已经在同一ObjectName下注册,则默认行为是失败(并抛出InstanceAlreadyExistsException)。

MBean注册到MBeanServer时,可以精确控制行为。当注册过程发现MBean已经在同一ObjectName下注册时,Spring 的 JMX 支持允许三种不同的注册行为来控制注册行为。下表总结了这些注册行为:

表 31.1. 注册行为

Registration behaviorExplanation
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有效替换先前的实例)。

上面的值定义为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>

31.3 控制 Bean 的 Management 界面

在前面的示例中,您几乎无法控制 Bean 的 Management 界面。每个导出 bean 的* public 属性和方法的所有分别公开为 JMX 属性和操作。为了对已导出的 bean 的哪些属性和方法实际上作为 JMX 属性和操作公开而进行更细粒度的控制,Spring JMX 提供了一种全面且可扩展的机制来控制 bean 的 Management 接口。

31.3.1 MBeanInfoAssembler 接口

在幕后,MBeanExporter委托org.springframework.jmx.export.assembler.MBeanInfoAssembler接口的实现,该接口负责定义要公开的每个 bean 的 Management 接口。默认实现org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler只是定义了一个 Management 接口,该接口公开了所有公共属性和方法(如前面的示例所示)。 Spring 提供了MBeanInfoAssembler接口的两个附加实现,允许您使用源级元数据或任何任意接口来控制生成的 Management 接口。

31.3.2 使用源级元数据:Java 注解

使用MetadataMBeanInfoAssembler,可以使用源级元数据为 bean 定义 Management 界面。元数据的读取由org.springframework.jmx.export.metadata.JmxAttributeSource接口封装。 Spring JMX 提供了一个使用 JavaComments 的默认实现,即org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource。必须为MetadataMBeanInfoAssembler配置JmxAttributeSource接口的实现实例,才能使其正常运行(默认为* no *)。

要标记要导出到 JMX 的 bean,应使用ManagedResourceComments 对 bean 类进行 Comments。您希望作为操作公开的每个方法都必须带有ManagedOperation注解,并且您希望公开的每个属性都必须带有ManagedAttribute注解。标记属性时,可以省略 getter 或 setter 的 Comments,以分别创建只写或只读属性。

Note

带有ManagedResourceComments 的 Bean 以及公开操作或属性的方法都必须是公共的。

下面的示例显示了您先前看到的JmxTestBean类的带 Comments 的版本:

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

}

在这里您可以看到JmxTestBean类标记有ManagedResourceComments,并且此ManagedResourceComments 配置了一组属性。这些属性可用于配置MBeanExporter生成的 MBean 的各个方面,稍后在标题为第 31.3.3 节“源级元数据类型”的部分中将进行详细说明。

您还将注意到,agename属性都用ManagedAttributeComments 进行了 Comments,但是对于age属性,仅标记了吸气剂。这将导致这两个属性都作为属性包含在 Management 界面中,但是age属性将是只读的。

最后,您会注意到add(int, int)方法标记有ManagedOperation属性,而dontExposeMe()方法未标记。使用MetadataMBeanInfoAssembler时,这将导致 Management 界面仅包含一个操作add(int, int)

以下配置显示了如何配置MBeanExporter以使用MetadataMBeanInfoAssembler

<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 the ObjectName from the 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>

在这里,您可以看到已为MetadataMBeanInfoAssembler bean 配置了AnnotationJmxAttributeSource类的实例,并通过汇编程序属性将其传递给MBeanExporter。这是为 Spring 公开的 MBean 利用元数据驱动的 Management 接口所需要的全部。

31.3.3 源级元数据类型

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

表 31.2 源级元数据类型

PurposeAnnotationAnnotation Type
Class的所有实例标记为 JMX 托管资源@ManagedResourceClass
将方法标记为 JMX 操作@ManagedOperationMethod
将获取器或设置器标记为 JMX 属性的一半@ManagedAttribute方法(仅 getter 和 setter)
定义操作参数的描述@ManagedOperationParameter@ManagedOperationParametersMethod

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

表 31.3 源级元数据参数

ParameterDescriptionApplies to
ObjectNameMetadataNamingStrategy用于确定托管资源的ObjectNameManagedResource
description设置资源,属性或操作的友好描述ManagedResource , ManagedAttribute , ManagedOperation , ManagedOperationParameter
currencyTimeLimit设置currencyTimeLimitDescriptors 字段的值ManagedResource , ManagedAttribute
defaultValue设置defaultValueDescriptors 字段的值ManagedAttribute
log设置logDescriptors 字段的值ManagedResource
logFile设置logFileDescriptors 字段的值ManagedResource
persistPolicy设置persistPolicyDescriptors 字段的值ManagedResource
persistPeriod设置persistPeriodDescriptors 字段的值ManagedResource
persistLocation设置persistLocationDescriptors 字段的值ManagedResource
persistName设置persistNameDescriptors 字段的值ManagedResource
name设置操作参数的显示名称ManagedOperationParameter
index设置操作参数的索引ManagedOperationParameter

31.3.4 AutodetectCapableMBeanInfoAssembler 接口

为了进一步简化配置,Spring 引入了AutodetectCapableMBeanInfoAssembler接口,该接口扩展了MBeanInfoAssembler接口以添加对自动检测 MBean 资源的支持。如果用AutodetectCapableMBeanInfoAssembler的实例配置MBeanExporter,则可以在包含 Bean 的情况下对其进行“投票”以暴露给 JMX。

开箱即用,AutodetectCapableMBeanInfo接口的唯一实现是MetadataMBeanInfoAssembler,它将投票以包括任何带有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>

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

</beans>

注意,在此配置中,没有将任何 bean 传递给MBeanExporter;但是,JmxTestBean仍将被注册,因为它已被标记为ManagedResource属性,并且MetadataMBeanInfoAssembler对此进行了检测并对其进行投票以将其包括在内。这种方法的唯一问题是JmxTestBean的名称现在具有商业意义。您可以通过更改第 31.4 节“控制 bean 的对象名”中定义的ObjectName创建的默认行为来解决此问题。

31.3.5 使用 Java 接口定义 Management 接口

除了MetadataMBeanInfoAssembler之外,Spring 还包括InterfaceBasedMBeanInfoAssembler,它使您可以基于一组接口中定义的一组方法来约束公开的方法和属性。

尽管公开 MBean 的标准机制是使用接口和简单的命名方案,但是InterfaceBasedMBeanInfoAssembler扩展了此功能,从而消除了对命名约定的需要,允许您使用多个接口,并且不再需要您的 bean 来实现 MBean 接口。 。

考虑以下接口,该接口用于为您先前看到的JmxTestBean类定义 Management 接口:

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 以使用此接口作为 Management 接口的定义:

<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 构造 Management 接口时,将InterfaceBasedMBeanInfoAssembler配置为使用IJmxTestBean接口。重要的是要理解,对于实现用于生成 JMXManagement 接口的接口,不需要InterfaceBasedMBeanInfoAssembler处理的 bean。

在上述情况下,IJmxTestBean接口用于为所有 bean 构造所有 Management 接口。在许多情况下,这不是理想的行为,您可能想对不同的 bean 使用不同的接口。在这种情况下,您可以通过interfaceMappings属性传递InterfaceBasedMBeanInfoAssembler一个Properties实例,其中每个条目的键是 Bean 名称,每个条目的值是一个用逗号分隔的接口名称列表,用于该 Bean。

如果未通过managedInterfacesinterfaceMappings属性指定任何 Management 接口,则InterfaceBasedMBeanInfoAssembler将反映在 Bean 上,并使用该 Bean 实施的所有接口来创建 Management 接口。

31.3.6 使用 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 属性的相应一半公开。在上面的代码中,方法 Map 适用于公开给 JMX 的 bean。要控制每个 bean 的方法公开,请使用MethodNameMBeanInfoAssemblermethodMappings属性将 bean 名称 Map 到方法名称列表。

31.4 控制 bean 的对象名

在幕后,MBeanExporter委派ObjectNamingStrategy的实现,以便为其注册的每个 bean 获得ObjectName。默认实现KeyNamingStrategy默认情况下将beans Map的键用作ObjectName。此外,KeyNamingStrategy可以将beans Map的键 Map 到Properties文件(或多个文件)中的条目以解析ObjectName。除了KeyNamingStrategy之外,Spring 还提供了两个附加的ObjectNamingStrategy实现:IdentityNamingStrategy基于 bean 的 JVM 身份构建ObjectName,以及MetadataNamingStrategy使用源级别元数据获取ObjectName

31.4.1 从属性读取对象名称

您可以配置自己的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>

在这里,KeyNamingStrategy的实例配置有Properties实例,该实例从 maping 属性定义的Properties实例和位于 mappings 属性定义的路径中的属性文件合并而成。在此配置中,testBean bean 将被赋予ObjectName bean:name=testBean1,因为这是Properties实例中的条目,该条目具有与 bean 密钥相对应的密钥。

如果在Properties实例中找不到任何条目,则将 bean 键名用作ObjectName

31.4.2 使用元数据命名策略

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.annotation.AnnotationJmxAttributeSource"/>

</beans>

如果没有为ManagedResource属性提供objectName,则将使用以下格式创建ObjectName:* [完全合格的软件包名称]:type = [短类名称],name = [bean 名称] 。例如,为以下 bean 生成的ObjectName将是: com.foo:type = MyClass,name = myBean *。

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

31.4.3 配置基于 Comments 的 MBean 导出

如果您更喜欢使用基于 Comments 的方法定义 Management 界面,则可以使用MBeanExporter的便利子类:AnnotationMBeanExporter。在定义此子类的实例时,不再需要namingStrategyassemblerattributeSource配置,因为它将始终使用标准的基于 JavaComments 的元数据(也始终启用自动检测)。实际上,@EnableMBeanExport @ConfigurationComments 支持一种甚至更简单的语法,而不是定义MBeanExporter bean。

@Configuration
@EnableMBeanExport
public class AppConfig {

}

如果您更喜欢基于 XML 的配置,则'context:mbean-export'元素可以达到相同的目的。

<context:mbean-export/>

如有必要,可以提供对特定 MBean server的引用,并且defaultDomain属性(AnnotationMBeanExporter的属性)为生成的 MBean“ ObjectNames”域接受备用值。它将代替上一章节MetadataNamingStrategy中描述的标准软件包名称。

@EnableMBeanExport(server="myMBeanServer", defaultDomain="myDomain")
@Configuration
ContextConfiguration {

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

Note

请勿将基于接口的 AOP 代理与 bean 类中的 JMXComments 的自动检测结合使用。基于接口的代理“隐藏”目标类,该类也隐藏了 JMXManagement 的资源 Comments。因此,在这种情况下,请使用目标类代理:通过在<aop:config/><tx:annotation-driven/>等上设置'proxy-target-class'标志。否则,启动时可能会静默忽略您的 JMX bean…

31.5 JSR-160 连接器

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

31.5.1 服务器端连接器

要创建 Spring JMX,使用以下配置启动和公开 JSR-160 JMXConnectorServer

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

默认情况下ConnectorServerFactoryBean创建绑定到"service:jmx:jmxmp://localhost:9875"JMXConnectorServer。因此,serverConnector bean 通过 localhost 上的 JMXMP 协议(端口 9875)向 Client 端公开本地MBeanServer。请注意,JSR 160 规范将 JMXMP 协议标记为可选:当前,主要的开源 JMX 实现 MX4J,还有一个 JDK 随附的支持 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>

31.5.2Client 端连接器

要为启用了远程 JSR-160 的MBeanServer创建MBeanServerConnection,请使用MBeanServerConnectionFactoryBean,如下所示:

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

31.5.3 粗麻布/黑森 State/ SOAP 上的 JMX

JSR-160 允许扩展 Client 端与服务器之间进行通信的方式。上面的示例使用 JSR-160 规范(IIOP 和 JRMP)和(可选)JMXMP 所需的基于 RMI 的强制实现。通过使用其他提供程序或 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>

在上面的示例中,使用的是 MX4J 3.0.0;有关更多信息,请参见官方 MX4J 文档。

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

在这里,您可以看到为在ObjectNamebean:name=testBean下注册的 MBean 创建了代理。代理将实现的接口集由proxyInterfaces属性控制,将这些接口上的方法和属性 Map 到 MBean 上的操作和属性的规则与InterfaceBasedMBeanInfoAssembler使用的规则相同。

MBeanProxyFactoryBean可以为可通过MBeanServerConnection访问的任何 MBean 创建代理。默认情况下,已找到并使用了本地MBeanServer,但是您可以覆盖它并提供一个指向远程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>

在这里您可以看到我们使用MBeanServerConnectionFactoryBean创建了一个指向远程计算机的MBeanServerConnection。然后,此MBeanServerConnection通过server属性传递到MBeanProxyFactoryBean。创建的代理将通过此MBeanServerConnection将所有调用转发到MBeanServer

31.7 Notifications

Spring 的 JMX 产品包括对 JMX 通知的全面支持。

31.7.1 注册侦听器以接收通知

Spring 的 JMX 支持使注册任意数量的NotificationListeners到任意数量的 MBean 变得非常容易(这包括 Spring MBeanExporter导出的 MBean 和通过其他机制注册的 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)BroadcastJMX Notification时,都会通知通过notificationListenerMappings属性注册为侦听器的ConsoleLoggingNotificationListener bean。然后ConsoleLoggingNotificationListener bean 可以响应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属性 Map 中条目的键;例如:

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

如果需要进行相反操作(即,针对 MBean 注册许多不同的侦听器),则必须使用notificationListeners list 属性(并且优先使用notificationListenerMappings属性)。这次,不是为单个 MBean 简单配置NotificationListener,而是配置NotificationListenerBean实例…NotificationListenerBean封装了NotificationListener和要在MBeanServer中注册的ObjectName(或ObjectNames)。 NotificationListenerBean还封装了许多其他属性,例如NotificationFilter和可以在高级 JMX 通知场景中使用的任意 handback 对象。

使用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)中名为“ JMX 通知模型”的部分。)

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

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

31.7.2 发布通知

Spring 不仅仅提供注册支持以接收Notifications,还提供发布Notifications的支持。

Note

请注意,本节实际上仅与已通过MBeanExporter公开为 MBean 的 Spring 托管 Bean 有关。任何现有的用户定义的 MBean 都应使用标准的 JMX API 进行通知发布。

Spring 的 JMX 通知发布支持中的关键接口是NotificationPublisher接口(在org.springframework.jmx.export.notification包中定义)。任何要通过MBeanExporter实例作为 MBean 导出的 bean 都可以实现相关的NotificationPublisherAware接口来访问NotificationPublisher实例。 NotificationPublisherAware接口只是通过一个简单的 setter 方法将NotificationPublisher的实例提供给实现 Bean,然后该 bean 可以用来发布Notifications

NotificationPublisher类的 javadocs 中所述,通过NotificationPublisher机制发布事件的托管 bean 负责任何通知侦听器等的状态 Management……Spring 的 JMX 支持将负责处理所有 JMX 基础结构问题。作为应用程序开发人员,所有需要做的就是实现NotificationPublisherAware接口并使用提供的NotificationPublisher实例开始发布事件。注意,在托管 Bean 已通过MBeanServer注册之后,将设置NotificationPublisher

使用NotificationPublisher实例非常简单……只需创建一个 JMX Notification实例(或适当的Notification子类的实例),然后使用与要发布的事件相关的数据填充通知,然后在sendNotification(Notification)上调用sendNotification(Notification) NotificationPublisher实例,传入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;
    }

}

NotificationPublisher界面和使一切正常运行的机制是 Spring 的 JMX 支持的更好的功能之一。但是,它确实带有将类与 Spring 和 JMX 耦合的代价。和往常一样,这里的建议是实用的…如果您需要NotificationPublisher提供的功能并且可以接受到 Spring 和 JMX 的耦合,则可以这样做。

31.8 其他资源

本节包含有关 JMX 的更多资源的链接。