Clearly one of the best features of Scala is the ability to mix-in code into your classes.
This post shows how I have used Scala traits and Java annotations to automatically enable JMX monitoring to your application.
The idea is the following:
- Create a Java method annotation, @EnableForMonitoring, which will allow the JMX trait to discover all methods that should be exposed to JMX
- Create a Scala trait which will use the scala.reflect.runtime.universe api to discover the annotated methods
- Expose those methods via JMX using RequiredModelMBean
Let's start with the simple method annotation:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface EnableForMonitoring {
}
The Scala trait:
trait JMXAble { self =>
import scala.reflect.runtime.{ universe => ru }
val dynMon = DynMon
dynMon.register(self)
}
object DynMon extends TLogger {
import javax.management.Attribute
import javax.management.AttributeList
val mbs = ManagementFactory.getPlatformMBeanServer
def register(jmxe: JMXAble) = {
val rm = runtimeMirror(getClass.getClassLoader)
val mType = rm.classSymbol(jmxe.getClass).selfType
var ops = List[ModelMBeanOperationInfo]()
mType.declarations.foreach(symbol => {
symbol.annotations.find(a => a.tpe == ru.typeOf[EnableForMonitoring]) match {
case Some(_) => {
import language.reflectiveCalls
val cmx = rm.asInstanceOf[{ def methodToJava(sym: scala.reflect.internal.Symbols#MethodSymbol): java.lang.reflect.Method }]
val jm = cmx.methodToJava(symbol.asMethod.asInstanceOf[scala.reflect.internal.Symbols#MethodSymbol])
ops = ops :+ new ModelMBeanOperationInfo(jm.toString, jm)
}
case None =>
}
})
val mbeansOps = ops.toArray
val modelMBeanInfoSupport = new ModelMBeanInfoSupport(
getClass().getName(),
jmxe + " Model MBean",
null, null, mbeansOps, null)
val requiredModelMBean = new RequiredModelMBean(modelMBeanInfoSupport);
requiredModelMBean.setManagedResource(jmxe, "ObjectReference");
val mBeanServer = ManagementFactory.getPlatformMBeanServer();
mBeanServer.registerMBean(requiredModelMBean, new ObjectName("org.ws:type="+jmxe));
}
}
ModelMBeanOperationInfo takes a Java method, and because of
SI-7317, you have to use a hack to convert a Scala MethodSymbol to a Java method.
This is how to use this trait:
class D extends JMXAble with TLogger {
def m1 = {}
@EnableForMonitoring def m2(i: Int) = {}
def m3(s: String): Int = -1
@EnableForMonitoring def m4(d: Double) = {}
@EnableForMonitoring
def time:String = (new Date).toGMTString
}
And that's it.
Another point to note, if you want to expose a map of [String, String] for example, you would need to convert your Scala map to a Java map.
For example:
@EnableForMonitoring def displayRoutees: java.util.Map[String, String] = {
import scala.collection.JavaConversions._
val data = routees.map(r => (r.path.toString, r.toString)).toMap
new java.util.HashMap[String, String](data)
}
No comments:
Post a Comment