使用 JMX

1. 简介

Java 管理扩展 (JMX) 技术为管理 JDK 上的资源(如应用程序、设备和服务)提供了一种标准方法。每个要管理的资源都由一个 *管理 Bean*(或 *MBean*)表示。鉴于 Groovy 直接位于 Java 之上,Groovy 可以利用为 Java 完成的大量 JMX 工作。此外,Groovy 在 groovy-jmx 模块中提供了 GroovyMBean 类,该类使 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

首先启动带有 JMX 监控功能的 Tomcat,方法是设置以下内容

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://127.0.0.1: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=//127.0.0.1/docs,J2EEApplication=none,J2EEServer=none
Catalina:j2eeType=WebModule,name=//127.0.0.1/manager,J2EEApplication=none,J2EEServer=none
Catalina:j2eeType=WebModule,name=//127.0.0.1/,J2EEApplication=none,J2EEServer=none
Catalina:j2eeType=WebModule,name=//127.0.0.1/examples,J2EEApplication=none,J2EEServer=none
Catalina:j2eeType=WebModule,name=//127.0.0.1/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://127.0.0.1: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://127.0.0.1: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://127.0.0.1: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://127.0.0.1:9000/jmxrmi"

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

Caught: java.io.IOException: Cannot bind to URL [rmi://127.0.0.1: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 的管理实体都放置在该容器中。您可以在 export() 节点的子节点中放置一个或多个 bean() 或 timer() 节点。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** - 如果同一个 Bean 已经注册,则正在导出的 Bean 将被忽略。

  • **error** - JmxBuilder.export() 在注册期间遇到 Bean 名字冲突时抛出错误。

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() 节点将**将代表**声明的 POJO** 实例的 MBean 导出到 MBeanServer。

  • 构建器将**为 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" 被完整描述**,使用了支持所有 JMX 属性描述符(即 desc、readable、writable、defaultValue)。但是,我们使用通配符来描述属性 **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 允许您**完整描述**要导出到 MBeanServer 的构造函数(请参阅上面的语法)。

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 事件类型字符串(默认值为 **"jmx.builder.event"**)。

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

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

  • **startDate**:- 定时器的启动时间。有效值的集合 [ "now",日期对象 ]。默认值为 "now"

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

  • 次数: - 一个数字,表示定时器重复的次数。默认值为无限次。

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 将使用此格式将“event”对象传递给闭包。event 对象包含有关拦截的事件的信息,以便处理程序可以处理它。参数将包含不同的信息集,具体取决于捕获的事件。

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
            ]
        ]
    )
}

上面的代码片段展示了如何声明一个“onCall”闭包用作监听器,当在 MBean 上调用操作“start()”时。这个示例使用方法指针语法来展示 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 (required): 要监听的组件的 JMX ObjectName。这可以指定为字符串或 ObjectName 的实例。

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

以下是一个 JmxBuilder 的 listener 节点的示例

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!")

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