天天看點

JMX(Java管理擴充)

Spring中的JMX(Java管理擴充)支援提供了一些特性,使你能夠輕松、透明地将Spring應用程式內建到JMX基礎設施中。

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

  • 将任何Spring Bean自動注冊為 JMX MBean。
  • 控制bean管理接口的靈活機制。
  • 通過遠端JSR-160連接配接器聲明性地公開MBeans。
  • 本地和遠端MBean資源的簡單代理。

這些特性被設計成在不将應用程式元件耦合到Spring或JMX接口和類的情況下工作。實際上,在大多數情況下,應用程式類不需要知道Spring或JMX,就可以利用spring jmx特性。

一、JMX介紹

JMX(Java Management Extensions,即Java管理擴充)是一個為應用程式、裝置、系統等植入管理功能的架構。JMX可以跨越一系列異構作業系統平台、系統體系結構和網絡傳輸協定,靈活的開發無縫內建的系統、網絡和服務管理應用。

JMX架構圖
JMX(Java管理擴充)

從圖中我們可以看到,JMX的結構一共分為三層:

基礎層:主要是Mbean,被管理的java bean

Mbean分為如下四種:

  • standard MBean:這種類型的MBean最簡單,它能管理的資源(包括屬性,方法,時間)必須定義在接口中,然後MBean必須實作這個接口。它的命名也必須遵循一定的規範,例如我們的MBean為Hello,則接口必須為HelloMBean。
  • dynamic MBean:必須實作javax.management.DynamicMBean接口,所有的屬性,方法都在運作時定義。
  • model MBean:與标準和動态MBean相比,你可以不用寫MBean類,隻需使用javax.management.modelmbean.RequiredModelMBean即可。RequiredModelMBean實作了ModelMBean接口,而ModelMBean擴充了DynamicMBean接口,是以與DynamicMBean相似,Model MBean的管理資源也是在運作時定義的。與DynamicMBean不同的是,DynamicMBean管理的資源一般定義在DynamicMBean中(運作時才決定管理那些資源),而model MBean管理的資源并不在MBean中,而是在外部(通常是一個類),隻有在運作時,才通過set方法将其加入到model MBean中。

适配層:MbeanServer,提供對資源的注冊和管理

接入層: 提供遠端通路的入口

standard MBean示範

根據standard MBean的要求,我們首先要定義一個MBean接口,接口的命名規範以具體的實作類為字首,為了後續可以注冊到MBean Server中。

package jmx;

public interface HelloMBean
{
     public String getName();
        
     public void setName(String name);
        
     public String getAge();

     public void setAge(String age);
        
     public void helloWorld();
        
     public void helloWorld(String str);
        
     public void getTelephone();
}      

定義一個實作類

package jmx;

/*
 * 該類名稱必須與實作的接口的字首保持一緻(即MBean前面的名稱
 */
public class Hello implements HelloMBean
{
    private String name;
        
    private String age;

    public void getTelephone()
    {
        System.out.println("get Telephone");
    }

    public void helloWorld()
    {
        System.out.println("hello world");
    }

    public void helloWorld(String str)
    {
        System.out.println("helloWorld:" + str);
    }

    public String getName()
    {
        System.out.println("get name 123");
        return name;
    }

    public void setName(String name)
    {
        System.out.println("set name 123");
        this.name = name;
    }

    public String getAge()
    {
        System.out.println("get age 123");
        return age;
    }

    public void setAge(String age)
    {
        System.out.println("set age 123");
        this.age = age;
    }      
}      

定義agent層

package jmx;

import java.lang.management.ManagementFactory;

import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.ObjectName;

public class HelloAgent
{
    public static void main(String[] args) throws JMException, Exception
    {
         MBeanServer server = ManagementFactory.getPlatformMBeanServer();
         ObjectName helloName = new ObjectName("jmxBean:name=hello");
         //create mbean and register mbean
         server.registerMBean(new Hello(), helloName);
         Thread.sleep(60*60*1000);
    }
}      
  • 通過工廠類擷取Mbean Server,用來做Mbean的容器
  • ObjectName的取名規範:域名:name=Mbean名稱,其中域名和Mbean的名稱可以任取。這樣定義後,我們可以唯一标示我們定義的這個Mbean的實作類了
  • 最後将Hello這個類注冊到MbeanServer中,注入需要建立一個ObjectName類,我們可以用jdk自帶的Jconsole用來觀察,可以設定屬性值和調用相關方法。
通知(Notification)

MBean之間的通信是必不可少的,Notification起到了在MBean之間溝通橋梁的作用。JMX 的通知由四部分組成:

  • Notification這個相當于一個資訊包,封裝了需要傳遞的資訊
  • Notification broadcaster這個相當于一個廣播器,把消息廣播出。
  • Notification listener 這是一個監聽器,用于監聽廣播出來的通知資訊。
  • Notification filiter 這個一個過濾器,過濾掉不需要的通知。這個一般很少使用。

保留Hello及HelloMBean,增加如下:

package jmx;

public interface JackMBean
{
    public void hi();
}      
package jmx;

import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;

public class Jack extends NotificationBroadcasterSupport implements JackMBean
{
    private int seq = 0;
    public void hi()
    {
         //建立一個資訊包
        Notification notify = 
            //通知名稱;誰發起的通知;序列号;發起通知時間;發送的消息
            new Notification("jack.hi",this,++seq,System.currentTimeMillis(),"jack");
        sendNotification(notify);
    }

}      

這裡的類Jack不僅實作了MBean接口,還繼承了NotificationBroadcasterSupport。jack在這裡建立并發送了一個消息包。

package jmx;

import javax.management.Notification;
import javax.management.NotificationListener;

public class HelloListener implements NotificationListener
{

    public void handleNotification(Notification notification, Object handback)
    {
        if(handback instanceof Hello)
        {
            Hello hello = (Hello)handback;
            hello.printHello(notification.getMessage());
        }
    }
    
}      

對HelloAgent做以下修改

package jmx;

import java.lang.management.ManagementFactory;

import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.ObjectName;

public class HelloAgent
{
    public static void main(String[] args) throws JMException, Exception
    {
        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
        ObjectName helloName = new ObjectName("yunge:name=Hello");    
        Hello hello=new Hello();          
        server.registerMBean(hello, helloName);  
        Jack jack = new Jack();
        server.registerMBean(jack, new ObjectName("jack:name=Jack"));
        jack.addNotificationListener(new HelloListener(), null, hello);
        Thread.sleep(500000);
    }
}      

我們利用jconsole調用jack的hi方法,這裡當jack發出消息後,Notification被廣播至所有的MBean,當有MBean屬于Hello類時則調用Hello的printHello()方法。

JMX的應用

在linux下利用jmx監控Tomcat,在catlina.sh中進行一些環境變零的配置

  • Dcom.sun.management.jmxremote=true:相關 JMX 代理偵聽開關
  • Djava.rmi.server.hostname:伺服器端的IP
  • Dcom.sun.management.jmxremote.port=29094:相關 JMX 代理偵聽請求的端口
  • Dcom.sun.management.jmxremote.ssl=false:指定是否使用 SSL 通訊
  • Dcom.sun.management.jmxremote.authenticate=false:指定是否需要密碼驗證

二、将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。在預設配置中,bean映射中每個條目的鍵被用作相應條目值引用的bean的對象名。你可以更改此行為,如控制bean的ObjectName執行個體中所述。

使用此配置,testBean bean将作為ObjectName下的MBean公開bean:name=testBean1。預設情況下,bean的所有公共屬性都作為屬性公開,所有公共方法(繼承自對象類的方法除外)作為操作公開。

MBeanExporter是一個生命周期bean。預設情況下,在應用程式生命周期中盡可能晚地導出MBean。你可以通過設定autoStartup标志來配置導出發生的階段或禁用自動注冊。
建立MBeanServer

上一節中顯示的配置假定應用程式運作在一個(且隻有一個)已運作MBeanServer的環境。在本例中,Spring嘗試定位正在運作的MBeanServer,并在該伺服器上注冊bean(如果有的話)。當你的應用程式在具有自己的MBeanServer的容器(如Tomcat或ibm websphere)中運作時,此行為非常有用。

但是,這種方法在獨立環境中或在不提供MBeanServer的容器中運作時沒有用處。為了解決這個問題,你可以通過添加org.springframework.jmx.support.MBeanServerFactoryBean類到你的配置。你還可以通過将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方法,如下例所示:

<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,直到對代理進行第一次調用。

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 MBeanExporter嘗試使用ObjectName向MBeanServer注冊MBeanbean:name=testBean1。如果已經在同一個ObjectName下注冊了一個MBean執行個體,預設行為是fail(并抛出InstanceAlreadyExistsException)。

你可以精确地控制當MBean注冊到MBeanServer時發生的事情。Spring的JMX支援允許三種不同的注冊行為來控制注冊行為,當注冊過程發現一個MBean已經注冊在同一個ObjectName下時。下表總結了這些注冊行為:

  • FAIL_ON_EXISTING:這是預設的注冊行為。如果一個MBean執行個體已經在同一個ObjectName下注冊,那麼正在注冊的MBean不會被注冊,并且會抛出InstanceAlreadyExistsException。現有的MBean不受影響。
  • IGNORE_EXISTING:如果一個MBean執行個體已經在同一個ObjectName下注冊,那麼正在注冊的MBean不會被注冊。現有的MBean不會受到影響,也不會引發異常。在多個應用程式希望在共享MBeanServer中共享一個公共MBean的設定中,這很有用。
  • REPLACE_EXISTING:如果一個MBean執行個體已經在同一個ObjectName下注冊,那麼先前注冊的現有MBean将被登出,而新的MBean将在其位置上注冊(新的MBean實際上替換了以前的執行個體)。

上述中的值定義為RegistrationPolicy類上的枚舉。如果要更改預設注冊行為,則需要将MBeanExporter定義上的registrationPolicy屬性的值設定為這些值之一。

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

三、控制bean的管理接口

時刻與技術進步,每天一點滴,日久一大步!!!

本部落格隻為記錄,用于學習,如有冒犯,請私信于我。