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的结构一共分为三层:

基础层:主要是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的管理接口

在上一节的示例中,你几乎无法控制bean的管理接口。每个导出bean的所有公共属性和方法分别作为JMX属性和操作公开。为了对导出bean的哪些属性和方法实际公开为JMX属性和操作进行更细粒度的控制,springjmx提供了一个全面的、可扩展的机制来控制bean的管理接口。

 

posted @ 2020-07-14 22:26  codedot  阅读(804)  评论(0编辑  收藏  举报