Wstęp.
Dawno nie pisałem. Jest to też trochę podyktowane tym, że ciężko pisać, gdy bije się z myślami: "Niee, ten temat jest za prosty żeby o nim pisać, przecież to żałosne!", ale przypomniałem sobie pewne słowa (a raczej literki napisane na blogu), człowieka, którego uważam za kogoś, kogo warto naśladować. Brzmiało to tak: "Chętnie służę pomocą w razie potrzeby *zmniejszenia* własnych oczekiwań odnośnie zawartości wpisu.". Melduję, że zmniejszam! :).
Nie wiem, czy tylko ja tak mam, ale czasami ciężko się zmotywować do nauki. Dobrym lekarstwem na to okazał się, już kiedyś przeze mnie wspominany, serwis knowledgeblackbelt - jasno określony cel i wymagania pomagają w skutecznym motywowaniu. Właśnie udało mi się zdać egzamin z Java Reflection API, dzięki któremu zdobyłem zielony pas. Z tej okazji pomyślałem, że naskrobię coś w końcu na blogu. Dziś będzie o wykorzystaniu Dynamic Proxy na przykładzie kodu do pomiaru wydajności metody (ile czasu zajmuje jej wykonanie powierzonego zadania). Zapewne istnieją do tego rozbudowane narzędzia, może jakieś pluginy do IDE - ot, to ma być tylko taki przykładzik.
Ogólne informacje o wzorcu Proxy (Pośrednik).
Wzorca Proxy używamy do stworzenia obiektu zastępczego, który kontroluje dostęp do innego obiektu, kiedy mamy do czynienia z takim obiektem zdalnym, którego stworzenie wiąże się z dużym kosztem lub który wymaga zabezpieczeń.
Cytując za książką Wzorce Projektowe. Rusz Głową! (ang. Head First Design Patterns)
Żeby zrozumieć ideę wzorca rzućmy okiem na diagram klas.
Pośrednik i obiekt, do którego kontroluje dostęp, implementują wspólny interfejs. Dzięki temu zastosowanie pośrednika może być niewidoczne - w kodzie możemy te dwa obiekty traktować tak samo. Pośrednik posiada referencję na obiekt kontrolowany (Prawdziwy Przedmiot) i po wykonaniu swojego zadania może oddelegować żądanie do właściwego obiektu.
Przygotujmy klasy.
Wyobraźmy sobie, taką sytuację: mamy pracownika, który wykonuje pewne zadania. Zajmijmy się implementacją zadań - bajecznie proste. Symulujemy czas trwania zadania poprzez usypianie wątku.
Tworzenie dynamicznych pośredników.
Pośrednik i obiekt, do którego kontroluje dostęp, implementują wspólny interfejs. Dzięki temu zastosowanie pośrednika może być niewidoczne - w kodzie możemy te dwa obiekty traktować tak samo. Pośrednik posiada referencję na obiekt kontrolowany (Prawdziwy Przedmiot) i po wykonaniu swojego zadania może oddelegować żądanie do właściwego obiektu.
Przygotujmy klasy.
Wyobraźmy sobie, taką sytuację: mamy pracownika, który wykonuje pewne zadania. Zajmijmy się implementacją zadań - bajecznie proste. Symulujemy czas trwania zadania poprzez usypianie wątku.
// Interfejs zadania public interface TaskInterface { void execute(); }
// Sprzątanie (Implementacja1) public class Cleaning implements TaskInterface { @Override public void execute(){ try { TimeUnit.MILLISECONDS.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Cleaning done!"); } }
// Piłowanie (Implementacja2) public class Sawing implements TaskInterface { @Override public void execute() { try { TimeUnit.MILLISECONDS.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Sawing done!"); } }Następnie stwórzmy klasę pracownika, który będzie wykonywał pewne zadania.
public class Worker { static TaskInterface[] tasks = new TaskInterface[] { new Sawing(), new Cleaning() }; public static void main(String[] args) { doSomeTasks(tasks); } private static void doSomeTasks(TaskInterface[] tasks) { for(TaskInterface task : tasks) { task.execute(); } } }System działa, pracownik może wykonać swoje zadania. Dla uproszczenia do klasy Worker dodamy kod odpowiedzialny za stworzenie pośrednika (lepszym wyjściem było by zastosowanie tutaj fabryki i wtedy albo korzystalibyśmy z fabryki obiektów rzeczywistych, albo pośredników - w zależności od potrzeb).
Tworzenie dynamicznych pośredników.
- Pierwszy jej parametr, to obiekt proxy od którego pochodzi wywołanie (bo można do kilku wykorzystać ten sam InvocationHandler).
- Drugi przedstawia metodę, która została wywołana.
- Trzeci to lista argumentów tej metody.
public class PerformanceAnalyzer implements InvocationHandler { TaskInterface proxied; public PerformanceAnalyzer(TaskInterface proxied) { this.proxied = proxied; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long startTime = System.currentTimeMillis(); Object result = method.invoke(proxied, args); long elapsedTime = System.currentTimeMillis() - startTime; System.out.println("[takes " + elapsedTime + " milliseconds]"); return result; } }Poprzez konstruktor przekazujemy referencję na obiekt prawdziwy, którego przesłania pośrednik. Mierzymy czas (moglibyśmy użyć dokładniejszej metody System.nanoTime()) i po prostu wypisujemy ten czas. Wynik który zwrócił prawdziwy obiekt jest zwracany przez pośrednika do klienta.
Wróćmy do klasy Worker. Dodamy do niej metodę, która przyjmie prawdziwy obiekt i zwróci opakowującego go pośrednika. Pośrednika tworzymy za pomocą wywołania statycznej metody Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h).
- Pierwszy parametr to class loader, który posłuży to utworzenia instancji obiektu pośrednika.
- Drugi to lista obiektów Class interfejsów, które pośrednik ma implementować.
- InvocationHandler, do którego pośrednik będzie przekazywał wywołania.
private static TaskInterface createAnalyzedTask(TaskInterface realObj) { return (TaskInterface) Proxy.newProxyInstance( TaskInterface.class.getClassLoader(), new Class[] {TaskInterface.class}, new PerformanceAnalyzer(realObj)); }Pozostało nam tylko jeszcze wykorzystać tą metodę do utworzenia pośredników, zmodyfikujmy tablicę tasks.
static TaskInterface[] tasks = new TaskInterface[] { createAnalyzedTask(new Sawing()), createAnalyzedTask(new Cleaning()) };Naszym oczom powinien ukazać się taki wydruk:
Sawing done!
[takes 2002 milliseconds]
Cleaning done!
[takes 1001 milliseconds]
Ten komentarz został usunięty przez administratora bloga.
OdpowiedzUsuń