Hallo,
Sobald ihr euch mit SOAP-Toolkits oder den zahlreichen Java-Frameworks beschäftigt, ist es quasi unmöglich dem Begriff "JavaBean" nicht zu begegnen. Oft wird auch der Begriff Plain Old Java Object (POJO) so verwendet, dass er implizit JavaBean meint.
JavaBeans sollten
nicht mit
Enterprise Java Beans verwechselt werden. Eigentlich war das Hervorkramen der JavaBeans Konventionen für freie Frameworks und Toolkits sogar eine Abwehr-Reaktion der freien Welt gegen den Wahnsinn, den IBM und andere mit Enterprise Java Beans vorhatte. Mit Erfolg übrigens. Die dunkle Seite der Macht mußte aufgeben. Lustigerweise sind Enterprise Java Beans nach der EJB3-Spec JavaBeans/POJOs.
von links nach rechts: Die frühen Enterprise Java Beans Kritiker und Gavin King, Rod Johnson, Martin Fowler nach der EJB-3 Spec (Quelle:
http://www.filmstarts.de)
I. Was ist das eigentlich, ein JavaBeander Eintrag im englischen Wikipedia hilft ein bischen weiter:
http://en.wikipedia.org/wiki/JavaBeanOft wird der Begriff POJO (Plain Old Java Object) ein wenig austauschbar mit JavaBeans verwendet.
So überrascht es nicht, dass man im Plain_Old_Java_Object Eintrag im englischen wikipedia wieder auf JavaBeans stösst. Auch das hilft weiter (2. Abschnitt):
http://en.wikipedia.org/wiki/Plain_Old_Java_Object A JavaBean is a POJO that is serializable, has a no-argument constructor, and allows access to properties using getter and setter methods.
Eine JavaBean Klasse ist folglich eine Java-Klasse, die zusätzlich bestimmte Konventionen erfüllt.
Leider fehlt im JavaBeans Wikipedia Eintrag die wichtige Tatsache, dass Teile der JavaBean Konventionen von vielen Frameworks und Toolsets wiederverwendet wird. Ursprünglich waren die JavaBeans ja nur für GUI-Programmierung gedacht.
Der Wikipedia Eintrag zu JavaBeans nennt die Konventionen. Dahinter in der Folgespalte meine Anmerkung, ob diese Konvention von Frameworks oder Toolsets erwartet wird, wenn die von JavaBeans sprechen:
| Konvention | Benutzt in Frameworks |
1. | It has to have a no-argument constructor | oft, aber nicht immer |
2. | Its properties must be accessible using get, set and other methods following a standard naming convention | eigentlich immer |
3. | The class should be serializable (able to persistently save and restore its state) | manchmal bis oft |
4. | It should not contain any required event-handling methods | eigentlich nie und dieser Teil machte den Hauptteil der ursprünglichen Java Bean Spec für GUI-Programmierung aus. |
Beispiel Bean:
public class Stock implements java.io.Serializable {
// Properties
private Integer id;
private String name;
private BigDecimal price;
private Set stockpositions = new HashSet(0);
/** no-arg constructor */
public Stock() {
}
// Property getter and setters
// type-Property::get<propertyname> () wobei der erste Buchstabe groß geschrieben wird.
public Integer getId() {
return this.id;
}
// void::set<propertyname> (type-Property) wobei der erste Buchstabe groß geschrieben wird.
public void setId(Integer id) {
this.id = id;
}
// type-Property::get<propertyname> () wobei der erste Buchstabe groß geschrieben wird.
public String getName() {
return this.name;
}
// void::set<propertyname> (type-Property) wobei der erste Buchstabe groß geschrieben wird.
public void setName(String name) {
this.name = name;
}
// type-Property::get<propertyname> () wobei der erste Buchstabe groß geschrieben wird.
public BigDecimal getPrice() {
return this.price;
}
// void::set<propertyname> (type-Property) wobei der erste Buchstabe groß geschrieben wird.
public void setPrice(BigDecimal price) {
this.price = price;
}
// type-Property::get<propertyname> () wobei der erste Buchstabe groß geschrieben wird.
public Set getStockpositions() {
return this.stockpositions;
}
// void::set<propertyname> (type-Property) wobei der erste Buchstabe groß geschrieben wird.
public void setStockpositions(Set stockpositions) {
this.stockpositions = stockpositions;
}
}
Punkt 1 wird erfüllt: Für Eigenschaften der Klasse gibts je 1 public getter und setter mit der Namenskonvention:
getter -> typeProperty::get<propertyname>() wobei der erste Buchstabe von <propertyname> groß geschrieben wird.
void::set<propertyname>(typeProperty) wobei der erste Buchstabe von <propertyname> groß geschrieben wird.
Punkt 2 wird erfüllt (no argument Constructor)
Punkt 3 wird erfüllt (implements serializable)
Zusätzlich gibts für Punkt 1) noch eine kleine Zusatzkonvention für boolean properties. Die können nämlich neben typeProperty::get<propertyname>() auch typeProperty::is<propertyname>() heissen. Dabei wird wiederum der erste Buchstabe von <propertyname> groß geschrieben.
II. Wofür braucht man diese Konvention?Für Framework-Programmierer sind solche Konventionen sehr praktisch. Ich erkläre das anhand eines Hibernate-Beispiels. Man muß nicht Hibernate verstehen für dieses Beispiel. Die Prinzipien sind für SOAP Toolsets (z.B. Axis) und Spring, etc. sehr ähnlich. Einfach ein bischen Geduld.
Hibernate ist sehr vereinfachend gesprochen dafür da, um Java Objekte in Tabellen von Relationalen Datenbanken zu schreiben, updaten, lesen und löschen, ohne dass der Programmierer langweiligen JDBC-code programmieren müsste.
Hibernate stellt den Kontrakt auf, dass die von ihm mit der Datenbank zu synchronisierenden Klassen eben die ersten 3 Punkte der JavaBean Spezifikation erfüllen müssen.
Alles was ich (bzw. hier bestimmte Eclipse plug-ins von MyEclipse) für diese Klasse in Hibernate konfiguriert hat ist:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!--
Mapping file autogenerated by MyEclipse - Hibernate Tools
-->
<hibernate-mapping>
<class name="de.aja.fussi.persistence.Stock" table="stock" schema="public">
<id name="id" type="java.lang.Integer">
<column name="id" />
<generator class="sequence" />
</id>
<property name="name" type="java.lang.String">
<column name="name" length="40" not-null="true" unique="true" />
</property>
<property name="price" type="java.lang.Double">
<column name="price" precision="5" not-null="true" />
</property>
<set name="stockpositions" inverse="true">
<key>
<column name="idstock" not-null="true" />
</key>
<one-to-many class="de.aja.fussi.persistence.Stockposition" />
</set>
</class>
</hibernate-mapping>
Das sieht viel aus. Aber was von dieser Konfiguration betrifft eigentlich zum Beispiel die property id in der Klasse Stock?
Richtig. Nur diese eine Zeile:
<id name="id" type="java.lang.Integer">
Aufgrund des JavaBeans
Namens-Konventions Kontrakts weiss Hibernate z.B., dass es für das property id in der Klasse die Methoden:
public Integer getId()
public void setId(Integer id)
geben muss.
Und Hibernate benutzt diese getter und setter fleissig.
Sollte es die nicht geben, hab ich als Hibernate-Nutzer den JavaBeans Namenskonventions Kontrakt gebrochen und Hibernate wird an gegebener Stelle eine Exception werfen.
Gäbe es die Namenskonvention nicht, müsste ich als Hibernate Benutzer noch viel mehr konfigurieren (
conventions over configuration).
Mit den Standardeinstellungen unterstützt übrigens
Eclipse die Konvention der JavaBean Spezifkationen für getters und setters.
Rechte Maustaste auf Klasse mit properties. Dann in Kontextmenü Source-> Generate Getters and Setters oder Alt-Shift-S und dann Generate Getters and Setters.
III. Unter der HaubeWie benutzen Framework die Informationen, die sich aus den JavaBeans Namenskonventionen ergeben? Es ist schön für Hibernate zu wissen, wie bestimmte getter und setter Methoden gibt, 1 no-args Konstruktor. Aber wie benutzt Hibernate und andere Frameworks diese Information?
Es ist nicht so schwierig in Java kleinere eigene Frameworks zu schreiben. Ich hatte jetzt z.B. einen Fall, in denen Werte aus SOAP Nachrichten in JavaBeans geschrieben wird. Dafür benutze ich leicht modifizierte JavaBean Konventionen. Bei mir muss es immer einen setter mit String Argument geben.
Vereinfacht gesprochen läuft das so:
xml-Nachricht->
<person>
<name>Mr. Jones</name>
</person>
Ich will. In Reaktion auf diese einkommende Nachricht eine Instanz der Klasse Person erzeugen und da das Mr. Jones per setter in das Property name schreiben.
Mein Framework reagiert da so:
1. Suche in package de.kingmedia.webservices.beans.soap die Klasse Person und erzeuge eine Instanz mit dem no-argument Constructor über die
Reflection API von Java (ist btw. in java.lang):
String className = Helper.firstLetterUppercase(String tagName);
Class c = Class.forName("de.kingmedia.webservices.beans.soap." + className);
ReceiveBean recBean = (ReceiveBean) c.newInstance(); // würd ohne no-argument Constructor nicht funktionieren.
2. Gehe die tags unter person durch und setze deren Textnode (was zwischen öffnenden und schliessenden Tag steht) in die Klasse. Und zwar wieder mit Hilfe der Reflection API:
String methodName = HelperFirstLetterUppercaseAndLeadingSet(); // für <name> wäre das setName
Class[] formparas = new Class[1]; // parameter der set-Methode
formparas[0] = String.TYPE; // bei mir immer String, eigene Vereinfachung der JavaBean-Spec für meine Zwecke.
Method meth = c.getMethod(methodName, formparas); // hole Methode. Ja Methoden von Klassen sind in der Reflection API ihrerseits Objekte.
String args[] = new String[1]; // hier wird der konkrete Wert des Parameters des setters reingeschrieben.
args[0] = wertInDerTextnode; //im Beispiel Mr. Jones
meth.invoke(recBean, args); // setter-Methode für Objekt recBean (s.o.) mit Argument aufrufen.
Das sieht vielleicht voll merkwürdig aus, läuft aber sehr stabil und ist einfach, wenn mans einmal gemacht hat. Die Qualität der Reflection-API ist ein wichtiger Grund, warum in Java so viele Frameworks geschrieben werden. Framework heisst: Wenig Code für viel Funktionalität ohne viel Konfiguration. Die Kapitel 42ff. des "Handbuch der Java-Programmierung" beschreiben die Reflection API von Java sehr gut.
Gruß Axel