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
输出将如下所示
注意:如果运行此脚本时出现错误,请参阅下面的“故障排除”部分。
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)
看起来像
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 连接到它。它看起来像这样
我们使用 -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 ->
// 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 组件都可以注册以接收来自此发射器的消息。