-[ 0x07 ]-------------------------------------------------------------------- -[ Programacion orientada a aspectos ]--------------------------------------- -[ by Paler ]--------------------------------------------------------SET-31-- PROGRAMACION ORIENTADA A ASPECTOS AOP -- La proxima degeneracion Por Paler paler@adinet.com.uy PRESENTACION ------------- Buenas, mi nombre es Paler y es la primera vez que vengo, digo.. que escribo. Agradezco al publico presente y muy especialmente al ausente, el que lo desee puede levantarse e irse ahora (o, mejor, pasar al siguiente articulo). Por ser mi primera intervencion en este, ejem, prestigioso medio de comunicacion escrita, me voy a permitir presentarme: pueden llamarme Paler y soy un aficionado a la programacion y muchas de las cosas que acostumbran ustedes a hacer (excepto por la parte de robarle bolsos a las ancianas); soy de y vivo en Uruguay (un peque~o pais en Sudamerica, entre Argentina y Brasil y no, no somos una provincia de Argentina ni de Paraguay) y digo esto porque el lenguaje y las expresiones que uso seguramente no sean las mismas que las de la mayoria, pero supongo que igual nos vamos a entender. Ahora si, ya que todos nos conocemos puedo proceder a la parte interesante. INTRODUCCION ------------- Casi con seguridad algunos de ustedes habran oido hablar, o leido escribir, acerca de la programacion orientada a aspectos o mas generalmente a la orientacion a aspectos en la programacion (que no son lo mismo). Se que la gran mayoria de ustedes prefiere tratar temas relacionados con el hack y otros demases, pero a alguno le puede resultar interesante. Primero voy a intentar hacer un poco de historia para saber mas o menos como llegamos a meternos en todo esto y despues pasare a dar los detalles. UN POCO DE HISTORIA -------------------- Hubo un momento en la historia que la tierra estuvo dominada por gigantes dinosaurios que hacian mucho ruido y se comian a la gente (sacado de la TV). Despues esos dinosaurios desaparecieron y poco tiempo despues aparecieron otros dinosaurios con lucecitas que prendian, apagaban y relampagueaban, con millones de palancas que gente con uniformes blancos movian de arriba para abajo, de abajo para arriba y de derecha a izquierda y viceversa logrando asi que las lucecitas prendieran, apagaran y relampaguearan. Con su infinita sabiduria, esa gente que usaba uniformes blancos, llamados cientificos, se dieron cuenta que mover palancas no era forma de controlar esos modernos dinosaurios llamados computadoras, que seguian haciendo mucho ruido y se comian el tiempo de la gente. Esto dio lugar a inventar algo maravilloso nunca visto hasta entonces (y aun hoy tampoco se lo puede ver) llamado software y casi simultaneamente surgio el arte de programar. En un principio, programar significaba poner juntos muchos ceros y unos, tantos como fuera posible anter de perder la razon y comenzar a ver elefantes rosados y pitufos amarillos y pronto se dieron cuenta de que a algunas secuencias de 0s y 1s se le podia asignar un nombre y que mejor, hasta los parametros se podian codificar de una forma mas sencilla, quedando inventado entonces el lenguaje ensamblador. Pero el lenguaje ensamblador tampoco era tan sencillo de manejar, principalmente porque cada maquina tenia un lenguaje muy diferente al de las demas. Se inventaron pues, lenguajes de mas alto nivel que, con ayuda de un compilador y demas, se transformaban a 1s y 0s, y era entonces tan comun ver listados de programas con los cuales era posible empapelar diez estadios de futbol (football o balonpie para los fundamentalistas de los idiomas). En esos listados, podia suceder que el mismo codigo que aparecia en el banderin del corner tambien aparecia en la mitad de la cancha y ademas pegado al palo derecho de uno de los arcos. Entonces alguien se pregunto si era posible escribir ese codigo tan repetitivo una sola vez y usarlo cada vez que fuese necesario simplemente incluyendo una referencia; el resultado fue la adopcion del concepto matematico de funcion o mas ampliamente el concepto de procedimiento. Los procedimientos acortaron enormemente el codigo fuente de todos los programas y por un tiempo todos estuvieron felices y programaron matrices, pero por donde se mirara habia variables, variables y mas variables... los datos estaban tan dispersos como cuando se caen las monedas al piso en el medio de una avenida transitada. No seria posible agrupar toda la informacion relacionada de alguna manera en un solo lado? si, la respuesta son los TAD, o mejor conocida como ATD (tipos abstractos de datos?, abstract data types?, eh.. archivos punto h?) y sus mejores aliados las estructuras (structs, unions, records, etcetera). Logicamente, los programadores todos contentos, los programas funcionaban a las mil maravillas, a veces se daba algun que otro "segmentation fault" pero todo era color celeste (que la vamos a hacer, no me gusta el color rosa, ademas, mi pais se distingue por el color celeste). Un dia, alguien que no tenia nada que hacer dijo: "hagamos que los datos sean privados y las operaciones publicas", bueno, no, dijo algo como "si el mundo esta lleno de objetos, porque los programas que modelan el mundo no estan hechos con objetos?" y surgio entonces la programacion orientada a objetos y comenzo la perdicion del software. Al principio no parecia tan malo y solo era cuestion de moda, pero no mucho despues aparecio Java(tm), los programas se hicieron tan pesados que la ultra-super-mega-hipercomputadora mas potente en el laboratorio mejor equipado se quedara rapidamente sin memoria y ejecutar el tetris programado en el susodicho lenguaje resultara como jugar al basquetbol (basketball o balon-canasta) con un globo lleno de helio. Esto ultimo no tiene nada que ver con lo que voy a tratar mas adelante pero me lo tenia que sacar de adentro. AOP...ESTA VIVO! ---------------- La verdad es que la programacion orientada a objetos simplifica bastante el desarrollo de software de aplicacion, nos guste o no. Ademas tiene un aire medio como de elegante, no? Bueno, el hecho es que ya todos creian que estaba todo inventado y OOP (Object Oriented Programming, opa! como ando) era el invento supremo y que mas alla solo habia vacio y el avance no estaba permitido para nadie so pena ser nombrado paria informatico o peor aun: hacker!. Pero habia un detalle que algunos notaron: todo muy encapsulado, cada clase con sus propios atributos y operaciones y nadie tenia que conocer como hacia el otro tal cosa mientras garantizara que lo hiciera y lo hiciera bien... pero habia cosas que eran muy dificiles de determinar quien debia hacerlas, o donde debian hacerse. Por ejemplo, si alguien le pide a un programador que escriba una clase que permita dividir dos numeros enteros y obtener el resultado, el programador debe tambien chequear que los numeros sean correctos? Levanten la mano quienes opinan que si... si a ustedes les pagan por hacer algo, hacen mas de lo que les piden solo porque es mas prolijo o se limitan a hacer lo que les pidieron? No es responsabilidad del tipo que quiere dividir los numeros asegurarse que son correctos? (A los que siguen pensando que si, pasen por mi casa que tengo que podar un arbol, pero quedaria mas prolijo podar los de todo el barrio para no desentonar). Para ser mas claros: todos conocemos la maxima "nadie deberia inventar la rueda dos veces" y tambien conocemos la otra "nadie deberia perder nada de su tiempo resolviendo problemas poco interesantes habiendo tantos problemas interesantes esperando ser resueltos"; entonces, por que un programador deberia distraerse en detalles secundarios en lugar de dedicarse de lleno a resolver el problema que tiene entre manos? (no, eso no, en horas de trabajo no) EL MEJOR EJEMPLO ----------------- La traza de un programa es algo bastante comun: muchos programas, por su importancia, deben dejar trazas (logs) de lo que sucede durante su ejecucion. El codigo que permite hacer estas trazas debe necesariamente estar disperso por todo el programa o, en el mejor de los casos, encapsulado en un objeto o TAD pero en ultimo caso siempre debe ser invocado cada vez que sea requerido registrar un hecho o una accion (el que nos hayan querido entrar al programa y se hayan equivocado en la contrase~a 11 veces es algo anormal y merece ser registrado en algun lado) y esto es responsabilidad de los programadores de cada modulo (piensen que esto es igual con el control de acceso, la persistencia de informacion, la salida a pantalla, y muchos etcetera). Que pasaria si un programador se olvidara de poner o invocar el codigo necesario? O peor, que pasa si de pronto se decide sacar todas las trazas? O mucho peor aun: que pasa si Bush gana las elecciones por tercera vez? FUNDAMENTOS FUNDAMETALES FUNDAMENTADOS --------------------------------------- Enumerando algunos problemas: * Algun programador puede olvidarse de alguna llamada o linea de codigo y el error sera seguramente dificil de notar * Hacer que una caracteristica (trazas, control de acceso, salvar a disco) sea opcional sin recompilar exige demasiadas lineas adicionales o chequeos que no hayudan a mejorar el comportamiento de la aplicacion * Hacer que el programador este atento a cosas no especificas de su problema hace que pierda concentracion (a que muchos de ustedes se enojan si tienen que dejar de programar para ocuparse de otra cosa?) y por lo tanto a reducir su productividad (el tiempo es dinero, el tiempo libre es placer). * A veces, durante la etapa de analisis o dise~o, no se conocen todos los requerimientos a cumplir (el usuario cambia de idea ni bien creemos que tenemos todo controlado) Por todas estas razones y muchas mas, la programacion orientada a aspectos se esta ganando un lugar en los papeles de los dise~adores, los dedos de los programadores y las pantallas de los usuarios. TEORIA DE LA AOP ---------------- Aviso: aca comienza la zona tecnica generalmente aburrida, con definiciones, conceptos abstractos y murmullos de gente entre los que se escuchan muchos "no entendi", "que dijo", "y eso que significa". Si continuan, estan advertidos y no repito si se duermen. Para empezar, quiero aclarar que la AOP no tiende a remplazar a la OOP ni es algo que se mira de costado, es mas bien un paradigma (yo avise) que toma todas sus ideas y le agrega algunas cosas. De hecho, casi todos las herramientas que permiten programar orientado a aspectos se basan en, o dependen de, algun lenguaje orientado a objetos, en su gran mayoria Java. Segundo: AOP es uno de los tantos paradigmas que se agrupan bajo la llamada AO u orientacion a aspectos; otros paradigmas son MDSOC (Multi-Dimensional Separation of Concerns, separacion de asuntos multi dimensionales, sueno como algo sacado de un libro de fisica cuantica), SOP (Subject Oriented Programming, programacion orientada a los temas) y AP (Adaptive Programming, programacion adaptativa), entre otros; si quieren mas detalles sobre estas, ya saben, internet es grande y libre, sientanse como en su casa, pero aca solo servimos AOP y bien amarga. Para continuar, quiero definir un concepto fundamental de los muchos que son propios de AOP. Se dice que un asunto es *transversal* cuando afecta a varios componentes (modulos, tads u objetos) pero no es inherente a ninguno de ellos, es decir, no se puede decir que es exclusivo de alguno. Por ejemplo, el manejo de excepciones en un programa OOP no es propio de ningun componente sino que todas las clases deben hacer en mayor o menor medida algun tratamiento ("estamos en tratamiento... el trata y yo miento", chiste local). Se le llama transversal porque "corta" transversalmente varios componentes y no cae en ninguno. Mucha gente les llama asuntos ortogonales, crosscutting concerns en ingles, asi que si ven por la interne' algo de eso, ya saben. Los asuntos transversales se transforman en aspectos. De otra forma, una pieza de software (toma pa' vos) tiene dos tipos de requerimientos: funcionales y no funcionales. Un requerimiento es funcional si es una tarea que el programa *debe* cumplir, como por ejemplo calcular el total de una venta o describir una propiedad inmobiliaria (lease, una casa) para un posible comprador. Por otra parte, un requerimiento no funcional es aquel que no es propio del programa pero que es conveniente que lo tenga, o, si bien es solicitado por el usuario no es la parte medular (cuanta palabra rara que estoy usando, me siento un politico diciendo que no voy a hacer una mierd? si me votan, pero sin que la gente se de cuenta). Para que quede mas claro, a continuacion listo algunos de los ejemplos mas tipicos de requerimientos no funcionales: * Ya mencione anteriormente las trazas * Salida a pantalla: claramente, la mayoria de los programas muestran la informacion en la pantalla (se acuerdan de las lucecitas que prendian, apagaban y relampagueaban?) pero no es propio de un programa determinado. * Tambien ya hable del manejo de errores: un programa que no maneje errores y se cierre a cada rato o muestre pantallas azules con muchos numeros raros no tiene mucho futuro (hay excepciones, por supuesto). * Seguridad: tal vez sea necesario controlar el acceso segun el usuario que este trabajando; un programa de facturacion o control de stock puede estar en esta situacion, pero a ningun analista, dise~ador o ingeniero se le deberia dar por pensar que la calculadora debe verificar que el usuario actual tenga permisos suficientes para ejecutarla. Tiene que quedar claro (tan claro como la piel de Michael Jackson) que un requerimiento no funcional es una caracteristica deseable de un sistema, pero sin la cual el mismo igual puede funcionar cumpliendo los minimos requisitos necesarios para lo cual fue solicitado. Desgraciadamente, la linea entre ambos tipos de requerimientos a veces no es tan facil de determinar, es decir, cuando lo principal de un programa es la seguridad y cuando no?. En un programa para un banco la seguridad es lo principal junto con el calculo de intereses y el debito del monto retirado, en un programa para una tornilleria no. DESARROLLO DE UN PROGRAMA USANDO AOP ------------------------------------- Nos piden un programa para llevar el control de stock y las ventas en una tornilleria (un lugar donde solo venden tornillos, pero tienen de todo tipo de tornillos, grandes, chicos, de cabeza redonda, de cabeza cuadrada, de rosca derecha, de rosca izquierda, sin rosca, sin cabeza, sin nada de nada, y eso). Que hacemos primero? pensamos cuanto le vamos a cobrar al tornillero. Que hacemos despues? nos rascamos un testiculo hasta que nos de ganas de trabajar. Despues de eso, nos ponemos a pensar que quiere este muchacho y que necesitamos para hacerlo: tornillos, tenemos una clase Tornillo (me olvidaba, vamos a usar Java porque no tenemos ganas de complicarnos la vida, y porque sino no meto AOP ni por la ventana), tambien una clase Factura, otra Venta, bla, bla, bla. Nos ponemos a programar (dejemonos de documentacion y todas esas manos, despues escribimos algunas paginas y se lo explicamos por telefono) pero dejamos de lado algunas cosas: las trazas (para que quiere el muchacho tornillero que su programa registre lo que hace, problema de el y a mi me sirve para explicar todo esto). Lo compilamos... corregimos errores, volvemos a compilar... mas errores que corregimos, compilamos. OK. Lo probamos y milagro! no funciona, tira excepciones de todos los colores. Arreglamos todos los problemas (y los que no podemos los ocultamos), compilamos, ejecutamos y voila! (iba a poner eureka pera esta muy gastado). La parte de salida a pantalla y la persistencia en bases de datos la pusimos en el codigo porque no viene al caso). Ahora nos queda agregarle las trazas y para eso tenemos que hacer un alto y pensar: y ahora? que corno hacemos? No desesperen que ya seguimos. HERRAMIENTAS PARA AOP --------------------- Existen muchas herramientas surgidas en los ultimos a~os para programar con AOP (de hecho, el paradigma mismo de la programacion orientada a aspectos tiene menos de 10 a~os desde que se le paso por la cabeza de alguien, tal vez sentado en el ba~o). Las mas conocidas son AspectJ, JBossAOP y AspectWerkz, todas ellas dise~adas con Java en mente; existen otras, incluso para C++ y hasta para C, pero no tienen camino por ahora. AspectJ es un lenguaje en si mismo ya que toma Java como base y la a~ade construcciones (por ejemplo, ademas de poder declarar clases con Class e interfaces con Interface, tambien permite declarar aspectos con Aspect) mientras que JBossAOP y AspectWerkz son herramientas combinatorias ya que combinan dos lenguajes existentes (ambos lo hacen con Java y XML) para lograr su cometido. Mi primer acercamiento a AOP fue con AspectWerkz (AW para los amigos) pero actualmente estoy trabajando con JBossAOP (perdon Jonas) y es este ultimo el que vamos a usar. DESARROLLO DE UN PROGRAMA USANDO AOP (CONT) -------------------------------------------- Bueno, ya elegimos JBossAOP, ahora como seguimos? Pensamos: "cada vez que el programa crea una nueva instancia de la clase tornillo, lo registramos en un archivo determinado"; bien, esto se parece a decir "cada vez que se ejecute el constructor de la clase Tornillo, queremos registrar el evento". Lo primero que tenemos que hacer es crear una clase con un metodo cuyo codigo lo unico que haga sea escribir "Se creo una instancia de tornillo"; la documentacion de JBossAOP indica que esta clase debe implementar la interfaz org.jboss.aop.advice.Interceptor (si no tienen JBossAOP instalado, y se interesaron un poco por el tema, esperen, terminen de leer y despues se meten en internet y bajan todo lo que quieran, al final del chapucerio este doy algunas direcciones utiles) la cual a su vez declara dos metodos: * public String getName() * public Object invoke(Invocation invocation) throws Throwable. La primera de ellas hace tan poco como devolver el nombre (o lo que nosotros queramos) de la clase, pero lo interesante es el segundo metodo que tenemos que implementar: este metodo sera el que se ejecute cada vez que se cree una nueva instancia de la clase Tornillo. Como notaran tiene un unico parametro y es mediante el cual JBossAOP nos pasa informacion contextual del aspecto (por ejemplo, que clase intento crear la instancia, que parametros le paso al constructor, etc. Nosotros en este caso lo vamos a ignorar pero si les interesa me dicen y para la proxima hacemos un programa real con utilidad real. Nosotros todo lo que queremos es escribir "Se creo una instancia de tornillo" y eso es lo que haremos, pero hay un detalle: es responsabilidad nuestra decirle al programa que continue con la creacion del objeto y eso se hace con el metodo invokeNext() del parametro invocation. En definitiva, nuestro codigo quedaria algo asi (al fin, amague, regate, pero nunca un codigo real!): public class CreacionTornillo implements Interceptor { public String getName() { return "CreacionTornillo"; } public Object invoke(Invocation invocation) throws Throwable { try { ret=invocation.invokeNext(); System.out.println("se creo una instancia de Tornillo"); } catch(Throwable t) { System.out.println("No se pudo crear una instancia de Tornillo"); t.printStackTrace(); } } } Bueno, mejor lo imprimimos en pantalla porque no tengo ganas de complicarme con archivos (no tengo ganas de buscar un codigo donde este hecho para copiar y pegar). Ya tenemos casi todo pronto: nuestro programa con una clase llamada Tornillo (que compila y hasta funciona y ya es utilizable solo que no registra ninguna traza) y nuestra clase que implementa la funcionalidad del aspecto. Ahora solo resta combinar todo esto y rezar que todo funcione (y despues cobrar, si es que ya no lo hicieron). La combinacion del programa con el aspecto se hace mediante un archivo XML en el cual le indicamos a JBossAOP que es lo que se debe combinar con que y cuando. MAS TEORIA (JURO QUE ES LA ULTIMA VEZ) --------------------------------------- Necesitamos definir algunas cosillas mas: * Joinpoint (punto de union): es un punto en la ejecucion de un programa; se parece al concepto de brakpoint pero con la diferencia de que este ultimo es un punto en el codigo mientras que aquel se da en el programa en ejecucion. Un breakpoint indica *donde* mientras que un joinpoint *donde* y *cuando*, por ejemplo la construccion de un objeto o la aparicion de una excepcion. * Pointcut (punto de corte): un pointcut selecciona uno o mas puntos de union. Es un predicado (se acuerdan de la primaria cuando estudiaban sujeto, verbo y predicado? no tiene nada que ver pero se me paso por la cabeza). En general, los pointcuts se definen mediante expresiones regulares: por ejemplo la expresion "invocacion de todos los metodos que comiencen con el prefijo set, que esten en la clase Pitufo y que tomen un unico argumento de tipo Zarzaparrilla", podria escribirse "call(* Pitufo->set*(Zarzaparrilla))" donde call representa la invocacion de un metodo, el primer asterisco indica que no importa el tipo de retorno, la flecha entre el nombre de la clase y el nombre del metodo une ambas cosas y el segundo asterisco indica que no importa como sigue el nombre del metodo si empieza con set. Dos o mas expresiones pueden combinarse mediante operadores logicos (and, or y not) en un mismo pointcut. * Advice (se traduce como consejo o aviso, pero realmente no se como decirlo en espa~ol): es el metodo propiamente dicho que se ha de ejecutar cuando se alcanza un determinado pointcut. Corresponde a la logica que resuelve el trabajo del aspecto. DESARROLLO DE UN PROGRAMA USANDO AOP (Y III) --------------------------------------------- En nuestro caso, el pointcut que tenemos que definir es "la invocacion del constructor de la clase Tornillo" y el advice es el metodo invoke de la clase CreacionTornillo. En JBossAOP, al igual que en AspectWerkz, los constructores no se llaman por su nombre sino con el nombre new (nuevo). Supongamos que la clase Tornillo es como sigue: public Class Tornillo { ... public Tornillo() { ... } public Tornillo(int largo, int ancho) { ... } } (notar que tiene dos constructores) entonces nuestro pointcut se escribiria como sigue: "call(Tornillo->new(..))" Por ser un constructor no indicamos tipo de retorno (los constructores no declaran tipo de retorno) e indicamos que no nos interesa cuales sean los parametros del constructor (eso es lo que indicamos con los puntos suspensivos, a mi en la escuela me ense~aron que los puntos suspensivos eran tres, pero todo el mundo usa dos, yo estoy en el mundo, asi que uso dos, igual que JBossAOP). Ahora bien, por 1000 euros: donde metemos ese pointcut? La respuesta es... en un archivo de configuracion en lenguaje XML, cuya estructura es muy complicada como para explicar aca (ademas, esto se esta llendo muy largo) asi que vamos derecho al grano (como dijo un dermatologo y dejo la mazorca limpita): Tan simple como eso: con la etiqueta bind y su atributo pointcut le decimos a JBossAOP que queremos interceptar (se dice que se intercepta la invocacion de un constructor) la instanciacion de la clase Tornillo mediante la clase CreacionTornillo (JBossAOP, antes de comenzar a ejecutar el constructor, nos dara paso a nosotros, a traves del metodo invoke de la clase CreacionTornillo). Guardamos el archivo anterior con el nombre que mas nos guste, pero con extension xml (si queremos le ponemos otra extension, pero para que complicar las cosas si ya de por si nosotros somos los complicados) y procedemos a compilar y ejecutar todo (esta es la parte mas dificil porque hay que agregar algunas clases que JBossAOP precisa, aunque en principio alcanza con agregar el paquete jboss-aspect-library-jdk50.jar *al principio* de la variable de entorno CLASSPATH si utilizan Java 5 (Java 1.5). Para compilar y ejecutar escribimos lo siguiente (suponiendo que la clase principal, la que contiene el metodo main se llama Tornilleria.java): java -Djboss.aop.path=tornillos.xml org.jboss.aop.standalone.Compiler \ Tornilleria.java .. java -Djboss.aop.path=tornillos.xml Tornilleria .. Listo! ya tenemos nuestro programa compilado, combinado con el aspecto de traza y ejecutando! cada vez que se agregue un nuevo tipo de tornillo y por lo tanto se cree una instancia de la clase Tornillo, se imprimira una linea en la consola (nada util pero todos los ejemplos que se me ocurrian tambien eran muy muy chotos como este, o muy muy complicados para una primera vez, y como para algunos la primera vez duele.. mejor que sea leve). MAS SOBRE AOP (AHORA SI, LO ULTIMO) ------------------------------------ AOP, y JBossAOP, AspectWerkz, AspectJ y el resto no solo sirve para interceptar constructores, por supuesto. Se pueden interceptar tambien le invocacion o ejecucion de cualquier metodo, el acceso a cualquier atributo, ya sea para lectura o escritura (por ejemplo, podemos querer registrar cada vez que cierto atributo es modificado, y cual es el nuevo valor), podemos interceptar excepciones, y muchas otras cosas. Otra cosa que resulta realmente muy pero muy interesante son las introducciones (introductions, las bromas las dejo a cargo de cada uno de ustedes, yo ya hice demasidas) que nos permiten hacer que una clase tenga atributos que no tiene, que tenga metodos que no le fueron declarados o que implemente interfaces que no implementa (un uso muy practico de esto es hacer que una clase implemente la interfaz-bandera java.util.Serializable para poder serializarla) pero esto ya queda para otro capitulo, por hoy ya ha sido demasiado. CONCLUSIONES FINALES --------------------- La programacion orientada a aspectos es un paradigma que promete, y que promete de verdad (no como ese chiste que dice que un futbolista conocido era un chico muy prometedor.. porque hacia a~os que prometia jugar mejor pero siempre estaba igual, chiste local). Al principio puede constar un poco asimilar los conceptos detras del paradigma, pero para los que tengan planes de desarrollar sistemas de aplicacion (de algo hay que vivir, yo ya intente quedarme en casa pero nadie llega a golpear la puerta a regalar dinero) pero vale la pena. Ademas, para todos ustedes, gente avida de conocimiento, sed de conocimiento y hambre de comida, seguro que aunque sea alguna ojeadita le van a dar al manual de JBossAOP, o al de AspectWerkz (Jonas se lo merece, grande Jonas) o algun otro. AGRADECIMIENTOS Y DESPEDIDA ---------------------------- Agradezco a mi familia que me alento a levantarme de la cama hoy (es feo dormir cuando el sol te da de lleno en la cara a la ma~ana, creo que eran como las 11), a toda la gente que hace, deshace y lee SET, a toda la gente que hizo y deshizo SET y que ya no esta (hay alguno?), al carnaval uruguayo que es lo mas grande que hay, a mi perra que quiere jugar todo el dia, a Peyo por haber inventado a los Pitufos, a von Neumman por haber propuesto su famosa arquitectura, a los japoneses por hacer tecnologia tan barata, a mi mismo por decir estas estupideces, a ustedes (bah, a esta altura, a ti, que sos el unico que llego hasta aca) y en fin, al mundo por ser mundo, y al dia de ayer que fue mejor que el de hoy, pero me queda la tranquilidad que el dia de hoy es mejor que el de ma~ana. Es todo por ahora, que la pasen bien, o que se la pasen bien, como ustedes decidan y prefieran, y recuerden, nunca pierdan la sonrisa, que alguien siempre la precisa, aunque mas no sea para olvidar que Bush sigue siendo presidente del planeta. Nos leemos, y los dejo con algunas direcciones utiles JBossAOP: http://www.jboss.org AspectWerkz: http://aspectwerkz.codehause.org AspectC: http://www.cs.ubc.ca/labs/spl/projects/aspectc.html Dudas, comentarios, insultos, numeros de tarjetas de credito (propias, no vale robados), chistes, articulos relacionados o no, cualquier cosa, a paler@adinet.com.uy. PD: Me olvidaba, agradezco al inventor de las lucecitas que prenden, apagan y relampaguean... prenden, apagan y relampaguean... prenden, apagan y... *EOF*