Návod pro použití knihovny Ujorm.

UJO IconsObsah

Co je UJO ?

UjoUml.png Ujorm je Java knihovna, která poskytuje netradiční architekturu objektů odlišných od JavaBeans. Původní nápad vznikl jako hříčka s generickými datovými typy Java 5.0, postupem času se však ukázalo, že architektura má několik zajímavých vlastností. Současná verze frameworku nabízí: UJO je zkratka pro Unified Java Object a označuje takový objekt, který implementuje rozhraní Ujo. Objekt UJO si lze představit například jako mapu (objekt mapující hodnoty ke klíčům), ke které se přistupuje pomocí metod klíče. Klíč je zde tvořen implementací rozhraní Key obsahující (mimo jiné) dvě typově bezpečné metody: Na rozdíl od metod objektu JavaBean lze tyto klíče posílat jednoduše dalším objektům, příkladem využití může být seznam zobrazovaných sloupců tabulky poslaný do datového modelu typu TableModel, nebo odkazy k metodám beanu na JSP stránce. Jádrem projektu jsou tedy dvě rozhraní Ujo a Key, ke kterým jsou k dispozici tři abstraktní implementace: Inspirací pro rozvoj tohoto projektu byla architektura perzistentních objektů v projektu Cayenne (nástroj pro řešení ORM)

Proč ?

Proč používat architekturu UJO? Objekty UJO nabízí několik zajímavých vlastností, které je odlišují od tradičních JavaBeans objektů:
Objekt typu JavaBean očekává implementaci metody equals() pro každou třídu a implementace jsou zpravidla různé pro různé třídy. Na rozdíl od toho objekt UJO umožňuje implementaci této metody pouze v rodičovské třídě. Pokud z nějakého důvodu není možné vytvořit společného rodiče, metodu equals() lze implementovat jedním programovým řádkem společným pro všechny třídy. Příklad implementace je uveden dále. Objekty UJO jsou otevřené pro implementaci dalších vlastností a v případě potřeby lze implementovat vlastnosti typu JavaBean, Serizalizable atd. Protože implementace byla postavena na Java™ 5.0, tak i příklady v dokumentu jsou demonstrovány na této verzi jazyka Java. S určitým omezením (ztráta typové kontroly) je však možné UJO implementovat v nižších verzích jazyka Java.

Jak ?

Jak napíšeme třídu objektu UJO? Nejrychlejší cesta je využít nějakou abstraktní implementaci UJO z tohoto projektu, pomocí pár řádů kódu však můžeme vyrobit snadno i svoji vlastní implementaci. Příkladem nechť je implementace třídy Person pomocí abstraktní třídy MapUjo.

Kód třídy

Jednoduché vzorové vytvoření třídy Person je zde:
import org.ujorm.implementation.map.*;
public class Person extends MapUjo {
public static final Key<Person,String > NAME = newKey("Name", String.class); public static final Key<Person,Boolean> MALE = newKey("Male", Boolean.class); public static final Key<Person,Double > CASH = newKey("Cash", Double.class);
}
Teď, když máme hotovou třídu Person můžeme vytvořit její instanci a zapsat či načíst nějaká data. Pro zápis atributů objektu lze využít jednu ze dvou metod API:
  1. Person.writeValue(Key, Object), nebo
  2. Key.setValue(person, Object).
Z pohledu výsledku jsou obě metody ekvivalentní, nicméně druhé řešení poskytuje typovou kontrolu při zápisu i čtení hodnoty. Ukázka použití druhého (2) přístupu je zde:
import static org.Person.*;
Person person = new Person();
// Writing: NAME.setValue(person, "Pavel Ponec"); MALE.setValue(person, true); CASH.setValue(person, 34.5);
// Reading: String name = NAME.of(person); // method of() is an alias for MapProperty.getValue() boolean male = MALE.of(person); double cash = CASH.of(person);
Od verze 0.80 je možné použít rozšířené API které nabízí čitelnější kód pro přístup a attributům a umožňuje mimo jiné i řetězení keys podobně jako v jazyku Groovy. Nové API jen rozšiřuje to původní a tak je možné kombinovat oba typy objektů.
import static org.Person.*;
Person person = new Person(); // An extension of MapUjoExt
// Writing: person.set(NAME, "Pavel Ponec"); person.set(MALE, true); person.set(CASH, 34.5);
// Reading: String name = person.get(NAME); boolean male = person.get(MALE); double cash = person.get(CASH);
Poznámka:
Pokud používáte kompilační parametr -Xlint:unchecked, kompilátor vás občas upozorní na chybějící deklarace Generických datových typů při inicializaci MapProperty. Máte tři možnosti:

Implementace metody equals()

Naše třída Person má funkci equals() už implementovanou, protože ji zdědila z rodičovské třídy MapUjo. Pokud však z nějakého důvodu chcete napsat vlastní implementaci UJO s touto metodou, stačí do třídy zapsat kód:
public boolean equals(Object obj) {
  return UjoManager.getInstanceManager().equals(this, (Ujo) obj );
}

Implementace metody clone()

Podobně jak v případě metody equals můžete vytvořit implementaci metody clone():
public Object clone() {
  return UjoManager.getInstanceManager().clone(this, 1, null);
}

Implementace metody hash()

Podobný příklad implementace metody hash() :
public int hash() {
  return UjoManager.getInstanceManager().getHash(this);
}
Všechny uvedené implementace jsou zpravidla vhodné i pro všechny potomky Ujo objektů.

XML serializace

Použití serializace je velice jednoduché, kód vypadá takto:
 Person person;
...
// Make Serialization: UjoManagerXML.getInstance().saveXML(writer, person, null, "My Export");
// Make Deserialization: person = UjoManagerXML.getInstance().parseXML(inputStream, Person.class, "My Import");
Obsah vytvořeného XML vypadá takto:
<?xml version="1.0" encoding="UTF-8"?>
<body>
  <Name>Pavel Ponec</Name>
  <Male>true</Male>
  <Cash>34.5</Cash>
</body>

List Property

Často se hodí, aby nějaký atribut obsahoval seznam dalších UJO objektů. Je sice možné vytvořit atribut typu ArrayList<UjoItem>, nicméně vhodnější je použít pro atribut třídu KeyList. Prvním z důvodů je čistší obsah exportu do XML, protože export neobsahuje informace o datových typech položek Listu. Pro podrobnější informaci doporučuji prostudovat API.
 
Doporučené řešení pro implementaci vazby 1:N pro objekty Person - Child.
public class Person extends MapUjo { 
  public static final MapProperty    <Person,String> NAME  = newKey("Name", String.class);
  public static final MapPropertyList<Person,Child> CHILDS = newListProperty("Child", Child.class);
}

Zákaz exportu

Co když je třeba exportovat jen některé atributy UJO objektu a jiné ignorovat?
Pro tento případ slouží metoda readAuthorization(...), která může povolit účast vybraných atributů v závislosti na: Na následujícím případě si ukážeme, jak vypadá potlačení atributu NAME při exportu do XML:
public boolean readAuthorization(int action, Key property, Object value, Object context) {
  switch(action) {
    case ACTION_XML_EXPORT: 
      return property!=NAME;
    default: {
      return super.readAuthorization(action, property, value, context);
    }
  }
}
Poznámka: protože všechny vlastnosti (Keys) UJO objektu jsou typu final, není třeba je porovnávat metodou equals(), ale je možné použít rychlejší porovnání operátorem ==.

Zápis XML atributu

Ujorm nabízí možnost ukládat vybrané property jako atribut na místo XML elementu (defaultní vlastnost). K dosažení cíle vedou dvě cesty:

Jak vytvořit JavaBean?

Z UJO objektu snadno vyrobíte JavaBean, stačí implementovat setter a getter pro každý atribut. Příklad použití:
import org.ujorm.implementation.map.*;
public class Person extends MapUjo {
public static final MapProperty<Person,String> NAME = newKey("Name", String.class); public static final MapProperty<Person,Double> CASH = newKey("Cash", Double.class);
public void setName(String name) { NAME.setValue(this, name); } public String getName() { return NAME.getValue(this); } public void setCash(Integer cash) { CASH.setValue(this, cash); } public Integer getCash() { return CASH.getValue(this); } }
Od verze Ujo Framework 0.74 je pro snadnou implementaci UJO do objektů JavaBeans připravena třída BeanUjo.

Podpora komponenty JTable

Ujorm obsahuje podporu pro zobrazování seznamu (List) UJO objektů v tabulce JTable. Pro vytvoření jednoduché editovatelné tabulky není třeba vytvářet žádnou novou třídu, stačí pouze vytvořit instanci třídy UjoTableModel a tu pak vložit do objektu JTable metodou setModel(). Příklad jednoduchého použití následuje:
// Create a model for all attributes:
UjoTableModel<Person> model = new UjoTableModel(Person.NAME, Person.MALE, Person.BIRTH);
// or simply: ... model = new UjoTableModel(Person.class)
jTable.setModel(model);
// We can set an data optionaly: List<Person> persons = new ArrayList(); model.setRows(persons);

Další možnosti třídy UjoTableModel:
// Add a row:
model.addRow(new Person());
// Set a value: model.setValueAt("Prokop", 0, Person.NAME );
// Get the last row of table: Person person = model.getRowLast();
// Sort the model: model.sort(true, Person.NAME);

Srovnání s AbstractTableModel

Pro vytvoření datového modelu editovatelné tabulky je třeba implementovat či přepsat (overwrite) několik metod abstraktní třídy AbstractTableModel.
Všechny tyto metody ve třídě UjoTableModel dostanete už implementované. Jméno sloupce a odpovídající třída se berou z přímo objektu Key, seznam sloupců se vkládá v konstruktoru.

Jak na rozšíření?

Pokud potřebujete vyrobit potomka třídy Person, stačí v potomkovi definovat pouze nové atributy. Při prvním volání metody readProperties() se seznam KeyList získá pomocí Java reflexe, všechny ostatní hodnoty se už berou z cache.
public class PersonExt extends Person {
public static final MapProperty<PersonExt,Float> WEIGHT = new MapProperty("Weight", Float.class); public static final MapProperty<PersonExt,Date> BORN = new MapProperty("Born" , Date.class);
}

Výkon

Rychlost zpracování UJO objektů je závislá na implementaci. Při zpracování objektu typu MapUjo je rychlost (zápis/čtení) přibližně shodná s operacemi zápis/čtení objektu HashMap. Rychlejší implementaci poskytuje řešení ArrayUjo, nejrychlejší je objekt JavaBean.

Výsledky jednoduchého měření jsou uvedeny v následující tabulce.
Prostředí Windows XP, procesor Intel DuoCore 1.66 MHz, JRE 1.6.0_04, Ujorm 0.74 .
Popis testu:

Implementace Rychlost
[sec / za 5M cyklů]
Poměr
[%]
ArrayUjo 2,000 183
MapUjo 7,032 643
BeanUjo 20,250 1851
JavaBean 1,094 100

Z tabulky je zřejmé, že rychlost implementace ArrayUjo a blíží se rychlosti JavaBeans objektů. Výsledky může ovlivnit v některých případech režie spojená s vytvářením instancí hodnoty atributů. Rychlost přístupu JavaBeans se však výrazně sníží v případě, že objektům JavaBeans budeme přistupovat přes nástroje Java reflexe, hodnoty pak budou odpovídat času BeanUjo.

Serializace UJO objektů z XML je postavena na rychlém SAX parseru, rychlostní testy jsou velice příznivé zejména ve srovnání s rychlostí XML serializace (XMLEncoder, XMLDecoder) implementované v JRE.

Měření bylo provedeno za stejných podmínek jako minulý test (UJO verze 0.74), výsledky jsou opět v tabulce.
Popis testu:

Implementace Rychlost serializace
[sec]
Rychlost deserializace
[sec]
Poměr celkem
[%]
ArrayUjo 0,343 0,500 18,5
MapUjo 0,344 0,531 19,2
BeanUjo 0,383 0,516 19,7
JavaBean 3,832 1,726 100,0
JAXB 2.1 0,203 0,484 15,1

Závěr: XML perzistence implementovaná v Ujormu je více než pětkrát rychlejší ve srovnání s XML perzistencí implementovanou v JRE 6.0. Výsledky také napovídají, že rychlost zápisu UJO objektů je ve srovnání ostatními úkoly serializace málo významná.

Jak?

Jaké jsou další možnosti využití ?

Perzistence

Servlet

Využití UJO v servletech zatím není podrobně popsáné, nicméně základní myšlenka pro zpracování textových parametrů objektu ServletRequest je asi tato:
ServletRequest request;
UjoTextable person;
Key personName;
...
person.writeValueString(personName, request.getParameters(personName.getName());

Referenční aplikace

Referenční implementací je Swingový projekt jWorkSheet, který vytvořil autor Ujormu a který je zveřejněn pod otevřenou licencí na domovské stránce http://jworksheet.ponec.net/. Aplikace jWorkSheet slouží k měření vašeho času stráveného na projektech.
Na zdrojových kódech lze studovat využití perzistence UJO objektů stejně tak jako snadné propojení kolekce UJO objektů s grafickou komponentou JTable.

Vlastnosti aplikace jWorkSheet:
Projekt JWorkSheet je napsaný v Java 5.0, vývoj byl realizován v NetBeans IDE .

Kde?

Domovská stránka projektu: http://ujorm.org/
Referenční implementace: http://jworksheet.ponec.net/.
Další implementnace: (zde může být odkaz i na váš projekt).

Licence

Kód byl uvolněn pod licencí Apache License, Version 2.0.

Copyright 2007-2013 Pavel Ponec Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

FAQ


Seznam změn

Podrobějśí informace jsou ve změnovém listu, který je dostuplný v textovém formátu.