BeanLint: uno strumento per la risoluzione dei problemi JavaBeans, parte 1

Ogni paio di mesi, ricevo e-mail in preda al panico o confuso da un neofita di JavaBeans che sta cercando di creare un JavaBean contenente un Imagee che non riesce a capire perché BeanBox non carica il bean. Il problema è che java.awt.Imagenon lo è Serializable, quindi nessuno dei due è nulla che contenga un java.awt.Image, almeno senza serializzazione personalizzata.

Io stesso ho passato innumerevoli ore a inserire println()dichiarazioni nel codice BeanBox e poi a ricompilarlo, cercando di capire perché i miei fagioli non si caricano. A volte è dovuto a qualcosa di semplice e stupido, come dimenticare di definire il costruttore senza argomenti, o anche la classe, come public. Altre volte, risulta essere qualcosa di più oscuro.

Il caso del fagiolo mancante

Sebbene i requisiti per scrivere una classe Java come JavaBean siano semplici e diretti, ci sono alcune implicazioni nascoste che molti strumenti di creazione di bean non affrontano. Questi piccoli trucchi possono facilmente consumare un pomeriggio, mentre cerchi il tuo codice, cercando il motivo per cui il tuo strumento di creazione non riesce a trovare il tuo fagiolo. Se sei fortunato, visualizzerai una finestra di dialogo popup con un messaggio di errore criptico, qualcosa del tipo "NoSuchMethodException caught in FoolTool Introspection. "Se sei sfortunato, il JavaBean in cui hai versato così tanto sudore si rifiuterà di apparire nel tuo strumento di costruzione e trascorrerai il pomeriggio provando il vocabolario di cui tua madre ha cercato così duramente di curarti. Il BeanBox ha è stato a lungo un grave delinquente in questo senso, e sebbene sia stato migliorato, lascerà comunque cadere le proprietà e persino i fagioli interi senza fornire allo sviluppatore un solo indizio sul perché.

Questo mese ti porterò fuori dalla "terra del fagiolo mancante" introducendo un nuovo strumento chiamato, stranamente BeanLint, che analizza le classi all'interno dei file jar, alla ricerca di possibili problemi che renderebbero le classi inutilizzabili come bean. Sebbene questo strumento non copra tutti i possibili problemi dei bean, identifica alcuni dei principali problemi comuni che rendono i bean scaricabili.

Per capire come BeanLintfunziona la sua magia, questo mese e il prossimo approfondiremo alcuni degli angoli meno noti dell'API Java standard:

  • Creeremo un caricatore di classi personalizzato , che carica nuove classi Java da un file jar

  • Useremo il meccanismo di riflessione , che consente ai programmi Java di analizzare le classi Java, per identificare cosa c'è dentro i nostri file di classe

  • Useremo il Introspectorper produrre un report di tutte le proprietà beanlike della classe per qualsiasi classe nel file jar che supera tutti i test (ed è, quindi, un potenziale bean)

Quando avremo finito, avrai uno strumento utile per il debug dei tuoi bean, capirai meglio i requisiti dei bean e imparerai allo stesso tempo alcune delle nuove fantastiche funzionalità di Java.

Nozioni di base sui fagioli

Affinché un file di classe sia un JavaBean, ci sono due semplici requisiti:

  1. La classe deve avere un costruttore pubblico senza argomenti (un costruttore di zero arg )

  2. La classe deve implementare l'interfaccia tag vuota java.io.Serializable

Questo è tutto. Segui queste due semplici regole e la tua classe sarà un JavaBean. Il JavaBean più semplice, quindi, assomiglia a questo:

import java.io. *; la classe pubblica TinyBean implementa Serializable {public TinyBean () {}}

Ovviamente, il fagiolo sopra non è buono per molto, ma poi non ci abbiamo messo molto lavoro. Basta provare a scrivere una componente fondamentale come questo in un altro framework di componenti. (E non è giusto usare "procedure guidate" o altri generatori di codice per creare classi wrapper o implementazioni predefinite. Questo non è un buon confronto tra l'eleganza di JavaBeans e un'altra tecnologia.)

La TinyBeanclasse non ha proprietà (tranne, forse, "nome"), nessun evento e nessun metodo. Sfortunatamente, è ancora facile creare accidentalmente classi che sembrano seguire le regole, ma non funzionano correttamente in un contenitore JavaBeans come BeanBox o il tuo IDE preferito (ambiente di sviluppo integrato).

Ad esempio, il BeanBox non caricherebbe quanto TinyBeansopra se ci fossimo dimenticati di includere la parola chiave publicnella definizione della classe. javaccreerebbe un file di classe per la classe, ma il BeanBox si rifiuterebbe di caricarlo e (fino a poco tempo fa) non darebbe alcuna indicazione sul motivo per cui rifiuterebbe. Per dare credito al personale Java di Sun, il BeanBox ora riporta di solito il motivo per cui un bean non viene caricato, o il motivo per cui una proprietà non viene visualizzata in un foglio delle proprietà e così via. Non sarebbe bello, tuttavia, se avessimo uno strumento per controllare quante più cose possibili su tali classi e avvisarci di quelle che potrebbero causare problemi se utilizzate in un ambiente JavaBeans? Questo è l'obiettivo diBeanLint: per aiutarti, come programmatore JavaBeans, ad analizzare i bean all'interno dei loro file jar, alla ricerca di possibili problemi in modo da poterli risolvere prima di incapparli nel processo di test o - peggio ancora - sul campo.

Potenziali problemi con i fagioli

Poiché ho sviluppato JavaBean per questa colonna, probabilmente ho commesso la maggior parte degli errori che si possono fare quando si scrive un JavaBean. In un certo senso, la natura taciturna del BeanBox mi ha costretto a saperne di più sui bean - e su Java - di quanto avrei fatto altrimenti. La maggior parte degli sviluppatori JavaBeans, tuttavia, preferirebbe semplicemente produrre JavaBeans funzionanti che funzionino correttamente e salvare le "esperienze di crescita" per le loro vite personali. Ho raccolto un elenco di possibili problemi con un file di classe che può creare scompiglio con un JavaBean. Questi problemi si verificano durante il processo di caricamento del bean in un contenitore o durante l'utilizzo del bean in un'applicazione. È facile perdere i dettagli nella serializzazione, quindi prestiamo particolare attenzione ai requisiti di serializzabilità.

Di seguito sono riportati alcuni problemi comuni che non causano errori in fase di compilazione, ma possono impedire a un file di classe di essere un JavaBean o di non funzionare correttamente una volta caricato in un contenitore:

  • La classe non ha un costruttore a zero argomenti. Questa è semplicemente una violazione del primo requisito sopra elencato ed è un errore non spesso riscontrato dai non principianti.

  • La classe non implementa Serializable. Questa è una violazione del secondo requisito sopra elencato ed è facile da individuare. Una classe può pretendere di implementare Serializablee tuttavia non portare a termine il contratto. In alcuni casi possiamo rilevare automaticamente quando ciò si è verificato.

  • La classe stessa non viene dichiarata public.

  • La classe non viene caricata per qualche motivo. Le classi a volte generano eccezioni durante il caricamento. Spesso ciò è dovuto al fatto che altre classi da cui dipendono non sono disponibili ClassLoaderdall'oggetto utilizzato per caricare la classe. Scriveremo un programma di caricamento classi personalizzato in questo articolo (vedi sotto).

  • La classe è astratta. Mentre una classe componente, in teoria, potrebbe essere astratta, un'istanza in esecuzione effettiva di un JavaBean è sempre un'istanza di una classe concreta (cioè non astratta). Le classi astratte non possono essere istanziate, per definizione, e quindi non considereremo le classi astratte come candidate per essere fagioli.

  • La classe implements Serializable, ma essa o una delle sue classi base contiene campi non serializzabili. Il design del meccanismo di serializzazione Java predefinito consente di definire una classe come implements Serializable, ma consente che fallisca quando si tenta effettivamente la serializzazione. La nostra BeanLintclasse garantisce che tutti i campi appropriati di una Serializableclasse lo siano effettivamente Serializable.

Una classe che fallisce uno dei problemi precedenti può essere abbastanza certa di non funzionare correttamente come JavaBean, anche se i due requisiti di base del bean, indicati all'inizio, vengono soddisfatti. Per ciascuno di questi problemi, quindi, definiremo un test che rilevi il problema particolare e lo segnala. Nella BeanLintclasse, qualsiasi file di classe nell'essere file jar analizzato che fa passare tutti questi test viene poi introspected (analizzato utilizzando la classe java.beans.Introspector) per produrre un rapporto di attributi del bean (proprietà, set di eventi, customizer, e così via). java.beans.Introspectorè una classe in package java.beansche utilizza il meccanismo di riflessione di Java 1.1 per trovare (o creare) un java.beans.BeanInfooggetto per un JavaBean. Tratteremo la riflessione e l'introspezione il mese prossimo.

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 ===

L'output è stato accorciato in qualche modo perché l'elenco dei set di eventi e dei metodi è molto lungo non aggiunge molto alla nostra discussione qui. Puoi vedere l'intero output nel file output.html, se vuoi un'idea della quantità di cose che vengono emesse BeanLint.

Si noti che ha BeanLintidentificato correttamente i problemi con i file di classe errati:

la classe u0 non è un JavaBean perché: la classe non è pubblica la classe z non è un JavaBean perché: la classe non è pubblica la classe y non è un JavaBean perché: non ha un costruttore a zero argomenti la classe x non è un JavaBean perché: la classe non è Serializzabile la classe w non è un JavaBean perché: il suo costruttore senza argomenti non è pubblica la classe u non è un JavaBean perché: i seguenti campi della classe non sono Serializzabili: classe java.awt.Immagine i (definita in u0)