BeanLint: Инструмент за отстраняване на неизправности JavaBeans, част 1

На всеки няколко месеца получавам паникьосан или объркан имейл от неофит на JavaBeans, който се опитва да създаде JavaBean, съдържащ Imageи който не може да разбере защо BeanBox няма да зареди зърното. Проблемът е, че java.awt.Imageне е Serializable, следователно, нито е нещо, което съдържа java.awt.Image, поне без персонализирана сериализация.

Аз самият прекарах безброй часове, поставяйки println()изявления в кода на BeanBox, след което го прекомпилирах, опитвайки се да разбера защо зърната ми няма да се заредят. Понякога това се дължи на някакво просто, глупаво нещо - като забравяне да дефинираме конструктора на нулев аргумент или дори класа като public. Друг път се оказва нещо по-неясно.

Случаят с изчезналия боб

Въпреки че изискванията за писане на Java клас като JavaBean са прости и ясни, има някои скрити последици, които много инструменти за изграждане на боб не адресират. Тези малки gotchas лесно може да изяде един следобед, докато търсите чрез своя код, търсейки причината си строител инструмент не може да намери боб. Ако имате късмет, ще получите изскачащ диалогов прозорец със загадъчно съобщение за грешка - нещо по линия на "NoSuchMethodException caught in FoolTool Introspection. "Ако нямате късмет, JavaBean, в който сте изляли толкова много пот, ще откаже да се появи във вашия инструмент за изграждане и ще прекарате следобеда в репетиции на речника, от който майка ви се е опитвала толкова много отдавна е груб нарушител в това отношение и макар да се подобрява, все пак ще отпадне свойствата и дори цели зърна, без да предостави на разработчика само една представа защо.

Този месец ще ви изведа от "земята на липсващия боб", като въведа нов инструмент, наречен, странно BeanLint, който анализира класовете в jar файловете, търсейки възможни проблеми, които биха направили класовете неизползваеми като боб. Въпреки че този инструмент не покрива всички възможни проблеми с боб, той идентифицира някои от основните често срещани проблеми, които правят зърната невъзможни за зареждане.

За да разберем как BeanLintработи магията му, този и следващия месец ще се задълбочим в някои от по-малко известните ъгли на стандартния Java API:

  • Ще създадем потребителски loader , който зарежда нови Java класове от jar файл

  • Ще използваме механизма за отражение , който позволява на Java програмите да анализират Java класове, за да идентифицираме какво има в нашите файлове с класове

  • Ще използваме, за Introspectorда изготвим отчет за всички свойства на класа като боб за всеки клас в jar файла, който преминава всички тестове (и следователно е потенциален боб)

Докато приключим, ще имате полезен инструмент за отстраняване на грешки във вашия фасул, ще разберете по-добре изискванията за боб и ще научите за някои от страхотните нови функции на Java едновременно.

Основи на боб

За да бъде файлът на класа JavaBean, има две прости изисквания:

  1. Класът трябва да има публичен конструктор без аргументи (конструктор с нулев аргумент )

  2. Класът трябва да реализира празния интерфейс на маркера java.io.Serializable

Това е. Следвайте тези две прости правила и вашият клас ще бъде JavaBean. Тогава най-простият JavaBean изглежда по следния начин:

импортиране на java.io. *; публичен клас TinyBean изпълнява Serializable {public TinyBean () {}}

Разбира се, горният боб не е добър за много, но тогава не сме вложили много работа в него. Просто опитайте да напишете основен компонент като този в друга рамка за компоненти. (И не е честно използването на "съветници" или други генератори на код за създаване на класове на обвивки или внедряване по подразбиране. Това не е честно сравнение на елегантността на JavaBeans с друга технология.)

Най- TinyBeanкласа не притежава свойства (с изключение, може би, "име"), няма събития и никакви методи. За съжаление, все още е лесно случайно да създадете класове, които изглежда спазват правилата, но въпреки това не работят правилно в контейнер на JavaBeans като BeanBox или вашата любима IDE (интегрирана среда за разработка).

Например BeanBox не би заредил TinyBeanгорното ни, ако бяхме забравили да включим ключовата дума publicв дефиницията на класа. javacще създаде файл на класа за класа, но BeanBox ще откаже да го зареди и (доскоро така или иначе) няма да даде индикация защо би отказал. За да даде кредит на хората на Java на Sun, BeanBox сега обикновено съобщава причината, поради която бобът няма да се зареди или причината, поради която дадено свойство не се появява на лист със свойства и т.н. Не би ли било хубаво обаче, ако разполагахме с инструмент, който да провери възможно най-много неща за такива класове - и да ни предупреди за тези, които могат да създадат проблеми, когато се използват в среда на JavaBeans? Това е целта наBeanLint: за да ви помогна, като програмист на JavaBeans, да анализирате зърна във файловете им в jar, като търсите възможни проблеми, така че да можете да ги поправите, преди да ги срещнете в процеса на тестване или - още по-лошо - в полето.

Потенциални проблеми с боб

Тъй като разработих JavaBeans за тази колона, вероятно съм допуснал повечето грешки, които човек може да направи, когато пише JavaBean. В известен смисъл мълчаливата природа на BeanBox ме принуди да науча повече за боб - и за Java - отколкото бих искал иначе. Повечето разработчици на JavaBeans обаче биха предпочели просто да създадат работещи JavaBeans, които работят правилно, и да запазят „опита за растеж“ за личния си живот. Събрах списък с възможни проблеми с файл с клас, който може да причини хаос с JavaBean. Тези проблеми възникват по време на процеса на зареждане на зърното в контейнер или при използването на зърното в приложение. Лесно е да пропуснете подробности при сериализацията, затова обръщаме специално внимание на изискванията за сериализация.

Ето някои често срещани проблеми, които не водят до грешки при компилация, но могат да причинят клас файл или да не бъде по-JavaBean, или да не функционира правилно, когато вече е заредена в контейнер:

  • Класът няма конструктор с нулев аргумент. Това е просто нарушение на първото изискване, изброено по-горе, и е грешка, която не често се среща от не-начинаещи.

  • Класът не се изпълнява Serializable. Това е нарушение на второто изискване, изброено по-горе и е лесно забележимо. Класът може да претендира за изпълнение Serializableи въпреки това да не изпълни договора. В някои случаи можем да открием автоматично, когато това се е случило.

  • Самият клас не се декларира public.

  • Класът не успява да се зареди по някаква причина. Класовете понякога изхвърлят изключения, докато се зареждат. Често това е така, защото други класове, от които зависят, не са налични от ClassLoaderобекта, използван за зареждане на класа. В тази статия ще напишем потребителски зареждач за клас (вижте по-долу).

  • Класът е абстрактен. Докато класът компонент, на теория, може да бъде абстрактен, действителният работещ екземпляр на JavaBean винаги е екземпляр на някакъв конкретен (т.е. необятен) клас. Абстрактните класове не могат да бъдат създадени по дефиниция и затова няма да разглеждаме абстрактните класове като кандидати за боб.

  • Класът implements Serializable, но той или един от неговите базови класове съдържа несериализуеми полета. Дизайнът на механизма за сериализация по подразбиране на Java позволява даден клас да бъде дефиниран като implements Serializable, но позволява да се провали, когато сериализацията всъщност се опита. Нашият BeanLintклас гарантира, че Serializableвсъщност са всички подходящи полета на даден клас Serializable.

Клас, който се провали по някой от горепосочените проблеми, може да бъде доста сигурен, че не работи правилно като JavaBean, дори ако са изпълнени двете основни изисквания за боб, посочени в началото. Тогава за всеки от тези проблеми ще определим тест, който открива конкретния проблем и го докладва. В BeanLintкласа, който и да е клас файл в буркан файл, който се анализираната че прави премине всички тези тестове след това се introspected (анализират с помощта на класа java.beans.Introspector), за да изготви доклад от атрибути на Бийн (свойства, комплекти за събития, за персонализиране, и така нататък). java.beans.Introspectorе клас в, package java.beansкойто използва механизма за отразяване Java 1.1, за да намери (или създаде) java.beans.BeanInfoобект за JavaBean. Ще разгледаме размислите и самоанализата следващия месец.

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

Резултатът е съкратен до известна степен, тъй като списъците със събития и методи са много дълги, не добавя много към нашата дискусия тук. Можете да видите целия изход във файла output.html, ако искате представа за количеството BeanLintизложени неща .

Забележете, че BeanLintправилно са идентифицирани проблемите с файловете с лош клас:

клас u0 не е JavaBean, защото: класът не е публичен клас z не е JavaBean, защото: класът не е публичен клас y не е JavaBean, защото: той няма конструктор с нулев аргумент клас x не е JavaBean, защото: class не е Serializable class w не е JavaBean, защото: неговият конструктор с нулев аргумент не е публичен клас u не е JavaBean, защото: следните полета на класа не са Serializable: class java.awt.Image i (дефиниран в u0)