La monitorización de sistemas es algo realmente fundamental, al menos para aplicaciones de medio y gran tamaño. Hoy en día, sin un sistema que nos permita examinar en tiempo real el estado de nuestras aplicaciones, se hace realmente complicado el encontrar errores, especialmente en sistemas distribuidos. Por poner un ejemplo, si tenemos un sistema de caché que funciona en cluster puede ser muy importante el saber en cada momento cual es el contenido de la caché, el saber si unas cachés están más llenas que otras, el ser capaz de ejecutar operaciones sobre la caché, etc.
En Java, JMX (en .NET ¿WMI?) permite la instrumentación de sistemas y la obtención de datos en tiempo real a través de herramientas como jconsole. Un libro que ya he recomendado en algún otro post, Pro Java EE 5 Performance Management and Optimization tiene varios capítulos donde recalca la importancia de instrumentar nuestro código en sistemas distribuidos complejos y pone algunos ejemplos con JMX.
La primera opción que nos puede venir a la cabeza a la hora de instrumentar nuestro código es simplemente crear nuestros managed beans y añadir el código de instrumentación directamente a nuestros proyectos:
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); ObjectName name = new ObjectName("com.example.mbeans:type=Hello"); Hello mbean = new Hello(); mbs.registerMBean(mbean, name);
Esta opción es sostenible y perfectamente justificable para proyectos pequeños. En cuanto los proyectos son de mayor calibre e involucran una cantidad considerable de componentes y desarrolladores es mejor buscar una solución más mantenible.
La alternativa natural sería crear un framework de monitorización que centralize toda instrumentación del proyecto. Esta es una opción muy recomendable que puede perfectamente cubrir todas nuestras necesidades.
Una vez que tenemos creado nuestro framework de monitorización, lo único que tenemos que hacer es decirle a nuestros desarrolladores como utilizarlo, y listo. ¿En serio? Pues la verdad es que depende mucho de nuestros desarrolladores, del movimiento que haya en la empresa (¿están continuamente saliendo y entrando nuevos programadores?), y de nuestra capacidad de formar a los miembros más junior del equipo. Porque aunque el framework de monitorización nos ahorrará mucho trabajo, lo cierto es que si nuestros programadores no saben como utilizarlo, entonces a la larga puede ser una solución menos productiva. Por ejemplo nos podemos encontrar con que un desarrollador ha colocado objetos gigantescos en memoria, que otro ha utilizado el framework para exponer operaciones que exponen nuestro sistema, que otra persona ha ejecutado código muy costoso dentro de los managed beans, o en resumen que cada persona utiliza el framework de una manera diferente.
Una solución a este problema es hacer mucho más fácil y estándar el uso de nuestro framework de monitorización, y ahí es donde entra en juego el concepto de las anotaciones. Si añadimos anotaciones al framework, los desarrolladores sólo tendrán por ejemplo que etiquetar sus clases, métodos o atributos, y nuestro framework de monitorización realizará todo el trabajo sucio: crear los managed beans, gestionar su registro en la plataforma de monitorización, controlar el árbol de nombres, o incluso operaciones más avanzadas como filtrar los componentes que se monitorizan y los que no se monitorizan.
Una opción muy parecida es la que ofrece Spring JMX o incluso de algún modo lo que ofrece el JDK 6. El problema del segundo es que el soporte no es demasiado avanzado, simplemente es un conjunto de anotaciones para no tener que extender clases o implementar interfaces. El problema del primero es que te obliga a utilizar Spring, algo que quizás no sea posible o simplemente no se quiera añadir por cualquier otra razón.
En los siguientes pasos voy a mostrar una aproximación al uso de anotaciones para este escenario. Lo primero es crear las anotaciones. En este caso serán muy simples a modo de ejemplo.
Anotación para cualquier clase que sea un MBean:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface MBean { String name(); String root(); }
Anotación para un atributo instrumentado:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface MBeanAttribute { String name(); }
Anotación para un método instrumentado:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MBeanMethod { }
Una vez definidas las anotaciones ya se podría marcar cualquier clase Java como un MBean. El siguiente código muestra un servicio instrumentado:
@MBean (root="org.test:",name="TestService") public class TestService { public static boolean testBoolean = true; public static String testString = "Test service"; @MBeanAttribute(name="stringAttribute") public String stringAttribute; private long lastLong; public String getStringAttribute() { return testString; } @MBeanMethod() public boolean getTestState() { return testBoolean; } @MBeanMethod() public long customOperation() { // do some monitoring calcs here lastLong = Math.round(Math.random()*1000); return lastLong; } public void nonMBeanMethod() {} public long getLastLong() { return lastLong; } }
Hasta aquí todo bien, pero el que todavía siga leyendo se preguntará... ¿y ahora qué? La idea original era ser capaz de monitorizar nuestro sistema, es decir, cualquier clase Java, sin tener que llenarla de código de monitorización ni obligar a nuestros programadores a utilizar un framework, pero es que hasta este momento el código de monitorización está en nuestro framework imaginario. Así que, ¿cómo se enlaza dicho framework y estas clases Java?
Para hacer eso inyectaremos directamente el código de instrumentación dentro de las clases Java. Esto es lo que normalmente se conoce como weaving, un término relacionado con la programación orientada a aspectos (de hecho todo lo que estoy mostrando aquí se podría realizar también con AOP). Para inyectar el código en este ejemplo utilizaré el framework javassist ya que es bastante descriptivo, pero otros frameworks podrían ser más útiles por ser de bajo nivel como BCEL o ASM. Migrar el ejemplo a estos entornos puede quedar como ejercicio :)
El proceso de weaving de nuestras clases es el más laborioso y la verdad es que resulta difícil poner el código fuente aquí. Voy a escribir a continuación el pseudocódigo del proceso de instrumentación, y podéis leer directamente el código fuente en el enlace de descarga que pongo al final.
- Escanear una clase en busca de la anotación @MBean.
- En caso de encontrar la anotación, buscar todos los métodos (anotación @MBeanMethod), y los atributos (anotacion @MBeanAttribute) expuestos por la clase que hemos encontrado.
- Crear una interfaz utilizando javaassist. Esta interfaz contendrá todos los métodos instrumentados que hemos encontrado, más una serie de métodos get que se generarán para cada uno de los atributos instrumentados (otra opción sería olvidarse de la anotación de atributos y asumir que todo método get que encontremos expondrá un atributo).
- A continuación, crear una nueva clase que actuará de proxy e implementará la interfaz que hemos generado en el paso anterior. Esta clase proxy extenderá a StandardMBean, MXBean, o cualquier otra clase que creemos que sea un MBean y que contenga métodos genéricos. Así, en todos los proxies que creemos, tendremos una serie de funcionalidad común.
- Crear el constructor del proxy, de forma que se guarde una referencia local al objeto que realmente se quiere monitorizar (esa referencia podría ser una soft o weak reference). El constructor también deberá registrar el managed bean en nuestro framework una vez que ha sido creado.
- Implementar todos los métodos de la interfaz en la clase proxy que hemos creado. Estos métodos simplemente delegarán las llamadas sobre el objeto original que se ha recibido en el constructor del proxy.
- Por último, modificar el constructor (o los constructores) del objeto original a monitorizar, de forma que cuando se cree una instancia de dicho objeto se cree también un proxy MBean.
comments
3 Respuestas a "Instrumentando clases Java con anotaciones y class weaving"12:52
I covered a similar approach in this blog entry. But using Javassist rather than hand-generated class files seems like a much saner idea. :-)
Éamonn McManus -- JMX Spec Lead --
http://weblogs.java.net/blog/emcmanus
13:11
Eamonn, it's the first time I see your entry. Actually... you're absolutely right the approaches are quite similar! I promise I didn't copy you :) So I guess, instrumenting code through annotations it's some kind of natural evolution.
Anyways, if you managed to read the article your Spanish (or your translator) is superb :)
12:01
I can't claim any credit for superb Spanish -- Google's translator does an excellent job on technical language like this!
Publicar un comentario