BeanLint: una herramienta de resolución de problemas de JavaBeans, Parte 1

Cada par de meses, recibo un correo electrónico en pánico o desconcertado de un neófito de JavaBeans que está tratando de crear un JavaBean que contenga un Imagey que no puede entender por qué el BeanBox no carga el bean. El problema es que java.awt.Imageno lo es Serializable, por lo tanto tampoco hay nada que contenga java.awt.Image, al menos sin serialización personalizada.

Yo mismo he pasado incontables horas poniendo println()declaraciones en el código de BeanBox y luego volviéndolo a compilar, tratando de averiguar por qué mis beans no se cargan. A veces se debe a algo simple y estúpido, como olvidar definir el constructor de argumento cero, o incluso la clase, como public. Otras veces, resulta ser algo más oscuro.

El caso del frijol perdido

Si bien los requisitos para escribir una clase Java como un JavaBean son simples y directos, existen algunas implicaciones ocultas que muchas herramientas de creación de beans no abordan. Estas pequeñas trampas pueden devorar fácilmente una tarde, mientras busca en su código, buscando la razón por la que su herramienta de construcción no puede encontrar su bean. Si tiene suerte, aparecerá un cuadro de diálogo emergente con un mensaje de error críptico, algo como "NoSuchMethodException caught in FoolTool Introspection. "Si no tienes suerte, el JavaBean en el que tanto sudas se negará a aparecer en tu herramienta de construcción, y pasarás la tarde ensayando el vocabulario del que tu madre se esforzó tanto en curarte. El BeanBox tiene Durante mucho tiempo ha sido un infractor atroz en este sentido, y aunque ha ido mejorando, seguirá perdiendo propiedades e incluso granos enteros sin proporcionar al desarrollador una sola pista de por qué.

Este mes, lo guiaré fuera de la "tierra del frijol perdido" presentando una nueva herramienta llamada, curiosamente BeanLint, que analiza las clases dentro de los archivos jar, buscando posibles problemas que harían que las clases fueran inutilizables como beans. Si bien esta herramienta no cubre todos los posibles problemas de los frijoles, sí identifica algunos de los principales problemas comunes que hacen que los frijoles no se puedan descargar.

Para entender cómo BeanLintfunciona su magia, este mes y el próximo profundizaremos en algunos de los rincones menos conocidos de la API estándar de Java:

  • Crearemos un cargador de clases personalizado , que carga nuevas clases de Java desde un archivo jar

  • Usaremos el mecanismo de reflexión , que permite a los programas Java analizar las clases de Java, para identificar qué hay dentro de nuestros archivos de clases.

  • Usaremos el Introspectorpara producir un informe de todas las propiedades tipo frijol de la clase para cualquier clase en el archivo jar que pase todas las pruebas (y sea, por lo tanto, un bean potencial)

Cuando terminemos, tendrá una herramienta útil para depurar sus beans, comprenderá mejor los requisitos de los beans y, al mismo tiempo, aprenderá sobre algunas de las nuevas características interesantes de Java.

Conceptos básicos de frijoles

Para que un archivo de clase sea un JavaBean, existen dos requisitos simples:

  1. La clase debe tener un constructor público sin argumentos (un constructor de cero argumentos )

  2. La clase debe implementar la interfaz de etiqueta vacía. java.io.Serializable

Eso es. Siga esas dos reglas simples y su clase será un JavaBean. El JavaBean más simple, entonces, se parece a esto:

importar java.io. *; public class TinyBean implementa Serializable {public TinyBean () {}}

Por supuesto, el bean anterior no sirve para mucho, pero no le dedicamos mucho trabajo. Simplemente intente escribir un componente básico como este en otro marco de componentes. (Y no es justo usar "asistentes" u otros generadores de código para crear clases contenedoras o implementaciones predeterminadas. Esa no es una comparación justa de la elegancia de JavaBeans con otra tecnología).

La TinyBeanclase no tiene propiedades (excepto, tal vez, "nombre"), eventos ni métodos. Desafortunadamente, todavía es fácil crear accidentalmente clases que parecen seguir las reglas, pero no funcionan correctamente en un contenedor JavaBeans como BeanBox o su IDE (entorno de desarrollo integrado) favorito.

Por ejemplo, el BeanBox no cargaría nuestro TinyBeananterior si nos hubiéramos olvidado de incluir la palabra clave publicen la definición de la clase. javaccrearía un archivo de clase para la clase, pero el BeanBox se negaría a cargarlo, y (hasta hace poco de todos modos) no daría ninguna indicación de por qué se negaría. Para dar crédito a la gente de Java de Sun, BeanBox ahora generalmente informa la razón por la que un bean no se carga, o la razón por la que una propiedad no aparece en una hoja de propiedades, y así sucesivamente. ¿No sería bueno, sin embargo, si tuviéramos una herramienta para verificar tantas cosas como sea posible acerca de tales clases y advertirnos de aquellas que pueden causar problemas cuando se usan en un entorno JavaBeans? Ese es el objetivo deBeanLint: para ayudarte, como programador de JavaBeans, a analizar beans dentro de sus archivos jar, buscando posibles problemas para que puedas solucionarlos antes de encontrarte con ellos en el proceso de prueba o, peor aún, en el campo.

Problemas potenciales con los frijoles

Como he desarrollado JavaBeans para esta columna, probablemente he cometido la mayoría de los errores que se pueden cometer al escribir un JavaBean. En cierto modo, la naturaleza taciturna de BeanBox me ha obligado a aprender más sobre los frijoles, y sobre Java, de lo que hubiera aprendido de otra manera. La mayoría de los desarrolladores de JavaBeans, sin embargo, preferirían simplemente producir JavaBeans que funcionen correctamente y guardar las "experiencias de crecimiento" para sus vidas personales. He recopilado una lista de posibles problemas con un archivo de clase que puede causar estragos en un JavaBean. Estos problemas ocurren durante el proceso de cargar el frijol en un contenedor o al usar el frijol en una aplicación. Es fácil pasar por alto detalles en la serialización, por lo que prestamos especial atención a los requisitos de serialización.

A continuación, se muestran algunos problemas comunes que no causan errores en tiempo de compilación, pero pueden hacer que un archivo de clase no sea un JavaBean o que no funcione correctamente una vez que se carga en un contenedor:

  • La clase no tiene un constructor de argumento cero. Esto es simplemente una violación del primer requisito mencionado anteriormente, y es un error que los no principiantes no suelen encontrar.

  • La clase no se implementa Serializable. Esta es una violación del segundo requisito mencionado anteriormente y es fácil de detectar. Una clase puede afirmar que está implementando Serializabley, sin embargo, no cumple con el contrato. En algunos casos podemos detectar automáticamente cuando esto ha ocurrido.

  • La clase en sí no está declarada public.

  • La clase no se carga por alguna razón. Las clases a veces arrojan excepciones a medida que se cargan. A menudo, esto se debe a que otras clases de las que dependen no están disponibles en el ClassLoaderobjeto utilizado para cargar la clase. Escribiremos un cargador de clases personalizado en este artículo (ver más abajo).

  • La clase es abstracta. Mientras que una clase de componente, en teoría, podría ser abstracta, una instancia real en ejecución de un JavaBean es siempre una instancia de alguna clase concreta (es decir, no abstracta). Las clases abstractas no pueden instanciarse, por definición, por lo que no consideraremos clases abstractas como candidatas a beans.

  • La clase implements Serializable, sin embargo, ella o una de sus clases base contiene campos no serializables. El diseño predeterminado del mecanismo de serialización de Java permite definir una clase como implements Serializable, pero permite que falle cuando se intenta la serialización. Nuestra BeanLintclase asegura que todos los campos apropiados de una Serializableclase realmente lo sean Serializable.

Una clase que falla en cualquiera de los problemas anteriores puede estar bastante seguro de que no funcionará correctamente como un JavaBean, incluso si se cumplen los dos requisitos básicos del bean, establecidos al principio. Para cada uno de estos problemas, definiremos una prueba que detecte el problema en particular y lo informe. En la BeanLintclase, cualquier archivo de clase en el ser archivo jar analizó que hace pasar todas estas pruebas es entonces la introspección (analizados utilizando la clase java.beans.Introspector) para producir un informe de los atributos del frijol (propiedades, conjuntos de eventos, personalizador, y así sucesivamente). java.beans.Introspectores una clase en el package java.beansque utiliza el mecanismo de reflexión de Java 1.1 para encontrar (o crear) un java.beans.BeanInfoobjeto para un JavaBean. Cubriremos la reflexión y la introspección el próximo mes.

Now let's take a look at the source code for BeanLint to see how to analyze potential bean classes.

Introducing BeanLint

In the "good old days" (which usually means, "back when I still thought I knew everything"), C programmers on the Unix operating system would use a program called lint to look for potential runtime trouble spots in their C programs. In honor of this venerable and useful tool, I have called my humble bean-analysis class BeanLint.

Instead of presenting the entire source code in one huge, indigestible chunk, we're going to look at it one piece at a time, and I will explain along the way various idioms concerning how Java deals with class files. By the time we're through, we'll have written a class loader, used a respectable number of classes in java.lang.reflect, and have acquired a nodding acquaintance with the class java.beans.Introspector. First, let's have a look at BeanLint in action to see what it does, and then we'll delve into the details of its implementation.

Bad beans

In this section you'll see some class files with various problems, with the problem indicated below the code. We're going to create a jar file containing these classes, and see what BeanLint does with them.


import java.io.*;

public class w implements Serializable { w() { } }

Problem:

 Zero-argument constructor not

public


public class x { public x() { } } 

Problem:

 Not

Serializable.


import java.io.*;

public class y implements Serializable { public y(String y_) { } }

Problem:

 No zero-argument constructor.


import java.io.*;

class z implements Serializable { public z() { } }

Problem:

 Class not public.


import java.io.*; import java.awt.*;

class u0 implements Serializable { private Image i; public u0() { } }

public class u extends u0 implements Serializable { public u() { } }

Problem:

 Contains a nonserializable object or reference.


import java.io.*;

public class v extends java.awt.Button implements Serializable { public v() { } public v(String s) { super(s); } }

Problem:

 Nothing -- should work fine!


Each of these aspiring beans, except the last one, has potential problems. The last one not only is a bean, but operates as one. After compiling all of these classes, we create a jar file like this:

$ jar cvf BadBeans.jar *.class adding: u.class (in=288) (out=218) (deflated 24%) adding: u0.class (in=727) (out=392) (deflated 46% adding: w.class (in=302) (out=229) (deflated 24%) adding: x.class (in=274) (out=206) (deflated 24%) adding: y.class (in=362) (out=257) (deflated 29%) adding: z.class (in=302) (out=228) (deflated 24%) adding: v.class (in=436) (out=285) (deflated 34%) 

We aren't going to include a manifest file (which is a file inside a jar file that describes the jar file's contents -- see "Opening the jar" below) in the jar file because BeanLint doesn't deal with manifest files. Parsing the manifest file and comparing it to the contents of the jar would be an interesting exercise if you want to extend what BeanLint can do.

Let's run BeanLint on the jar file and see what happens:

=== Analyzing class u0 === class u0 is not a JavaBean because: the class is not public

=== Analyzing class z === class z is not a JavaBean because: the class is not public

=== Analyzing class y === class y is not a JavaBean because: it has no zero-argument constructor

=== Analyzing class x === class x is not a JavaBean because: the class is not Serializable

=== Analyzing class w === class w is not a JavaBean because: its zero-argument constructor is not public

=== Analyzing class v === Note: java.awt.Button defines custom serialization Note: java.awt.Component defines custom serialization v passes all JavaBean tests

Introspection Report -------------------- Class: v Customizer class: none

Properties: boolean enabled {isEnabled, setEnabled} (... many more properties)

Event sets: java.awt.event.MouseListener mouse (... many more event sets)

Methods: public boolean java.awt.Component.isVisible() (... many, many more methods -- sheesh!)

=== End of class v ===

=== Analyzing class u === class u is not a JavaBean because: the following fields of the class are not Serializable: class java.awt.Image i (defined in u0) === End of class u ===

La salida se ha acortado un poco porque la lista de conjuntos de eventos y métodos es muy larga y no agrega mucho a nuestra discusión aquí. Puede ver la salida completa en el archivo output.html, si quiere una idea de la cantidad de material que se BeanLintpublica.

Observe que BeanLintidentificó correctamente los problemas con los archivos de clase incorrecta:

la clase u0 no es un JavaBean porque: la clase no es pública la clase z no es un JavaBean porque: la clase no es pública clase y no es un JavaBean porque: no tiene un constructor de argumento cero la clase x no es un JavaBean porque: clase no es serializable clase w no es un JavaBean porque: su constructor de argumento cero no es público clase u no es un JavaBean porque: los siguientes campos de la clase no son serializables: clase java.awt.Image i (definida en u0)