JMX 使用指南

1. 介绍

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

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)

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

  • 使用 MXBeans 监控 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://: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=///docs,J2EEApplication=none,J2EEServer=none
Catalina:j2eeType=WebModule,name=///manager,J2EEApplication=none,J2EEServer=none
Catalina:j2eeType=WebModule,name=///,J2EEApplication=none,J2EEServer=none
Catalina:j2eeType=WebModule,name=///examples,J2EEApplication=none,J2EEServer=none
Catalina:j2eeType=WebModule,name=///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://: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://: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://: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 感知。

这是一个示例类 (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,通过 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() 支持完整的连接器 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://:9000/jmxrmi"

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

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

这发生在安装了 Groovy 1.6 的 Mac 和 Linux (CentOS 5) 上。也许对 /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。

  • ignore - 如果要导出的 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 和所有其他 MBean 描述符信息生成默认的 ObjectName

  • 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 允许您完全描述要导出到 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", date object]。默认为"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 为开发人员提供了监听 MBeanServer 中注册的 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 (必需): 要监听的组件的 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 秒分辨率的计时器。然后,我们为该计时器指定一个监听器,该监听器将每秒打印“哔”。

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

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