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ń