将你的 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 {

	private lateinit var name: String
	private var age = 0

	override fun getAge(): Int {
		return age
	}

	override fun setAge(age: Int) {
		this.age = age
	}

	override fun setName(name: String) {
		this.name = name
	}

	override fun getName(): String {
		return name
	}

	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。你可以更改此行为,如 控制 Bean 的 ObjectName 实例 所述。

通过此配置,testBean bean 将作为 MBean 公开,其 ObjectNamebean:name=testBean1。默认情况下,bean 的所有 public 属性都作为属性公开,所有 public 方法(除了从 Object 类继承的方法)都作为操作公开。

MBeanExporter 是一个 Lifecycle bean(参见 启动和关闭回调)。默认情况下,MBean 在应用生命周期中尽可能晚地导出。你可以配置导出发生的 phase,或者通过设置 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>

在前面的例子中,MBeanServer 实例由 MBeanServerFactoryBean 创建,并通过 server 属性提供给 MBeanExporter。当你提供自己的 MBeanServer 实例时,MBeanExporter 不会尝试定位正在运行的 MBeanServer,而是使用提供的 MBeanServer 实例。为了使其正常工作,你的 classpath 中必须有 JMX 实现。

复用现有的 MBeanServer

如果没有指定服务器,MBeanExporter 会尝试自动检测正在运行的 MBeanServer。这在大多数环境中都能正常工作,因为只有一个 MBeanServer 实例。然而,当存在多个实例时,导出器可能会选择错误的服务器。在这种情况下,你应该使用 MBeanServeragentId 来指示使用哪个实例,如下例所示

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

对于现有 MBeanServeragentId 是动态的(或未知)并通过查找方法获取的平台或情况,你应该使用 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>

懒加载初始化的 MBean

如果你使用配置为懒加载初始化的 MBeanExporter 配置一个 bean,MBeanExporter 不会违反此契约,并避免实例化该 bean。相反,它会向 MBeanServer 注册一个代理,并延迟从容器中获取该 bean,直到代理上发生第一次调用。

这也影响 FactoryBean 的解析,其中 MBeanExporter 会定期内省生成的对象,从而有效地触发 FactoryBean.getObject()。为了避免这种情况,请将相应的 bean 定义标记为 lazy-init。

MBean 的自动注册

任何通过 MBeanExporter 导出且已经是有效 MBean 的 bean 都会按原样注册到 MBeanServer,无需 Spring 进一步干预。你可以通过将 autodetect 属性设置为 true 来使 MBean 由 MBeanExporter 自动检测,如下例所示

<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 支持提供了三种不同的注册行为,用于控制当注册过程发现同一 ObjectName 下已注册 MBean 时如何处理。下表总结了这些注册行为

表 1. 注册行为
注册行为 解释

FAIL_ON_EXISTING

这是默认的注册行为。如果已存在相同 ObjectNameMBean 实例,则当前注册的 MBean 将不会注册,并抛出 InstanceAlreadyExistsException。已存在的 MBean 不受影响。

IGNORE_EXISTING

如果已存在相同 ObjectNameMBean 实例,则当前注册的 MBean 将不会注册。已存在的 MBean 不受影响,也不会抛出 Exception。这在多个应用程序希望在共享 MBeanServer 中共享同一个 MBean 的场景中很有用。

REPLACE_EXISTING

如果已存在相同 ObjectNameMBean 实例,则先取消注册之前注册的现有 MBean,然后注册新的 MBean(新 MBean 有效地替换了之前的实例)。

上表中的值在 RegistrationPolicy 类中被定义为枚举。如果你想更改默认注册行为,你需要将 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>