将您的Bean导出到JMX
Spring的JMX框架中的核心类是MBeanExporter。这个类负责将你的Spring Bean注册到JMX的MBeanServer中。例如,考虑以下类:
- Java
- Kotlin
public class JmxTestBean implements IJmxTestBean {
private String name;
private int age;
@Override
public int getAge() {
return age;
}
@Override
public void setAge(int age) {
this.age = age;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public int add(int x, int y) {
return x + y;
}
@Override
public void dontExposeMe() {
throw new RuntimeException();
}
}
class JmxTestBean : IJmxTestBean {
override lateinit var name: String
override var age = 0
override fun add(x: Int, y: Int): Int {
return x + y
}
override fun dontExposeMe() {
throw RuntimeException()
}
}
要将这个bean的属性和方法作为MBean的属性和操作来暴露,你可以在配置文件中配置一个MBeanExporter类的实例,并传递该bean,如下例所示:
- Java
- Kotlin
- Xml
@Configuration
public class JmxConfiguration {
@Bean
MBeanExporter exporter(JmxTestBean testBean) {
MBeanExporter exporter = new MBeanExporter();
exporter.setBeans(Map.of("bean:name=testBean1", testBean));
return exporter;
}
@Bean
JmxTestBean testBean() {
JmxTestBean testBean = new JmxTestBean();
testBean.setName("TEST");
testBean.setAge(100);
return testBean;
}
}
@Configuration
class JmxConfiguration {
@Bean
fun exporter(testBean: JmxTestBean) = MBeanExporter().apply {
setBeans(mapOf("bean:name=testBean1" to testBean))
}
@Bean
fun testBean() = JmxTestBean().apply {
name = "TEST"
age = 100
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- this bean must not be lazily initialized if the exporting is to happen -->
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=testBean1" value-ref="testBean"/>
</map>
</property>
</bean>
<bean id="testBean" class="org.example.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。你可以根据Controlling ObjectName Instances for Your Beans中的描述来改变这种行为。
通过这种配置,testBean 被暴露为 MBean,其 ObjectName 为 bean:name=testBean1。默认情况下,该 Bean 的所有 public 属性都会作为属性被暴露出来,而所有 public 方法(除了从 Object 类继承的方法)都会作为操作被暴露出来。
MBeanExporter 是一个 Lifecycle 类型的 bean(参见 启动和关闭回调)。默认情况下,MBeans 会在应用程序生命周期的尽可能晚的阶段被导出。你可以通过设置 autoStartup 标志来配置导出的发生阶段,或者禁用自动注册功能。
创建一个MBeanServer
在前一节中展示的配置假设应用程序运行在一个已经有一个(且仅有一个)MBeanServer正在运行的环境中。在这种情况下,Spring会尝试找到正在运行的MBeanServer,并将你的Bean注册到该服务器上(如果有的话)。当你的应用程序在拥有自己的MBeanServer的容器(如Tomcat或IBM WebSphere)中运行时,这种行为非常有用。
然而,这种方法在独立环境中或在不提供MBeanServer的容器中运行时是无效的。为了解决这个问题,你可以通过在配置中添加org.springframework.jmx.support.MBeanServerFactoryBean类的实例来声明性地创建一个MBeanServer实例。你还可以通过将MBeanExporter实例的server属性的值设置为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>
在前面的例子中,MBeanServerFactoryBean 创建了一个 MBeanServer 的实例,并通过 server 属性将其提供给 MBeanExporter。当你提供自己的 MBeanServer 实例时,MBeanExporter 将不会尝试查找正在运行的 MBeanServer,而是直接使用所提供的 MBeanServer 实例。为了使这一机制正常工作,你的类路径中必须包含 JMX 实现。
重用现有的 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>
懒惰初始化的MBeans
如果你配置了一个带有 MBeanExporter 的bean,而该 MBeanExporter 也被配置为延迟初始化模式,那么 MBeanExporter 将不会违反这一约定,也不会立即实例化该 bean。相反,它会向 MBeanServer 注册一个代理,并将获取 bean 的操作推迟到第一次调用该代理时才执行。
这也会影响FactoryBean的解析过程,因为MBeanExporter会定期检测生成的对象,从而实际上触发了FactoryBean.getObject()方法。为了避免这种情况,应将相应的bean定义标记为延迟初始化(lazy-init)。
MBeans的自动注册
任何通过MBeanExporter导出且已经是有效MBean的Bean,都会原样注册到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,其bean名称将被用作ObjectName。你可以覆盖这种行为,具体细节请参见控制Bean的ObjectName实例。
控制注册行为
假设一个Spring的MBeanExporter尝试使用ObjectName bean:name=testBean1来将一个MBean注册到MBeanServer中。如果已经有一个MBean实例以相同的ObjectName注册过了,那么默认的行为是失败(并抛出一个InstanceAlreadyExistsException)。
当一个MBean被注册到MBeanServer时,你可以精确地控制发生什么。Spring的JMX支持提供了三种不同的注册行为,以便在注册过程中发现某个MBean已经以相同的ObjectName被注册时进行控制。下表总结了这些注册行为:
表1. 注册行为
| 注册行为 | 说明 |
|---|---|
FAIL_ON_EXISTING | 这是默认的注册行为。如果某个 MBean 实例已经使用相同的 ObjectName 进行过注册,那么此次尝试注册的 MBean 将不会被注册,并会抛出 InstanceAlreadyExistsException 异常。现有的 MBean 保持不变。 |
IGNORE_EXISTING | 如果某个 MBean 实例已经使用相同的 ObjectName 进行过注册,那么此次尝试注册的 MBean 也不会被注册。现有的 MBean 保持不变,且不会抛出任何异常。这种行为在多个应用程序希望共享同一个 MBeanServer 中的同一个 MBean 时非常有用。 |
REPLACE_EXISTING | 如果某个 MBean 实例已经使用相同的 ObjectName 进行过注册,那么之前注册的 MBean 将被注销,新的 MBean 会取而代之(新 MBean 实际上替换了之前的实例)。 |
上表中的值在 RegistrationPolicy 类中被定义为枚举类型(enum)。如果您想要更改默认的注册行为,就需要将您的 MBeanExporter 定义中的 registrationPolicy 属性值设置为这些值之一。
以下示例展示了如何将默认的注册行为更改为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="registrationPolicy" value="REPLACE_EXISTING"/>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
</beans>