匯出 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 會以 ObjectName bean:name=testBean1 的 MBean 公開。預設情況下,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 實例。為了使其正確運作,您的類別路徑上必須有 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>

延遲初始化 MBean

如果您使用也組態為延遲初始化的 MBeanExporter 組態 Bean,則 MBeanExporter 不會破壞此合約,並避免實例化 Bean。相反地,它會在 MBeanServer 中註冊 Proxy,並延遲從容器取得 Bean,直到 Proxy 上第一次調用發生。

這也會影響 FactoryBean 解析,其中 MBeanExporter 會定期內省產生的物件,有效地觸發 FactoryBean.getObject()。為了避免這種情況,請將對應的 Bean 定義標記為延遲初始化。

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=testBean1MBeanServer 註冊 MBean。如果已使用相同的 ObjectName 註冊了 MBean 實例,則預設行為是失敗 (並拋出 InstanceAlreadyExistsException)。

您可以精確控制當 MBean 註冊到 MBeanServer 時會發生什麼情況。Spring 的 JMX 支援允許三種不同的註冊行為,以控制在註冊程序發現已使用相同的 ObjectName 註冊了 MBean 時的註冊行為。下表總結了這些註冊行為

表 1. 註冊行為
註冊行為 說明

FAIL_ON_EXISTING

這是預設的註冊行為。如果已使用相同的 ObjectName 註冊了 MBean 實例,則正在註冊的 MBean 不會註冊,並且會拋出 InstanceAlreadyExistsException。現有的 MBean 不受影響。

IGNORE_EXISTING

如果已使用相同的 ObjectName 註冊了 MBean 實例,則正在註冊的 MBean 不會註冊。現有的 MBean 不受影響,並且不會拋出 Exception。這在多個應用程式想要在共用的 MBeanServer 中共用通用 MBean 的設定中很有用。

REPLACE_EXISTING

如果已使用相同的 ObjectName 註冊了 MBean 實例,則先前註冊的現有 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>