1. 简介

Java 管理扩展 (JMX) 技术提供了一种标准方法,用于管理 JDK 上的资源,例如应用程序、设备和服务。要管理的每个资源都由一个管理 Bean(或MBean)表示。鉴于 Groovy 直接位于 Java 之上,Groovy 可以利用为 Java 完成的大量 JMX 工作。此外,Groovy 提供了GroovyMBean 类(在groovy-jmx 模块中),它使 MBean 看起来像一个普通的 Groovy 对象,并简化了与 MBean 交互的 Groovy 代码。例如,以下代码

println server.getAttribute(beanName, 'Age')
server.setAttribute(beanName, new Attribute('Name', 'New name'))
Object[] params = [5, 20]
String[] signature = [Integer.TYPE, Integer.TYPE]
println server.invoke(beanName, 'add', params, signature)

可以简化为

def mbean = new GroovyMBean(server, beanName)
println mbean.Age
mbean.Name = 'New name'
println mbean.add(5, 20)

本页的其余部分将向您展示如何

  • 使用 MXBean 监控 JVM

  • 监控 Apache Tomcat 并显示统计信息

  • 监控 Oracle OC4J 并显示信息

  • 监控 BEA WebLogic 并显示信息

  • 利用 Spring 的 MBean 注解支持将您的 Groovy bean 导出为 MBean

2. 监控 JVM

MBean 不是由应用程序直接访问的,而是由称为MBean 服务器的存储库管理。Java 包含一个名为平台 MBean 服务器的特殊 MBean 服务器,它内置于 JVM 中。平台 MBean 使用唯一名称在该服务器中注册。

您可以通过以下代码通过其平台 MBean 监控 JVM

import java.lang.management.*

def os = ManagementFactory.operatingSystemMXBean
println """OPERATING SYSTEM:
\tarchitecture = $os.arch
\tname = $os.name
\tversion = $os.version
\tprocessors = $os.availableProcessors
"""

def rt = ManagementFactory.runtimeMXBean
println """RUNTIME:
\tname = $rt.name
\tspec name = $rt.specName
\tvendor = $rt.specVendor
\tspec version = $rt.specVersion
\tmanagement spec version = $rt.managementSpecVersion
"""

def cl = ManagementFactory.classLoadingMXBean
println """CLASS LOADING SYSTEM:
\tisVerbose = ${cl.isVerbose()}
\tloadedClassCount = $cl.loadedClassCount
\ttotalLoadedClassCount = $cl.totalLoadedClassCount
\tunloadedClassCount = $cl.unloadedClassCount
"""

def comp = ManagementFactory.compilationMXBean
println """COMPILATION:
\ttotalCompilationTime = $comp.totalCompilationTime
"""

def mem = ManagementFactory.memoryMXBean
def heapUsage = mem.heapMemoryUsage
def nonHeapUsage = mem.nonHeapMemoryUsage
println """MEMORY:
HEAP STORAGE:
\tcommitted = $heapUsage.committed
\tinit = $heapUsage.init
\tmax = $heapUsage.max
\tused = $heapUsage.used
NON-HEAP STORAGE:
\tcommitted = $nonHeapUsage.committed
\tinit = $nonHeapUsage.init
\tmax = $nonHeapUsage.max
\tused = $nonHeapUsage.used
"""

ManagementFactory.memoryPoolMXBeans.each { mp ->
    println "\tname: " + mp.name
    String[] mmnames = mp.memoryManagerNames
    mmnames.each{ mmname ->
        println "\t\tManager Name: $mmname"
    }
    println "\t\tmtype = $mp.type"
    println "\t\tUsage threshold supported = " + mp.isUsageThresholdSupported()
}
println()

def td = ManagementFactory.threadMXBean
println "THREADS:"
td.allThreadIds.each { tid ->
    println "\tThread name = ${td.getThreadInfo(tid).threadName}"
}
println()

println "GARBAGE COLLECTION:"
ManagementFactory.garbageCollectorMXBeans.each { gc ->
    println "\tname = $gc.name"
    println "\t\tcollection count = $gc.collectionCount"
    println "\t\tcollection time = $gc.collectionTime"
    String[] mpoolNames = gc.memoryPoolNames
    mpoolNames.each { mpoolName ->
        println "\t\tmpool name = $mpoolName"
    }
}

运行时,您将看到类似以下内容

OPERATING SYSTEM:
            	architecture = amd64
            	name = Windows 10
            	version = 10.0
            	processors = 12

RUNTIME:
            	name = 724176@QUOKKA
            	spec name = Java Virtual Machine Specification
            	vendor = Oracle Corporation
            	spec version = 11
            	management spec version = 2.0

CLASS LOADING SYSTEM:
            	isVerbose = false
            	loadedClassCount = 6962
            	totalLoadedClassCount = 6969
            	unloadedClassCount = 0

COMPILATION:
            	totalCompilationTime = 7548

MEMORY:
            HEAP STORAGE:
            	committed = 645922816
            	init = 536870912
            	max = 8560574464
            	used = 47808352
            NON-HEAP STORAGE:
            	committed = 73859072
            	init = 7667712
            	max = -1
            	used = 70599520

	name: CodeHeap 'non-nmethods'
		Manager Name: CodeCacheManager
		mtype = Non-heap memory
		Usage threshold supported = true
	name: Metaspace
		Manager Name: Metaspace Manager
		mtype = Non-heap memory
		Usage threshold supported = true
	name: CodeHeap 'profiled nmethods'
		Manager Name: CodeCacheManager
		mtype = Non-heap memory
		Usage threshold supported = true
	name: Compressed Class Space
		Manager Name: Metaspace Manager
		mtype = Non-heap memory
		Usage threshold supported = true
	name: G1 Eden Space
		Manager Name: G1 Old Generation
		Manager Name: G1 Young Generation
		mtype = Heap memory
		Usage threshold supported = false
	name: G1 Old Gen
		Manager Name: G1 Old Generation
		Manager Name: G1 Young Generation
		mtype = Heap memory
		Usage threshold supported = true
	name: G1 Survivor Space
		Manager Name: G1 Old Generation
		Manager Name: G1 Young Generation
		mtype = Heap memory
		Usage threshold supported = false
	name: CodeHeap 'non-profiled nmethods'
		Manager Name: CodeCacheManager
		mtype = Non-heap memory
		Usage threshold supported = true

THREADS:
	Thread name = Reference Handler
	Thread name = Finalizer
	Thread name = Signal Dispatcher
	Thread name = Attach Listener
	Thread name = Common-Cleaner
	Thread name = Java2D Disposer
	Thread name = AWT-Shutdown
	Thread name = AWT-Windows
	Thread name = Image Fetcher 0
	Thread name = AWT-EventQueue-0
	Thread name = D3D Screen Updater
	Thread name = DestroyJavaVM
	Thread name = TimerQueue
	Thread name = Thread-0

GARBAGE COLLECTION:
	name = G1 Young Generation
		collection count = 6
		collection time = 69
		mpool name = G1 Eden Space
		mpool name = G1 Survivor Space
		mpool name = G1 Old Gen
	name = G1 Old Generation
		collection count = 0
		collection time = 0
		mpool name = G1 Eden Space
		mpool name = G1 Survivor Space
		mpool name = G1 Old Gen

3. 监控 Tomcat

首先启动 Tomcat,并通过设置以下内容启用 JMX 监控

set JAVA_OPTS=-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9004\
 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false

您可以在启动脚本中执行此操作,并且可以选择任何可用的端口,我们使用的是 9004。

以下代码使用 JMX 在运行的 Tomcat 中发现可用的 MBean,确定哪些是 Web 模块,提取每个 Web 模块的处理时间,并使用 JFreeChart 在图表中显示结果

import groovy.swing.SwingBuilder
import groovy.jmx.GroovyMBean

import javax.management.ObjectName
import javax.management.remote.JMXConnectorFactory as JmxFactory
import javax.management.remote.JMXServiceURL as JmxUrl
import javax.swing.WindowConstants as WC

import org.jfree.chart.ChartFactory
import org.jfree.data.category.DefaultCategoryDataset as Dataset
import org.jfree.chart.plot.PlotOrientation as Orientation

def serverUrl = 'service:jmx:rmi:///jndi/rmi://localhost:9004/jmxrmi'
def server = JmxFactory.connect(new JmxUrl(serverUrl)).MBeanServerConnection
def serverInfo = new GroovyMBean(server, 'Catalina:type=Server').serverInfo
println "Connected to: $serverInfo"

def query = new ObjectName('Catalina:*')
String[] allNames = server.queryNames(query, null)
def modules = allNames.findAll { name ->
    name.contains('j2eeType=WebModule')
}.collect{ new GroovyMBean(server, it) }

println "Found ${modules.size()} web modules. Processing ..."
def dataset = new Dataset()

modules.each { m ->
    println m.name()
    dataset.addValue m.processingTime, 0, m.path
}

def labels = ['Time per Module', 'Module', 'Time']
def options = [false, true, true]
def chart = ChartFactory.createBarChart(*labels, dataset,
                Orientation.VERTICAL, *options)
def swing = new SwingBuilder()
def frame = swing.frame(title:'Catalina Module Processing Time', defaultCloseOperation:WC.DISPOSE_ON_CLOSE) {
    panel(id:'canvas') { rigidArea(width:800, height:350) }
}
frame.pack()
frame.show()
chart.draw(swing.canvas.graphics, swing.canvas.bounds)

运行时,我们将看到进度跟踪

Connected to: Apache Tomcat/9.0.37
Found 5 web modules. Processing ...
Catalina:j2eeType=WebModule,name=//localhost/docs,J2EEApplication=none,J2EEServer=none
Catalina:j2eeType=WebModule,name=//localhost/manager,J2EEApplication=none,J2EEServer=none
Catalina:j2eeType=WebModule,name=//localhost/,J2EEApplication=none,J2EEServer=none
Catalina:j2eeType=WebModule,name=//localhost/examples,J2EEApplication=none,J2EEServer=none
Catalina:j2eeType=WebModule,name=//localhost/host-manager,J2EEApplication=none,J2EEServer=none

输出将如下所示

catalina

注意:如果您在运行此脚本时遇到错误,请参见下面的“故障排除”部分。

4. OC4J 示例

这是一个访问 OC4J 并打印有关服务器、其运行时(例如)和配置的 JMS 目标的一些信息的脚本

import javax.management.remote.*
import oracle.oc4j.admin.jmx.remote.api.JMXConnectorConstant

def serverUrl = new JMXServiceURL('service:jmx:rmi://localhost:23791')
def serverPath = 'oc4j:j2eeType=J2EEServer,name=standalone'
def jvmPath = 'oc4j:j2eeType=JVM,name=single,J2EEServer=standalone'
def provider = 'oracle.oc4j.admin.jmx.remote'
def credentials = [
    (JMXConnectorConstant.CREDENTIALS_LOGIN_KEY): 'oc4jadmin',
    (JMXConnectorConstant.CREDENTIALS_PASSWORD_KEY): 'admin'
]
def env = [
    (JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES): provider,
    (JMXConnector.CREDENTIALS): credentials
]
def server = JmxFactory.connect(serverUrl, env).MBeanServerConnection
def serverInfo = new GroovyMBean(server, serverPath)
def jvmInfo = new GroovyMBean(server, jvmPath)
println """Connected to $serverInfo.node. \
Server started ${new Date(serverInfo.startTime)}.
OC4J version:  $serverInfo.serverVersion from $serverInfo.serverVendor
JVM version:   $jvmInfo.javaVersion from $jvmInfo.javaVendor
Memory usage:  $jvmInfo.freeMemory bytes free, \
$jvmInfo.totalMemory bytes total
"""

def query = new javax.management.ObjectName('oc4j:*')
String[] allNames = server.queryNames(query, null)
def dests = allNames.findAll { name ->
    name.contains('j2eeType=JMSDestinationResource')
}.collect { new GroovyMBean(server, it) }

println "Found ${dests.size()} JMS destinations. Listing ..."
dests.each { d -> println "$d.name: $d.location" }

以下是运行此脚本的结果

Connected to LYREBIRD. Server started Thu May 31 21:04:54 EST 2007.
OC4J version:  11.1.1.0.0 from Oracle Corp.
JVM version:   1.6.0_01 from Sun Microsystems Inc.
Memory usage:  8709976 bytes free, 25153536 bytes total

Found 5 JMS destinations. Listing ...
Demo Queue: jms/demoQueue
Demo Topic: jms/demoTopic
jms/Oc4jJmsExceptionQueue: jms/Oc4jJmsExceptionQueue
jms/RAExceptionQueue: jms/RAExceptionQueue
OracleASRouter_store: OracleASRouter_store

略有变化,此脚本使用 JFreeChart 显示内存使用情况的饼图

import org.jfree.chart.ChartFactory
import javax.swing.WindowConstants as WC
import javax.management.remote.*
import oracle.oc4j.admin.jmx.remote.api.JMXConnectorConstant

def url = 'service:jmx:rmi://localhost:23791'
def credentials = [:]
credentials[JMXConnectorConstant.CREDENTIALS_LOGIN_KEY] = "oc4jadmin"
credentials[JMXConnectorConstant.CREDENTIALS_PASSWORD_KEY] = "password"
def env = [:]
env[JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES] = "oracle.oc4j.admin.jmx.remote"
env[JMXConnector.CREDENTIALS] = credentials
def server = JMXConnectorFactory.connect(new JMXServiceURL(url), env).MBeanServerConnection
def jvmInfo = new GroovyMBean(server, 'oc4j:j2eeType=JVM,name=single,J2EEServer=standalone')

def piedata = new org.jfree.data.general.DefaultPieDataset()
piedata.setValue "Free", jvmInfo.freeMemory
piedata.setValue "Used", jvmInfo.totalMemory - jvmInfo.freeMemory

def options = [true, true, true]
def chart = ChartFactory.createPieChart('OC4J Memory Usage', piedata, *options)
chart.backgroundPaint = java.awt.Color.white
def swing = new groovy.swing.SwingBuilder()
def frame = swing.frame(title:'OC4J Memory Usage', defaultCloseOperation:WC.EXIT_ON_CLOSE) {
    panel(id:'canvas') { rigidArea(width:350, height:250) }
}
frame.pack()
frame.show()
chart.draw(swing.canvas.graphics, swing.canvas.bounds)

它看起来像

oc4jpie

5. WebLogic 示例

此脚本打印有关服务器的信息,然后打印有关 JMS 目标的信息(例如)。许多其他 mbean 都可用

import javax.management.remote.*
import javax.management.*
import javax.naming.Context
import groovy.jmx.GroovyMBean

def urlRuntime = '/jndi/weblogic.management.mbeanservers.runtime'
def urlBase = 'service:jmx:t3://localhost:7001'

def serviceURL = new JMXServiceURL(urlBase + urlRuntime)
def h = new Hashtable()
h.put(Context.SECURITY_PRINCIPAL, 'weblogic')
h.put(Context.SECURITY_CREDENTIALS, 'weblogic')
h.put(JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES, 'weblogic.management.remote')
def server = JMXConnectorFactory.connect(serviceURL, h).MBeanServerConnection
def domainName = new ObjectName('com.bea:Name=RuntimeService,Type=weblogic.management.mbeanservers.runtime.RuntimeServiceMBean')
def rtName = server.getAttribute(domainName, 'ServerRuntime')
def rt = new GroovyMBean(server, rtName)
println "Server: name=$rt.Name, state=$rt.State, version=$rt.WeblogicVersion"
def destFilter = Query.match(Query.attr('Type'), Query.value('JMSDestinationRuntime'))
server.queryNames(new ObjectName('com.bea:*'), destFilter).each { name ->
    def jms = new GroovyMBean(server, name)
    println "JMS Destination: name=$jms.Name, type=$jms.DestinationType, messages=$jms.MessagesReceivedCount"
}

以下是输出

Server: name=examplesServer, state=RUNNING, version=WebLogic Server 10.0  Wed May 9 18:10:27 EDT 2007 933139
JMS Destination: name=examples-jms!exampleTopic, type=Topic, messages=0
JMS Destination: name=examples-jms!exampleQueue, type=Queue, messages=0
JMS Destination: name=examples-jms!jms/MULTIDATASOURCE_MDB_QUEUE, type=Queue, messages=0
JMS Destination: name=examplesJMSServer!examplesJMSServer.TemporaryQueue0, type=Queue, messages=68
JMS Destination: name=examples-jms!quotes, type=Topic, messages=0
JMS Destination: name=examples-jms!weblogic.wsee.wseeExamplesDestinationQueue, type=Queue, messages=0
JMS Destination: name=examples-jms!weblogic.examples.ejb30.ExampleQueue, type=Queue, messages=0

6. Spring 示例

您还可以使用 Spring 自动将 bean 注册为 JMX 敏感 bean。

这是一个示例类(Calculator.groovy)

import org.springframework.jmx.export.annotation.*

@ManagedResource(objectName="bean:name=calcMBean", description="Calculator MBean")
public class Calculator {

    private int invocations

    @ManagedAttribute(description="The Invocation Attribute")
    public int getInvocations() {
        return invocations
    }

    private int base = 10

    @ManagedAttribute(description="The Base to use when adding strings")
    public int getBase() {
        return base
    }

    @ManagedAttribute(description="The Base to use when adding strings")
    public void setBase(int base) {
        this.base = base
    }

    @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) {
        invocations++
        return x + y
    }

    @ManagedOperation(description="Add two strings representing numbers of a particular base")
    @ManagedOperationParameters([
        @ManagedOperationParameter(name="x", description="The first number"),
        @ManagedOperationParameter(name="y", description="The second number")])
    public String addStrings(String x, String y) {
        invocations++
        def result = Integer.valueOf(x, base) + Integer.valueOf(y, base)
        return Integer.toString(result, base)
    }
}

这是 Spring 配置文件 (beans.xml)

<?xml version="1.0" encoding="UTF-8"?>
<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 http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="mbeanServer"
          class="org.springframework.jmx.support.MBeanServerFactoryBean">
        <property name="locateExistingServerIfPossible" value="true"/>
    </bean>

    <bean id="exporter"
          class="org.springframework.jmx.export.MBeanExporter">
        <property name="assembler" ref="assembler"/>
        <property name="namingStrategy" ref="namingStrategy"/>
        <property name="beans">
            <map>
                <entry key="bean:name=defaultCalcName" value-ref="calcBean"/>
            </map>
        </property>
        <property name="server" ref="mbeanServer"/>
        <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="calcBean"
          class="Calculator">
        <property name="base" value="10"/>
    </bean>
</beans>

这是一个使用此 bean 和配置的脚本

import org.springframework.context.support.ClassPathXmlApplicationContext
import java.lang.management.ManagementFactory
import javax.management.ObjectName
import javax.management.Attribute
import groovy.jmx.GroovyMBean

// get normal bean
def ctx = new ClassPathXmlApplicationContext("beans.xml")
def calc = ctx.getBean("calcBean")

Thread.start {
    // access bean via JMX, use a separate thread just to
    // show that we could access remotely if we wanted
    def server = ManagementFactory.platformMBeanServer
    def mbean = new GroovyMBean(server, 'bean:name=calcMBean')
    sleep 1000
    assert 8 == mbean.add(7, 1)
    mbean.Base = 8
    assert '10' == mbean.addStrings('7', '1')
    mbean.Base = 16
    sleep 2000
    println "Number of invocations: $mbean.Invocations"
    println mbean
}

assert 15 == calc.add(9, 6)
assert '11' == calc.addStrings('10', '1')
sleep 2000
assert '20' == calc.addStrings('1f', '1')

以下是生成的输出

Number of invocations: 5
MBean Name:
  bean:name=calcMBean

Attributes:
  (rw) int Base
  (r) int Invocations
Operations:
  int add(int x, int y)
  java.lang.String addStrings(java.lang.String x, java.lang.String y)
  int getInvocations()
  int getBase()
  void setBase(int p1)

您甚至可以使用 jconsole 在进程运行时连接到它。它看起来像

jconsole

我们使用 -Dcom.sun.management.jmxremote JVM 参数启动了 Groovy 应用程序。

另请参阅

7. 故障排除

7.1. java.lang.SecurityException

如果您遇到以下错误,则容器的 JMX 访问受到密码保护

java.lang.SecurityException: Authentication failed! Credentials required

要解决此问题,请在连接时添加包含凭据的环境,如下所示(密码必须先设置好)

def jmxEnv = null
if (password != null) {
    jmxEnv = [(JMXConnector.CREDENTIALS): (String[])["monitor", password]]
}
def connector = JMXConnectorFactory.connect(new JMXServiceURL(serverUrl), jmxEnv)

您尝试监控/管理的软件的详细信息可能略有不同。如果适用,请查看上面使用凭据的其他示例(例如 OC4J 和 WebLogic)。如果您仍然遇到问题,您将必须咨询您尝试监控/管理的软件的文档以了解有关如何提供凭据的详细信息。

8. JmxBuilder

JmxBuilder 是一个基于 Groovy 的特定于领域的语言,用于 Java 管理扩展 (JMX) API。它使用构建器模式(FactoryBuilder)创建一个内部 DSL,该 DSL 通过 MBean 服务器简化了 POJO 和 Groovy bean 作为管理组件的公开。JmxBuilder 隐藏了通过 JMX API 创建和导出管理 bean 的复杂性,并提供了一组自然的 Groovy 结构来与 JMX 基础设施交互。

8.1. 实例化 JmxBuilder

要开始使用 JmxBuilder,只需确保 jar 文件位于您的类路径上。然后,您可以在代码中执行以下操作

def jmx = new JmxBuilder()

就是这样!您现在可以使用 JmxBuilder。

注意

  • 您可以将您自己的 MBeanServer 实例传递给构建器 (JmxBuilder(MBeanServer))

  • 如果未指定 MBeanServer,则构建器实例将默认为底层平台 MBeanServer。

获得 JmxBuilder 实例后,您就可以调用它的任何构建器节点。

8.2. JMX 连接器

远程连接是 JMX 架构的关键部分。JmxBuilder 通过最少的编码量来简化连接器服务器和连接器客户端的创建。

8.2.1. 连接器服务器

JmxBuilder.connectorServer() 支持完整的 Connector api 语法,并允许您指定属性、覆盖 URL、指定您自己的主机等。

语法

jmx.connectorServer(
    protocol:"rmi",
    host:"...",
    port:1099,
    url:"...",
    properties:[
        "authenticate":true|false,
        "passwordFile":"...",
        "accessFile":"...",
        "sslEnabled" : true | false
        // any valid connector property
    ]
)

请注意,serverConnector 节点将接受四个 ServerConnector 属性别名(authenticate、passwordFile、accessFile 和 sslEnabled)。您可以使用这些别名或提供任何 RMI 支持的属性。

示例 - 连接器服务器(请参见下面的更正)

jmx.connectorServer(port: 9000).start()

上面的代码片段返回一个 RMI 连接器,它将在端口 9000 上开始监听。默认情况下,构建器将在内部生成 URL "service:jmx:rmi:///jndi/rmi://localhost:9000/jmxrmi"

注意:遗憾的是,当尝试运行上面的代码片段时,您可能会遇到类似以下内容(示例不完整,请参见下面)

Caught: java.io.IOException: Cannot bind to URL [rmi://localhost:9000/jmxrmi]: javax.naming.ServiceUnavailableException [Root exception is java.rmi.ConnectException: Connection refused to host: localhost; nested exception is:
?????? java.net.ConnectException: Connection refused]
??

这在 Mac 和 Linux(CentOS 5)上使用安装的 Groovy 1.6 发生。也许是对 /etc/hosts 文件配置的假设?

正确的示例如下所示。

连接器示例(已更正) - 连接器服务器

上面的示例不会创建 RMI 注册表。因此,为了导出,您必须首先导出 RMI 对象注册表(确保导入 java.rmi.registry.LocateRegistry)。

import java.rmi.registry.LocateRegistry
//...

LocateRegistry.createRegistry(9000)
jmx.connectorServer(port: 9000).start()

8.2.2. 连接器客户端

JmxBuilder.connectorClient() 节点允许您创建 JMX 连接器客户端对象以连接到 JMX MBean 服务器。

语法

jmx.connectorClient (
    protocol:"rmi",
    host:"...",
    port:1099,
    url:"...",
)

示例 - 客户端连接器

创建连接器客户端非常容易。只需一行代码,您就可以创建一个 JMX 连接器客户端实例,如下所示。

def client = jmx.connectorClient(port: 9000)
client.connect()

然后,您可以使用以下方法访问与连接器关联的 MBeanServerConnection

client.getMBeanServerConnection()

8.3. JmxBuilder MBean 导出

您可以导出 Java 对象或 Groovy 对象,代码量很少。JmxBuilder 甚至会找到并导出在运行时注入的动态 Groovy 方法

8.3.1. 隐式与显式描述符

使用构建器时,您可以让 JmxBuilder 隐式生成所有 MBean 描述符信息。当您想编写最少的代码来快速导出 bean 时,这很有用。您也可以显式声明所有 bean 的描述符信息。这使您可以完全控制如何描述要为底层 bean 导出的所有信息。

8.3.2. JmxBuilder.export() 节点

JmxBuilder.export() 节点提供了一个容器,其中放置了要导出到 MBeanServer 的所有管理实体。您可以将一个或多个 bean() 或 timer() 节点作为 export() 节点的子节点。JmxBuilder 将自动批量导出节点所描述的实体,以便在 MBean 服务器中进行管理(请参见下面的示例)。

def beans = jmx.export {
    bean(new Foo())
    bean(new Bar())
    bean(new SomeBar())
}

在上面的代码片段中,JmxBuilder.export() 将导出三个管理 bean 到 MBean 服务器。

8.3.3. JmxBuilder.export() 语法

JmxBuilder.export() 节点支持registrationPolicy 参数,用于指定 JmxBuilder 在 MBean 注册期间如何处理 bean 名称冲突

jmx.export(policy:"replace|ignore|error")
or
jmx.export(regPolicy:"replace|ignore|error")
  • replace - JmxBuilder.export() 将替换导出期间已在 MBean 中注册的任何 bean。

  • ignore - 如果在 MBean 中已注册相同 bean,则将忽略正在导出的 bean。

  • error - 在注册期间遇到 bean 名称冲突时,JmxBuilder.export() 将抛出错误。

8.3.4. 与 GroovyMBean 类集成

将 MBean 导出到 MBeanServer 时,JmxBuilder 将返回一个 GroovyMBean 实例,该实例代表已由构建器导出的管理 bean。例如bean()timer() 等节点在调用时将返回 GroovyMBean 的实例。export() 节点返回一个GroovyMBean[] 数组,该数组代表导出到 MBean 服务器的所有管理对象。

8.3.5. 使用 JmxBuilder.bean() 注册 MBean

本参考部分使用类RequestController来说明如何使用JmxBuilder导出运行时管理 Bean。此类仅用于说明目的,可以是 POJO 或 Groovy Bean。

RequestController

class RequestController {
    // constructors
    RequestController() { super() }
    RequestController(Map resource) { }

    // attributes
    boolean isStarted() { true }
    int getRequestCount() { 0 }
    int getResourceCount() { 0 }
    void setRequestLimit(int limit) { }
    int getRequestLimit() { 0 }

    // operations
    void start() { }
    void stop() { }
    void putResource(String name, Object resource) { }
    void makeRequest(String res) { }
    void makeRequest() { }
}
隐式导出

如前所述,您可以使用 JmxBuilder 的灵活语法导出任何 POJO/POGO,而无需描述符。构建器可以使用隐式默认值自动描述管理 Bean 的所有方面。这些默认值可以很容易地被覆盖,我们将在下一节中看到。

导出 POJO 或 POGO 的最简单方法如下所示。

jmx.export {
    bean(new RequestController(resource: "Hello World"))
}

作用

  • 首先,JmxBuilder.export() 节点将导出一个 MBean 到 MBeanServer,代表声明的 POJO实例。

  • 构建器将为 MBean 生成默认的 ObjectName,以及所有其他 MBean 描述符信息。

  • JmxBuilder 将自动导出实例上声明的所有属性(MBean getter/setter)、构造函数操作

  • 导出的属性将具有只读可见性。

请记住,JmxBuilder.export() 返回一个 GroovyMBean[] 对象数组,用于所有导出的实例。因此,一旦您调用 JmxBuilder.export(),您就可以立即访问底层 MBean 代理(通过 GroovyMBean)。

8.3.6. JmxBuilder.bean() 语法

JmxBuilder.bean() 节点支持一组广泛的描述符来描述您的 Bean 以进行管理。JMX MBeanServer 使用这些描述符来公开有关 Bean 的元数据,这些元数据用于管理。

jmx.export {
    bean(
        target:bean instance,
        name:ObjectName,
        desc:"...",
        attributes:"*",
        attributes:[]
        attributes:[ "AttrubuteName1","AttributeName2",...,"AttributeName_n" ]
        attributes:[
            "AttributeName":"*",
            "AttributeName":[
                desc:"...",
                defaultValue:value,
                writable:true|false,
                editable:true|false,
                onChange:{event-> // event handler}
            ]
        ],

        constructors:"*",
        constructors:[
            "Constructor Name":[],
            "Constructor Name":[ "ParamType1","ParamType2,...,ParamType_n" ],
            "Constructor Name":[
                desc:"...",
                params:[
                    "ParamType1":"*",
                    "ParamType2":[desc:"...", name:"..."],...,
                    "ParamType_n":[desc:"...", name:"..."]
                ]
            ]
        ],

        operations:"*",
        operations:[ "OperationName1", "OperationName2",...,"OperationNameN" ],
        operations:[
            "OperationName1":"*",
            "OperationName2":[ "type1","type2,"type3" ]
            "OperationName3":[
                desc:"...",
                params:[
                    "ParamType1":"*"
                    "ParamType2":[desc:"...", name:"..."],...,
                    "ParamType_n":[desc:"...", name:"..."]
                ],
                onInvoked:{event-> JmxBuilder.send(event:"", to:"")}
            ]
        ],

        listeners:[
            "ListenerName1":[event: "...", from:ObjectName, call:{event->}],
            "ListenerName2":[event: "...", from:ObjectName, call:&methodPointer]
        ]

    )
}

为了不描述整个节点,以下部分将分别探讨每个属性。

8.3.7. Bean() 节点 - 指定 MBean ObjectName

使用 bean() 节点描述符,您可以指定自己的 MBean ObjectName。

def ctrl = new RequestController(resource:"Hello World")
def beans = jmx.export {
    bean(target: ctrl, name: "jmx.tutorial:type=Object")
}

ObjectName 可以指定为字符串或 ObjectName 的实例。

8.4. Bean() 节点 - 属性导出

JMX 属性是底层 Bean 上的 setter 和 getter。JmxBuilder.bean() 节点提供了几种方法来灵活地描述和导出 MBean 属性。您可以将它们组合起来,以实现任何级别的属性可见性。让我们来看看。

8.4.1. 使用通配符 "*" 导出所有属性

以下代码段将描述和导出 Bean 上的所有属性,并设置为只读。JmxBuilder 将使用默认值来描述导出以进行管理的属性。

def objName = new ObjectName("jmx.tutorial:type=Object")
def beans = jmx.export {
    bean(target: new RequestController(),
    name: objName,
    attributes: "*")
}

8.4.2. 导出属性列表

JmxBuilder 允许您指定要导出的属性列表。

def objName = new ObjectName("jmx.tutorial:type=Object")
def beans = jmx.export {
    bean(
        target: new RequestController(),
        name: objName,
        attributes: ["Resource", "RequestCount"]
    )
}

在上面的代码段中,只有 "Resource" 和 "RequestCount" 属性将被导出。同样,由于没有提供描述符,JmxBuilder 将使用合理的默认值来描述导出的属性。

8.4.3. 使用显式描述符导出属性

JmxBuilder 的优势之一是它在描述 MBean 方面的灵活性。使用构建器,您可以描述要导出到 MBeanServer 的 MBean 属性的所有方面(参见上面的语法)。

def objName = new ObjectName("jmx.tutorial:type=Object")
def beans = jmx.export {
    bean(
        target: new RequestController(),
        name: objName,
        attributes: [
            "Resource": [desc: "The resource to request.", readable: true, writable: true, defaultValue: "Hello"],
            "RequestCount": "*"
        ]
    )
}

在上面的代码段中,属性"Resource" 被完全描述,使用所有支持的描述符(即 desc、readable、writable、defaultValue)来描述 JMX 属性。但是,我们使用通配符来描述属性RequestCount,它将被导出并使用默认值进行描述。

8.5. Bean() 节点 - 构造函数导出

JmxBuilder 支持对底层 Bean 中定义的构造函数进行显式描述和导出。导出构造函数时,有多种选项可用。您可以将它们组合起来以实现所需的管理级别。

8.5.1. 使用 "*" 导出所有构造函数

您可以使用构建器的特殊 "*" 符号导出底层 Bean 上声明的所有构造函数。构建器将使用默认值来描述 MBean 构造函数。

def objName = new ObjectName("jmx.tutorial:type=Object")
def beans = jmx.export {
    bean(
        target: new RequestController(),
        name: objName,
        constructors: "*"
    )
}

8.5.2. 使用参数描述符导出构造函数

JmxBuilder 允许您通过描述参数签名来指定要导出的特定构造函数。当您有多个具有不同参数签名的构造函数,而您只想导出特定的构造函数时,这很有用。

def objName = new ObjectName("jmx.tutorial:type=Object")
def beans = jmx.export {
    bean(
        target: new RequestController(),
        name: objName,
        constructors: [
            "RequestController": ["Object"]
        ]
    )
}

在这里,JmxBuilder 将导出一个带有 "Object" 类型的一个参数的构造函数。同样,JmxBuilder 将使用默认值来填充构造函数和参数的描述。

8.5.3. 使用显式描述符导出构造函数

JmxBuilder 允许您完全描述要导出的构造函数(参见上面的语法)。

def objName = new ObjectName("jmx.tutorial:type=Object")
def beans = jmx.export {
    bean(target: new RequestController(), name: objName,
        constructors: [
            "RequestController": [
                desc: "Constructor takes param",
                params: ["Object" : [name: "Resource", desc: "Resource for controller"]]
            ]
        ]
    )
}

在上面的代码中,JmxBuilder 将指定一个带有参数的构造函数来导出到 MBeanServer。请注意,构造函数可以使用所有可选的描述符键进行完全描述,包括参数描述符。

8.6. Bean() 节点 - 操作导出

与构造函数类似,JmxBuilder 支持使用灵活的符号描述和导出 MBean 操作(参见上面的语法)。您可以将这些符号组合起来以实现所需的任何级别的操作可管理性。

8.6.1. 使用 "*" 导出所有操作

您可以使用构建器的特殊 "*" 符号导出 Bean 上定义的所有操作,以进行管理。构建器将使用默认的描述符值来描述要导出的操作。

def objName = new ObjectName("jmx.tutorial:type=Object")
def beans = jmx.export {
    bean(
        target: new RequestController(),
        name: objName,
        operations: "*"
    )
}

在此代码段中,JmxBuilder 将导出所有 Bean 操作,并将使用默认值来描述它们在 MBeanServer 中。

8.6.2. 导出操作列表

JmxBuilder 具有一个简写符号,允许您通过提供一个要导出的方法列表来快速指定要导出的操作。

def objName = new ObjectName("jmx.tutorial:type=Object")
def beans = jmx.export {
    bean(
        target: new RequestController(),
        name: objName,
        operations: ["start", "stop"]
    )
}

在上面的代码段中,构建器将只导出方法 start() 和 stop()。所有其他方法将被忽略。JmxBuilder 将使用默认的描述符值来描述要导出的操作。

8.6.3. 按签名导出操作

使用 JmxBuilder,您可以使用方法的参数签名来指定要导出以进行管理的方法。当您想要区分具有相同名称但要导出的方法(例如 stop() 而不是 stop(boolean))时,这很有用。

def objName = new ObjectName("jmx.tutorial:type=Object")
def beans = jmx.export {
    bean(
        target: new RequestController(),
        name: objName,
        operations: [
            "makeRequest": ["String"]
        ]
    )
}

在上面的代码段中,JmxBuilder 将选择方法 makeRequest(String) 进行导出,而不是另一个不带参数的版本 makeRequest()。在这个简写上下文中,签名被指定为一个类型列表(例如 "String")。

8.6.4. 使用显式描述符导出操作

JmxBuilder 支持 Bean 操作的详细描述符。您可以提供有关 Bean 上任何操作的深度描述符信息,包括名称、描述、方法参数、参数类型和参数描述。

def objName = new ObjectName("jmx.tutorial:type=Object")
def beans = jmx.export {
    bean(target: new RequestController(), name: objName,
        operations: [
            "start": [desc: "Starts request controller"],
            "stop": [desc: "Stops the request controller"],
            "setResource": [params: ["Object"]],
            "makeRequest": [
                desc: "Executes the request.",
                params: [
                    "String": [name: "Resource", desc: "The resource to request"]
                ]
            ]
        ]
    )
}

上面的代码段展示了 JmxBuilder 允许您描述要管理的操作的所有方法。

  • 操作start() 和 stop() 通过 "desc" 键进行描述(这已经足够了,因为没有参数)。

  • 在操作setResource() 中使用了一个params的简写版本:来描述方法的参数。

  • makeRequest() 使用扩展的描述符语法来描述操作的所有方面。

8.7. 嵌入描述符

JmxBuilder 支持将描述符直接嵌入到您的 Groovy 类中。因此,您可以将您的 JMX 描述符直接嵌入到您的类中,而不是将您的描述符封装在声明的对象周围(正如我们在这里所见)。

RequestControllerGroovy

class RequestControllerGroovy {
    // attributes
    boolean started
    int requestCount
    int resourceCount
    int requestLimit
    Map resources

    // operations
    void start() { }
    void stop(){ }
    void putResource(String name, Object resource) { }
    void makeRequest(String res) { }
    void makeRequest() { }

    static descriptor = [
        name: "jmx.builder:type=EmbeddedObject",
        operations: ["start", "stop", "putResource"],
        attributes: "*"
    ]
}

// export
jmx.export(
    bean(new RequestControllerGroovy())
)

上面的代码中有两件事。

  • Groovy 类 RequestControllerGroovy 被定义,并包含一个静态描述符成员。该成员用于声明一个 JmxBuilder 描述符,以描述要导出到 JMX 的类的成员。

  • 代码的第二部分展示了如何使用 JmxBuilder 来导出该类以进行管理。

8.8. 计时器导出

JMX 标准规定,API 的实现提供了一个计时器服务。由于 JMX 是一个基于组件的体系结构,因此计时器提供了一种优秀的信号机制,用于与 MBeanServer 中注册的监听器组件进行通信。JmxBuilder 支持使用我们已经看到的相同简单的语法来创建和导出计时器。

8.8.1. 计时器节点语法

timer(
    name:ObjectName,
    event:"...",
    message:"...",
    data:dataValue
    startDate:"now"|dateValue
    period:"99d"|"99h"|"99m"|"99s"|99
    occurrences:long
)

timer() 节点支持几个属性。

  • name: - 必需。计时器的限定 JMX ObjectName 实例(或字符串)。

  • event: - 将在每个定时信号(默认值为 "jmx.builder.event")广播的 JMX 事件类型字符串。

  • message: - 可以发送给监听器的可选字符串值。

  • data: - 可以发送给定时信号监听器的可选对象。

  • startDate: - 计时器开始的时间。有效的取值集合 [ "now",日期对象 ]。默认值为 "now"

  • period: - 计时器的周期,表示为毫秒数或时间单位(天、小时、分钟、秒)。请参见下面的描述。

  • occurrences: - 一个数字,表示重复计时器的次数。默认值为永远。

8.8.2. 导出计时器

def timer = jmx.timer(name: "jmx.builder:type=Timer", event: "heartbeat", period: "1s")
timer.start()

上面的代码段描述、创建和导出一个标准的 JMX 计时器组件。在这里,timer() 节点返回一个 GroovyMBean,它代表在 MBeanServer 中注册的计时器 MBean。

导出计时器的另一种方法是在 JmxBuilder.export() 节点中。

def beans = jmx.export {
    timer(name: "jmx.builder:type=Timer1", event: "event.signal", period: "1s")
    timer(name: "jmx.builder:type=Timer2", event: "event.log", period: "1s")
}
beans[0].start()
beans[1].start()

8.8.3. 计时器周期

timer() 节点支持灵活的符号来指定计时器周期值。您可以用秒、分钟、小时和天来指定时间。默认值为毫秒。

  • timer(period: 100) = 100 毫秒

  • timer(period: "1s") = 1 秒

  • timer(period: "1m") = 1 分钟

  • timer(period: "1h") = 1 小时

  • timer(period: "1d") = 1 天

节点会自动转换。

8.9. JmxBuilder 和事件

JMX 的一个重要部分是它的事件模型。注册的管理 Bean 可以通过在 MBeanServer 的事件总线上广播事件来相互通信JmxBuilder 提供了几种方法来轻松监听和响应在 MBeanServer 的事件总线上广播的事件。开发人员可以捕获总线上的任何事件,或者抛出他们自己的事件,供在 MBeanServer 上注册的其他组件使用。

8.9.1. 事件处理闭包

JmxBuilder 利用 Groovy 对闭包的使用,提供了简单而优雅的方式来响应 JMX 事件。JmxBuilder 支持两种闭包签名。

无参数
callback = { ->
    // event handling code here.
}

JmxBuilder 执行闭包,并不会传递有关在总线上捕获的事件的任何信息。

带有事件参数
callback = { event ->
    // event handling code
}

JmxBuilder 会使用这种格式将一个**“事件”对象传递给闭包**。 事件对象包含关于拦截事件的信息,以便事件处理程序能够处理。 参数将包含根据捕获的事件而不同的信息集。

8.9.2. 处理属性 onChange 事件

在描述属性时(参见上面的 bean() 节点部分),您可以**提供一个闭包(或方法指针)作为回调,在导出 MBean 上更新属性值时执行**。 这为开发人员提供了一个监听和响应 MBean 上状态更改的机会。

jmx.export {
    bean(
        target: new RequestController(), name: "jmx.tutorial:type=Object",
        attributes: [
            "Resource": [
                readable: true, writable: true,
                onChange: { e ->
                    println e.oldValue
                    println e.newValue
                }
            ]
        ]
    )
}

上面的示例片段展示了如何在描述 MBean 属性时**指定“onChange”回调闭包**。 在此示例代码中,只要通过导出的 MBean 更新属性“Resource”,**onChange 事件就会被执行**。

8.9.3. 属性 onChange 事件对象

在处理属性 onChange 事件时,处理程序闭包将接收一个包含以下信息的事件对象

  • event.oldValue - 更改事件之前的先前属性值。

  • event.newValue - 更改后的属性的新值。

  • event.attribute - 发生事件的属性名称。

  • event.attributeType - 导致事件的属性的数据类型。

  • event.sequenceNumber - 表示事件序列号的数值。

  • event.timeStamp - 事件发生的日期戳。

8.9.4. 处理操作 onCall 事件

与 mbean 属性类似,JmxBuilder 为开发人员提供了**监听 MBeaServer 中注册的 MBean 上的操作调用**的能力。 JmxBuilder 接受一个**回调闭包,该闭包将在 MBean 方法调用后执行**。

class EventHandler {
    void handleStart(e){
        println e
    }
}
def handler = new EventHandler()

def beans = jmx.export {
    bean(target: new RequestController(), name: "jmx.tutorial:type=Object",
        operations: [
            "start": [
                desc:"Starts request controller",
                onCall:handler.&handleStart
            ]
        ]
    )
}

上面的代码片段**展示了如何在操作“start()”在 MBean 上调用时声明“onCall”闭包作为监听器**。 此示例**使用方法指针语法**来说明 JmxBuilder 的通用性。

8.9.5. 操作 onCall 事件对象

在处理操作 onCall 事件时,回调闭包将接收一个包含以下信息的事件对象

  • event.event - 广播的事件类型字符串。

  • event.source - 调用方法的对象。

  • event.data - 导致事件的属性的数据类型。

  • event.sequenceNumber - 表示事件序列号的数值。

  • event.timeStamp - 事件发生的日期戳。

8.10. 监听器 MBean

当您使用 bean() 节点导出 MBean 时,您可以定义 MBean 可以监听和响应的事件。 bean() 节点提供了一个“listeners:”属性,允许您定义 bean 可以响应的事件监听器。

def beans = jmx.export {
    timer(name: "jmx.builder:type=Timer", event: "heartbeat", period: "1s").start()
    bean(target: new RequestController(), name: "jmx.tutorial:type=Object",
        operations: "*",
        listeners: [
            heartbeat: [
                from: "jmx.builder:type=Timer",
                call: { e ->
                    println e
                }
            ]
        ]
    )
}

在上面的示例中,我们看到了**将监听器添加到导出 MBean 的语法**。

  • 首先,**导出并启动计时器**。

  • 然后,**声明一个 MBean 来监听计时器事件并执行一些有意义的操作**。

  • **“heartbeat:”** 名称是任意的,与上面声明的计时器无关。

  • 使用**“from:”属性指定事件的**来源**。

您还可以指定您有兴趣从广播器接收的**事件类型**(因为广播器可以发出多个事件)。

8.10.1. 监听 JMX 事件

在某些情况下,您需要创建独立的事件监听器(未附加到导出的 MBean)。 JmxBuilder 提供了 Listener() 节点,允许您创建可以监听 MBeanServer 事件的 JMX 监听器。 这在创建 JMX 客户端应用程序以监视/管理远程 JMX MBeanServer 上的 JMX 代理时非常有用。

8.10.2. 监听器节点语法

jmx.listener(
    event: "...",
    from: "object name" | ObjectName,
    call: { event-> }
)

以下是**listener()** 节点属性的描述

  • event: 一个可选的字符串,用于标识要监听的 JMX 事件类型。

  • from (必需): 要监听的组件的 JMX ObjectName。 这可以指定为字符串或 ObjectName 的实例。

  • call: 捕获事件时要执行的闭包。 这也可以指定为 Groovy 方法指针。

以下是一个 JmxBuilder 的监听器节点示例

jmx.timer(name: "jmx.builder:type=Timer", period: "1s").start()

jmx.listener(
    from: "jmx.builder:type=Timer",
    call: { e ->
        println "beep..."
    }
)

此示例展示了如何使用独立的监听器(在 MBean 导出之外)。 在这里,我们**导出一个分辨率为 1 秒的计时器**。 然后,我们为该计时器指定一个监听器,该监听器每秒打印“beep”。

8.11. 发射 JMX 事件

JmxBuilder 提供了在 MBeanServer 的事件总线上**广播您自己的事件所需工具**。 您要广播的事件类型没有限制。 您只需**声明您的发射器**和要发送的事件类型,然后在任何时间**广播您的事件**。 MBeanServer 中的任何已注册组件都可以注册为监听您的事件。

8.11.1. 发射器语法

jmx.emitter(name:"Object:Name", event:"type")

节点 Emitter() 的属性可以总结如下

  • name: 一个可选的 JMX ObjectName,用于在 MBeanServer 中注册您的发射器。 默认为 jmx.builder:type=Emitter,name=Emitter@OBJECT_HASH_VALUE

  • event: 一个可选的字符串值,描述 JMX 事件类型。 默认为**“jmx.builder.event.emitter”**。

8.11.2. 声明发射器

def emitter = jmx.emitter()

此代码片段**使用隐式描述符语法声明发射器**。 JmxBuilder 将执行以下操作

  • 使用默认 ObjectName 创建并注册一个发射器 MBean。

  • 使用值为**“jmx.builder.event.emitter”**的**默认事件类型**进行设置。

  • 返回一个代表发射器的 GroovyMBean。

与构建器中的其他节点一样,**您可以覆盖 emitter() 节点中的所有键**。 您可以指定**ObjectName** 和**事件类型**。

8.11.3. 广播事件

声明发射器后,您可以广播事件。

emitter.send()

上面的示例展示了**发射器在声明后发送事件**。 MBeanServer 中注册的任何 JMX 组件都可以注册为接收来自此发射器的信息。

8.11.4. 发送事件对象

在发送消息时,您可以选择将数据传递给接收器。

emitter.send("Hello!")

如果您使用**接受参数的事件监听器闭包(见上文)**,则可以访问该值。

9. 更多 JMX 信息