Buscar
Social
Ofertas laborales ES
« JavaHispano Podcast - 131 - Introducción a la base de datos Oracle (Entrevista a Comunidad Oracle Hispana) | Main | La última semana en javaHispano »
lunes
dic122011

Compilar y ejecutar código Java en memoria

En esta ocasión me ha tocado a mi (por solicitud del “presi”, Abraham Otero) ponerme delante del teclado para escribir un artículo técnico, en lugar de para coordinar la evaluación de uno realizado por alguna otra persona, que es (como algunos saben) mi labor habitual.

No se trata de un manual, si no de una breve explicación sobre un problema muy concreto: ¿Cómo puedo compilar y ejecutar una clase o un trozo de código Java en memoria, sin tocar disco?

Vamos a verlo.

 

Introducción

 

Desde Java 6.0 disponemos de un API estándar (y multiplataforma) que nos permite acceder al compilador del JDK para pasar de código fuente (archivos .java) a código compilado en bytecode (archivos .class) listos para ser cargados y ejecutados en la máquina virtual.

No voy a entrar en la importancia o la utilidad que a esto se le pueda dar, eso depende de cada cual...

El caso es que, aunque habitualmente esta compilación se hace sobre disco, es decir, el resultado va a ser un archivo .class almacenado en algún lugar del disco duro, esto no tiene por qué ser así. El framework de compilación es muy flexible en casi todos los aspectos involucrados en el proceso.
Uno de los muchos aspectos que se pueden alterar del proceso de compilación es la procedencia del código fuente, de este modo, podríamos hacer que el compilador lo leyese desde una base de datos, desde un puerto de comunicaciones, o como en nuestro caso, de una String. Igualmente podemos cambiar (entre otras muchas cosas, insisto) la ubicación del archivo compilado, por ejemplo, podemos decidir que el .class se guarde en una base de datos o que se envíe por red o simplemente que se quede en memoria, que es de lo que se va a tratar en este artículo.

Una vez compilado el fuente, los bytecodes que lo representan tienen que ser cargados (desde donde quiera que se encuentren) en la JVM, en su memoria de trabajo; como sabemos, esta tarea la realiza un cargador de clases. En nuestro caso, estos bytecodes tienen que pasar desde la zona de datos de memoria de la JVM a la zona de clases de la memoria de la JVM. Está claro que la JVM cuando le pide a un cargador de clases que le cargue una clase determinada, tiene que poner esta información en una zona de su memoria que le permite identificarla como “clase Java compilada”.

Aunque esto suene un poco lioso, si se piensa bien es muy simple: como sabemos las instrucciones de un programa, internamente, no se diferencian de los datos del programa salvo por quién los interpreta y cómo son interpretados: al fin y al cabo, todo se reduce a números más o menos grandes (8, 16, 32 y recientemente 64 bits) que residen en la RAM (si no estuvieran allí, alguien tiene que llevarlos allí).

Ahora vamos a ver los ficheros .java (sólo dos) que nos permiten realizar las tareas que he descrito en esta introducción.

 

InMemoryCompiler.java

 

En este fichero la clase principal es el compilador de código, a quien (por diseño del compilador) tenemos que proporcionarle una instancia de “SimpleJavaFileObject”, la cual contiene el código a compilar. Como en nuestro caso el código se encuentra en una cadena, creamos una clase auxiliar (“JavaObjectFromString”) que realiza la labor almacenar esta cadena y entregarla cuando le sea requerida.

Una vez que el compilador puede compilar código almacenado en una cadena, tenemos que instruirlo para que sea capaz de almacenar el resultado (los bytecodes) en memoria y no en disco, que sería su operación-por-defecto.

El compilador utiliza además la figura del “FileManager”, de un modo simplificado podemos decir que el compilador le entrega al “FileManager” nombre de un clase y los bytecodes de la clase una vez que la ha compilado. El “FileManager” que se utiliza por defecto, lee los .java desde disco y que almacena los bytecodes (.class) en disco.

Pero nosotros queremos un “FileManager” que almacene en memoria, por ello creamos la clase “JavaMemFileManager”, que internamente utiliza un HashMap cuyas claves (keys) son el nombre de la clase java y cuyas entradas (entry) son la secuencia de bytes que representan los bytecodes. Para hacerlo de un modo más elegante (y porque así se requiere), en lugar de hacer que las entradas sean byte[], haremos que sean instancias de la clase “ClassMemFileObject”, que esta vez sí, son básicamente byte[].

Con sólo estas cuatro mini-clases (una public y tres private) podemos compilar en memoria, vayamos ahora a la segunda parte del problema.

 

InMemoryExecutor.java

 

Esta clase es muy simple (como decimos en España “es una chorrada”).

Contiene un conjunto de métodos executeXXX(...) que permiten ejecutar código fuente Java, desde una clase completa hasta una simple línea de código. Estos métodos public acaban llamado al método private executeBlock( String , Object... ).

Esta clase se apoya (además de en el compilador) en una inner class llamada ByteArrayClassLoader, quien no es otra cosa más que un cargador de clases instruido para cargarlas desde memoria.

Además tenemos el notificador de errores más simple posible, y con esto cerramos el círculo y tenemos todas las piezas.

Como se puede ver en los ejemplos del método main de esta clase, se pueden hacer un montón de compilaciones, algunas bastante curiosas diría yo.

 

Nota final

 

Estas clases son sólo un ejemplo de cómo utilizar el API de Java para compilar código “on the fly”, y su único objetivo es didáctico: no han sido concebidas para ser utilizadas en producción.

Declino toda responsabilidad sobre los resultados y efectos secundarios que éste código pueda producir y los desastres a los que arrastre al incauto que lo utilice en un proyecto real.

Así mismo estaré encantado de recibir sugerencias, mejoras y ejemplos de utilización.

 

Referencias

Dónde encontrar el código fuente:

 

Se compone de:
InMemoryCompiler.java
InMemoryExecutor.java

Algunas páginas de interés:

Francisco Morero Peyrona
http://peyrona.com

PrintView Printer Friendly Version

EmailEmail Article to Friend

Reader Comments (12)

Casualidad de casualidades. Precisamente he decidido crear una bitácora y mi primer artículo que aun estoy preparando es exactamente sobre este tema. Cuando lo termine envío el enlace.

Saludos.

diciembre 12, 2011 | Registered CommenterAntonio Sánchez

yo sigo consternado por que alguien quedria hacer eso que utilidad le encuentran me mata la curiosidad

diciembre 13, 2011 | Unregistered Commenterluis

Así a primera vista le veo muchas utilidades, como la posibilidad de tener en base de datos scripts que dependan por ejemplo de un momento dado, y no necesitar modificar la aplicación principal (el war) ni subir archivos a un sistema de archivos. Bastaría con definir una convención de recuperación del script para que pudiésemos tener un sistema dinámico sin necesidad de tocar la línea principal de desarrollo.

diciembre 13, 2011 | Unregistered Commentercoutemeier

Te tomo la palabra, Antonio. :-)
Me encantará ver tu artículo.

Por otro lado y sobre los otros dos comentarios: lo cierto es que este tipo de "trucos" tienen muchas aplicaciones prácticas. De hecho si lo hice es porque lo necesitaba, un día se lo comenté a Abraham y él me propuso que lo compartiese, lo que he hecho encantado.

diciembre 13, 2011 | Unregistered Commenterpeyrona

Es verdad no tiene muchas aplicaciones, pero las que tiene son MUY interesantes

Sin ir más lejos aunque en mi caso en aquel momento lo más parecido era Javassist.

diciembre 14, 2011 | Registered Commenterjmarranz

Está muy bien el artículo y es verdaderamente interesante, no solo por lo que cuenta, sino porque lo que cuenta está muy poco difundido, creo yo.

Otro tema sobre el que no he visto nada (tampoco es que haya buscado muy a fondo todavía) es el de cargar clases Java de forma dinámica para poder implementar una arquitectura de tipo "plugin". No muchas aplicaciones de escritorio lo usan y para ciertas cosas me parece una funcionalidad muy interesante.

diciembre 14, 2011 | Registered Commenterzx81

@zx81m para lo que dices de cargar en forma de plugins, lo mas extendido actualmente es que uses OSGi, que parece complicado pero no lo es tansisimo. Este es el sistema que usan muchas aplicaciones (Eclipse, Glassfish, Spring...). Por otro lado tb existe un sistema similar que es el que usa Netbeans y son, hasta cierto punto, compatibles.

En cuanto a lo de este articulo, yo lo veo muy interesante con utilidades practicas, pero claro, mi trabajo es muy concreto. Nosotros implementamos una herramienta que genere aplicaciones web mediante modelado y muy poco codigo, por lo que se escribe en un lenguaje grafico y se compila a java. En algun paso del proceso se hace una diferencia clara de lo que es estatico (identico para aplicacion) y lo que depende del usuario (entendiendo como usuario el diseñador de la aplicacion a generar, no el cliente final).

Cada vez que se genera una aplicacion, la parte dependiente del usuario tiene que compilarse mientras que la parte estatica se suministra como una serie de jars. Ahora mismo, lo que hacemos, es crear un proyecto maven que es en el que el usuario coloca sus modelos y sus clases, se compila llama a maven con la clase Runtime (si, muy feo) y luego se desempaqueta un war al que le falta la parte del usuario, se añade el jar del usuario y se vuelve a empaquetar.

Gracias a poder compilar on the fly podria ser posible ahorrar ciertos pasos.

Resumiendo. Esto es muy util para programas que generen programas. Obviamente son programas muy concretos, pero hay algunos que nos ganamos los cuartos con ello! ;)

diciembre 14, 2011 | Registered Commentergolthiryus

Existe una alternativa a OSGi mucho, mucho más simple y que cubre el 90 y pico % de las necesidades de un proyecto "normal" y el 90% del funcionamiento de OSGi, pero no recuerdo cuaĺ es. Seguro que Abraham se acuerda.

Hay un JSR, el 198 (http://www.jcp.org/en/jsr/detail?id=198), pero había otro JSR que se ajusta mejor a lo que buscas.

diciembre 14, 2011 | Registered Commenterpeyrona

Estoy perdido ¿de qué estamos hablando?

La única alternativa OSGi que se me viene a la cabeza es el java module system que se supone que traerá Java 8.

diciembre 15, 2011 | Registered CommenterAbraham

No Abraham, hay una alternativa vieja, hemos hablado de ella tú y yo alguna vez, hace tiempo. Espera que la busco.....

Vale, me rindo: después de un buen rato leyendo JSRs no lo encuentro. Pero recuerdo una conversación con Abrham en la que hablamos de un JSR que hacía cosas muy parecidas a OSGi, con una gran salvedad, los componentes (a diferencia de OSGi) no podían cargarse y descargarse en caliente.

Si alguien lo encuentra, por favor que lo ponga aquí.

diciembre 15, 2011 | Unregistered Commenterpeyrona

http://openjdk.java.net/projects/jigsaw/

diciembre 15, 2011 | Registered CommenterAbraham

Lo prometido:

http://juntandolineas.wordpress.com/2011/12/18/compilacion-dinamica-con-java-compiler-api/

Saludos.

diciembre 19, 2011 | Registered CommenterAntonio Sánchez

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>