Signal Framework dla platformy Java ME
Wprowadzenie
Signal Framework to open-source’owa biblioteka IoC, AOP oraz MVC przeznaczona dla Javy ME (J2ME) oraz oparta o Springa. Framework został zaprojektowany w taki sposób, aby przezwyciężyć ograniczenia CLDC, które uniemożliwiają uruchamianie kontenerów IoC opartych o Jave SE na platformie J2ME. Signal Framework używa zwykłych XMLowych plików konfiguracyjnych Springa, pozwalając programistom na wykorzystanie istniejących narzędzi i umiejętności.
Poniższy diagram przedstawia architekturę biblioteki:
Inspiracją dla nazwy frameworka jest biblioteka Antenna oraz, oczywiście, Spring Framework.
Kontener IoC
Wsparcie dla refleksji w CLDC jest bardzo ograniczone w porównaniu do Javy SE. API pozwala jedynie na tworzenie obiektów za pomocą domyślnych (bezargumentowych) konstruktorów. Refleksja nie pozwala na przekazywanie argumentów do konstruktorów, wywoływanie metod, dostęp do pól, ani tworzenie dynamicznych proxy.
Aby przezwyciężyć te ograniczenia framework IoC przetwarza pliki konfiguracyjne kontekstu podczas kompilowania aplikacji oraz generuje kod Javy odpowiedzialny za tworzenie kontekstu po uruchomieniu aplikacji. Kiedy aplikacja J2ME jest uruchamiana, wykonuje ona wygenerowany kod zamiast przetwarzania plików konfiguracyjnych. Dzięki temu kontekst może zostać utworzony bez parsowania plików XML lub używania zaawansowanej refleksji. Rozmiar wygenerowanego kodu jest bardzo mały, ponieważ biblioteka IoC zawiera jedynie około 10 klas. Wygenerowany kod nie zależy od bibliotek Springa.
Mimo tego, że framework został stworzony na potrzeby platformy J2ME, kontener IoC może być używany w dowolnych aplikacjach Java, które potrzebują wykorzystać funkcjonalność Springa bez polegania na refleksji lub parsowania plików XML (np. platformy Android oraz GWT).
Signal Framework wspiera następujące funkcje kontenera IoC zaimplementowanego w Springu:
-
pliki konfiguracyjne XML,
-
komponenty (beans) typu singleton,
-
wstrzykiwanie zależności poprzez argumenty konstruktorów oraz własności (properties) obiektów,
-
autowiring,
-
inicjalizacja na żądanie (lazy initialization),
-
przetwarzanie komponentów (bean post-processors) (
com.aurorasoftworks.signal.runtime.core.context.IBeanProcessor
– odpowiednik interfejsuorg.springframework.beans.factory.config.BeanPostProcessor
), -
lekkie AOP oparte o automatycznie generowane klasy proxy,
-
com.aurorasoftworks.signal.runtime.core.context.IInitializingBean
- odpowiednik interfejsuorg.springframework.beans.factory.InitializingBean
, -
com.aurorasoftworks.signal.runtime.core.context.IContextAware
– odpowiednik interfejsuorg.springframework.beans.factory.BeanFactoryAware
.
Signal Framework to
open-source’owa biblioteka IoC, AOP oraz MVC
przeznaczona dla Javy ME
Generator kodu jest na ogół uruchamiany jako wtyczka Mavena, która wymaga 2 parametrów: nazwy pliku konfiguracyjnego Springa oraz nazwy klasy Java, która zostanie wygenerowana. Generator obsługuje znacznik <import resource="...">, jest więc możliwe przetworzenie wielu plików konfiguracyjnych poprzez jedno uruchomienie wtyczki.
Poniżej przedstawiono przykład pliku konfiguracyjnego Springa oraz odpowiadającą mu wygenerowaną klasę:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="greeter" class="com.aurorasoftworks.signal.examples.context.core.Greeter"> <constructor-arg ref="msgSource" /> </bean> <bean id="msgSource" class="com.aurorasoftworks.signal.examples.context.core.MessageSource"> <constructor-arg> <map> <entry key="greeting" value="Hello!" /> </map> </constructor-arg> </bean> </beans>
public class ApplicationContext extends com.aurorasoftworks.signal.runtime.core.context.Context { public ApplicationContext() throws Exception { /* Begin msgSource */ ApplicationContext.this.registerBean("msgSource", new com.aurorasoftworks.signal.examples.context.core.MessageSource( new java.util.Hashtable(){{put("greeting", "Hello!"); }} )); /* End msgSource */ /* Begin greeter */ ApplicationContext.this.registerBean("greeter", new com.aurorasoftworks.signal.examples.context.core.Greeter( ((com.aurorasoftworks.signal.examples.context.core.MessageSource) GreeterContext.this.getBean("msgSource")) )); /* End greeter */ } }
Biblioteka obecnie nie obsługuje narzędzia Ant, ale zadania Anta (tasks) mogą być łatwo zaimplementowane jako lekka nakładka na istniejący generator.
Framework AOP
Oprócz utrudniania implementacji IoC, ograniczenia API CLDC uniemożliwiają rownież użycie istniejących frameworków AOP na urządzeniach J2ME. API AOP Alliance oraz popularne biblioteki AOP, takie jak AspectJ oraz JBoss AOP, zależą od typów zawartych w pakiecie java.lang.reflect.*
, które nie są zaimplementowane w Javie ME. Dodatkowo implementacje AOP często wykorzystują własne implementacje class-loaderów i/lub dynamiczne proxy które nie są obsługiwane przez implementacje CLDC.
Implementacja AOP dostarczona przez Signal Framework została zaprojektowana na potrzeby urządzeń J2ME: ma niewielkie wymagania pamięciowe, używa jedynie klas obecnych w API CLDC oraz alokuje podczas działania najmniejszą możliwą ilość obiektów. To podejście pociąga jednak za sobą pewne ograniczenia: framework nie jest tak rozbudowany jak jego odpowiedniki dla aplikacji desktopowych i klasy enterprise oraz obsługuje jedynie przechwytywanie metod wywoływanych na interfejsach.
Framework AOP przezwycięża ograniczenia Javy ME poprzez generację kodu. Kiedy kontekst aplikacji jest przetwarzany podczas kompilacji, framework identyfikuje komponenty implementujące interfejs com.aurorasoftworks.signal.runtime.core.context.proxy.IProxy
, a następnie tworzy dla nich klasy proxy. Instancje klas proxy są stosowane do przechwytywania wywołań metod oraz wywoływania interceptorów.
Implementacja AOP
dostarczona przez Signal Framework
ma niewielkie wymagania pamięciowe
Ta koncepcja jest podobna do mechanizmu dynamicznych proxy wspieranych przez Jave SE. Większość klas zawartych we frameworku AOP ma swoje odpowiedniki w Javie SE, co przedstawiono poniżej:
Tabela 1. Odpowiedniki klas Signal AOP w Javie SE
Typ w bibliotece Signal AOP ( com.aurorasoftworks.signal.runtime.core.* ) |
Odpowiednik w Javie SE |
---|---|
context.proxy.ProxyFactory |
java.lang.reflect.Proxy |
context.proxy.IInvocationHandler |
java.lang.reflect.InvocationHandler |
context.proxy.IMethodInterceptor |
org.aopalliance.intercept.MethodInterceptor |
context.proxy.IProxy |
wartość zwraca przez java.lang.reflect.Proxy.newInstance |
context.proxy.IProxyTarget |
dowolny obiekt |
context.proxy.IProxyClass |
java.lang.Class |
context.proxy.IMethodHandler |
java.lang.reflect.Method |
Obiekty proxy są na ogół tworzone przez klasę ProxyFactory
. Najbardziej powszechną metodą tworzenia instancji proxy jest przekazanie listy interceptorów do metody IProxyFactory#createProxy(IProxyTarget target, IMethodInterceptor [] interceptors)
. Zwracany obiekt implementuje te same interfejsy, co przekazany argument i może być bezpiecznie rzutowany na te interfejsy.
Przechwytywanie metod działa w następujący sposób:
Diagram 1. Przechwytywanie metod
Z uwagi na swoją prostotę biblioteka posiada pewne ograniczenia. Obiekty proxy stworzone przez bibliotekę wielokrotnie wykorzystują instancje tablic przeznaczone do przekazywania argumentów do interceptorów aby uniknąć tworzenia tymczasowych obiektów. To jednak oznacza, że kod klas proxy musi być synchronizowany (odpowiada za to generator kodu). W wielowątkowych aplikacjach może to prowadzić do problemów z wydajnością. Kod proxy jest synchronizowany na instancji proxy, co oznacza że wiele instancji proxy tego dla samego obiektu może być używanych współbieżnie bez blokowania żadnych wątków.
Kiedy typy podstawowe są przekazywane do lub zwracane przez metodę proxy muszą one zostać opakowane za pomocą typów obiektowych takich jak java.lang.Integer
. Jest to jedyny scenariusz, który wymaga tworzenia tymczasowych obiektów. W pozostałych przypadkach biblioteka AOP nie potrzebuje tworzyć żadnych tymczasowych obiektów i może być bezpiecznie używana niezależnie od jakości garbage collectora.
Framework MVC
Framework MVC jest oparty o funkcjonalność IoC oraz AOP opisaną w poprzednich sekcjach. Framework wspiera zarówno API MIDP jak i LWUIT oraz w razie potrzeby może być łatwo rozszerzony o wsparcie dla innych technologii prezentacji.
Framework wspiera zarówno API MIDP jak i LWUIT
Framework nie nakłada żadnych ograniczeń na model domenowy, ani widoki, pod warunkiem że używany jest LWUIT lub MIDP. Zamiast tego, biblioteka jest zaprojektowana tak, aby ułatwić tworzenie kontrolerów. Najważniejsze funkcje zaimplementowane w warstwie kontrolera to inicjalizacja kontrolerów (oraz powiązanych widoków, jeśli istnieją) na żądanie (lazy initialization) oraz deklaratywne reguły nawigacji zdefiniowane w pliku konfiguracyjnym IoC.
Kontroler to zwykły komponent (bean) zdefiniowany w kontekście IoC aplikacji. Wszystkie kontrolery muszą implementować interfejs com.aurorasoftworks.signal.runtime.ui.mvc.ICotroller
lub interfejsy dziedziczące po nim. Kontroler zarządza częścią interfejsu aplikacji, którą może być pojedynczy widok lub skomplikowany kreator (wizard) składający sie z wielu kroków.
Najbardziej powszechym rodzajem kontrolera jest kontroler widoku. Kontrolery widoku dla API MIDP oraz LWUIT powinny implementować odpowiednio interfejsy com.aurorasoftworks.signal.runtime.ui.mvc.midp.IViewController
oraz com.aurorasoftworks.signal.runtime.ui.mvc.lwuit.IViewController
. Framework automatycznie przekazuje komendy (javax.microedition.lcdui.Command
i com.sun.lwuit.Command
) do kontrolera widoku, który je wygenerował. Wiele kontrolerów może być powiązanych z widokiem.
Drugim typem kontrolera jest kontroler przepływu (flow controller), który zarządza reużytkowalnym procesem, na ogół kreatorem składającym się z wielu widoków. Po zakończeniu działania przepływ powraca do miejsca, w którym został rozpoczęty, podobnie jak wywołanie metody. Kontrolery przepływu powinny implementować interfejs com.aurorasoftworks.signal.runtime.ui.mvc.IFlowController
. Przepływy można ze sobą łączyć: jeden przepływ może startować inne przepływy, w tym nowe instancje tej samej klasy kontrolera przepływu.
Zależności między kontrolerami są definiowane jako zależności między komponentami, dzięki czemu kontrolery mogą "generować" zdarzenia wywołując metody interfejsów. Takie rozwiązanie skutkuje luźnym powiązaniem kontrolerów, deklaratynymi definicjami reguł nawigacji i silnym typowaniem. Wywołania metod interfejsów są przechwytywane przez framework aby w przezroczysty sposób zrealizować niezbędne przetwarzanie takie jak wyświetlanie właściwego widoku, rejestrowanie obserwatorów dla komend oraz inicjaowanie i kończenie przepływu.
Najważniejszym komponentem w aplikacji MVC
jest dyspozytor (dispatcher)
W większości przypadków pożądane jest aby kontrolery i widoki były inicjalizowane na żądanie. W takim wypadku kontekst aplikacji musi być odpowiednio skonfigurowany:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" default-lazy-init="true"> <!-- ... --> </beans>
Najważniejszym komponentem w aplikacji MVC jest dyspozytor (dispatcher). Dyspozytor to obiekt dostarczany przez bibliotekę, który przechwytuje wywołania metod i realizuje niezbędne przetwarzanie, takie jak wyświetlanie odpowiedniego widoku lub rejestracja obserwatora komend:
<bean id="dispatcher" class="com.aurorasoftworks.signal.runtime.ui.mvc.midp.Dispatcher" />
Istnieją dwie implementacje koncepcji dyspozytora: com.aurorasoftworks.signal.runtime.ui.mvc.midp.Dispatcher
oraz com.aurorasoftworks.signal.runtime.ui.mvc.lwuit.Dispatcher
, używane odpowiednio w aplikacjiach MIDP oraz LWUIT.
Po definicji dyspozytora należy umieścić definicje kontrolerów oraz widoków, co pokazano poniżej. Zależności między kontrolerami są definiowane jako zależności między komponentami IoC.
<bean id="accountListViewCtl" class="com.aurorasoftworks.signal.examples.ui.mvc.midp.AccountListViewController"> <constructor-arg ref="accountListView" /> <constructor-arg ref="accountService" /> <property name="newAccountEvent" ref="newAccountCtl" /> <property name="editAccountEvent" ref="editAccountCtl" /> </bean>
W powyższym przykładzie kontroler nazwany accountListViewCtl
obsługuje dwa rodzaje zdarzeń: newAccountEvent
oraz editAccountEvent
, które są powiązane z dwoma innymi kontrolerami jako własności komponentów. Te zdarzenia to zwykłe referencje do interfejsów zdefiniowanych poniżej. Kontrolery "generują" zdarzenia poprzez wywoływanie metod interfejsów, które są z kolei przechwytywane przez dyspozytora.
public interface INewAccountEvent { void onNewAccount(); } public interface IEditAccountEvent { void onEditAccount(IAccount account); }
Następujący diagram przedstawia sekwencję wywołań niezbędną do obsłużenia akcji użytkownika w scenariuszu opisanym powyżej.
Diagram 2. Sekwencja wywołań obsługi akcji
Komenda wybrana przez użytkownika jest wysyłana do dyspozytora (CommandListener.commandAction
), który z kolei przekazuje ją do aktywnego kontrolera (ICommandHandler.handleCommand
). Instancja AccountListViewController
reaguje na komendę generując zdarzenie INewAccountEvent.onNewAccount
, które jest przechwytywane przez dyspozytora. Dyspozytor deaktywuje instancję AccountListViewController
, zmienia bieżący widok na ten, który jest powiązany z instancją NewAccountViewController
oraz aktywuje go. Przejście z jednego kontrolera na drugi jest prawidłowo odzwierciedlone w stanie aplikacji: NewAccountViewController
staje się aktywnym kontrolerem, a jego powiązany widok jest wyświetlony na ekranie.
Dystrybucja kodu źródłowego frameworka
zawiera przykładową aplikację
Dystrybucja kodu źródłowego frameworka zawiera przykładową aplikację zaimplementowaną w technologiach MIDP oraz LWUIT, która demonstruje prawidłowe użycie biblioteki MVC.
Podsumowanie
Biblioteka została zaprojektowana aby pogodzić dwa sprzeczne wymagania: wykorzystać jak największą część funkcjonalności Springa oraz ułatwić dodanie wsparcia dla innych kontenerów IoC w przyszłości, jeśli będzie to potrzebne. Funkcjonalność opisana w tym artykule została zrealizowana dość małym kosztem: około 10 000 linii kodu nie licząc przykładowych aplikacji.
Pewne uproszczenia musiały zostać zaakceptowane z uwagi na ograniczenia platformy Java ME; przede wszystkim framework nie wspiera annotacji i zamiast tego wymaga w pewnych sytuacjach aby klasy aplikacji implementowały interfejsy biblioteki. Z tego samego powodu szablony klas (generics
) nie są stosowane w API frameworka.
Biblioteka została zaprojektowana
aby pogodzić dwa sprzeczne wymagania
Po kilku wersjach beta framework stał się dość dojrzały i zawiera wszystkie znaczące funkcje, które były planowane. Wersja produkcyjna powinna być dostępna najpóźniej w styczniu 2010.
Dodatkowe informacje można znaleźć w następującej lokalizacji: http://www.aurorasoftworks.com/products/signalframework.
Nie ma jeszcze komentarzy.