Friday, December 28, 2007

API do kopiowania JavaBeans

Ostatnimi czasy często spotykam się z problemem konieczności stworzenia kopii JavaBean'a. O ile dla Javy napisano już tysiące API to jednak do tej prostej czynności wciąż nie ma rozwiązania idealnego.

Omówię teraz pokrótce o co mi chodzi. Przede wszystkim API takie powinno dostarczać następujące funkcjonalności:

  • kopiować wgłąb (tzw. deep copy) - kopiowanie referencji nie wchodzi w grę gdyż nie jest to wtedy tak naprawdę operacja kopiowania
  • nie kopiować n razy tej samej referencji - jeśli w oryginalnym obiekcie znajduje się wiele referencji do 1 obiektu, to w kopii też powinno być wiele referencji do skopiowanego obiektu
  • radzić sobie z cyklami - w przypadku wystąpienia cyklu nie powinien wyrzucać wyjątków
  • wykorzystywać dostarczony ClassLoader do wczytywania klas - tak aby można było dostarczyć zmodyfikowane klasy

Na www znalazłem kilka rozwiązań. Między innymi dobrze znane Commons Beanutils. Niestety ono nie spełnia ani jednego wymogu. Drugą biblioteką, która mi wpadła do rąk była Sojo. API jest w fazie rozwoju ale już teraz udostępnia wiele funkcjonalności. Obsługuje kopiowanie wgłąb i radzi sobie z cyklami. Nie kopiuje też uprzednio skopiowanej referencji. Niestety nie znalazłem sposobu na dostarczenie własnego ClassLoadera.

Ze względu na brak wymarzonej biblioteki zacząłem szukać alternatywnych rozwiązań. Pierwszą techniką, którą wykorzystałem była wbudowana serializacja. Niestety ta metoda nie pozwala na dostarczenie własnego ClassLoadera (wykorzystywany jest ClassLoader wywołujący metodę deserializacji) i wymaga by każda klasa implementowała interfejs Serializable.

Kolejnym rozwiązaniem, które przetestowałem było użycie koderów JavaBean<->XML dostarczanych wraz z JRE (klasy XMLEncoder, XMLDecoder). Technika ta jest zdecydowanie najwolniejsza, jednak na pierwszy rzut oka wydawało się, że posiada wszystkie cechy, które od takiego API wymagam. Niestety pomimo dostarczenia własnego ClassLoader'a nie jest on wykorzystywany do wczytania wszystkich niezbędnych klas.

Po kilku dniach próbowania różnych technologii stwierdziłem, że szybciej będzie jak sam zaimplementuję taką funkcjonalność. I w ten oto sposób w 2 dni napisałem proste API do kopiowania beanów ze wszystkimi funkcjonalnościami. API nosi roboczą nazwę BeanCopier i na razie dostępne jest do ściągnięcia na repozytorium Maven 2 pod adresem: http://eggframework.org/maven2.

Aby użyć API we własnym projekcie Maven 2 należy dodać do pom.xml:

<project>
    <dependencies>
        <dependency>
            <groupId>com.jacekolszak</groupId>
            <artifactId>beancopier</artifactId>
            <version>0.9</version>
        </dependency>
    </dependencies>
    <repositories>
        <repository>
            <id>Egg Framework</id>
            <url>http://eggframework.org/maven2</url>
        </repository>
    </repositories>
</project>

Sposób użycia BeanCopier:

BeanCopier beanCopier = new BeanCopierImpl();
copy = beanCopier.copy(original, classLoader);

Niebawem wrzucę źródła na CVS. Póki co są one dostępne w jarze beancopier-0.9-sources.jar na repozytorium Maven 2.

UPDATE: Źródła są dostępne na repozytorium Bazaar po adresem: http://bazaar.launchpad.net/~jacekolszak/beancopier/trunk. Wystarczy wydać polecenie:
bzr branch http://bazaar.launchpad.net/~jacekolszak/beancopier/trunk
aby ściągnąć najnowsze źródła.

Monday, December 17, 2007

Fluent Interface w Egg Framework 2.0

Jedną z najciekawszych funkcjonalności wprowadzonych do Egg Framework 2.0 jest Fluent Interface - API, za pomocą którego można tworzyć czytelny kod aplikacji www w języku Java - wyglądający o dziwo bardzo podobnie do HTMLa.

Fluent Interface to pojęcie wprowadzone przez Martina Fowlera i Erica Evansa. Nazwali oni tym terminem pewien styl tworzenia interfejsów programowych. Więcej można przeczytać na blogu Martina Fowlera.

Oto przykład strony www stworzonej przy użyciu Fluent Interface zaimplementowanym w Egg Framework 2.0:

package org.eggframework2.view.examples.xhtml;

import static org.eggframework2.view.elements.xhtml.ext.FluentInterfaceMethods.*;

import org.eggframework2.view.elements.IElement;
import org.eggframework2.view.elements.xhtml.tags.Html;

public class FluentInterfaceUsageExample extends Html {

    public FluentInterfaceUsageExample() {
        IElement body = //
        body( //
                p("Hello World"), //
                a("Egg Framework Page", href("http://eggframework.org")), //
                form(action("/action.do"), method("post"), //
                        input(type("text"), name("Login")), br(), //
                        input(type("password"), name("Password")), br(), //
                        input(type("submit"), value("Please log me in"))//
                ),//
                table(//
                        tr(//
                                td("Cell 1"), td("Cell 2"), td("Cell 3")//
                        ),//
                        tr(//
                                td("Cell 1"), td("Cell 2"), td("Cell 3")//
                        )//
                ));

        add(body);
    }

    public static void main(String[] args) {
        System.out.println(new FluentInterfaceUsageExample());
    }
}

Jak widać kod Javy jest bardzo czytelny i podobny do wynikowego kodu HTML. Wszystko dzięki wykorzystaniu metod statycznych i statycznych importów. Dzięki zaimportowaniu wszystkich metod z klasy FluentInterfaceMethods możemy ich użyć w kodzie strony tak jakby były one jej częścią. Nie musimy więc poprzedzać nazwy metody nazwą klasy w jakiej jest ona zdefiniowana. To sprawia, że kod jest znacznie bardziej zwięzły.

Klasa FluentInterfaceMethods oprócz metod tworzących instancje elementów (znaczników) HTML posiada metody tworzące atrybuty np. type, name, id. Dzięki temu można w jednej linijce utworzyć element i ustawić jego atrybuty.

Jeśli często używasz układania kodu źródłowego (Pretty Print) to musisz być przygotowany na to, iż większość IDE ułoży kod takiej strony w jednej linijce (albowiem całą zawartość tworzysz poprzez zagnieżdżone wywołania metod). To na pewno nie przyczyni się do zwiększenia czytelności kodu, wręcz odwrotnie - znacznie ją pogorszy. Aby ustrzec się przed taką sytuacją należy użyć komentarzy ("//") w miejscach gdzie IDE ma złamać linię. Dzięki nim nawet po operacji układania kodu kod będzie ładnie się prezentował.

Fluent Interface w Egg Framework to sposób na zwiększenie czytelności tworzonego kodu. Jest to swojego rodzaju nakładka i wykorzystuje istniejące API. Po kilkunastu/kilkudziesięciu minutach pracy powinieneś się przyzwyczaić do tego "dziwnego stylu programowania". Od Ciebie zależy czy będziesz go wykorzystywał czy nie - w wielu wypadkach może okazać się lepszy, jednak mogą zdarzyć się sytuacje gdy utworzenie zawartości strony "w standardowy sposób" będzie prostsze i szybsze.