Recommended: Sing it, brah! 5 fabulous songs for developers
JW's Top 5
When you can't do something simple with it.
Check out this absurdly simple Groovy script:
import javax.management.ObjectName
import javax.management.MBeanServerConnection
import javax.management.remote.JMXConnectorFactory as JMXFactory
import javax.management.remote.JMXServiceURL as JMXUrldef serverUrl = 'service:jmx:rmi:///jndi/rmi://127.0.0.1:9004/jmxrmi'
def serv = JMXFactory.connect(new JMXUrl(serverUrl))
def on = new ObjectName('Catalina:type=Server')
def gmb = new GroovyMBean(serv, new ObjectName('Catalina:type=Server')).serverInfo
For those of you not up on your JMX, this is a simple connection via an RMI connector
to the JMX server running at port 9004 (which happens to be my local Tomcat installation). This
is straight off the Groovy-JMX documentation page, but slimmed down because the
GroovyMBean constructor throws an exception, claiming that the desired constructor
cannot be found:
C:\Projects\Exploration\Groovy>groovy GroovyJMX.groovy
Caught: groovy.lang.GroovyRuntimeException: Could not find matching constructor
for: groovy.util.GroovyMBean(javax.management.remote.rmi.RMIConnector, javax.management.ObjectName)
at GroovyJMX.run(GroovyJMX.groovy:9)
at GroovyJMX.main(GroovyJMX.groovy)
C:\Projects\Exploration\Groovy>
C'mon, folks. I have no idea what the problem is, and debugging this is a nightmare.
I've looked around various Groovy forums, and nobody appears to have any real idea
what's going on. Either nobody is really using Groovy as a JMX client (in which case,
just remove the GroovyMBean from the library), or else Groovy has a bug within it
(thus reducing its efficicacy as a production-ready language).
I'm fully willing to accept that the problem is with me or my environment somehow.
The challenge, however, is for somebody to take a stock JDK 1.6 and Groovy 1.0 download
(oh, and 1.1-rc2 fails with the same error, so that's not the issue, either), run
the above 8-line script, and tell me why mine isn't working. (I've already done the
suggested step of removing the mx4j jar out of the groovy-1.0/lib directory, so that
doesn't help, either.)
Oh, and if you're going to write in claiming that this is a ClassLoader issue or something,
you'd be wrong--all of the JMX types being loaded are coming out of rt.jar (which
I verified using -verbose:class, doing which required me to edit the Groovy launcher
scripts, which I find to be just silly--don't make it hard for me to
use the basic management & monitoring facilities of the JVM). The only ClassLoader
player I don't know for certain is the org.codehaus ClassLoader that's established
by Groovy itself, so if the problem is in ClassLoaders, it's inside of the Groovy
implementation, which means it's a Groovy problem, not mine.
I've even taken the step to compile the Groovy code into .class files, and run those:
C:\Projects\Exploration\Groovy>groovyc -d compiled
GroovyJMX.groovy
C:\Projects\Exploration\Groovy>cd compiledC:\Projects\Exploration\Groovy\compiled>java
-classpath .;libg\groovy-1.0.jar;li
bg\asm-2.2.jar GroovyJMX
Exception in thread "main" groovy.lang.GroovyRuntimeException: Could not find ma
tching constructor for: groovy.util.GroovyMBean(javax.management.remote.rmi.RMIC
onnector, javax.management.ObjectName)
at groovy.lang.MetaClassImpl.invokeConstructor(MetaClassImpl.java:776)
at groovy.lang.MetaClassImpl.invokeConstructor(MetaClassImpl.java:688)
at org.codehaus.groovy.runtime.Invoker.invokeConstructorOf(Invoker.java:
163)
at org.codehaus.groovy.runtime.InvokerHelper.invokeConstructorOf(Invoker
Helper.java:140)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeNewN(ScriptBy
tecodeAdapter.java:243)
at GroovyJMX.run(GroovyJMX.groovy:9)
at gjdk.GroovyJMX_GroovyReflector.invoke(Unknown Source)
at groovy.lang.MetaMethod.invoke(MetaMethod.java:115)
at org.codehaus.groovy.runtime.MetaClassHelper.doMethodInvoke(MetaClassH
elper.java:713)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:560)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:450)
at org.codehaus.groovy.runtime.Invoker.invokeMethod(Invoker.java:131)
at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.
java:111)
at org.codehaus.groovy.runtime.InvokerHelper.runScript(InvokerHelper.jav
a:408)
at gjdk.org.codehaus.groovy.runtime.InvokerHelper_GroovyReflector.invoke
(Unknown Source)
at groovy.lang.MetaMethod.invoke(MetaMethod.java:115)
at org.codehaus.groovy.runtime.MetaClassHelper.doMethodInvoke(MetaClassH
elper.java:713)
at groovy.lang.MetaClassImpl.invokeStaticMethod(MetaClassImpl.java:664)
at org.codehaus.groovy.runtime.Invoker.invokeMethod(Invoker.java:111)
at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.
java:111)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodN(Scrip
tBytecodeAdapter.java:187)
at GroovyJMX.main(GroovyJMX.groovy)C:\Projects\Exploration\Groovy\compiled>
And here we can obviously see, by the light of the .jars passed in, that the mx4j
jars are nowhere to be found (unless, of course, Groovy is explicitly searching the
hard drive for them, because I deleted them entirely out of Groovy's lib directory).
I've asked a couple of the Groovy heavyweights (not mentioning anybody by name) if
they know what's up. Silence. Not a good sign.
I'm about to go off and try the same thing using JRuby. If it works, out of the box,
then the problem is with Groovy, not with me. If you're a Groovy expert, or know someone
who is, then have them email me the solution (assuming they can find one), because
this is a point where I'm about to close the door on Groovy forever: if you can't
do something simple with it, it's not ready for prime-time. It may be OK to whip
up dirt-simple websites where 90% of the stuff is pre-generated, but if I can't use
it for something like being a JMX client, then it ain't worth my time.
'Nuff said.
Update: OK, I may have to eat my words.
Playing around with some JRuby/JMX stuff (which is absurdly simple, even without jmx4r,
which I'm trying to get gem to install right now), I went back and decided to try
a little JMX without using GroovyMBean:
import javax.management.*
import javax.management.remote.*
import java.lang.management.*def serverUrl = 'service:jmx:rmi:///jndi/rmi://127.0.0.1:9004/jmxrmi'
def connector = JMXConnectorFactory.connect(new JMXServiceURL(serverUrl))
def mbsc = connector.mBeanServerConnectiondef memory_mbean =
ManagementFactory.newPlatformMXBeanProxy(mbsc, "java.lang:type=Memory", MemoryMXBean.class)
This, by the way, is almost a straight port of Jeff's
corresponding JRuby/JMX example code. Works just fine. From there, I thought,
"Let's see if I can get past the problem I was having with GroovyMBean a few minutes
ago." So I add the one-line GroovyMBean constructor (taking the mBeanServerConnection
as the first parameter):
def gmb = new GroovyMBean(mbsc, new ObjectName('Catalina:type=Server')).serverInfo
Voila! Success! A quick execution test verifies that I'm all good:
def query = new ObjectName('Catalina:*')
String[] allNames = mbsc.queryNames(query, null)
def modules = allNames.findAll{ name ->
name.contains('j2eeType=WebModule')
}.collect{ new GroovyMBean(mbsc, it) }println "Found ${modules.size()} web modules. Processing
..."modules.each{ m ->
println "Found ${m.name()} at ${m.path} (${m.processingTime})"
}
returns:
C:\Projects\Exploration\Groovy>groovy GroovyJMX.groovy
Found 5 web modules. Processing ...
Found Catalina:j2eeType=WebModule,name=//localhost/docs,J2EEApplication=none,J2E
EServer=none at /docs (0)
Found Catalina:j2eeType=WebModule,name=//localhost/host-manager,J2EEApplication=
none,J2EEServer=none at /host-manager (0)
Found Catalina:j2eeType=WebModule,name=//localhost/examples,J2EEApplication=none
,J2EEServer=none at /examples (0)
Found Catalina:j2eeType=WebModule,name=//localhost/,J2EEApplication=none,J2EESer
ver=none at (0)
Found Catalina:j2eeType=WebModule,name=//localhost/manager,J2EEApplication=none,
J2EEServer=none at /manager (0)
C:\Projects\Exploration\Groovy>
Now if that ain't cool, I don't know what is.
Now for the hard part: What happened? Why'd my earlier code fail?
Looking back at my example script above, I clearly see that somewhere along the way
in my debugging/exploration, I accidentally left out the call to obtain the MBeanServerConnection
from the Connector and pass that in to the GroovyMBean constructor. Ugh.
Formal apologies to the Groovy crowd. However, I stipulate, for your own consideration,
that