JBoss Seam zmienne kontekstowe

18 11 2009

W zasisęgu kontekst możemy przechowywać dowolne wartości, jednak zazwyczaj
przechowywane są tam stany (wartości) różnego rodzaju komponentów.

Stany obiektów dostępne w ramach danego kontekstu przechowywane są jako
para [klucz, wartość].

Możemy w łatwy sposób uzyskać wartość obiektu z kontekstu:

User user = (User) Contexts.getSessionContext().get("user");

Równie łatwo można zmienić wartość obiektu:

Contexts.getSessionContext().set("user", user);

Zazwyczaj jednak do uzyskiwania dostępu do zmiennych kontekstowych
wykorzystywany jest mechanizm wstrzykiwania (injection) a do zapisu
zmiennej w ramach kontekstu outjection.

Aby pobrać lub zapisać wartość do zmiennej kontekstowej, musi być ona
uprzednio odszukana. Wyszukiwanie odbywa się według określonej
hierarchii (od najbardziej szczegółowego kontekstu do najbardziej ogólnego):

  1. kontekst zdarzenia (request)
  2. kontekst strony
  3. kontekst konwersacji
  4. kontekst sesji
  5. kontekst biznesowy
  6. kontekst aplikacji

Więcej o kontekstach na stronie: http://rpodhajny.wordpress.com/2009/11/18/jboss-seam-kontekst-komponentu/





JBOSS Seam kontekst komponentu

18 11 2009

Konteks bezstanowy

Komponenty bezstanowe żyją zawsze w ramach kontekstu bezstanowego.
Prawdopodobnie nie są byt obiektowe, jednak ważne i czasami użyteczne.

Konteks zdarzeniowy (request[żądanie])

Jest to najbardziej szczegółowy kontekst. Komponenty powiązane z nim
są niszczone tuż po zakończeniu zdarzenia (requestu), ale ich stan
dostępny jest conajmniej tak długo jak trwa żądanie.

Konteks strony

Stany obiektów (mozna w skrócie powiedzieć obiekty) umieszczone w ramach
kontekstu strony są powiązanie z konkretną instancją(egzemplarzem)
tejże strony. Obiekty zachowane w kontekście danej strony, mogą być
wykorzystane w eventach z niej wygenerowanych. Jest on szczególnie
przydatny przy funkcjonalnościach np. typu aktywnych list, gdzie
każde klikniecie na wiersz z listy powoduje zaczynanie danych z serwera
np. szczegółów zamówienia. Stan jest przypisywany do konkretnej instancji
strony u użytkownika, umożliwia swobodne korzystanie z przycisku wstecz,
strona załadowana z historii, będzie posiadać swój oryginalny stan
(stan w jakim zostala opuszczona).

Konteks konwersacji

Jest głównym kontekstem Seamowym. Może obejmować kilka interakcji
z użytkownikiem, requestów oraz kilka odwołań do bazy danych, wszystko
co jest konieczne aby wykonać daną funkcjonalność.

Użytkownik może mieć rozpoczętych kilka konwersacji, zazwyczaj w osobnych oknach,żadna z nich
nie ma jednak możliwości narobienia bałaganu w drugiej
(konwersacje nie widzą siebie nawzajem).

Konwersacje mogą być zagnieżdżane.

Konwersacje mogą wygasać (timeout), jest to parametr który można
konfirugować, zabezpiecza przed niebezpecznym rozrostem konwersacji
np. gdy użytkownik opuści stronę bez wylogowania się.

Można tak skonfigurować Seama, aby konwersacje trzymane były
w przeglądarce użytkownika.

Kontekst sesji

Kontekst sesji przechowuje obiekty powiązane z użytkownikiem po
jego zalogowaniu się. Zazwyczaj nie jest stosowany do przechowywania
globalnych informacji, wyjątkiem może być przechowywanie informacji
wykorzystywanych przez kilka różnych konwersacji w ramach danej sesji.

Kontekst procesu biznesowego

Kontekst biznesowy przechowuje obiekty powiązane z długo trwającym procesem
biznesowym. Jest zarządzamy poprzez JBoss jBPM. Proces biznesowy obejmuje
interakcje pomiędzy wieloma użytkownikami.

Kontekst aplikacji

Kontekst aplikacji jest najszerszym kontekstem i jest wykorzystywany
przeważnie do przechowywania statycznych danych np. dotyczących
konfiguracji.

Artykuł napisany w oparciu o dokument Seam reference.




Widening, autoboxing, var-args i Java 5.0

2 04 2009

Widening - cecha Javy poniżej 5.0, jest to po prostu poszerzanie zakresu argumentu podanego metodzie. Załóżmy, że metoda przyjmuje argumenty typu int, a my podajemy jej byte, w tym momencie kompilator automatycznie poszerza byte, konwertując go do int, aby sprostać wymaganiom metody. Możliwe jest to ze względu na to, iż byte jest pod przedziałem zakresu liczbowego int, w drugą stronę (int na byte) automatyczna konwersja nie będzie mieć miejsca, a jeżeli ją wymusimy (poprzez rzutowanie), stracimy część danych, bo jak wiadomo int zapisywany jest na 4 bajtach, byte na jednym, rzutując int na byte odcinane są 3 najbardziej znaczące bajty i w efekcie zakres liczb ograniczony jest do min -128, max 127:

class Main {
    static void test(int a) {
        System.out.println("int");
    }
    public static void main( String[] args ) {
        byte b = 12;
        test(b); // na ekranie zostanie wypisane: "int"
    }
}

A co w przypadku, kiedy będziemy mieć do czynienia z tzw wrapperem (ang. wrap – opakować), czyli typem prostym opakowanym w obiekt? Jak zachowa się kompilator widząc coś takiego?:

class Main {
    static void test(Byte a) {
        System.out.println("Byte");
    }
    static void test(int a) {
        System.out.println("int");
    }
    public static void main( String[] args ) {
        byte b = 12;
        test(b); // na ekranie zostanie wypisane: "int"
    }
}

Dlaczego tak się dzieje, skoro mamy dostępną metodę, która jako argument przyjmuje opakowany typ byte? Otóż programiści implementujący nowe funkcjonalności w Javie 5.0, przyjęli za zasadę, że kod napisany w poprzednich wersjach języka, powinien działać tak, jak w pierwotnych założeniach (kiedy nie było jeszcze mechanizmów typu autoboxing i autounboxing). Tak więc kompilator najpierw próbuje poszerzyć zakres liczby (szuka odpowiedniej metody), a gdy jej nie znajdzie skorzysta z metody, która jako argument przyjmuje obudowany typ prosty (w tym konkretnym przypadku Byte, aby to sprawdzić, wystarczy za komentować przeciążoną metodę static void test(int a)).

Dodać trzeba, że widening wygrywa nie tylko z autoboxingiem, ale też z var-arg, ale czy var-arg wygra z autoboxingiem?

class Main {
    static void test(Byte a) {
        System.out.println("Byte");
    }
    static void test(int... a) {
        System.out.println("int...");
    }
    public static void main( String[] args ) {
        byte b = 12;
        test(b);        // na ekranie zostanie wypisane: "Byte"
        test(b, b);     // na ekranie zostanie wypisane: "int..."
    }
}

Jak widać, podczas, gdy jako argument podajemy jedną zmienną typu byte, autoboxing wygrywa. Natomiast, gdy podajemy do metody test dwa argumenty typu byte (lub więcej), kompilator szuka metod gdzie mógłby zastosować widening, potem autoboxing, a na końcu var-arg (w takiej kolejności), dopiero ostatnia spełnia wymagania.

Referencje

W przypadku referencji sprawa jest prosta, jeżeli metoda test oczekiwałaby na argument typu Owoc (Owoc jest klasą), a dostałaby argument typu Jabłko (Jabłko jest klasą dziedziczącą po klasie Owoc), to jeżeli zachodzi relacja IS-A (a w tym przypadku zachodzi), kompilator wykona automatycznie widening i wykonana zosanie metora test(Owoc 0).

Różne kombinacje

Nie można przekazać argumentu typu byte metodzie, która oczekuje argumentu typu Integer, gdyż, co prawda kompilator poprawnie przekonwertowałby go na typ Byte (autoboxing), ale widening na Integer nie jest już możliwi, z tego prostego powodu, że typu Integer, Byte, Short, itp., nie są między sobą w relacji IS-A, są że się tak wyrażę w jednym szeregu. Kompilator wyrzuci zatem wyjątek w miejscu, gdzie metoda test(Integer i) dostaje jako argument typ prosty byte.

Aczkolwiek, jezeli naszą metodę test(Integer i) zmienimy lekko na test(Object o), wszystko byłoby w porządku, wiemy przecież, że w Javie wszystkie klasy dziedziczą po głównej klasie Object, kompilator wykona autoboxing na typ Byte, który jest w relacji IS-A z typem Object, kompilacja zostanie przeprowadzona pomyślnie i metoda będzie mogła być wywoływana bez problemów.

Zasady dla metod przeciążonych, używających wideningu, boxingu i var-arg:

  • Widening w przypadku typów prostych, zawsze poszerza do najmniejszego możliwego typu prostego,
  • Nie można używać wideningu, pomiędzy dwoma typami opakowującymi typy proste (nie są w relacji IS-A),
  • Nie można rozszerzać, a potem opakowywać, (int nie może stać się Long`iem),
  • Można za to opakowywać, a następnie rozszerzać (int -> Integer -> Object),
  • W przypadku metod przeciążonych boxing i var-arg, używane osobno są poprawne,
  • Var-arg działa zarówno z boxingiem, jak i z wideningiem.




Java kohezja i luźne powiązania między klasami (coupling)

19 03 2009

Kohezja

Klasy, które posiadają metody do różnych zadań, np odczyt/zapis do pliku + obróbka pliku + korekta błędów w tekście, to klasy o niskim współczynniki kohezji (robią wiele rzeczy). Kohezja, to inaczej specjalizacja klasy, a klasa o najwyższym współczynniku kohezji, to ta, która ma tylko jedną konkretną funkcję. Powyżej przedstawiony opis klasy, aby spełniał założenia wysokiej kohezji, musimy rozbić na większą ilość, ale za to bardziej wyspecjalizowanych klas. Powstanie więc np. osobna klasa to zapisu, osobna do odczytu, oraz osobne do obróbki pliku i sprawdzania poprawności tekstu.

Zauważyć należy, że teraz, kiedy rozbiliśmy jedną klasę na kilka mniejszych, możemy je wykorzystywać w dowolny sposób tam, gdzie jest to konieczne. Przed zmianami, stworzenie klasy, która np. potrzebowałaby jedynie odczytu z pliku, wymagałoby albo rozszerzania klasy głównej a tym samym dziedziczenie od niej wielu niepotrzebnych  rzeczy, albo napisanie takiej samej metody kolejny raz. Po zmianach (rozbicie ogólnej klasy wg. zasady kohezji), daje nam możliwość skorzystania tylko z tego, co jest potrzebne, czyli klasy, która umożliwia odczyt danych z pliku.

Luźne powiązania między klasami (coupling)

class A {
    protected int x;
    public int getX() {
        return x;
    }
    public void setX( int x ) {
    	if( x > 0 ) {
    		this.x = x;
    	}
    }
}
class B extends A {
    public int getBadMultiplyX( int factor ) { // metoda bezpośrednio odwołuje
                                               // się do pola klasy nadrzędnej (źle)
        return x * factor; // bezpośredni dostęp do pola klasy nadrzędnej (żle)
    }
    public int getGoodMultiplyX( int factor ) { // metoda uzyskuje dostęp do
                                                // pola klasy nadrzędnej za
                                                // pośrednictwem metody (gettera) (dobrze)
        return getX() * factor; // pośredni dostęp do pola instancji (dobrze)
    }
}
public class Main {
    public static void main( String[] args ) {
        A a = new A();
        B b = new B();
        a.x = 2; // bezpośrednie ustawienie wartości pola klasy (źle)
        a.setX( 2 ); // pośrednie ustawianie wartości pola klasy, sprawdzana jest
                     // również poprawność wprowadzanych danych (dobrze)
        System.out.println( "a.x: " + a.x );            // bezpośredni dostęp do pola
                                                        // instancji (źle)
        System.out.println( "a.getX(): " + a.getX() );  // pośredni dostęp do pola
                                                        // instancji (dobrze)
        System.out.println( "b.x: " + b.x );            // bezpośredni dostęp do pola
                                                        // klasy nadrzędnej (źle)
        System.out.println( "b.getX(): " + b.getX() );  // pośredni dostęp do pola klasy
                                                        // nadrzędnej (dobrze)
        System.out.println( "b.getBadMultiplyX( 2 ): " + b.getBadMultiplyX( 2 ) );
        System.out.println( "b.getGoodMultiplyX( 3 ): " + b.getGoodMultiplyX( 3 ) );
    }
}

Po przejrzeniu powyższego kodu (oraz przeczytaniu komentarzy), widać, co nie jest dobrze napisane. Powiązania między klasami powinny być tak “luźne”, jak się tylko da. Oczywiście nikt nikomu nie może zabronić, aby klasy pochodne miały bezpośredni dostęp do pól klasy nadrzędnej ( tak jak w naszym przypadku), lecz jest to zła praktyka. Mając bezpośredni dostęp do pola klasy, możemy przypisać mu dowolną wartość, programista musi więc sam zadbać o to, aby dane wprowadzane, były poprawne. Tą samą czynność muszą zrobić wszyscy, którzy będą chcieli z danej klasy skorzystać. A można przecież wszystkie te powtarzające się problemy załatwić dopisując metody, która będą umożliwiać pobieranie oraz poprawne ustawianie wartości pola klasy (getter, setter). Metody takie to interfejs (API – Application Program Interface), sposób komunikacji naszej klasy z otoczeniem (innymi klasami). Dodatkowo istnieje poważne zagrożenie, że w przyszłości, kiedy klasa ulegnie zmianom, np. zmieni się typ pola “x” klasy “A”, wszystkie klasy dziedziczące po niej, i korzystające z bezpośredniego dostępu jej do pola, po aktualizacji, przestaną działać ze względu na konflikt typów. Natomiast, gdyby klasa A posiadła pole x jako prywatne, i udostępniała jedynie swój API do edycji i pobierania wartości, nic złego by się nie stało. Po zmianach do klasy dopisane byłyby odpowiednie wersje getterów i setterów, lub zmodyfikowane zostały by tylko te, które już są, klasy pochodne nie odczułyby tych zmian.

Aby pisać kod zgodnie z zasadą luźnych powiązań między klasami, należy dla każdej z klas napisać odpowiednie API. Nawet, jeżeli getter/setter nie realizuje dodatkowych czynności, oprócz zwrócenia/ustawienia wartości pola klasy, to i tak warto go napisać. W przyszłości może się bowiem okazać koniecznym dopisanie np. walidacji w setterze, która nie będzie problemem, odkąd nasza klasa porozumiewa się z innymi za pomocą swojego API.

Idea luźnych powiązań jest taka, aby dawać innym tylko to, co chcą zobaczyć, a całą resztę trzymać za zamkniętymi drzwiami.





Java konstruktor

17 03 2009

Konstruktor jest nieodłączną częścią klasy (abstrakcyjna klasa również posiada swój konstruktor), istnieje on nawet wtedy, kiedy go jawnie nie napiszemy (konstruktor domyślny). Wiąże się to z tym, że nie jest możliwe stworzenie obiektu klasy bez udziału konstruktora.

Ponad to:

  • Konstruktor MUSI mieć taką samą nazwę, jak nazwa klasy,
  • Konstruktor nie może zwracać żadnego typu (nawet void),
  • konstruktor domyślny jest zawsze bezparametrowy,
  • konstruktor domyślny można wywołać tylko wtedy, gdy klasa nie posiada żądnego, zaimplementowanego konstruktora,
  • Konstruktor może być przeciążany,
  • podczas wywołania konstruktora danej klasy, wywoływane są również wszystkie konstruktory klas nadrzędnych,
  • Konstruktor, o ile posiada parametry, może ustawiać pola wartościami, które dostał jako argumenty, jeżeli konstruktor nie ma żadnych parametrów, wszystkie wartości pól obiektu, będą zainicjalizowane domyślnymi wartościami.
  • Konstruktor może być oznaczony dowolnym specyfikatorem dostępu, nawet private (wykorzystywane np. przy implementacji wzorca projektowego Singleton),
  • każdy konstruktor ma jako pierwsze wyrażenie albo odwołanie do przeciążonego konstruktora ( this() ), albo do konstruktora z klasy nadrzędnej ( super() ), pamiętać należy, że wyrażenia te mogą być dodane przez kompilator w trakcie kompilacji, jeżeli nie zostaną napisane (nie znaczy to jednak, że zawsze trzeba je jawnie wpisywać w ciało konstruktora),
  • Konstruktor nie może mieć zaimplementowane (używać) jednocześnie operatora super() i this(),
  • operator super(), może być wywoływany bez parametrów, oraz z parametrami (dokładnie takimi, jakie ma konstruktor w klasie nadrzędnej),
  • nie można wywołać metody, lub odwołać się do pola instancji klasy, przed operatorem super(),
  • tylko zmienne lub metody statyczne mogą być użyte jako część wywołania operatora super (np. super(ClassX.PoleStatyczne) ),
  • Interfejsy nie posiadają konstruktorów,
  • Konstruktor nie może być wywołany bezpośrednio (tak jak metoda), wywołanie takie może mieć miejsce tylko w innym konstruktorze,
  • Konstruktory nie są dziedziczone.

Możliwe jest napisanie metody o takiej samej nazwie, jak klasa (aczkolwiek nie jest to zalecane), jednak nie będzie ona konstruktorem z uwagi na zweracany typ.

Przyjrzyjmy się teraz przykładowi:

class MyPoint2D {
    private int x;
    private int y;
    public MyPoint2D(int x, int y) {
        this.x = x;
        this.y = y;
    }
    public void getInfo() {
        System.out.print( "Punkt:\tx=" + x + "\ty=" + y);
        // symbol "\t" - interpretowany jest jako tabulacja
    }
}
class MyPoint3D extends MyPoint2D {
    private int z;
    public MyPoint3D( int x, int y, int z ) {
        super(x, y);         // wywołanie dwuparametrowego konstruktora klasy nadrzędnej
        this.z = z;
    }

    public void getInfo() {  // przesłonięta metoda klasy nadrzędnej
        super.getInfo();     // za pomocą operatora super, można
                             // dostać się do metod klasy nadrzędnej
        System.out.print( "\tz=" + z );
    }
}
class Main {
    public static void main( String[] args ) {
        MyPoint2D point2D = new MyPoint2D(-1, -2);
        point2D.getInfo();
        System.out.println();
        MyPoint3D point3D = new MyPoint3D(3, 4, 5);
        point3D.getInfo();
    }
}

Dzięki temu, że klasa MyPoint3D dziedziczy po klasie MyPoint2D, nie jest konieczne deklarowanie tych samych zmiennyh dwa razy w różnych klasach. Ponad to kalsa MyPoint3D oszczędza sporo na ilości kodu, jaki konieczny jest do zaimplementowana, poprzez wykożystanie konstruktora i metody getInfo() z klasy nadrzędnej (a to jest przecież bardzo prosty przykład). Ale to nie są przecież największe zalety, istotą jest to, że instancja klasy MyPoint3D będzie teraz zarówno obiektem klasy MyPoint3D jak i MyPoint2D, umożliwi to np. trzymanie wszystkich obiektów oby tych klas w jednej tablicy typu MyPoint2D (rzutowanie).

Konstruktor klasy MyPoint3D musi posiadać co najmniej takie parametry, jak ten w klasie nadrzędnej (wynika to tego, że przy wywoływaniu konstruktora tej klasy, wywoływane są też konstruktory wszystkich klas nadrzędnych. Klasa nadrzędna posiada konstruktor który oczekuje dwóch argumentów typu int, dostarczyć je musi konstruktor z klasy MyPoint3D, aby wywołanie podczas kompilacji nie spowodowało błędu). W tym miejscu należy powiedzieć, że nie możemy starać się wywołać konstruktora bezparametrowego z klasy bazowej, gdyż ona go nie posiada. W pamięci mamy jednak to, że każda klasa posiada konstruktor domyślny, co jest oczywiście w 100% prawdą, jednak, gdy jakikolwiek konstruktor, z parametrami, czy bez zostanie zaimplementowany w klasie, kompilator to wykryje i nie będzie tworzył niepotrzebnie kolejnego (konstruktory domyślne tworzone są w momencie kompilacji, ale tylko wtedy, kiedy klasa nie posiada zaimplementowanego konstruktora).

Metoda getInfo() została w klasie MyPoint3D przesłonięta, a w jej ciele, za pomocą operatora “super”, została wywołana metoda klasy nadrzędnej, wyświetlająca na ekranie info na temat dwóch pierwszych współrzędnych, naszym zadaniem pozostaje już tylko dodanie info na temat ostatniej współrzędnej.

Przeciążanie konstruktorów

Przeciążanie konstruktorów jest bardzo często wykożystywaną praktyką, bowiem podając odpowiednie argumenty dla kosntruktora, możemy stworzyć obiekt, który będzie już na samym początku w sposób, jaki jest to wymagane, “skonfigurowany”.

import java.util.Random;
class Planet {
    String name;
    int size;
    Random random = new Random();
    public Planet() {
        name = "noname";
        size = Math.abs( random.nextInt() ) % 10000 + 1;
    }
    Planet( String name ) {
        this.name = name;
        size = Math.abs( random.nextInt() ) % 10000 + 1;
    }
    Planet( String name, int size ) {
        this.name = name;
        if ( size <= 10000 && size >=1 ) {
            this.size = size;
        } else if( size > 10000 ) {
            this.size = 10000;
        } else {
            this.size = 1;
        }
    }
    @Override
    public String toString() {
        return "Planeta " + name + " ma wielkość " + size + ".";
    }
}
class Main {
    public static void main( String[] args ) {
        Planet noName = new Planet();
        Planet mars = new Planet( "Mars" );
        Planet ziemia = new Planet( "Ziemia", 8456 );
        System.out.println( noName.toString() );
        System.out.println( mars.toString() );
        System.out.println( ziemia.toString() );
    }
}

Linia 4: Tworzenie obiektu klasy Random, umożliwia ona generowanie pseudolosowych liczb.

Linia 5: Konstruktor bezparametrowy (domyślny), wykorzystamy go np w przypadku, gdy zajdzie potrzeba stworzenia obiektu typu Planet, a nie będziemy znać, lub nie będzie istotna jego nazwa. Przypisuje on domyślną nazwę i losuje wartość z przedziału 1-10000, która będzie rozmiarem nowej planety.

Linia 9: Konstruktor z parametrem String, przeciąża on konstruktor bezparametrowy, umożliwia podanie nazwy planety podczas tworzenia nowego obiektu, wielkość planety generowana jest losowo.

Linia 13: Konstruktor z parametrami String i int (nazwa, wielkość). Konstrukora tego będzie możńa użyć w wypadku, gdy będziemy znać nazwę i rozmiar planety, jaką chcemy utworzyć.

Linia 24: Przesłonięta metoda toString() z klasy Object, napisana w taki sposób, aby zwracała info na temat obiektu kalsy Planet.

Kompilacja kodu i uruchomienie aplikacji wyświetli na ekranie np.:

Planeta noname ma wielkość 3106.  (domyślna nazwa, wielkość generowana losowo)
Planeta Mars ma wielkość 7032.    (wielkość generowana losowo)
Planeta Ziemia ma wielkość 8456.