![]() |
||
![]() |
||
Todo Java (IV): Judías, enchiladas y cajeta Ricardo Devis Botella Los Beans son granos de café que, molidos en un continente apropiado, generan el líquido javático del que se nutren los sueños … de Sun. Los APIs que aquí veremos (Introspection y Customization) se refieren a las normas de educación entre Beans continentes y componentes: un Bean sólo podrá formular ciertas preguntas a otro, y éste vendrá obligado a contestarlas [1] . Y es que antes de combinar componentes hay que obligarlos a entenderse entre sí. |
||
En el artículo anterior examinamos someramente el esquema de persistencia de Java Beans y el nuevo modelo de eventos de la AWT, aplicable a los mismos. En resumen se trataba de proporcionar, en el caso de la persistencia, un sencillo esquema por defecto para transformar objetos-componentes-beans en secuencias de bits (almacenables en un fichero o transportables por una red), y, en el caso del modelo de eventos, un soporte arquitectónico de notificaciones extensible y, a la vez, suficientemente rico como para que pudiera inter-operar con cualquier otro modelo. Así un Bean de tipo “PushButton” podrá mantener un estado inicial “serializado”, como también podrá comunicarse con otros beans-objetos (como widgets, bases de datos, etc.) mediante el ya visto esquema de notificación. Se trata de dos porciones básicas que, entre bastidores, procuran la carga, interacción y almacenamiento de Beans. Faltan, empero, las porciones escénicas que habrán de mostrar al público qué puede engarzarse y cómo tal podría llevarse a cabo, así que en lo que sigue hablaremos de introspección, reflexión y personalización. Y no, no se trata de una representación de los Ejercicios Espirituales de San Ignacio de Loyola, pero el leído lector seguro que encuentra más de una semejanza (en la estricta ordenación, en la parquedad, en el ánimo utilitarista y en la voluntad de expansión universal). Claro que tal exhibición de buenos deseos y abrigo de otros modelos de componentes pudiera parecerle utópica al desconfiado lector, pero no debería éste olvidar que, como bien decía Lamartine, “las utopías no son a menudo otra cosa que verdades prematuras”. INTROSPECCIÓN Cuando un continente/contenedor se encuentra en tiempo de ejecución con un Bean debería echar mano de algún mecanismo que le permitiera conocer a qué debe enfrentarse y cómo puede obtener cierto tipo de colaboración. Los APIs, pues, que permiten este tipo de análisis de un Bean se enmarcan en el área denominada “Introspección”, y permiten extraer información de propiedades, métodos y eventos de manera que un constructor visual pueda presentarla de forma asimilable a sus usuarios para que éstos puedan manipularla, así que … “Un momento -brama el lector-: ¿Qué son propiedades?” Ooops. De nuevo acierta el lector. A ello: Propiedades Según la especificación 1.0 del API de Java Beans, una propiedad es “el atributo nominado y discreto de un Bean que puede afectar su apariencia o comportamiento”. Se trata, en tosca brevedad, de un atributo público de un Bean: por ejemplo, el color de un diálogo, la etiqueta de un botón, el título de una ventana, la URL destino de un componente web, la base de datos en un generador de queries, etc. Los atributos de que aquí hablamos no representan estrictamente campos de una clase (como, en el caso de una Persona, nombre o hobby), sino también valores computados (como edad, calculada con la fecha de nacimiento), y pueden ser de lectura-escritura, de sólo lectura o de sólo escritura. El hecho de que las propiedades no se circunscriban a variables nos plantea la cuestión: ¿cómo identificar las propiedades de una clase? Pues fácilmente: mediante la observación de los métodos “getters” y “setters” de tal clase: esto es, de los métodos que comienzan con “get” (como “getEdad()”) y que leen/devuelven valores de atributos, y de los métodos que comienzan por “set” (como “setHobby( String miHobby)”) y que escriben/establecen valores de atributos. Claro que esta disposición sintáctica nos indica que habremos de cumplir con un particular (y muy extendido, por qué no decirlo) esquema de codificación de identificadores que no puede ser controlada por el compilador. La ventaja, sin embargo, es que tal estilo de codificación hace posible el análisis automático de las propiedades de un Bean (mediante Introspección, como inmediatamente veremos), de manera que si tenemos el par setter/getter
inferiremos que el Bean posee la propiedad Propiedad que, en este caso, es de lectura/escritura. Es posible, también, encontrar propiedades que contienen un valor lógico, y cuyos “getters” y “setters” adoptan la signatura “is…”:
de forma que ahora la propiedad sería nominada “Activo”. Pero aparte de estas propiedades de un sólo valor, denominadas Propiedades Simples, el paquete de Beans (sus bibliotecas) proveen otras de tipos: indexado, encadenado y restringido. Una Propiedad Indexada es, simplemente, una propiedad que representa un array de valores y cuyos “getters” y “setters” adoptan la siguiente signatura de lectura y escritura de arrays:
Una Propiedad Encadenada es aquélla cuya modificación pudiera ocasionar repercusiones en otras propiedades pertenecientes al Bean continente o a otros Beans, de manera que notifica a otros objetos cuando su valor (simple o de cualquiera de los elementos del array) cambia. Podríamos codificar manualmente el flujo de notificaciones, según vimos en el epígrafe anterior dedicado a eventos, pero para nuestra fortuna la clase utilitaria java.beans.PropertyChangeSupport proporciona buena parte de la funcionalidad necesaria para implementar propiedades encadenadas. Basta con extender dicha clase o añadir un miembro de la misma a nuestro Bean para así poder delegar en él parte del trabajo asociado a una propiedad encadenada, como la adición y eliminación de receptores del evento (implementadores del interfaz PropertyChangeListener) y el lanzamiento del evento de cambio (consistente en la invocación del método PropertyChangeListener.propertyChange en todos los receptores/listeners registrados). Naturalmente el observador lector se ha percatado de que este esquema de notificación de eventos está basado en la clase, no en la propiedad, así que ¿cómo discernirá el receptor con qué tipo de propiedad y modificación está tratando? Pues fácilmente, porque el evento “PropertyChangeEvent” contiene el nombre de la propiedad, el valor original de la misma y el nuevo valor tras la modificación. Una Propiedad Restringida es, conceptualmente, un caso especial de una propiedad encadenada, de forma que cuando se modifica y se produce la notificación a otros Beans, cualquiera de éstos podrá revisar el cambio de valor y, si no encaja en sus márgenes de validación, rechazarlo (o vetarlo, en la mejor tradición de la ONU). El procedimiento de veto es bien simple: el Bean objetor, si no está conforme con el valor modificado, lanzará una excepción del tipo “PropertyVetoException”, que deberá ser captada y convenientemente gestionada por la función setter:
Así, un Bean con propiedades restringidas debe implementar el par usual de métodos de registro de receptores de eventos VetoableChangeListeners:
de manera que cuando se produzca una modificación en el valor de una propiedad restringida mediante su “setter”, el Bean invocará el método “VetoableChangeListener.vetoableChange”, pasando como parámetro un objeto “PropertyChangeEvent”, en cada uno de los receptores/listeners registrados. Como vimos anteriormente, el objeto “PropertyChangeEvent” contiene información sobre el nombre de la propidad y valores antiguo y modificado, de forma que el Bean objetor puede verificar la validez del cambio y, en caso de no aceptarla, arrojar una excepción del tipo “PropertyVetoException”. Tras captar esta excepción, el Bean original debe reponer el antiguo valor (pues la modificación de la propiedad fue real) y lanzar un nuevo evento de cambio a los receptores registrados. Al igual que ocurría con las propiedades encadenadas respecto de “PropertyChangeSupport”, la clase utilitaria “java.beans.VetoableChangeSupport” proporciona similares facilidades de uso. Reflexión “Revisadas las propiedades, sigamos con la introspección [2] ”. La idea es proveer una implementación por defecto, asumible por la Máquina Virtual Java y denominada mecanismo de reflexión (reflection) de bajo nivel, que permita interrogar a un Bean (clase u objeto) por sus métodos, e incluso invocarlos, modificarlos o añadir nuevos, si las circunstancias de seguridad lo permiten. El importante API Java de “Reflection”, disponible en el package java.lang.reflect del JDK 1.1, consiste en tres nuevas clases -“Field”, “Method” y “Constructor”- que, además de “Array”, han de encerrar información “reflejada” de los campos pertinentes de las clases a inspeccionar. Los tipos de objetos son reflejados por clases, y los tipos predefinidos por sus envoltorios (wrappers) apropiados [3]. La clase Method, por ejemplo, permite manejar información (con la ayuda de una nueva clase utilitaria denominada “Modifier”) sobre el nombre de métodos asociados a una clase, su cualificación de acceso, sus tipos de parámetros y de devolución, así como también posibilita su invocación:
Se trata de que las clases Java puedan ser inspeccionadas mediante código Java que sería utilizado primariamente por depuradores, constructores de aplicaciones y entornos de scripting. A tal fin la clase “java.lang.Class” ha sido extendida en el nuevo JDK 1.1 para acomodar la “reflexión” [4], de forma que se han provisto métodos para crear nuevas instancias de Field, Method y Constructor, así como otros (getMethods, getConstructors, getFields) para determinar en una clase dada métodos y variables, heredados o no (si el análisis se quiere circunscribir únicamente a la clase, obviando a sus clases base, entonces habría que usar “getDeclaredMethods”, etc.). Patrones de Diseño Una vez que los APIs de introspección, mediante “reflection”, han determinado los métodos públicos de un Bean dado, se aplican a éstos lo que se denominan “patrones de diseño” para identificar propiedades y eventos. En realidad esto suena bastante más pomposo de lo que en realidad es: se trata de la aplicación de elementales técnicas de reconocimiento a los nombres de los métodos para de ahí inferir las propiedades o eventos notados. Pero ejemplifiquemos: si la “reflexión” nos procura dos métodos
la aplicación de los patrones de diseño (del sentido común, en definitiva) simplemente establece que existe en la clase una propiedad de lectura-escritura (get y set) que nominará como Dirección. Si un contenedor de Beans tuviera que mostrarnos en una hoja gráfica de propiedades de un Bean esta propiedad “Direccion”, compondría un campo de edición para el valor de la propiedad, significando así su posibilidad de lectura/escritura. Naturalmente el uso mismo de los patrones de diseño ha de instar a los programadores a ser más cuidadosos con la elección de los nombres de sus métodos, de manera que pueda ser extraída información consistente de los mismos [5] . El uso de convenciones para la codificación de identificadores y signaturas, además de convertirse en adecuada documentación para los lectores humanos, puede ayudar a una adecuada interpretación de la intención del programador por parte de herramientas software. Así, también por ejemplo, si hallamos un par de métodos de la forma:
donde “<TipoDeEventListener>” es el nombre de una clase que extiende el interfaz “java.util.EventListener” y acaba en “Listener” (como, verbigracia, “AddressUpdateListener”), los patrones de diseño para eventos inferirán que la clase que contenga tales anteriores métodos es un multi-emisor de eventos del tipo “<TipoDeEventListener>”, y así nos lo haría notar, por ejemplo, un constructor visual de aplicaciones. Como notará el lector, el nombrado asunto de los identificadores de los miembros de una clase no es asunto que pueda dejarse a la sola intuición. BeanInfo Existen patrones de diseño para propiedades simples, lógicas, indexadas, multi-emisores y uni-emisores de eventos, y todo esto parece lógico y muy aprovechado pero, a la vez, un tanto cogido por los pelos: esto es, no parece que pueda captarse información muy especializada con estos mecanismos. Pero es que para tratar esa información explícita el paquete de Beans provee los “BeanInfo”. La idea es simple: si un Bean desea explicitar expresamente qué propiedades, eventos y métodos soporta únicamente ha de proveer una clase que implemente el interfaz “java.beans.BeanInfo” y cuyo nombre, por convenio, será el del Bean seguido por el sufijo “BeanInfo” (como “AccesoAFicherosBetrieveBeanInfo” o “VisorDeBMPsBeanInfo”). El interfaz público BeanInfo provee métodos como “getPropertyDescriptors()” o “getAdditionalBeanInfo()” que, implementados por nuestra clase “asistente”, podrán proporcionar información sin tener que recurrir a la interpretación. Así un BeanInfo básicamente devuelve descriptores (Descriptors) sobre parámetros, propiedades, conjuntos de eventos, métodos, características, propiedades indexadas y los mismos Beans, pero también texto de ayuda, nombres especiales de visualización, etc., y tal información se accede desde los constructores visuales mediante “Instrospector.getBeanInfo()”. Pero que no se alarme el lector: no hay que implementar todos los métodos del interfaz BeanInfo, sino únicamente aquellos que necesiten cierta o mucha ayuda para su presentación correcta al usuario, de forma que el sistema intentará inferir mediante reflexión automática la información que no se aporte mediante BeanInfo. Otra posibilidad es que, en un esquema jerárquico de Beans, la clase Bean base aporte un BeanInfo completo, mientras que se dejarían al análisis automático los Beans derivados. Pero claro, algún bregado lector se quejará: “¡Aviado voy si tengo que someterme al estrecho interfaz de BeanInfo para procurar información sobre mis componentes: yo necesito utilizar mis propios esquemas y formatos de configuración!”. Desde luego el lector tiene las ideas claras. Pero no hay problema. De hecho el uso habitual de BeanInfos será el de filtro intermedio entre los Beans y sistemas propietarios/privados de configuración, de manera que el BeanInfo asociado a un Bean dado se limitará a saber interpretar los objetos de configuración provistos para tal Bean por sus creadores. “Pero -ataca de nuevo el hirsuto lector de antes- yo necesito también presentar a los usuarios Asistentes de Configuración (wizards) para guiarles por el proceso de revisar y modificar las propiedades, métodos y eventos de mis Beans”. Desde luego este lector está en todo, pero esto lo solucionan los APIs de personalización que revisaremos a continuación. APIS PARA CONSTRUCTORES DE APLICACIONES (DE PERSONALIZACIÓN) Como ya se indicó, los Beans asumen dos roles principales: bien como componentes activos en tiempo de ejecución insertos en aplicaciones, bien como componentes en constructores de aplicaciones. Respecto de estos últimos se han desarrollado APIs de adaptación de Beans, de manera que estos APIs son también llamados de personalización (Customization). Y es que, como se ha notado frecuentemente en los apartados anteriores, el usuario de común necesita personalizar los Beans al utilizarlos en constructores visuales, de forma que puedan ser cambiados su apariencia (colores, tipos de letra, tamaño, etc.) y comportamiento (nombre de la base de datos a acceder, etc.). El paquete de Beans provee dos tipos distintos de soluciones para solventar estas necesidades: por un lado facilita la creación de hojas de propiedades (ya saben, esas que suelen aparecer cosidas en un cuaderno con pestañas), y por otro permite el uso de Asistentes de Configuración (wizards). Hojas y Editores de Propiedades La idea es que las propiedades encontradas mediante Introspección puedan ser convenientemente editadas en constructores visuales. Lo elemental es que a cada propiedad el creador del Bean asocie un Editor de Propiedades particularizado, de forma que la unión de los pares (Propiedad, Editor de Propiedad) configurará una hoja de propiedades que podrá ser mostrada por el constructor visual al usuario, generalmente en su propia ventana de GUI. Los editores de propiedades para los tipos “normalizados” los proveerá JavaSoft, mientras que, naturalmente, cada creador de Beans podrá añadir editores para sus nuevos tipos de datos. El funcionamiento de tales editores es trivial: los valores de cada propiedad serán mostrados como una cadena a editar (dependiendo de sus posibilidades de lectura/escritura) o como un color o un mapa de bits que podrán ser cambiados mediante la oportuna selección en un adecuado control visual (una comboBox o una ventana separada con opciones de selección). Cada vez que se produzca un cambio el Editor de la Propiedad afectada lanzará un evento “PropertyChange”, lo que permitirá al software de más alto nivel advertir la variación y operar en consecuencia (lanzando, por ejemplo, otros eventos para mantener sincronizadas varias vistas, o invocando métodos para la modificación de repositorios, etc.). Cabe resaltar que un editor de propiedades está expresamente separado del bean en sí, de forma que sólo es necesario durante el desarrollo de aplicaciones, no durante la carga de componentes (a efectos del “downloading” via red). Es fácil ver que este esquema resulta conveniente para tratar con un número no muy grande de propiedades, pero resulta inadecuado para configuraciones complejas que necesitan de la interacción de propiedades o que, simplemente, acumulan demasiadas propiedades que editar. Para estos casos lo ideal es un asistente. Asistentes de Configuracion Los asistentes/magos de configuración que aparecen en distintos entornos (como en MS Developer Studio) no podían faltar en el universo (o mejor: cazuela) de los Beans. Esta especial clase de objetos de ayuda interactiva, asociados indisolublemente a un Bean dado, han de extender (subtipar) directa o indirectamente java.awt.Component (a fin de encajar en el esquema gráfico de la AWT) y además implementar el interfaz java.beans.Customizer. Cada asistente de personalización usualmente se mostrará en un diálogo AWT separado, aunque no existen restricciones de diseño respecto de su presentación, más allá de la prudencia, el decoro o el Código Penal. Claro que, ¿cómo ejecutar tal asistente desde un constructor visual, que es lo que nos interesa? Pues bien fácilmente: sólo hay que crear una clase BeanInfo apropiada para nuestro Bean (esto es prescriptivo), de forma que el contenedor invocará, hallada tal clase, el método “beanInfo.getCustomizerClass()” para localizar al asistente. JUDÍAS ENLATADAS Y DISTRIBUCIÓN EN GRANDES SUPERFICIES Los Beans serán empaquetados para su cómoda distribución en ficheros JAR, que, en breve, son ficheros ZIP a los que puede añadirse un “Manifiesto” con información adicional y que pueden contener clases Java (.class), Beans serializados (.ser) -esto es, objetos Beans con un estado interno persistente-, ficheros de ayuda (.htm*) y recursos (imágenes, texto, sonido, personalización geográfica, etc.). Los contenedores de Beans podrán, así, elegir cómo y cuándo instalar un Bean determinado incluido en un JAR. ¿ACTIVEX CON JUDÍAS O JUDÍAS CON ACTIVEX? Dado que uno de los principales objetivos de los Beans es la integración con otros modelos de componentes, es evidente suponer que sea precisamente la integración con ActiveX, debido a su extensión y disponibilidad actual, la que se lleve el grueso de los esfuerzos. Naturalmente los Beans tienen bien aprendidas las normas de comportamiento y aislamiento en el entorno ActiveX (ya saben que se trata de un mundo poblado de clases): deben parecer (simular) controles ActiveX y poseer una biblioteca de tipos asociada, pero sin tener que preocuparse de su entorno exterior. Es decir: los Beans, y por ende sus programadores, deberán abstenerse de usar, bajo pena de oscuridad eterna, herramientas Microsoft o funciones características ActiveX, código distinto de Java (como C++) y, en general, cualquier retazo de diseño Microsoft, por pequeño que sea, diferente de la mera comunicación educada y distante. La idea es que un Bean desarrollado exclusivamente en el entorno Java pueda ser vestido/empaquetado, mediante una herramienta apropiada, como un ActiveX y, en tal proceso y tras una revisión por medio de los APIs de Introspección, se genere una biblioteca de tipos y un fichero de registro asociados -además de ficheros específicos de configuración y de registro de eventos-, se instale en la máquina cliente y pueda ejecutarse en el universo (agujero negro, dicen algunos) ActiveX. |
||
Figura 1: OCX que oficia de puente entre COM y Beans |
||
En este punto la perplejidad surge clara: si los APIs de Java Beans van a embeberse en el JDK 1.1, como también va a formar parte de la plataforma Java la Invocación de Métodos Remotos (una visión unilenguaje de CORBA), serialización, etc., y se supone la necesidad de cambiar todas las instalaciones actuales de tal plataforma por la nueva basada en JDK 1.1 (pues ésta es la fuerza y la necesidad de Java), resultará que Microsoft vendrá forzado a aceptar en WinTel un conjunto arquitectónico que otorga preponderancia a un modelo distribuido de componentes bien distinto del de COM/DCOM, con especial hincapié en la importancia de CORBA. Microsoft, por otra parte, promete anuncios importantes. O sea, que vamos a tener material periodístico para rato. PUENTES, TÚNELES Y CONDUCTOS VARIOS Sin duda ActiveX, por su difusión, es uno de los modelos de componentes más importantes a considerar de cara a establecer puentes de comunicación [6] , pero existen otros modelos y arquitecturas:
BDK: KIT DE DESARROLLO DE BEANS Naturalmente el creador de Beans ha de sustraerse a la documentación de JavaSoft sobre la materia, pero, si quiere ver código “real”, debe desempaquetar el BDK (Beans Development Kit), que pretende ser una referencia básica para futuros desarrollos de Beans y contiene: una implementación de los APIs de Java Beans (en ficheros .class y .java), un contenedor Bean de referencia (denominado “BeanBox”) con su código fuente (que permitirá probar los nuevos beans y, a la vez, aprender sobre cómo construir un tal contenedor), y diferentes ejemplos y makefiles. El BDK no incluye, sin embargo, ninguno de los puentes ActiveX, OpenDoc o LiveConnect que, en cada caso, serán comercializados por sus propietarios respectivos. Acompañan al BDK, también, ejemplos y demostraciones de herramientas y Beans de empresas distintas a JavaSoft. PRÓXIMA ENTREGA Tras tantas judías algún lector podría cuestionar: “Mucho Bean, mucho Bean, pero ninguna referencia a Bean Crosby, ¿eh?”. Bueno, tiempo habrá para todo, aunque ciertamente quedaron fuera de este recetario proyectos actualmente en desarrollo (como San Francisco), herramientas (como Mojo, de Penumbra Software), beans (Tazza o J-Empower) y un cúmulo de intenciones, buenas o malas, pero en ningún caso lerdas. En definitiva: el modelo de componentes representado por Java Beans hace suyo el aforismo de Lichtenberg “No podría decir a ciencia cierta si la situación mejorará cuando las cosas cambien; lo que sí puedo decir es que tienen que cambiar para que la situación mejore”. Así en el próximo artículo abordaremos el espinoso y esperado asunto del acceso a bases de datos mediante la biblioteca de clases JDBC, pero sepa ya el lector que por encima de tales clases se operará, pues es lo razonable, con Beans. Ya pueden anticipar, por tanto, las cuitas comerciales que tal enfoque ha generado. En fin, como anuncia mi colega Antonio Vázquez: ¡Pasen y Bean! [1] Tal estilo se denomina genéricamente COP (Contract-Oriented Programming: Programación Orientada-a-Contratos), y el propio acrónimo sugiere que … ¡de anarquía nada! [2] Como la misma Guardia Civil ha anunciado en más de una ocasión. La intención la dejo al criterio del lector. [3] De hecho, y a partir de las necesidades de estos APIs, se han creado en el JDK 1.1 dos nuevos envoltorios “Byte” y “Short” para los tipos predefinidos homónimos. [4] Desafortunadamente los programadores difícilmente pueden extender su mente para acomodar la reflexión, la prudencia o cualquier tipo de ponderación de la que no resulte interesante hablar en una tertulia televisiva. [5] El asunto es simple: un programador Java no generará código correcto si no ha estudiado antes todos los APIs que conlleven el reconocimiento de patrones de codificación de identificadores. Así que volvemos a la idea básica: en Java lo principal no es el lenguaje, sino la plataforma. [6] El hombre se caracteriza por su ansia intercomunicadora (AI), lo que usualmente le procura muerte y dolor en el sexo (AI = promiscuidad y adulterio); dolor y muerte en la guerra (AI = expansión de fronteras); muerte y dolor en las zonas vírgenes (AI = carreteras en las selvas amazónicas), etc. ¿Qué modelo de componentes resultará mortalmente herido en el tendido de los puentes de Beans? Humm, habrá que estudiar el horóscopo de James Gosling. |
||
| Pº. Castellana 188, 14º e · 28046 - Madrid · info@a4devis.com |
||