1. 介绍
Java 管理扩展 (JMX) 技术提供了一种管理 JDK 上应用程序、设备和服务等资源的标准方法。每个要管理的资源都由一个 Managed Bean(或 MBean)表示。鉴于 Groovy 直接位于 Java 之上,Groovy 可以利用 JMX 与 Java 已经完成的大量工作。此外,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)
本页的其余部分将向您展示如何
-
使用 MXBean 监控 JVM
-
监控 Apache Tomcat 并显示统计信息
-
监控 Oracle OC4J 并显示信息
-
监控 BEA WebLogic 并显示信息
-
利用 Spring 的 MBean 注解支持将您的 Groovy bean 导出为 MBean
2. 监控 JVM
MBean 不直接由应用程序访问,而是由一个名为 MBean 服务器 的存储库管理。Java 包含一个特殊的 MBean 服务器,称为 平台 MBean 服务器,它内置于 JVM 中。平台 MBean 使用唯一名称注册到此服务器。
您可以使用以下代码通过其平台 MBean 监控 JVM
import java.lang.management.*
def os = ManagementFactory.operatingSystemMXBean
println """OPERATING SYSTEM:
\tarchitecture = $os.arch
\tname = $os.name
\tversion = $os.version
\tprocessors = $os.availableProcessors
"""
def rt = ManagementFactory.runtimeMXBean
println """RUNTIME:
\tname = $rt.name
\tspec name = $rt.specName
\tvendor = $rt.specVendor
\tspec version = $rt.specVersion
\tmanagement spec version = $rt.managementSpecVersion
"""
def cl = ManagementFactory.classLoadingMXBean
println """CLASS LOADING SYSTEM:
\tisVerbose = ${cl.isVerbose()}
\tloadedClassCount = $cl.loadedClassCount
\ttotalLoadedClassCount = $cl.totalLoadedClassCount
\tunloadedClassCount = $cl.unloadedClassCount
"""
def comp = ManagementFactory.compilationMXBean
println """COMPILATION:
\ttotalCompilationTime = $comp.totalCompilationTime
"""
def mem = ManagementFactory.memoryMXBean
def heapUsage = mem.heapMemoryUsage
def nonHeapUsage = mem.nonHeapMemoryUsage
println """MEMORY:
HEAP STORAGE:
\tcommitted = $heapUsage.committed
\tinit = $heapUsage.init
\tmax = $heapUsage.max
\tused = $heapUsage.used
NON-HEAP STORAGE:
\tcommitted = $nonHeapUsage.committed
\tinit = $nonHeapUsage.init
\tmax = $nonHeapUsage.max
\tused = $nonHeapUsage.used
"""
ManagementFactory.memoryPoolMXBeans.each { mp ->
println "\tname: " + mp.name
String[] mmnames = mp.memoryManagerNames
mmnames.each{ mmname ->
println "\t\tManager Name: $mmname"
}
println "\t\tmtype = $mp.type"
println "\t\tUsage threshold supported = " + mp.isUsageThresholdSupported()
}
println()
def td = ManagementFactory.threadMXBean
println "THREADS:"
td.allThreadIds.each { tid ->
println "\tThread name = ${td.getThreadInfo(tid).threadName}"
}
println()
println "GARBAGE COLLECTION:"
ManagementFactory.garbageCollectorMXBeans.each { gc ->
println "\tname = $gc.name"
println "\t\tcollection count = $gc.collectionCount"
println "\t\tcollection time = $gc.collectionTime"
String[] mpoolNames = gc.memoryPoolNames
mpoolNames.each { mpoolName ->
println "\t\tmpool name = $mpoolName"
}
}
运行时,您将看到类似以下内容
OPERATING SYSTEM: architecture = amd64 name = Windows 10 version = 10.0 processors = 12 RUNTIME: name = 724176@QUOKKA spec name = Java Virtual Machine Specification vendor = Oracle Corporation spec version = 11 management spec version = 2.0 CLASS LOADING SYSTEM: isVerbose = false loadedClassCount = 6962 totalLoadedClassCount = 6969 unloadedClassCount = 0 COMPILATION: totalCompilationTime = 7548 MEMORY: HEAP STORAGE: committed = 645922816 init = 536870912 max = 8560574464 used = 47808352 NON-HEAP STORAGE: committed = 73859072 init = 7667712 max = -1 used = 70599520 name: CodeHeap 'non-nmethods' Manager Name: CodeCacheManager mtype = Non-heap memory Usage threshold supported = true name: Metaspace Manager Name: Metaspace Manager mtype = Non-heap memory Usage threshold supported = true name: CodeHeap 'profiled nmethods' Manager Name: CodeCacheManager mtype = Non-heap memory Usage threshold supported = true name: Compressed Class Space Manager Name: Metaspace Manager mtype = Non-heap memory Usage threshold supported = true name: G1 Eden Space Manager Name: G1 Old Generation Manager Name: G1 Young Generation mtype = Heap memory Usage threshold supported = false name: G1 Old Gen Manager Name: G1 Old Generation Manager Name: G1 Young Generation mtype = Heap memory Usage threshold supported = true name: G1 Survivor Space Manager Name: G1 Old Generation Manager Name: G1 Young Generation mtype = Heap memory Usage threshold supported = false name: CodeHeap 'non-profiled nmethods' Manager Name: CodeCacheManager mtype = Non-heap memory Usage threshold supported = true THREADS: Thread name = Reference Handler Thread name = Finalizer Thread name = Signal Dispatcher Thread name = Attach Listener Thread name = Common-Cleaner Thread name = Java2D Disposer Thread name = AWT-Shutdown Thread name = AWT-Windows Thread name = Image Fetcher 0 Thread name = AWT-EventQueue-0 Thread name = D3D Screen Updater Thread name = DestroyJavaVM Thread name = TimerQueue Thread name = Thread-0 GARBAGE COLLECTION: name = G1 Young Generation collection count = 6 collection time = 69 mpool name = G1 Eden Space mpool name = G1 Survivor Space mpool name = G1 Old Gen name = G1 Old Generation collection count = 0 collection time = 0 mpool name = G1 Eden Space mpool name = G1 Survivor Space mpool name = G1 Old Gen
3. 监控 Tomcat
首先,通过设置以下内容,启动 Tomcat 并启用 JMX 监控
set JAVA_OPTS=-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9004\
-Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false
您可以在启动脚本中执行此操作,并且可以选择任何可用的端口,我们使用了 9004。
以下代码使用 JMX 发现正在运行的 Tomcat 中可用的 MBean,确定哪些是 Web 模块,提取每个 Web 模块的处理时间,并使用 JFreeChart 以图形显示结果
import groovy.swing.SwingBuilder
import groovy.jmx.GroovyMBean
import javax.management.ObjectName
import javax.management.remote.JMXConnectorFactory as JmxFactory
import javax.management.remote.JMXServiceURL as JmxUrl
import javax.swing.WindowConstants as WC
import org.jfree.chart.ChartFactory
import org.jfree.data.category.DefaultCategoryDataset as Dataset
import org.jfree.chart.plot.PlotOrientation as Orientation
def serverUrl = 'service:jmx:rmi:///jndi/rmi://: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] ??
这发生在 Mac 和 Linux (CentOS 5) 上,安装了 Groovy 1.6。也许对 /etc/hosts 文件的配置做了假设?
正确的示例如下所示。 |
连接器示例(已更正) - 连接器服务器
上面的例子没有创建 RMI 注册表。因此,为了导出,您必须首先导出 RMI 对象注册表(确保导入 java.rmi.registry.LocateRegistry
)。
import java.rmi.registry.LocateRegistry
//...
LocateRegistry.createRegistry(9000)
jmx.connectorServer(port: 9000).start()
8.2.2. 连接器客户端
JmxBuilder.connectorClient() 节点允许您创建 JMX 连接器客户端对象以连接到 JMX MBean 服务器。
语法
jmx.connectorClient ( protocol:"rmi", host:"...", port:1099, url:"...", )
示例 - 客户端连接器
创建连接器客户端也同样简单。只需一行代码,您就可以创建一个 JMX 连接器客户端实例,如下所示。
def client = jmx.connectorClient(port: 9000)
client.connect()
然后,您可以使用以下命令访问与连接器关联的 MBeanServerConnection
client.getMBeanServerConnection()
8.3. JmxBuilder MBean 导出
您可以以最少的编码导出 Java 对象或 Groovy 对象。JmxBuilder 甚至会找到并导出运行时注入的动态 Groovy 方法。
8.3.1. 隐式与显式描述符
使用构建器时,您可以让 JmxBuilder 隐式生成所有 MBean 描述符信息。当您想编写最少的代码快速导出 bean 时,这很有用。您还可以显式声明 bean 的所有描述符信息。这使您可以完全控制如何描述要为底层 bean 导出的每条信息。
8.3.2. JmxBuilder.export() 节点
JmxBuilder.export() 节点提供了一个容器,所有要导出到 MBeanServer 的管理实体都放置在此处。您可以将一个或多个 bean() 或 timer() 节点作为 export() 节点的子节点。JmxBuilder 将自动批量导出节点描述的实体到 MBean 服务器进行管理(请参阅下面的示例)。
def beans = jmx.export {
bean(new Foo())
bean(new Bar())
bean(new SomeBar())
}
在上面的代码片段中,JmxBuilder.export() 将导出三个管理 bean 到 MBean 服务器。
8.3.3. JmxBuilder.export() 语法
JmxBuilder.export() 节点支持 registrationPolicy 参数来指定 JmxBuilder 在 MBean 注册期间如何处理 bean 名称冲突
jmx.export(policy:"replace|ignore|error") or jmx.export(regPolicy:"replace|ignore|error")
-
replace - JmxBuilder.export() 将替换导出期间已在 MBean 中注册的任何 bean。
-
ignore - 如果要导出的 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() 节点将导出一个 MBean 到 MBeanServer,代表声明的 POJO 实例。
-
构建器将为 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 可以指定为 String 或 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 允许您完全描述您想要导出(请参阅上述语法)的构造函数。
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,并包含一个 static descriptor 成员。该成员用于声明 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 实例(或 String)。
-
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 秒分辨率的计时器。然后,我们为此计时器指定一个监听器,该监听器将每秒打印“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 组件都可以注册以接收来自此发射器的消息。