使用 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)
本頁面其餘部分將說明如何
-
使用 MXBean 監控 JVM
-
監控 Apache Tomcat 並顯示統計資料
-
監控 Oracle OC4J 並顯示資訊
-
監控 BEA WebLogic 並顯示資訊
-
利用 Spring 的 MBean 註解支援將 Groovy bean 匯出為 MBean
2. 監控 JVM
MBean 不是由應用程式直接存取,而是由一個稱為MBean 伺服器的儲存庫管理。Java 包含一個稱為平台 MBean 伺服器的特殊 MBean 伺服器,它內建於 JVM 中。平台 MBean 使用唯一名稱註冊在此伺服器中。
您可以使用下列程式碼透過其平台 MBean 監控 JVM
import java.lang.management.*
def os = ManagementFactory.operatingSystemMXBean
println """OPERATING SYSTEM:
\tarchitecture = $os.arch
\tname = $os.name
\tversion = $os.version
\tprocessors = $os.availableProcessors
"""
def rt = ManagementFactory.runtimeMXBean
println """RUNTIME:
\tname = $rt.name
\tspec name = $rt.specName
\tvendor = $rt.specVendor
\tspec version = $rt.specVersion
\tmanagement spec version = $rt.managementSpecVersion
"""
def cl = ManagementFactory.classLoadingMXBean
println """CLASS LOADING SYSTEM:
\tisVerbose = ${cl.isVerbose()}
\tloadedClassCount = $cl.loadedClassCount
\ttotalLoadedClassCount = $cl.totalLoadedClassCount
\tunloadedClassCount = $cl.unloadedClassCount
"""
def comp = ManagementFactory.compilationMXBean
println """COMPILATION:
\ttotalCompilationTime = $comp.totalCompilationTime
"""
def mem = ManagementFactory.memoryMXBean
def heapUsage = mem.heapMemoryUsage
def nonHeapUsage = mem.nonHeapMemoryUsage
println """MEMORY:
HEAP STORAGE:
\tcommitted = $heapUsage.committed
\tinit = $heapUsage.init
\tmax = $heapUsage.max
\tused = $heapUsage.used
NON-HEAP STORAGE:
\tcommitted = $nonHeapUsage.committed
\tinit = $nonHeapUsage.init
\tmax = $nonHeapUsage.max
\tused = $nonHeapUsage.used
"""
ManagementFactory.memoryPoolMXBeans.each { mp ->
println "\tname: " + mp.name
String[] mmnames = mp.memoryManagerNames
mmnames.each{ mmname ->
println "\t\tManager Name: $mmname"
}
println "\t\tmtype = $mp.type"
println "\t\tUsage threshold supported = " + mp.isUsageThresholdSupported()
}
println()
def td = ManagementFactory.threadMXBean
println "THREADS:"
td.allThreadIds.each { tid ->
println "\tThread name = ${td.getThreadInfo(tid).threadName}"
}
println()
println "GARBAGE COLLECTION:"
ManagementFactory.garbageCollectorMXBeans.each { gc ->
println "\tname = $gc.name"
println "\t\tcollection count = $gc.collectionCount"
println "\t\tcollection time = $gc.collectionTime"
String[] mpoolNames = gc.memoryPoolNames
mpoolNames.each { mpoolName ->
println "\t\tmpool name = $mpoolName"
}
}
執行後,您將看到類似這樣的內容
OPERATING SYSTEM: architecture = amd64 name = Windows 10 version = 10.0 processors = 12 RUNTIME: name = 724176@QUOKKA spec name = Java Virtual Machine Specification vendor = Oracle Corporation spec version = 11 management spec version = 2.0 CLASS LOADING SYSTEM: isVerbose = false loadedClassCount = 6962 totalLoadedClassCount = 6969 unloadedClassCount = 0 COMPILATION: totalCompilationTime = 7548 MEMORY: HEAP STORAGE: committed = 645922816 init = 536870912 max = 8560574464 used = 47808352 NON-HEAP STORAGE: committed = 73859072 init = 7667712 max = -1 used = 70599520 name: CodeHeap 'non-nmethods' Manager Name: CodeCacheManager mtype = Non-heap memory Usage threshold supported = true name: Metaspace Manager Name: Metaspace Manager mtype = Non-heap memory Usage threshold supported = true name: CodeHeap 'profiled nmethods' Manager Name: CodeCacheManager mtype = Non-heap memory Usage threshold supported = true name: Compressed Class Space Manager Name: Metaspace Manager mtype = Non-heap memory Usage threshold supported = true name: G1 Eden Space Manager Name: G1 Old Generation Manager Name: G1 Young Generation mtype = Heap memory Usage threshold supported = false name: G1 Old Gen Manager Name: G1 Old Generation Manager Name: G1 Young Generation mtype = Heap memory Usage threshold supported = true name: G1 Survivor Space Manager Name: G1 Old Generation Manager Name: G1 Young Generation mtype = Heap memory Usage threshold supported = false name: CodeHeap 'non-profiled nmethods' Manager Name: CodeCacheManager mtype = Non-heap memory Usage threshold supported = true THREADS: Thread name = Reference Handler Thread name = Finalizer Thread name = Signal Dispatcher Thread name = Attach Listener Thread name = Common-Cleaner Thread name = Java2D Disposer Thread name = AWT-Shutdown Thread name = AWT-Windows Thread name = Image Fetcher 0 Thread name = AWT-EventQueue-0 Thread name = D3D Screen Updater Thread name = DestroyJavaVM Thread name = TimerQueue Thread name = Thread-0 GARBAGE COLLECTION: name = G1 Young Generation collection count = 6 collection time = 69 mpool name = G1 Eden Space mpool name = G1 Survivor Space mpool name = G1 Old Gen name = G1 Old Generation collection count = 0 collection time = 0 mpool name = G1 Eden Space mpool name = G1 Survivor Space mpool name = G1 Old Gen
3. 監控 Tomcat
首先透過設定下列內容啟用 JMX 監控來啟動 Tomcat
set JAVA_OPTS=-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9004\
-Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false
您可以在啟動指令碼中執行此操作,並選擇任何可用的埠,我們使用 9004。
以下程式碼使用 JMX 找出正在執行的 Tomcat 中可用的 MBean,判斷哪些是網頁模組,擷取每個網頁模組的處理時間,並使用 JFreeChart 將結果顯示在圖形中
import groovy.swing.SwingBuilder
import groovy.jmx.GroovyMBean
import javax.management.ObjectName
import javax.management.remote.JMXConnectorFactory as JmxFactory
import javax.management.remote.JMXServiceURL as JmxUrl
import javax.swing.WindowConstants as WC
import org.jfree.chart.ChartFactory
import org.jfree.data.category.DefaultCategoryDataset as Dataset
import org.jfree.chart.plot.PlotOrientation as Orientation
def serverUrl = 'service:jmx:rmi:///jndi/rmi://127.0.0.1:9004/jmxrmi'
def server = JmxFactory.connect(new JmxUrl(serverUrl)).MBeanServerConnection
def serverInfo = new GroovyMBean(server, 'Catalina:type=Server').serverInfo
println "Connected to: $serverInfo"
def query = new ObjectName('Catalina:*')
String[] allNames = server.queryNames(query, null)
def modules = allNames.findAll { name ->
name.contains('j2eeType=WebModule')
}.collect{ new GroovyMBean(server, it) }
println "Found ${modules.size()} web modules. Processing ..."
def dataset = new Dataset()
modules.each { m ->
println m.name()
dataset.addValue m.processingTime, 0, m.path
}
def labels = ['Time per Module', 'Module', 'Time']
def options = [false, true, true]
def chart = ChartFactory.createBarChart(*labels, dataset,
Orientation.VERTICAL, *options)
def swing = new SwingBuilder()
def frame = swing.frame(title:'Catalina Module Processing Time', defaultCloseOperation:WC.DISPOSE_ON_CLOSE) {
panel(id:'canvas') { rigidArea(width:800, height:350) }
}
frame.pack()
frame.show()
chart.draw(swing.canvas.graphics, swing.canvas.bounds)
執行時,我們會看到進度追蹤
Connected to: Apache Tomcat/9.0.37 Found 5 web modules. Processing ... Catalina:j2eeType=WebModule,name=//127.0.0.1/docs,J2EEApplication=none,J2EEServer=none Catalina:j2eeType=WebModule,name=//127.0.0.1/manager,J2EEApplication=none,J2EEServer=none Catalina:j2eeType=WebModule,name=//127.0.0.1/,J2EEApplication=none,J2EEServer=none Catalina:j2eeType=WebModule,name=//127.0.0.1/examples,J2EEApplication=none,J2EEServer=none Catalina:j2eeType=WebModule,name=//127.0.0.1/host-manager,J2EEApplication=none,J2EEServer=none
輸出會類似這樣
注意:如果您在執行此腳本時遇到錯誤,請參閱以下疑難排解區段。
4. OC4J 範例
以下是一個腳本,用於存取 OC4J 並列印出一些關於伺服器、其執行時間以及(例如)已設定的 JMS 目的地的資訊
import javax.management.remote.*
import oracle.oc4j.admin.jmx.remote.api.JMXConnectorConstant
def serverUrl = new JMXServiceURL('service:jmx:rmi://127.0.0.1:23791')
def serverPath = 'oc4j:j2eeType=J2EEServer,name=standalone'
def jvmPath = 'oc4j:j2eeType=JVM,name=single,J2EEServer=standalone'
def provider = 'oracle.oc4j.admin.jmx.remote'
def credentials = [
(JMXConnectorConstant.CREDENTIALS_LOGIN_KEY): 'oc4jadmin',
(JMXConnectorConstant.CREDENTIALS_PASSWORD_KEY): 'admin'
]
def env = [
(JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES): provider,
(JMXConnector.CREDENTIALS): credentials
]
def server = JmxFactory.connect(serverUrl, env).MBeanServerConnection
def serverInfo = new GroovyMBean(server, serverPath)
def jvmInfo = new GroovyMBean(server, jvmPath)
println """Connected to $serverInfo.node. \
Server started ${new Date(serverInfo.startTime)}.
OC4J version: $serverInfo.serverVersion from $serverInfo.serverVendor
JVM version: $jvmInfo.javaVersion from $jvmInfo.javaVendor
Memory usage: $jvmInfo.freeMemory bytes free, \
$jvmInfo.totalMemory bytes total
"""
def query = new javax.management.ObjectName('oc4j:*')
String[] allNames = server.queryNames(query, null)
def dests = allNames.findAll { name ->
name.contains('j2eeType=JMSDestinationResource')
}.collect { new GroovyMBean(server, it) }
println "Found ${dests.size()} JMS destinations. Listing ..."
dests.each { d -> println "$d.name: $d.location" }
以下是執行此腳本的結果
Connected to LYREBIRD. Server started Thu May 31 21:04:54 EST 2007. OC4J version: 11.1.1.0.0 from Oracle Corp. JVM version: 1.6.0_01 from Sun Microsystems Inc. Memory usage: 8709976 bytes free, 25153536 bytes total Found 5 JMS destinations. Listing ... Demo Queue: jms/demoQueue Demo Topic: jms/demoTopic jms/Oc4jJmsExceptionQueue: jms/Oc4jJmsExceptionQueue jms/RAExceptionQueue: jms/RAExceptionQueue OracleASRouter_store: OracleASRouter_store
作為一個小小的變異,此腳本使用 JFreeChart 顯示記憶體使用率的圓餅圖
import org.jfree.chart.ChartFactory
import javax.swing.WindowConstants as WC
import javax.management.remote.*
import oracle.oc4j.admin.jmx.remote.api.JMXConnectorConstant
def url = 'service:jmx:rmi://127.0.0.1:23791'
def credentials = [:]
credentials[JMXConnectorConstant.CREDENTIALS_LOGIN_KEY] = "oc4jadmin"
credentials[JMXConnectorConstant.CREDENTIALS_PASSWORD_KEY] = "password"
def env = [:]
env[JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES] = "oracle.oc4j.admin.jmx.remote"
env[JMXConnector.CREDENTIALS] = credentials
def server = JMXConnectorFactory.connect(new JMXServiceURL(url), env).MBeanServerConnection
def jvmInfo = new GroovyMBean(server, 'oc4j:j2eeType=JVM,name=single,J2EEServer=standalone')
def piedata = new org.jfree.data.general.DefaultPieDataset()
piedata.setValue "Free", jvmInfo.freeMemory
piedata.setValue "Used", jvmInfo.totalMemory - jvmInfo.freeMemory
def options = [true, true, true]
def chart = ChartFactory.createPieChart('OC4J Memory Usage', piedata, *options)
chart.backgroundPaint = java.awt.Color.white
def swing = new groovy.swing.SwingBuilder()
def frame = swing.frame(title:'OC4J Memory Usage', defaultCloseOperation:WC.EXIT_ON_CLOSE) {
panel(id:'canvas') { rigidArea(width:350, height:250) }
}
frame.pack()
frame.show()
chart.draw(swing.canvas.graphics, swing.canvas.bounds)
如下所示
5. WebLogic 範例
此腳本會列印出關於伺服器以及 JMS 目的地的資訊(例如)。還有許多其他 mbean 可用。
import javax.management.remote.*
import javax.management.*
import javax.naming.Context
import groovy.jmx.GroovyMBean
def urlRuntime = '/jndi/weblogic.management.mbeanservers.runtime'
def urlBase = 'service:jmx:t3://127.0.0.1:7001'
def serviceURL = new JMXServiceURL(urlBase + urlRuntime)
def h = new Hashtable()
h.put(Context.SECURITY_PRINCIPAL, 'weblogic')
h.put(Context.SECURITY_CREDENTIALS, 'weblogic')
h.put(JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES, 'weblogic.management.remote')
def server = JMXConnectorFactory.connect(serviceURL, h).MBeanServerConnection
def domainName = new ObjectName('com.bea:Name=RuntimeService,Type=weblogic.management.mbeanservers.runtime.RuntimeServiceMBean')
def rtName = server.getAttribute(domainName, 'ServerRuntime')
def rt = new GroovyMBean(server, rtName)
println "Server: name=$rt.Name, state=$rt.State, version=$rt.WeblogicVersion"
def destFilter = Query.match(Query.attr('Type'), Query.value('JMSDestinationRuntime'))
server.queryNames(new ObjectName('com.bea:*'), destFilter).each { name ->
def jms = new GroovyMBean(server, name)
println "JMS Destination: name=$jms.Name, type=$jms.DestinationType, messages=$jms.MessagesReceivedCount"
}
以下是輸出
Server: name=examplesServer, state=RUNNING, version=WebLogic Server 10.0 Wed May 9 18:10:27 EDT 2007 933139 JMS Destination: name=examples-jms!exampleTopic, type=Topic, messages=0 JMS Destination: name=examples-jms!exampleQueue, type=Queue, messages=0 JMS Destination: name=examples-jms!jms/MULTIDATASOURCE_MDB_QUEUE, type=Queue, messages=0 JMS Destination: name=examplesJMSServer!examplesJMSServer.TemporaryQueue0, type=Queue, messages=68 JMS Destination: name=examples-jms!quotes, type=Topic, messages=0 JMS Destination: name=examples-jms!weblogic.wsee.wseeExamplesDestinationQueue, type=Queue, messages=0 JMS Destination: name=examples-jms!weblogic.examples.ejb30.ExampleQueue, type=Queue, messages=0
6. Spring 範例
您也可以使用 Spring 自動註冊 bean 作為 JMX 認識的 bean。
以下是一個範例類別 (Calculator.groovy)
import org.springframework.jmx.export.annotation.*
@ManagedResource(objectName="bean:name=calcMBean", description="Calculator MBean")
public class Calculator {
private int invocations
@ManagedAttribute(description="The Invocation Attribute")
public int getInvocations() {
return invocations
}
private int base = 10
@ManagedAttribute(description="The Base to use when adding strings")
public int getBase() {
return base
}
@ManagedAttribute(description="The Base to use when adding strings")
public void setBase(int base) {
this.base = base
}
@ManagedOperation(description="Add two numbers")
@ManagedOperationParameters([
@ManagedOperationParameter(name="x", description="The first number"),
@ManagedOperationParameter(name="y", description="The second number")])
public int add(int x, int y) {
invocations++
return x + y
}
@ManagedOperation(description="Add two strings representing numbers of a particular base")
@ManagedOperationParameters([
@ManagedOperationParameter(name="x", description="The first number"),
@ManagedOperationParameter(name="y", description="The second number")])
public String addStrings(String x, String y) {
invocations++
def result = Integer.valueOf(x, base) + Integer.valueOf(y, base)
return Integer.toString(result, base)
}
}
以下是 Spring 設定檔 (beans.xml)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="mbeanServer"
class="org.springframework.jmx.support.MBeanServerFactoryBean">
<property name="locateExistingServerIfPossible" value="true"/>
</bean>
<bean id="exporter"
class="org.springframework.jmx.export.MBeanExporter">
<property name="assembler" ref="assembler"/>
<property name="namingStrategy" ref="namingStrategy"/>
<property name="beans">
<map>
<entry key="bean:name=defaultCalcName" value-ref="calcBean"/>
</map>
</property>
<property name="server" ref="mbeanServer"/>
<property name="autodetect" value="true"/>
</bean>
<bean id="jmxAttributeSource"
class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>
<!-- will create management interface using annotation metadata -->
<bean id="assembler"
class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
<property name="attributeSource" ref="jmxAttributeSource"/>
</bean>
<!-- will pick up the ObjectName from the annotation -->
<bean id="namingStrategy"
class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
<property name="attributeSource" ref="jmxAttributeSource"/>
</bean>
<bean id="calcBean"
class="Calculator">
<property name="base" value="10"/>
</bean>
</beans>
以下是一個使用此 bean 和設定的腳本
import org.springframework.context.support.ClassPathXmlApplicationContext
import java.lang.management.ManagementFactory
import javax.management.ObjectName
import javax.management.Attribute
import groovy.jmx.GroovyMBean
// get normal bean
def ctx = new ClassPathXmlApplicationContext("beans.xml")
def calc = ctx.getBean("calcBean")
Thread.start {
// access bean via JMX, use a separate thread just to
// show that we could access remotely if we wanted
def server = ManagementFactory.platformMBeanServer
def mbean = new GroovyMBean(server, 'bean:name=calcMBean')
sleep 1000
assert 8 == mbean.add(7, 1)
mbean.Base = 8
assert '10' == mbean.addStrings('7', '1')
mbean.Base = 16
sleep 2000
println "Number of invocations: $mbean.Invocations"
println mbean
}
assert 15 == calc.add(9, 6)
assert '11' == calc.addStrings('10', '1')
sleep 2000
assert '20' == calc.addStrings('1f', '1')
以下是產生的輸出
Number of invocations: 5 MBean Name: bean:name=calcMBean Attributes: (rw) int Base (r) int Invocations Operations: int add(int x, int y) java.lang.String addStrings(java.lang.String x, java.lang.String y) int getInvocations() int getBase() void setBase(int p1)
您甚至可以使用 jconsole 在執行過程中附加到該程序。它會看起來像
我們使用 -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 是一個針對 Java 管理延伸模組 (JMX) API 的 Groovy 基礎網域特定語言。它使用建構器模式 (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://127.0.0.1:9000/jmxrmi"。
注意:遺憾的是,當您嘗試執行前一個程式碼片段時,您很可能會得到類似以下的結果 (範例不完整,請參閱下方)
Caught: java.io.IOException: Cannot bind to URL [rmi://127.0.0.1:9000/jmxrmi]: javax.naming.ServiceUnavailableException [Root exception is java.rmi.ConnectException: Connection refused to host: localhost; nested exception is: ?????? java.net.ConnectException: Connection refused] ??
這會在安裝了 Groovy 1.6 的 Mac 和 Linux (CentOS 5) 上發生。也許對 /etc/hosts 檔案的組態做了一些假設?
正確的範例如下所示。 |
Connector 範例(已更正)- Connector Server
上述範例不會建立 RMI 註冊表。因此,為了匯出,您必須先匯出 RMI 物件註冊表(請務必匯入 java.rmi.registry.LocateRegistry
)。
import java.rmi.registry.LocateRegistry
//...
LocateRegistry.createRegistry(9000)
jmx.connectorServer(port: 9000).start()
8.2.2. Connector Client
JmxBuilder.connectorClient() 節點讓您可以建立 JMX connector client 物件,以連線到 JMX MBean Server。
語法
jmx.connectorClient ( protocol:"rmi", host:"...", port:1099, url:"...", )
範例 - Client Connector
建立 connector client 可以很輕鬆地完成。使用一行程式碼,您可以建立 JMX Connector Client 的執行個體,如下所示。
def client = jmx.connectorClient(port: 9000)
client.connect()
然後,您可以使用存取與 connector 相關聯的 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 server 以進行管理(請參閱以下範例)。
def beans = jmx.export {
bean(new Foo())
bean(new Bar())
bean(new SomeBar())
}
在上述程式碼片段中,JmxBuilder.export() 會將三個管理 bean 匯出到 MBean server。
8.3.3. JmxBuilder.export() 語法
JmxBuilder.export() 節點支援 registrationPolicy 參數,以指定 JmxBuilder 在 MBean 註冊期間解決 bean 名稱衝突時將如何運作
jmx.export(policy:"replace|ignore|error") or jmx.export(regPolicy:"replace|ignore|error")
-
取代 - JmxBuilder.export() 會在匯出期間取代任何已註冊到 MBean 的 bean。
-
忽略 - 如果已註冊相同 bean,將忽略要匯出的 bean。
-
錯誤 - 註冊期間 bean 名稱發生衝突時,JmxBuilder.export() 會擲回錯誤。
8.3.4. 與 GroovyMBean 類別整合
將 MBean 匯出至 MBeanServer 時,JmxBuilder 會傳回 GroovyMBean 執行個體,代表由建構函式匯出的管理 bean。呼叫 bean() 和 timer() 等節點時,會傳回 GroovyMBean 執行個體。export() 節點會傳回 所有 GroovyMBean[] 的陣列,代表匯出至 MBean 伺服器的所有受管理物件。
8.3.5. 使用 JmxBuilder.bean() 進行 MBean 註冊
本參考文件的部分內容使用 RequestController 類別來說明如何使用 JmxBuilder 匯出執行時期管理 bean。此類別用於說明目的,可以是 POJO 或 Groovy bean。
RequestController
class RequestController {
// constructors
RequestController() { super() }
RequestController(Map resource) { }
// attributes
boolean isStarted() { true }
int getRequestCount() { 0 }
int getResourceCount() { 0 }
void setRequestLimit(int limit) { }
int getRequestLimit() { 0 }
// operations
void start() { }
void stop() { }
void putResource(String name, Object resource) { }
void makeRequest(String res) { }
void makeRequest() { }
}
隱式匯出
如前所述,您可以使用 JmxBuilder 的彈性語法匯出任何沒有描述符的 POJO/POGO。建構函式可以使用隱式預設值自動描述管理 bean 的所有面向。我們將在下一節中看到,這些預設值可以輕鬆覆寫。
以下是匯出 POJO 或 POGO 的最簡單方法。
jmx.export {
bean(new RequestController(resource: "Hello World"))
}
此方法的作用
-
首先,JmxBuilder.export() 節點會匯出一個 MBean 至 MBeanServer,代表已宣告的 POJO執行個體。
-
建構函式會為 MBean 產生預設 ObjectName 和所有其他 MBean 描述符資訊。
-
JmxBuilder 會自動匯出執行個體上的所有宣告屬性(MBean getter/setter)、建構函式和操作。
-
匯出的屬性將具有唯讀可見性。
請記住,JmxBuilder.export() 會傳回所有匯出執行個體的 GroovyMBean[] 物件陣列。因此,呼叫 JmxBuilder.export() 之後,您可以立即存取基礎 MBean 代理程式(透過 GroovyMBean)。
8.3.6. JmxBuilder.bean() 語法
JmxBuilder.bean() 節點支援廣泛的描述符,用於描述要管理的 bean。JMX MBeanServer 使用這些描述符公開有關要管理的 bean 的元資料。
jmx.export { bean( target:bean instance, name:ObjectName, desc:"...", attributes:"*", attributes:[] attributes:[ "AttrubuteName1","AttributeName2",...,"AttributeName_n" ] attributes:[ "AttributeName":"*", "AttributeName":[ desc:"...", defaultValue:value, writable:true|false, editable:true|false, onChange:{event-> // event handler} ] ], constructors:"*", constructors:[ "Constructor Name":[], "Constructor Name":[ "ParamType1","ParamType2,...,ParamType_n" ], "Constructor Name":[ desc:"...", params:[ "ParamType1":"*", "ParamType2":[desc:"...", name:"..."],..., "ParamType_n":[desc:"...", name:"..."] ] ] ], operations:"*", operations:[ "OperationName1", "OperationName2",...,"OperationNameN" ], operations:[ "OperationName1":"*", "OperationName2":[ "type1","type2,"type3" ] "OperationName3":[ desc:"...", params:[ "ParamType1":"*" "ParamType2":[desc:"...", name:"..."],..., "ParamType_n":[desc:"...", name:"..."] ], onInvoked:{event-> JmxBuilder.send(event:"", to:"")} ] ], listeners:[ "ListenerName1":[event: "...", from:ObjectName, call:{event->}], "ListenerName2":[event: "...", from:ObjectName, call:&methodPointer] ] ) }
以下各節不會描述整個節點,而是會個別探討每個屬性。
8.3.7. Bean() 節點 - 指定 MBean ObjectName
使用 bean() 節點描述符,您可以指定自己的 MBean ObjectName。
def ctrl = new RequestController(resource:"Hello World")
def beans = jmx.export {
bean(target: ctrl, name: "jmx.tutorial:type=Object")
}
ObjectName 可以指定為字串或 ObjectName 的實例。
8.4. Bean() 節點 - 屬性匯出
JMX 屬性是基礎 bean 上的 setter 和 getter。JmxBuilder.bean() 節點提供多種彈性方式來描述和匯出 MBean 屬性。您可以依自己的需求組合這些方式,以達成任何層級的屬性可見度。讓我們來看看。
8.4.1. 使用萬用字元「*」匯出所有屬性
下列程式碼片段將描述並匯出 bean 上的所有屬性,設定為唯讀。JmxBuilder 將使用預設值來描述匯出用於管理的屬性。
def objName = new ObjectName("jmx.tutorial:type=Object")
def beans = jmx.export {
bean(target: new RequestController(),
name: objName,
attributes: "*")
}
8.4.2. 匯出屬性清單
JmxBuilder 會讓您指定要匯出的屬性清單。
def objName = new ObjectName("jmx.tutorial:type=Object")
def beans = jmx.export {
bean(
target: new RequestController(),
name: objName,
attributes: ["Resource", "RequestCount"]
)
}
在上述片段中,只有「Resource」和「RequestCount」屬性會被匯出。同樣地,由於未提供任何描述符,JmxBuilder 將使用合理的預設值來描述匯出的屬性。
8.4.3. 使用明確描述符匯出屬性
JmxBuilder 的優點之一,在於它描述 MBean 的彈性。使用這個建構器,您可以描述要匯出到 MBeanServer 的 MBean 屬性的所有面向(請參閱上述語法)。
def objName = new ObjectName("jmx.tutorial:type=Object")
def beans = jmx.export {
bean(
target: new RequestController(),
name: objName,
attributes: [
"Resource": [desc: "The resource to request.", readable: true, writable: true, defaultValue: "Hello"],
"RequestCount": "*"
]
)
}
在上述片段中,屬性「Resource」已使用所有支援的描述符(例如,desc、readable、writable、defaultValue)完整描述,以作為 JMX 屬性。不過,我們使用萬用字元來描述屬性RequestCount,而它會使用預設值進行匯出和描述。
8.5. Bean() 節點 - 建構函式匯出
JmxBuilder 支援明確描述和匯出基礎 bean 中定義的建構函式。匯出建構函式時有幾個選項可用。您可以依自己的需求組合這些選項,以達成所需的管理層級。
8.5.1. 使用「*」匯出所有建構函式
您可以使用建構器的特殊「*」符號來匯出基礎 bean 上宣告的所有建構函式。建構器將使用預設值來描述 MBean 建構函式。
def objName = new ObjectName("jmx.tutorial:type=Object")
def beans = jmx.export {
bean(
target: new RequestController(),
name: objName,
constructors: "*"
)
}
8.5.2. 使用參數描述符匯出建構函式
JmxBuilder 讓你可以鎖定特定建構函式來匯出,透過描述參數簽章。當你有多個建構函式,且參數簽章不同,而你想要匯出特定建構函式時,這會很有用。
def objName = new ObjectName("jmx.tutorial:type=Object")
def beans = jmx.export {
bean(
target: new RequestController(),
name: objName,
constructors: [
"RequestController": ["Object"]
]
)
}
這裡,JmxBuilder 將會匯出一個建構函式,它會接收一個「Object」類型的參數。同樣地,JmxBuilder 會使用預設值來填入建構函式和參數的描述。
8.5.3. 使用明確描述匯出建構函式
JmxBuilder 讓你能夠完整描述你想要鎖定匯出的建構函式(請參閱上方的語法)。
def objName = new ObjectName("jmx.tutorial:type=Object")
def beans = jmx.export {
bean(target: new RequestController(), name: objName,
constructors: [
"RequestController": [
desc: "Constructor takes param",
params: ["Object" : [name: "Resource", desc: "Resource for controller"]]
]
]
)
}
在上面的程式碼中,JmxBuilder 將會鎖定一個建構函式,它會接收一個參數來匯出到 MBeanServer。請注意,建構函式可以使用所有選用描述符金鑰,包含參數描述符,來完整描述。
8.6. Bean() 節點 - 作業匯出
類似於建構函式,JmxBuilder 支援使用彈性符號來描述和匯出 MBean 作業(請參閱上方語法)。你可以任意組合這些符號,來達到你想要的作業可管理性等級。
8.6.1. 使用「*」匯出所有作業
你可以使用建構函式的特殊「*」符號來匯出所有作業,這些作業定義在要公開管理的 bean 中。建構函式會使用預設描述符值,來描述正在匯出的作業。
def objName = new ObjectName("jmx.tutorial:type=Object")
def beans = jmx.export {
bean(
target: new RequestController(),
name: objName,
operations: "*"
)
}
在這個片段中,JmxBuilder 將會匯出所有 bean 作業,並會使用預設值來描述它們在 MBeanServer 中。
8.6.2. 匯出作業清單
JmxBuilder 有個簡寫符號,讓你能夠快速鎖定要匯出的作業,方法是提供一個要匯出的方法清單。
def objName = new ObjectName("jmx.tutorial:type=Object")
def beans = jmx.export {
bean(
target: new RequestController(),
name: objName,
operations: ["start", "stop"]
)
}
在上面的片段中,建構函式只會匯出 start() 和 stop() 方法。所有其他方法都會被忽略。JmxBuilder 會使用預設描述符值來描述正在匯出的作業。
8.6.3. 使用簽章匯出作業
使用 JmxBuilder,你可以使用方法的參數簽章,來鎖定要匯出管理的方法。當你想要區分具有相同名稱,但你想要匯出的方法時,這會很有用(例如:stop() 而不是 stop(boolean))。
def objName = new ObjectName("jmx.tutorial:type=Object")
def beans = jmx.export {
bean(
target: new RequestController(),
name: objName,
operations: [
"makeRequest": ["String"]
]
)
}
在上面的程式片段中,JmxBuilder 會選擇方法 makeRequest(String)來匯出,而不是另一個不帶參數的 makeRequest() 版本。在此簡寫的內容中,簽章指定為類型清單(例如「String」)。
8.6.4. 使用明確描述符匯出作業
JmxBuilder 支援 bean 作業的詳細描述符。您可以提供 bean 上任何作業的深入描述符資訊,包括名稱、描述、方法參數、參數類型和參數描述。
def objName = new ObjectName("jmx.tutorial:type=Object")
def beans = jmx.export {
bean(target: new RequestController(), name: objName,
operations: [
"start": [desc: "Starts request controller"],
"stop": [desc: "Stops the request controller"],
"setResource": [params: ["Object"]],
"makeRequest": [
desc: "Executes the request.",
params: [
"String": [name: "Resource", desc: "The resource to request"]
]
]
]
)
}
上面的程式片段顯示 JmxBuilder 允許您描述針對管理的作業的所有方式
-
作業start() 和 stop()由「desc」金鑰描述(這就夠了,因為沒有參數)。
-
在作業setResource() 中使用params的簡寫版本來描述方法的參數。
-
makeRequest() 使用延伸描述符語法來描述作業的所有面向。
8.7. 內嵌描述符
JmxBuilder 支援直接在 Groovy 類別中內嵌描述符的功能。因此,您不必在宣告的物件周圍包覆您的描述(如我們在此所見),您可以直接在類別中內嵌 JMX 描述符。
RequestControllerGroovy
class RequestControllerGroovy {
// attributes
boolean started
int requestCount
int resourceCount
int requestLimit
Map resources
// operations
void start() { }
void stop(){ }
void putResource(String name, Object resource) { }
void makeRequest(String res) { }
void makeRequest() { }
static descriptor = [
name: "jmx.builder:type=EmbeddedObject",
operations: ["start", "stop", "putResource"],
attributes: "*"
]
}
// export
jmx.export(
bean(new RequestControllerGroovy())
)
上面的程式碼中有兩件事正在進行中
-
定義 Groovy 類別 RequestControllerGroovy,並包含靜態描述符成員。該成員用於宣告 JmxBuilder 描述符,以描述針對 JMX 匯出而設定的類別成員。
-
程式碼的第二部分顯示如何使用 JmxBuilder 匯出該類別以進行管理。
8.8. 計時器匯出
JMX 標準強制規定 API 的實作提供計時器服務。由於 JMX 是基於元件的架構,因此計時器提供了一個絕佳的訊號傳遞機制,用於與 MBeanServer 中註冊的監聽器元件進行通訊。JmxBuilder 支援使用我們到目前為止所見的相同簡易語法來建立和匯出計時器。
8.8.1. 計時器節點語法
timer( name:ObjectName, event:"...", message:"...", data:dataValue startDate:"now"|dateValue period:"99d"|"99h"|"99m"|"99s"|99 occurrences:long )
timer() 節點支援多個屬性
-
名稱:- 定時器的合格 JMX ObjectName 執行個體(或字串)。
-
事件:- 每個計時訊號都會廣播的 JMX 事件類型字串(預設為 "jmx.builder.event")。
-
訊息:- 可選的字串值,可傳送給監聽器。
-
資料:- 可選的物件,可傳送給計時訊號的監聽器。
-
開始日期:- 何時開始計時器。有效值集合 [ "now", 日期物件 ]。預設為 "now"
-
週期:- 計時器的週期,以毫秒數或時間單位(天、小時、分鐘、秒)表示。請參閱以下說明。
-
發生次數:- 表示計時器重複次數的數字。預設為永遠。
8.8.2. 匯出計時器
def timer = jmx.timer(name: "jmx.builder:type=Timer", event: "heartbeat", period: "1s")
timer.start()
上述程式片段說明、建立並匯出標準 JMX 計時器元件。在此,timer() 節點傳回 GroovyMBean,代表 MBeanServer 中已註冊的計時器 MBean。
匯出計時器的另一種方式是在 JmxBuilder.export() 節點中。
def beans = jmx.export {
timer(name: "jmx.builder:type=Timer1", event: "event.signal", period: "1s")
timer(name: "jmx.builder:type=Timer2", event: "event.log", period: "1s")
}
beans[0].start()
beans[1].start()
8.8.3. 計時器週期
timer() 節點支援彈性表示法,用於指定計時器週期值。您可以指定時間為秒、分鐘、小時和天。預設為毫秒。
-
timer(period: 100) = 100 毫秒
-
timer(period: "1s") = 1 秒
-
timer(period: "1m") = 1 分鐘
-
timer(period: "1h") = 1 小時
-
timer(period: "1d") = 1 天
節點會自動轉換。
8.9. JmxBuilder 和事件
JMX 的一個組成部分是其事件模型。已註冊的管理 Bean 可以透過在 MBeanServer 的事件匯流排上廣播事件,彼此通訊。JmxBuilder 提供多種方式,可輕鬆監聽和回應在 MBeanServer 的事件匯流排上廣播的事件。開發人員可以擷取匯流排上的任何事件,或拋出自己的事件,供註冊在 MBeanServer 上的其他元件使用。
8.9.1. 事件處理閉包
JmxBuilder 充分利用 Groovy 使用閉包,提供簡單但優雅的方式來回應 JMX 事件。JmxBuilder 支援兩個閉包簽章
有事件參數
callback = { event ->
// event handling code
}
JmxBuilder 會使用這種格式將"event" 物件傳遞給閉包。event 物件包含關於事件的資訊,以便處理常式可以處理它。參數會包含不同資訊集合,具體取決於擷取到的事件。
8.9.2. 處理屬性 onChange 事件
在描述屬性時(請參閱上方的 bean() 節點區段),您可以提供一個封閉函數(或方法指標)作為回呼,以便在匯出的 MBean 上更新屬性值時執行。這讓開發人員有機會監聽和對應 MBean 上的狀態變更。
jmx.export {
bean(
target: new RequestController(), name: "jmx.tutorial:type=Object",
attributes: [
"Resource": [
readable: true, writable: true,
onChange: { e ->
println e.oldValue
println e.newValue
}
]
]
)
}
上面的範例片段顯示如何在描述 MBean 屬性時指定「onChange」回呼封閉函數。在此範例程式碼中,每當透過匯出的 MBean 更新屬性「Resource」時,onChange 事件就會執行。
8.9.3. 屬性 onChange 事件物件
在處理屬性 onChange 事件時,處理常式會收到包含下列資訊的事件物件
-
event.oldValue - 變更事件之前的先前的屬性值。
-
event.newValue - 變更之後的屬性新值。
-
event.attribute - 發生事件的屬性名稱。
-
event.attributeType - 導致事件的屬性資料類型。
-
event.sequenceNumber - 代表事件順序號碼的數字值。
-
event.timeStamp - 事件發生的時間戳記。
8.9.4. 處理作業 onCall 事件
與 mbean 屬性類似,JmxBuilder 提供開發人員監聽在 MBeaServer 中註冊的 MBean 上的作業呼叫的能力。JmxBuilder 接受一個在 MBean 方法呼叫之後執行的回呼封閉函數。
class EventHandler {
void handleStart(e){
println e
}
}
def handler = new EventHandler()
def beans = jmx.export {
bean(target: new RequestController(), name: "jmx.tutorial:type=Object",
operations: [
"start": [
desc:"Starts request controller",
onCall:handler.&handleStart
]
]
)
}
上面的片段顯示如何宣告一個「onCall」封閉函數,在對 MBean 呼叫作業「start()」時用作監聽器。此範例使用方法指標語法來說明 JmxBuilder 的多樣性。
8.9.5. 作業 onCall 事件物件
在處理作業 onCall 事件時,回呼封閉函數會收到包含下列資訊的事件物件
-
event.event - 已廣播的事件類型字串。
-
event.source - 呼叫方法的物件。
-
event.data - 導致事件的屬性資料類型。
-
event.sequenceNumber - 代表事件順序號碼的數字值。
-
event.timeStamp - 事件發生的時間戳記。
8.10. 監聽器 MBean
當您使用 bean() 節點匯出 MBean 時,您可以定義 MBean 可以監聽和對應的事件。bean() 節點提供「listeners:」屬性,讓您可以定義您的 bean 可以對應的事件監聽器。
def beans = jmx.export {
timer(name: "jmx.builder:type=Timer", event: "heartbeat", period: "1s").start()
bean(target: new RequestController(), name: "jmx.tutorial:type=Object",
operations: "*",
listeners: [
heartbeat: [
from: "jmx.builder:type=Timer",
call: { e ->
println e
}
]
]
)
}
在上面的範例中,我們看到將監聽器新增到匯出的 MBean 的語法。
-
首先,匯出一個計時器並啟動它。
-
然後,宣告一個 MBean,它會監聽計時器事件並執行有意義的動作。
-
「heartbeat:」名稱是任意的,與上面宣告的計時器無關。
-
事件的來源使用「from:」屬性指定。
您也可以指定您有興趣從廣播器接收的事件類型(因為廣播器可以發出多個事件)。
8.10.1. 監聽 JMX 事件
在某些情況下,您會想要建立獨立的事件監聽器(不附加到已匯出的 MBean)。JmxBuilder 提供 Listener() 節點,讓您建立 JMX 監聽器,以監聽 MBeanServer 事件。這在建立 JMX 應用程式來監控/管理遠端 JMX MBeanServer 上的 JMX 代理時很有用。
8.10.2. Listener 節點語法
jmx.listener( event: "...", from: "object name" | ObjectName, call: { event-> } )
以下是listener() 節點屬性的說明
-
event: 一個選用的字串,用來識別要監聽的 JMX 事件類型。
-
from (必要): 要監聽元件的 JMX ObjectName。這可以指定為字串或 ObjectName 的執行個體。
-
call: 事件擷取時要執行的封閉。這也可以指定為 Groovy 方法指標。
以下是 JmxBuilder 的 listener 節點範例
jmx.timer(name: "jmx.builder:type=Timer", period: "1s").start()
jmx.listener(
from: "jmx.builder:type=Timer",
call: { e ->
println "beep..."
}
)
這個範例顯示您如何使用獨立的監聽器(MBean 匯出外部)。在此,我們匯出一個解析度為 1 秒的計時器。然後,我們為該計時器指定一個監聽器,它會每秒列印「嗶」。
8.11. 發出 JMX 事件
JmxBuilder 提供在 MBeanServer 的事件匯流排上廣播您自己的事件所需的工具。您可廣播的事件類型沒有限制。您只需宣告您的發射器和您要發送的事件類型,然後隨時廣播您的事件。MBeanServer 中任何已註冊的元件都可以註冊自己來監聽您的事件。
8.11.1. Emitter 語法
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 元件都可以註冊以接收此發射器的訊息。