1. 程式人生 > >《Spring 5官方文件》-JMX

《Spring 5官方文件》-JMX

27.1 引言

Spring對JMX的支援提供了你可以簡單、透明的將Spring應用程式整合到JMX的基礎架構中。

JMX?
        
本章不是介紹JMX的...它不會試圖去解釋為什麼要使用JMX(或JMX實際代表什麼含義)的動機。如果你是JMX的新手,請參考本章末尾的[第27.8節,更多資源](jmx.html#jmx-resources)。

具體來說,Spring JMX支援提供了四個核心功能:

  • 任何Spring bean都會自動註冊為JMX MBean
  • bean管理介面的靈活控制機制
  • 可以通過JSR-160聯結器將宣告的MBeans暴露給遠端
  • 遠端和本地MBean資源的簡單代理

這個功能的設計是應用程式元件在和Spring或JMX介面和類無需耦合的方式工作。事實上,在大多數情況下應用程式為了使用Spring JMX的特性,也不會去關心Spring或者JMX。

27.2 將Bean暴露給JMX

MBeanExporter是Spring JMX 框架中的核心類。它負責把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定義就是匯出的bean。beans的屬性準確的告訴MBeanExporter,哪個bean必須暴露給JMX MBeanServer。預設配置,在beans Map中的每個key都是相應的value引用的bean的物件名稱。可以像27.4中“控制bean的物件名稱”描述的那樣來修改此行為。 在這個配置下testBean被暴露為一個名字為bean:name=testBean1的MBean。預設,bean的所有公共的屬性都會被暴露為屬性並且所有的公共方法(那些從Object類繼承的)都被會暴露為操作。

MBeanExporter是一個有生命週期bean(參考“啟動和關閉回撥”)並且MBeans會在應用程式預設的生命週期中儘可能遲的被暴露。可以通過在暴露的階段配置或者通過設定自動啟動標誌位來禁止自動註冊。

27.2.1 建立MBeanServer

假設上述的配置是在一個正在執行的應用環境中,並且他有一個(只有一個) MBeanServer已經在運行了。在這種情形下,Spring將會嘗試定位正在執行的MBeanServer並把你的bean註冊到它上面去。當你的應用是執行在一個諸如Tomcat、IBM WebSphere等有自己MBeanServer的容器內時,這個就非常有用。

然而這種方法在獨立的環境或執行在一個沒有提供MBeanServer的容器中是沒用的。為了解決這個問題,你可以通過新增一個org.springframework.jmx.support.MBeanServerFactoryBean的類例項到你的配置中來建立一個MBeanServer例項。你還可以通過將MBeanServerFactoryBeanMBean返回的MBeanServer值賦給MBeanExporter的server屬性使用指定的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的實現。

27.2.2重用存在的MBeanServer

如果沒有指明的server,那麼MBeanExporter會嘗試去自動探測一個正在執行的MBeanServer。這在大多數情況下只有一個MBeanServer例項的情況下正常執行,但是當有多個例項存在時,exporter可能會選擇一個錯誤的server。在這種情況下,應該要指明你要用的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>

對於平臺化或一些情形來說,應該使用factory-method來查詢動態變化(未知)的MBeanServer的agentId:

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

27.2.3MBeans懶載入

如果你用MBeanExporter配置了一個bean,那麼它也配置了延遲初始化,MBeanExporter在不破壞他們關聯性的情況下會避免bean的例項化。相反,它會註冊一個MBeanServer的代理,直到第一次通過代理向容器獲取bean的時候才會初始化。

27.2.4MBeans自動註冊

任何通過MBeanExporter暴露的bean都是一個有效的MBean,它和MBeanServer注一樣,不需要Spring的介入。可以將MBeanExporter的autodetect屬性設定為true,MBeans就可以自動被發現:

<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都是有效的JMX MBean,它會被Spring自動註冊。預設,bean的名字會被用作ObjectName,JMX註冊時自動被發現。這個動作可以被重寫,詳情參考27.4,“控制bean的ObjectNames”。

27.2.5控制註冊的動作

考慮Spring MBeanExporter試圖使用’bean:name=testBean1’作為ObjectName將MBean註冊到MBeanServer的場景。如果一個MBean的例項已經註冊了相同的名字,預設情況下這此註冊行為將失敗(丟擲InstanceAlreadyExistsException異常)。 可以嚴格控制MBean在註冊到MBeanServer上何時發生了什麼的行為。Spring的JMX支援三種不同的註冊行為來控制在註冊過程中發現MBean有相同的ObjectName;這些註冊方法總結如下:

  • 表27.1 註冊方法
註冊方法 解釋
REGISTRATIONFAILON_EXISTING 這是預設的註冊方法。如果一個MBean例項已經被註冊了相同的ObjectName,這個MBean不能註冊,並且丟擲InstanceAlreadyExistsException異常。已經存在的MBean不受影響。
REGISTRATIONIGNOREEXISTING 如果一個MBean例項已經被註冊了相同的ObjectName,這個MBean不能註冊。已經存在的MBean不受影響,也沒有異常丟擲。這個設定在多個應用之間共享MBeanServer,共享MBean時非常有用。
REGISTRATIONREPLACEEXISTING 如果一個MBean例項已經被註冊了相同的ObjectName,之前存在的MBean將被沒有註冊的新MBean原地替換(新的MBean替換前一個)。

上面的值是定義在MBeanRegistrationSupport類中的常量(它是MBeanExporter的父類)。|如果你想改變預設的註冊行為,你只需要簡單的在MBeanExporter的registrationBehaviorName屬性上設定上面定義的值即可。

下面的示例說明了怎樣用REGISTRATIONREPLACEEXISTING改變預設的註冊行為:

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

27.3 bean的控制管理介面

在前面的例子中,每個已經被暴露為JMX屬性和操作的bean的所有public屬性和方法,你都可以通過bean的管理介面來控制。你可以精確的控制你所暴露的bean上的哪個屬性和方法作為JMX的屬性和操作,Spring JMX提供了全面的、可擴充套件的機制來控制bean的管理介面。

27.3.1 MBeanInfoAssembler介面

底層實現上,MBeanExporter委託了一個org.springframework.jmx.export.assembler.MBeanInfoAssembler介面的實現來負責在每個已經被暴露的bean上定義管理介面。預設實現是org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler,對所有public的屬性和方法(如你在前面例子中看到的)都會簡單的定義一個管理介面。Spring對MBeanInfoAssembler介面提供了兩種額外的實現,它允許使用原始碼級別的元資料或任意介面來控制管理介面的生成。

27.3.2 原始碼級別的元資料(Java註解)

使用MetadataMBeanInfoAssembler你可以對bean在原始碼級別上定義管理介面。org.springframework.jmx.export.metadata.JmxAttributeSource介面封裝了元資料的讀取。Spring JMX提供了使用Java註解提供了一個名為org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource的預設實現。為了MetadataMBeanInfoAssembler必須配置一個實現了JmxAttributeSource介面的例項才能正常工作(沒有預設)。

為了將一個bean標記為JMX的bean,你應該使用ManagedResource類級別的註解。每個你想要暴露為操作的方法都必須用ManagedOperation註解標記,每個想暴露的屬性的都必須用ManagedAttribute註解標記。當標記屬性的時候你可以忽略getter或setter然後自己建立一個只讀或只寫的屬性。

注意 一個被ManagedResource註解的bean,它暴露的操作或屬性必須是public的。

下面的例子展示了用註解實現的JmxTestBean類:

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類被配置了一系列屬性的ManagedResource註解標記。這些屬性可以通過MBeanExporter產生配置了各種切面的MBean,詳情請參考後面的27.3.3節,”原始碼級別的元資料型別“。

你也注意到了age和name屬性都用了ManagedAttribute註解,但是在age屬性上,只有getter上使用了。這會使得所有的屬性在management介面中都作為屬性,只有age屬性是隻讀的。

最終,你會注意到,add(int, int)方法被標記為ManagedOperation屬性,而dontExposeMe()這個方法卻不是。當使用MetadataMBeanInfoAssembler時,管理介面只包含一個add(int,int)操作。

下面的配置展示了在使用MetadataMBeanInfoAssembler時如何配置MBeanExporter:

<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被配置為AnnotationJmxAttributeSource的一個例項,並且通過assembler屬性傳遞給MBeanExporter。這對於利用Spring暴露的MBean元資料驅動的管理介面來說是必須的。

27.3.3 原始碼級別的元資料型別

使用Spring JMX有以下幾種原始碼級別的元資料型別:

  • 表 27.2. 原始碼級別的元資料型別
目標 註解 註解型別
把所有類的例項作為JMX管理的資源 @ManagedResource
將一個方法作為JMX的操作 @ManagedOperation 方法
將getter或setter標識為部分的JMX屬性 @ManagedAttribute 方法(只限getter或setter)
對一個操作引數定義描述符 @ManagedOperationParameter和@ManagedOperationParameters 方法

使用這些原始碼級別的元資料型別有以下的配置引數:

引數 描述 應用
ObjectName 使用元資料命名策略決定管理資源的ObjectName ManagedResource
description 設定友好的資源、屬性和操作的描述 ManagedResource, ManagedAttribute, ManagedOperation, ManagedOperationParameter
currencyTimeLimit 設定currencyTimeLimit描述欄位值 ManagedResource, ManagedAttribute
defaultValue 設定defaultValue描述欄位值 ManagedAttribute
log 設定log描述欄位值 ManagedResource
logFile 設定logFile描述欄位值 ManagedResource
persistPolicy 置persistPolicy描述欄位值 ManagedResource
persistPolicy 設定persistPolicy描述欄位值 ManagedResource
persistPeriod 設定persistPeriod描述欄位值 ManagedResource
persistLocation 設定persistLocation描述欄位值 ManagedResource
persistName 設定persistName描述欄位值 ManagedResource
name 設定操作引數展示的名字 ManagedOperationParameter
index 設定操作引數的索引 ManagedOperationParameter

27.3.4 AutodetectCapableMBeanInfoAssembler介面

為了進一步簡化配置,Spring引入了繼承了MBeanInfoAssembler介面來支援MBean資源的自動發現的AutodetectCapableMBeanInfoAssembler介面。如果你用AutodetectCapableMBeanInfoAssembler的例項配置了MBeanExporter,那麼你可以對暴露給JMX的bean進行“投票”。

AutodetectCapableMBeanInfo的唯一實現是MetadataMBeanInfoAssembler,它將對被標記為ManagedResource的屬性進行投票。預設使用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並且投票把它包含進來時就被註冊了。這種方法唯一問題是JmxTestBean的名字現在有業務含義。你可以通過更改ObjectName建立的預設行為來解決此問題,如27.4,“控制bean的ObjectName”。

27.3.5使用Java介面定義管理介面

除了MetadataMBeanInfoAssembler,Spring也提供了MetadataMBeanInfoAssembler,Spring也包含了InterfaceBasedMBeanInfoAssembler,它允許約束方法和用一些基於集合介面上定義的方法所暴露出來的屬性。 雖然標準的暴露機制是使用介面和一個簡單命名方案,InterfaceBasedMBeanInfoAssembler通過去除命名約定來繼承這個功能,允許你使用多個的介面,並且不需要bean實現MBean介面。 考慮你先前用來對JmxTestBean定義管理介面的介面:

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:

<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構建管理介面時會使用IJmxTestBean配置InterfaceBasedMBeanInfoAssembler。通過InterfaceBasedMBeanInfoAssembler處理的bean是不需要實現用來產生JMX管理介面的介面,明白這點很重要。 上面的例子中,所有bean的管理介面都是使用IJmxTestBean介面來構建。在許多情況下,不同的bean會使用不同的介面,而不是上面的單一行為。此例中,你可以通過interfaceMappings屬性,來傳遞InterfaceBasedMBeanInfoAssembler的例項,這樣key就是bean名字,value就是在這個bean上的用逗號分隔的方法列表。 如果即沒有通過managedInterfaces或interfaceMappings屬性指明一個管理介面,那麼InterfaceBasedMBeanInfoAssembler會通過反射在使用所有實現了該bean的介面來建立一個管理介面。

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

上面例項你可以看到,增加了暴露給JMX操作的方法myOperation和getName、setName(String) 和 getAge()等JMX的屬性。在上面的程式碼中,暴露給JMX的方法都和bean有對映的關係。為了控制一個bean一個bean的暴露,使用MethodNameMBeanInfoAssembler的methodMappings屬性來對映bean的名字到方法名字列表上。

27.4控制bean的ObjectNames

在底層,MBeanExporter委託了一個ObjectNamingStrategy的實現來獲取每個bean在註冊時的ObjectName。預設實現為KeyNamingStrategy,ObjectName作為為bean Map的key。而且,KeyNamingStrategy可以將bean Map的key對映到一個屬性檔案來解析ObjectName。除此KeyNamingStrategy之外,Spring還提供了兩個額外的ObjectNamingStrategy實現:一個基於bean的JVM標識來構建ObjectName的IdentityNamingStrategy和使用程式碼級元資料來獲取ObjectName的MetadataNamingStrategy。

27.4.4 從屬性中讀取ObjectName

你可以配置自己的KeyNamingStrategy例項,並從一個配置的屬性例項中讀取ObjectName,而不是bean 的key。KeyNamingStrategy將嘗試使用bean對應的key來找出它在Properties中的入口。如果沒有找到入口或Properties例項為空,那麼bean將使用它自己的key。 下面的程式碼展示了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例項來配置的,Properties是由mapping屬性定義的Properties例項和位於mapping屬性定義的目錄下的屬性檔案合併而成。在這個配置中,testBean的ObjectName由bean:name=testBean1定義,因此它是bean的key對應的屬性例項的入口。 如果Properties例項中找不到入口,那麼bean的key將是它的ObjectName。

27.4.2 使用MetadataNamingStrategy

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:[fully-qualified-package-name]:type=[short-classname],name=[bean-name]。例如,下面的bean產生的ObjectName為:com.foo:type=MyClass,name=myBean。

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

27.4.3基於MBean匯出的註解配置

如果你更喜歡使用基於註解的方式來定義management介面,則可以很方便的使用MBeanExporter的一個子類:AnnotationMBeanExporter。當定義一個子類的例項時,將不需要再配置namingStrategy,assembler和attributeSource,因為它會使用標註的基於Java 元資料的註解(自動檢測一直開啟)。實際上,不是定義一個MBeanExporter,@EnableMBeanExport和@Configuration註解支援更簡單的語法。

@[email protected]
public class AppConfig {

}

如果你更喜歡XML的配置方式,’context:mbean-export’可以達到相同的目的。

<context:mbean-export/>

如果需要,你可以對特定的MBean 服務提供一個引用,並且defaultDomain(AnnotationMBeanExporter的屬性)屬性接受生成“ObjectNames”的備用值。如上節的MetadataNamingStrategy所述,它將用於代替完全限定的包名稱。

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

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

不要使用基於介面的AOP代理和JMX註解自動發現相結合的方式。基於介面的代理對隱藏目標類,它也會隱藏JMX管理的resource註解。因此,在這種情況下使用目標類代理:通過在aop:config/, tx:annotation-driven/等上設定’proxy-target-class’標誌,否則,JMX也可能在啟動時被忽略。

27.5 JSR-160聯結器

對於遠端訪問,Spring JMX模組在org.springframework.jmx.support package包中提供了兩種FactoryBean實現,用於建立服務端和客戶端的聯結器。

27.5.1 服務端聯結器

為了使用Spring JMX 來建立,需要使用以下配置啟動並暴露JSR-160 JMXConnectorServer:

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

ConnectorServerFactoryBean建立的JMXConnectorServer預設會繫結到”service:jmx:jmxmp://localhost:9875″.serverConnector通過JMXMP協議在本地的9875埠上將本地的MBeanServer暴露給客戶端。注意,JMXMP協議是JSR 160標記為可選協議:當前 ,JMX主要的開源實現MX4J,它只提供了基於JDK的協議,而不支援JMXMP。

分別使用serviceUrl和ObjectName屬性來指明另一個URL並把JMXConnectorServer自身註冊為一個MBeanServer:

<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 or rmiregistry)來完成名稱註冊。如果你使用Spring 通過RMI來匯出遠端服務,那麼Spring已經構建了一個RMI註冊。如果沒有,你可以通過下面的配置簡單的啟動一個註冊:

<bean id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean">
    <property name="port" value="1099"/>
</bean>

27.5.2 客戶端聯結器

下面展示了使用MBeanServerConnectionFactoryBean建立遠端JSR-160 MBeanServer 的MBeanServerConnection:

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

27.5.3 通過Hessian 或 SOAP 的JMX

JSR-160允許客戶端和服務端之間進行通訊的方式進行擴充套件。上面的例子使用JSR-160規範(IIOP和JRMP)和(可選)JMXMP所需的強制基於RMI的實現。通過使用其他的提供者或JMX的實現(例如MX4J)你可以通過簡單的HTTP或SSL或其他方式利用諸如SOAP或Hessian之類的協議:

<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請參考官方文件.

27.6 通過代理訪問MBeans

Spring JMX 允許你建立代理,它將重新路由到本地或者遠端MBeanServer中註冊的MBean。這些代理提供了標準的Java介面來和MBean進行互動。下面的程式碼展示瞭如何在本地允許的MBeanServer中配置代理:

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

你可以看到在ObjectName: bean:name=testBean下注冊的MBean建立代理。通過proxyInterfaces屬性來控制代理實現的一系列介面,InterfaceBasedMBeanInfoAssembler使用和MBean上屬性相同規則來操作這些介面上的方法對映規則和屬性。

MBeanProxyFactoryBean可以通過MBeanServerConnection為任何可以訪問的MBean建立一個代理。預設,使用本地的MBeanServer,但是你可以重寫它,並提供一個指向遠端MBeanServer的MBeanServerConnection來滿足遠端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。

27.7 通知

Spring JMX提供了對JMX通知的全面支援。

27.7.1 註冊通知監聽器

Spring JMX的支援使得將對任意數量的NotificationListeners註冊到任意數量的MBean(包括通過Spring MBeanExporter 匯出的MBean和通過其他機制註冊的MBean)。示例,考慮這麼一個場景,當目標MBean的每個屬性每次改變的時候都會通知(通過通知)。

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)每次以廣播形式傳送JMX通知,通過notificationListenerMappings屬性註冊為ConsoleLoggingNotificationListener的監聽器將被通知。 ConsoleLoggingNotificationListener可以採取任何它認為合適的動作來響應通知。

你可以直接使用bean的名稱作為匯出bena的監聽器之間的連線:

 <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中的key;例如:

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

如果需要執行反轉(針對MBean註冊一些不同的監聽器),那麼必須使用notificationListeners的屬性列表(不是notificationListenerMappings屬性)。這次,不是簡單配置單個MBean的NotificationListener,而是配置NotificationListenerBean例項,NotificationListenerBean在MBeanServer中封裝了NotificationListener和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>

上面的例子和第一個例子是等價的。現在假設我們想每發出一個通知就給出一個handback物件,除此之外我們還想通過一個NotificationFilter過濾外來的通知。(關於什麼是handback物件,什麼是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>

27.7.2 釋出通知

Spring不僅提供了對註冊接受通知的支援,而且還用於釋出通知。

請注意,本節僅與通過MBeanExporter暴露的Spring管理的MBean相關。任何現有的使用者定義的MBean都應該使用標準的JMX API來發布通知。

Spring JMX支援的通知釋出的關鍵介面為NotificationPublisher(定義在org.springframework.jmx.export.notification包下面)。任何通過MBeanExporter例項匯出為MBean的bean都可以實現NotificationPublisherAware的相關介面來獲取NotificationPublisher例項。NotificationPublisherAware介面通過一個簡單的setter方法將NotificationPublisher的例項提供給實現bean,這個bean就可以用來發布通知。

如javadoc中的NotificationPublisher類所述,通過NotificationPublisher機制來發布事件被管理的bean是對任何通知監聽器狀態管理的不負責。Spring JMX支援將處理所有JMX基礎問題。所有人需要做的就是和應用開發人員一樣實現NotificationPublisherAware介面並通過NotificationPublisher例項開始釋出事件。注意,NotificationPublisher將在管理bean被註冊到MBeanServer之後被設定。

使用NotificationPublisher例項非常簡單,建立一個簡單的JMX通知例項(或一個適當的Notification子類例項),通知中包含釋出事件相關的資料 ,然後在NotificationPublisher例項上呼叫sendNotification(Notification),傳遞Notification。

下面是一個簡單的例子,在這種場景下,匯出的JmxTestBean例項在每次呼叫add(int, int)時會發佈一個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的耦合。

27.8 更多資源

這章節包含了關於JMX更多的資源連結: