Programmering

BeanLint: Et JavaBeans-fejlfindingsværktøj, del 1

Hvert par måneder modtager jeg panik eller forvirret e-mail fra en JavaBeans-neophyt, der prøver at oprette en JavaBean, der indeholder en Billede og hvem kan ikke finde ud af, hvorfor BeanBox ikke indlæser bønnen. Problemet er, at java.awt.billede ikke er Serialiserbar, derfor er heller ikke noget, der indeholder en java.awt.billede, i det mindste uden tilpasset serialisering.

Jeg har selv brugt utallige timer på at sætte println () udsagn i BeanBox-koden og derefter kompilere den igen og forsøge at finde ud af, hvorfor mine bønner ikke indlæses. Nogle gange skyldes det nogle enkle, dumme ting - som at glemme at definere nulargumentkonstruktøren eller endda klassen som offentlig. Andre gange viser det sig at være noget mere uklart.

Sagen om den manglende bønne

Mens kravene til at skrive en Java-klasse som en JavaBean er enkle og ligetil, er der nogle skjulte konsekvenser, som mange bean builder-værktøjer ikke adresserer. Disse små gotchas kan let spise en eftermiddag, når du jager gennem din kode og søger efter grunden til, at dit bygningsværktøj ikke kan finde din bønne. Hvis du er heldig, får du en pop op-dialogboks med en kryptisk fejlmeddelelse - noget i retning af "NoSuchMethodException fanget i FoolTool Introspection. "Hvis du er uheldig, vil den JavaBean, du har hældt så meget sved i, nægte at blive vist i dit bygningsværktøj, og du vil bruge eftermiddagen på at øve ordforrådet, din mor forsøgte så hårdt at helbrede dig for. BeanBox har længe været en voldsom gerningsmand i denne henseende, og selvom det er blevet forbedret, vil det stadig tabe egenskaber og endda hele bønner uden at give udvikleren et enkelt fingerpeg om hvorfor.

Denne måned fører jeg dig ud af "den manglende bønnes land" ved at introducere et nyt værktøj, der underligt kaldes BeanLint, der analyserer klasser inden for jar-filer og leder efter mulige problemer, der ville gøre klasserne ubrugelige som bønner. Selvom dette værktøj ikke dækker alle mulige bønneproblemer, identificerer det nogle af de største almindelige problemer, der gør bønner aflæselige.

For at forstå hvordan BeanLint fungerer sin magi, denne måned og næste dykker vi ned i nogle af de mindre kendte hjørner af standard Java API:

  • Vi opretter en brugerdefineret klasse læsser, som indlæser nye Java-klasser fra en jar-fil

  • Vi bruger afspejling mekanisme, der lader Java-programmer analysere Java-klasser for at identificere, hvad der er inde i vores klassefiler

  • Vi bruger Introspektor at producere en rapport om alle klassens bønnelignende egenskaber for enhver klasse i jar-filen, der består alle test (og derfor er en potentiel bønne)

Når vi er færdige, har du et nyttigt værktøj til fejlfinding af dine bønner, du vil bedre forstå bønnekravene, og du vil lære om nogle af Java's seje nye funktioner på samme tid.

Grundlæggende om bønner

For at en klassefil skal være en JavaBean, er der to enkle krav:

  1. Klassen skal have en offentlig konstruktør uden argumenter (a zero-arg konstruktør)

  2. Klassen skal implementere den tomme taggrænseflade java.io.Serialiserbar

Det er det. Følg disse to enkle regler, og din klasse bliver en JavaBean. Den enkleste JavaBean ser altså sådan ud:

import java.io. *; offentlig klasse TinyBean implementerer Serializable {public TinyBean () {}} 

Naturligvis er ovenstående bønne ikke god for meget, men så lagde vi ikke meget arbejde i det. Lige prøve at skrive en grundlæggende komponent som denne i en anden komponentramme. (Og ikke fair ved hjælp af "guider" eller andre kodegeneratorer til at oprette omslagsklasser eller standardimplementeringer. Det er ikke en rimelig sammenligning af JavaBeans elegance i forhold til en anden teknologi.)

Det TinyBean klasse har ingen egenskaber (undtagen måske "navn"), ingen begivenheder og ingen metoder. Desværre er det stadig nemt ved et uheld at oprette klasser, der ser ud til at følge reglerne, men alligevel ikke fungerer korrekt i en JavaBeans-container som BeanBox eller din foretrukne IDE (integreret udviklingsmiljø).

For eksempel ville BeanBox ikke indlæse vores TinyBean ovenfor, hvis vi havde glemt at medtage nøgleordet offentlig til klassedefinitionen. javac ville oprette en klassefil til klassen, men BeanBox ville nægte at indlæse den, og (indtil for nylig alligevel) ville ikke give nogen indikation på, hvorfor den ville nægte. For at give Suns Java-folk kredit rapporterer BeanBox nu normalt årsagen til, at en bønne ikke indlæses, eller årsagen til, at en ejendom ikke vises på et ejendomsark osv. Ville det dog ikke være rart, hvis vi havde et værktøj til at kontrollere så mange ting som muligt om sådanne klasser - og advare os om dem, der sandsynligvis vil forårsage problemer, når de bruges i et JavaBeans-miljø? Det er målet med BeanLint: for at hjælpe dig som JavaBeans-programmerer med at analysere bønner inde i deres jar-filer og se efter mulige problemer, så du kan rette dem, før du løber ind i dem i testprocessen eller - endnu værre - i marken.

Potentielle bønne problemer

Da jeg har udviklet JavaBeans til denne kolonne, har jeg sandsynligvis lavet de fleste af de fejl, man kan lave, når man skriver en JavaBean. På en måde har BeanBox's stiltiende natur tvunget mig til at lære mere om bønner - og om Java - end jeg ellers ville have gjort. De fleste JavaBeans-udviklere foretrækker dog simpelthen at producere fungerende JavaBeans, der fungerer korrekt, og gemme "vækstoplevelser" for deres personlige liv. Jeg har samlet en liste over mulige problemer med en klassefil, der kan skabe kaos med en JavaBean. Disse problemer opstår under ilægning af bønnen i en beholder eller ved anvendelse af bønnen i en applikation. Det er let at gå glip af detaljer i serialisering, så vi lægger særlig vægt på kravene til serialisering.

Her er nogle almindelige problemer, der ikke forårsager kompileringsfejl, men som kan medføre, at en klassefil ikke gør det være en JavaBean, eller hvis den ikke fungerer korrekt, når den er indlæst i en container:

  • Klassen har ingen nul-argument konstruktør. Dette er simpelthen en overtrædelse af det første krav, der er anført ovenfor, og er en fejl, der ikke ofte opstår af ikke-begyndere.

  • Klassen implementerer ikke Serialiserbar. Dette er en overtrædelse af det andet krav, der er anført ovenfor, og er let at få øje på. En klasse kan påstand at implementere Serialiserbarog alligevel ikke følge op på kontrakten. I nogle tilfælde kan vi registrere automatisk, når dette er sket.

  • Selve klassen er ikke erklæret offentlig.

  • Klassen kan ikke indlæses af en eller anden grund. Klasser kaster undertiden undtagelser, når de indlæses. Ofte er det fordi andre klasser, som de er afhængige af, ikke er tilgængelige fra ClassLoader objekt, der bruges til at indlæse klassen. Vi skriver en brugerdefineret klasselæsser i denne artikel (se nedenfor).

  • Klassen er abstrakt. Mens en komponentklasse i teorien kunne være abstrakt, er en faktisk kørende instans af en JavaBean altid en forekomst af en eller anden konkret (dvs. ikke-abstrakt) klasse. Abstrakte klasser kan pr. Definition ikke instantieres, og derfor betragter vi ikke abstrakte klasser som kandidater til at være bønner.

  • Klassen implementerer Serializable, alligevel indeholder den eller en af ​​dens baseklasser felter, der ikke kan omseries. Standard Java-serialiseringsmekanismen gør det muligt at definere en klasse som implementerer Serializable, men tillader, at det mislykkes, når der faktisk er forsøgt på serialisering. Vores BeanLint klasse sikrer, at alle relevante felter i en Serialiserbar klasse faktisk er Serialiserbar.

En klasse, der fejler nogen af ​​ovenstående problemer, kan være temmelig sikker på, at de ikke fungerer korrekt som JavaBean, selvom de to grundlæggende bønnekrav, der blev angivet i starten, er opfyldt. For hvert af disse problemer definerer vi derefter en test, der registrerer det særlige problem og rapporterer det. I BeanLint klasse, enhver klassefil i jar-filen, der analyseres gør bestå alle disse tests er derefter introspekteret (analyseret ved hjælp af klassen java.beans.Introspector) for at producere en rapport over bønnens attributter (egenskaber, begivenhedssæt, tilpasning osv.). java.beans.Introspector er en klasse i pakke java.bønner der bruger Java 1.1 refleksionsmekanismen til at finde (eller oprette) a java.bønner.bønneinfo objekt til en JavaBean. Vi dækker refleksion og introspektion næste måned.

Lad os nu se på kildekoden til BeanLint for at se, hvordan man analyserer potentielle bønneklasser.

Introduktion til BeanLint

I de "gode gamle dage" (som normalt betyder "tilbage, da jeg stadig troede, jeg vidste alt"), ville C-programmerere på Unix-operativsystemet bruge et program kaldet fnug for at se efter potentielle runtime-problemer i deres C-programmer. Til ære for dette ærværdige og nyttige værktøj har jeg kaldt min ydmyge bønne-analyse klasse BeanLint.

I stedet for at præsentere hele kildekoden i et stort, ufordøjeligt stykke, vil vi se på det et stykke ad gangen, og jeg vil undervejs forklare forskellige idiomer om, hvordan Java håndterer klassefiler. Når vi er færdige, har vi skrevet en klasselæsser, brugt et respektabelt antal klasser i java.lang.reflekteretog har fået et nikkende bekendtskab med klassen java.beans.Introspector. Lad os først se på BeanLint i aktion for at se, hvad det gør, og så dykker vi ned i detaljerne om dets implementering.

Dårlige bønner

I dette afsnit kan du se nogle klassefiler med forskellige problemer med problemet angivet under koden. Vi opretter en jar-fil, der indeholder disse klasser, og se hvad BeanLint gør med dem.


import java.io. *;

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

Problem:

Nul-argument konstruktør ikke

offentlig


offentlig klasse x {offentlig x () {}} 

Problem:

Ikke

Serialiserbar.


import java.io. *;

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

Problem:

Ingen nul-argument konstruktør.


import java.io. *;

klasse z implementerer Serialiserbar {offentlig z () {}}

Problem:

Klasse ikke offentlig.


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

klasse u0 redskaber Serialiserbar {privat billede i; offentlig u0 () {}}

offentlig klasse u udvider u0 redskaber Serialiserbar {offentlig u () {}}

Problem:

Indeholder et objekt eller en reference, der ikke kan nulstilles.


import java.io. *;

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

Problem:

Intet - skal fungere fint!


Hver af disse håbefulde bønner, undtagen den sidste, har potentielle problemer. Den sidste er ikke kun en bønne, men fungerer som en. Efter at have samlet alle disse klasser opretter vi en jar-fil som denne:

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

Vi vil ikke medtage en manifestfil (som er en fil inde i en jar-fil, der beskriver jar-filens indhold - se "Åbning af jar" nedenfor) i jar-filen, fordi BeanLint beskæftiger sig ikke med manifestfiler. At analysere manifestfilen og sammenligne den med krukkens indhold ville være en interessant øvelse, hvis du vil udvide hvad BeanLint kan gøre.

Lad os løbe BeanLint på jar-filen og se hvad der sker:

=== Analyse af klasse u0 === klasse u0 er ikke en JavaBean, fordi: klassen ikke er offentlig

=== Analyse af klasse z === klasse z er ikke en JavaBean, fordi: klassen ikke er offentlig

=== Analyse af klasse y === klasse y er ikke en JavaBean fordi: den har ingen nul-argumentkonstruktør

=== Analyse af klasse x === klasse x er ikke en JavaBean, fordi: klassen ikke kan serialiseres

=== Analyse af klasse w === klasse w er ikke en JavaBean, fordi: dens nul-argumentkonstruktør ikke er offentlig

=== Analysering af klasse v === Bemærk: java.awt.Button definerer brugerdefineret serialisering Bemærk: java.awt.Component definerer brugerdefineret serialisering v består alle JavaBean-tests

Introspektionsrapport -------------------- Klasse: v Tilpasningsklasse: ingen

Egenskaber: boolsk aktiveret {isEnabled, setEnabled} (... mange flere egenskaber)

Begivenhedssæt: java.awt.event.MouseListener mus (... mange flere begivenhedssæt)

Metoder: offentlig boolsk java.awt.Component.isVisible () (... mange, mange flere metoder - sheesh!)

=== Afslutning af klasse v ===

=== Analyse af klasse u === klasse u er ikke en JavaBean, fordi: følgende felter i klassen ikke kan serialiseres: klasse java.awt.Billed i (defineret i u0) === Klassen slutter u ===

Outputtet er blevet forkortet noget, fordi listerne over begivenhedssæt og metoder er meget lange, tilføjer ikke meget til vores diskussion her. Du kan se hele output i filen output.html, hvis du vil have en idé om mængden af ​​ting BeanLint lægger ud.

Læg mærke til det BeanLint identificerede korrekt problemerne med de dårlige klassefiler:

klasse u0 er ikke en JavaBean fordi: klassen er ikke offentlig klasse z er ikke en JavaBean fordi: klassen er ikke offentlig klasse y er ikke en JavaBean fordi: den har ingen nul-argument konstruktør klasse x er ikke en JavaBean fordi: den klasse er ikke Serialiserbar klasse w er ikke en JavaBean fordi: dens nul-argument-konstruktør ikke er offentlig klasse u er ikke en JavaBean fordi: følgende felter i klassen kan ikke serialiseres: klasse java.awt.Image i (defineret i u0)