1 Programowane dynamiczne 1. Ładowanie klas 2. Specyfikacja pliku class analiza przykładu: Hello world! narzędzie ASM 3. Programowanie aspektowe AspectJ
Klasa abstrakcyjna ClassLoader Do pobrania klasy JVM wykorzystuje instancję klasy java.lang.classloader. Każda klasa zawiera referencję do do ładującego ją obiektu. Programista może sterować procesem ładowania klas: ClassLoader loader = new NetworkClassLoader(host, port); Object main = loader.loadclass("main", true).newinstance();... Przykładowa klasa: class NetworkClassLoader extends ClassLoader { String host; int port; public Class findclass(string name) { byte[] b = loadclassdata(name); return defineclass(name, b, 0, b.length); } } private byte[] loadclassdata(string name) { // load the class data from the connection } 2
3 public class Main { Plik class } /** * @param args */ public static void main(string[] args){ System.out.println("Hello world!"); } Nagłówek klasy: CA FE BA BE identyfikator formatu pliku class. 00 00 00 2E numer wersji JVM: (46.0) zgodność z Java 1.2 Ogólnie JVM 1.k (k>1) obsługuje klasy od 45.0 do 44+k.0 włącznie 00 22 ilość deklarowanych elementów (Constant Pool). Po tej deklaracji następują kolejne, 33 elementy.
4 Plik class - Constant Pool 1. 01 00 04 4D 61 69 6E string kodowany w UTF-8 o długości 4: Main 2. 07 00 01 pierwszy z elementów to nazwa klasy 3. 01 00 10 java/lang/object 4. 07 00 03 trzeci element to nazwa klasy 5. 01 00 06 <init> 6. 01 00 03 ()V 7. 01 00 04 Code 8. 0C 00 05 00 06 piąty element to nazwa metody lub atrybutu, szósty to typ (sygnatura): brak argumentów: (), zwracany typ void: V 9. 0A 00 04 00 08 klasa (czwarty element) zawiera metodę (ósmy element) 10. 01 00 0F LineNumberTable 11. 01 00 12 LocalVariableTable
Plik class - Constant Pool 12. 01 00 04 this 13. 01 00 06 LMain; 14. 01 00 04 main 15. 01 00 16 ([Ljava/lang/String;)V 16. 01 00 10 java/lang/system 17. 07 00 10 szesnasty element to nazwa klasy 18. 01 00 03 out 19. 01 00 15 Ljava/io/PrintStream; 20. 0C 00 12 00 13 18-ty element to nazwa metody/atrybutu, 19-ty to jego typ 21. 09 00 11 00 14 17-ta klasa zawiera 20-ty atrybut 22. 01 00 0C Hello world! 23. 08 00 16 22-gi element to stała tekstowa 24. 01 00 13 java/io/printstream 25. 07 00 18 24-ty element to nazwa klasy 5
6 Plik class - Constant Pool 26. 01 00 07 println 27. 01 00 15 (Ljava/lang/String;)V 28. 0C 00 1A 00 1B 26-ty element to nazwa metody/atrybutu, 27-ty to jej sygnatura 29. 0A 00 19 00 1C 25-ta klasa zawiera 28-tą metodę 30. 01 00 04 args 31. 01 00 13 [Ljava/lang/String; 32. 01 00 0A SourceFile 33. 01 00 09 Main.java
7 Plik class 00 21 modyfikatory dostępu dla klasy: ACC_PUBLIC (00 01) ACC_SUPER (00 20) ze względu na kompatybilność ze starszymi JVM. 00 02 numer elementu określającego klasę definiowaną w tym pliku. 00 04 numer elementu określającego nadklasę klasy definiowanej w tym pliku. 00 00 liczba interfejsów 00 00 liczba atrybutów 00 02 liczba metod
8 Plik class metoda <init> 00 01 ACC_PUBLIC metoda publiczna 00 05 indeks elementu zawierającego nazwę metody: <init> 00 06 indeks elementu z sygnaturą metody:()v 00 01 liczba dodatkowych atrybutów metody 00 07 indeks elementu z nazwą atrybutu: Code 00 00 00 2F długość atrybutu 00 01 rozmiar stosu 00 01- rozmiar tablicy zmiennych lokalnych 00 00 00 05 długość kodu 2A B7 00 09 B1 00 00 długość tablicy wyjątków 00 02 liczba dodatkowych atrybutów
Plik class metoda <init> 00 0A LineNumberTable 00 00 00 06 długość atrybutu 00 01 długość tabeli numerów linii 00 00 indeks instrukcji w tabeli Code 00 06 odpowiadający jej numer linii w pliku źródłowym 00 0B LocalVariableTable 00 00 00 0C długość atrybutu 00 01 długość tabeli zmiennych lokalnych 00 00 początek zmiennej lokalnej w tablicy zmiennych 00 05 długość zmiennej 00 0C indeks zmiennej: this 00 0D typ zmiennej: LMain 00 00 indeks w lokalnej tablicy zmiennych 9
10 Plik class metoda <init> Kod metody <init>: 2A B7 00 09 B1 2A: ALOAD_0 // zmienna lokalna o adresie 0 jest wstawiana na stos B7 00 09: INVOKESPECIAL java/lang/object.<init>()v; // wywołuje metodę void Object.<init>(); B1: RETURN // zwraca typ void
Plik class metoda main 00 09 ACC_PUBLIC (00 01) ACC_STATIC (00 08) publiczna metoda statyczna 00 0E indeks elementu zawierającego nazwę metody: main 00 0F indeks elementu z sygnaturą metody:([ljava/lang/string;)v 00 01 liczba dodatkowych atrybutów metody 00 07 indeks elementu z nazwą atrybutu: Code 00 00 00 37 długość atrybutu 00 02 rozmiar stosu 00 01- rozmiar tablicy zmiennych lokalnych 00 00 00 09 długość kodu B2 00 15 12 17 B6 00 1D B1 00 00 długość tablicy wyjątków 00 02 liczba dodatkowych atrybutów 11
Plik class metoda main 00 0A LineNumberTable 00 00 00 0A długość atrybutu 00 02 długość tabeli numerów linii 00 00 indeks instrukcji w tabeli Code 00 0C odpowiadający jej numer linii w pliku źródłowym 00 08 00 0D to samo dla kolejnej linii 00 0B LocalVariableTable 00 00 00 0C długość atrybutu 00 01 długość tabeli zmiennych lokalnych 00 00 początek zmiennej lokalnej w tablicy zmiennych 00 09 długość zmiennej 00 1E indeks zmiennej: Hello world! 00 1F typ zmiennej: stała tekstowa 00 00 indeks w lokalnej tablicy zmiennych 12
13 Plik class metoda main Kod metody main: B2 00 15 12 17 B6 00 1D B1 B2 00 15: GETSTATIC java/lang/system.out : Ljava/io/PrintStream; // inicjalizacja klasy/obiektu System.out i odłożenie // go na stos 12 17: LDC Hello world! // załadowanie na stos stałej tekstowej B6 00 1D: INVOKEVIRTUAL java/io/printstream.println(ljava/lang/string;)v // wywołuje metodę System.out.println(String) B1: RETURN // zwraca typ void
14 Plik class 00 01 liczba atrybutów 00 20 SourceFile 00 00 00 02 długość atrybutu 00 21 Main.java KONIEC!!!
// class version 46.0 (46) // access flags 33 public class Main { // compiled from: Main.java Plik class zapis assemblerowy // access flags 1 public <init>()v L0 (0) LINENUMBER 6 L0 ALOAD 0 INVOKESPECIAL java/lang/object.<init>()v RETURN L1 (4) LOCALVARIABLE this LMain; L0 L1 0 MAXSTACK = 1 MAXLOCALS = 1 } // access flags 9 public static main([ljava/lang/string;)v L0 (0) LINENUMBER 12 L0 GETSTATIC java/lang/system.out : Ljava/io/PrintStream; LDC "Hello world!" INVOKEVIRTUAL java/io/printstream.println(ljava/lang/string;)v L1 (4) LINENUMBER 13 L1 RETURN L2 (6) LOCALVARIABLE args [Ljava/lang/String; L0 L2 0 MAXSTACK = 2 MAXLOCALS = 1 15
16 ASM ASM (http://asm.objectweb.org/index.html) jest narzędziem służącym do operacji na plikach binarnych Javy. Może być używany do dynamicznego generowania klas lub interfejsów bezpośrednio w formie binarnej lub modyfikacji istniejących klas w trakcie ich ładowania przez Wirtualną Maszynę Javy. Inne narzędzia tego typu: BCEL: http://jakarta.apache.org/bcel/ SERP: http://serp.sourceforge.net/ Javassist: http://www.csg.is.titech.ac.jp/~chiba/javassist/
17 ASM Przykład program generujący klasę z dzisiejszego wykładu (Hello world!): import org.objectweb.asm.classwriter; import org.objectweb.asm.methodvisitor; import org.objectweb.asm.opcodes; import org.objectweb.asm.type; import org.objectweb.asm.commons.generatoradapter; import org.objectweb.asm.commons.method; import java.io.fileoutputstream; import java.io.printstream; public class Helloworld extends ClassLoader implements Opcodes { public static void main(final String args[]) throws Exception { ClassWriter cw = new ClassWriter(false); cw.visit(v1_1, ACC_PUBLIC, "Example", null, "java/lang/object", null);
18 ASM // metoda init MethodVisitor mw = cw.visitmethod(acc_public, "<init>", "()V", null, null); mw.visitvarinsn(aload, 0); mw.visitmethodinsn(invokespecial, "java/lang/object", "<init>", ()V"); mw.visitinsn(return); mw.visitmaxs(1, 1); // glebokość stosu i liczba zmiennych lokalnych mw.visitend(); // metoda main mw = cw.visitmethod(acc_public + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null); mw.visitfieldinsn(getstatic, "java/lang/system", "out", "Ljava/io/PrintStream;"); mw.visitldcinsn("hello world!"); mw.visitmethodinsn(invokevirtual, "java/io/printstream", "println", "(Ljava/lang/String;)V"); mw.visitinsn(return);
19 ASM mw.visitmaxs(2, 2); mw.visitend(); byte[] code = cw.tobytearray(); // pobieramy kod klasy FileOutputStream fos = new FileOutputStream("Example.class"); fos.write(code); fos.close(); Helloworld loader = new Helloworld(); Class exampleclass = loader.defineclass("example",code,0,code.length); exampleclass.getmethods()[0].invoke(null, new Object[] { null });
20 ASM // inny sposob uzyskania tego samego efektu cw = new ClassWriter(true); cw.visit(v1_1, ACC_PUBLIC, "Example", null, "java/lang/object", null); Method m = Method.getMethod("void <init> ()"); GeneratorAdapter mg = new GeneratorAdapter(ACC_PUBLIC, m, null, null, cw); mg.loadthis(); mg.invokeconstructor(type.gettype(object.class), m); mg.returnvalue(); mg.endmethod(); m = Method.getMethod("void main (String[])"); mg = new GeneratorAdapter(ACC_PUBLIC + ACC_STATIC, m, null, null, cw); mg.getstatic(type.gettype(system.class), "out", Type.getType(PrintStream.class));
21 ASM mg.push("hello world!"); mg.invokevirtual(type.gettype(printstream.class), Method.getMethod("void println (String)")); mg.returnvalue(); mg.endmethod(); cw.visitend(); code = cw.tobytearray(); loader = new Helloworld(); exampleclass = loader.defineclass("example", code, 0, code.length); exampleclass.getmethods()[0].invoke(null, new Object[] { null }); } }
Programowanie aspektowe Każde realizowane zagadnienie pociąga za sobą w praktyce potrzebę realizacji zagadnień pobocznych. Najczęściej spotykane zagadnienia są związane z logowaniem, bezpieczeństwem, spójnością transakcyjną, autoryzacją, synchronizacją wielowątkową itd. Jest to zjawisko normalne, wynikające ze złożoności wymagań klienta. Zagadnienia te są w dużym stopniu rozłączne pomiędzy sobą pod względem funkcjonalnym. Aby je zrealizować programista musi poprzeplatać ich implementacje (tzw. warkocz), co czyni kod mniej czytelnym, bardziej podatnym na błędy, trudniejszym w modyfikacji. Programowanie aspektowe (Aspect Oriented Programming) zapobiega tym negatywnym skutkom oddzielając fizycznie kod każdego zagadnienia poprzez umieszczenie go w oddzielnych aspektach wraz z określniem punktów interakcji pomiędzy nim a kodem programu. Źródło: http://pl.wikipedia.org/wiki/programowanie_aspektowe 22
23 AspectJ AspectJ to aspektowy język programowania opracowany w Xerox Palo Alto Research Center. Kluczowe cechy języka: AspectJ jest rozszerzeniem języka Java. Kompilator (ajc) tworzy kod źródłowy w Javie kompilowany dalej do postaci binarnej (pliki class). aspekty implementowane przez AspectJ są obiektami obsługuje wszystkie techniki programowania aspektowego, w tym: punkty złączenia, punkty przekroju, rady, mechanizmy zarządzające cyklem życia aspektu Obecnie AspectJ jest rozwijany w ramach projektu Eclipse: http://www.eclipse.org/aspectj/. Istnieją również narzędzia developerskie (AJDT).
24 Model punktów złączenia Każdy język programowania określa zbiór potencjalnych punktów złączenia model punktów złączenia. Przykładowe potencjalne punkty złączenia w Javie to: wywołanie metody, wykonanie metody, wejście do statycznego bloku kodu, wywołanie konstruktora. Punkty złączenia są miejscami w które można wstawić tzw. logikę przekrojową. Po wstawieniu dodatkowej logiki (kodu programu) w punkt złączenia staje się on punktem przekroju
Punkty przekroju Deklaracja punktów złączenia: public pointcut myclasstaskcalled() : call(public void MyClass.task()); Punkt złączenia dla wszystkich metod task() niezależnie od klasy: public pointcut anyclasstaskcalled() : call(* *.task(..)); Przekazanie wartości do punktu złączenia: public pointcut anyclasstaskcalled(int value) : call(* *.task(..)) && args(number); Typy punktów złączenia: call (Signature) wywołanie metody execution (Signature) wykonanie metody handler(typepattern) przechwytywanie wyjątków 25
Rady Rada (advice) określa kod wykonywany w momencie zarejestrowania punktu złączenia. Od zwykłej metody różni ją dodatkowe słowo kluczowe określające miejsce wykonania kodu względem punktu złączenia: before() przed punktem złączenia, around() zamiast punktu złączenia, chyba że metoda proceed() mówi inaczej after() po punkcie złączenia niezależnie od wyniku jego działania after() returning po punkcie zgłoszenia jeśli nie było zgłoszenia wyjątku after() throwing po punkcie zgłoszenia jeśli został zgłoszony wyjątek. Przykład: before(int value) : anyclasstaskcalled(value){ } System.out.println("Wartosc" + value);... 26
Aspekty Aspekt przypomina zwykłą klasę Javy. Instancja aspektu jest pełnoprawnym obiektem Javy i składnia jego obsługi jest analogiczna jak zwykłych obiektów. Domyślnie pojedyncza instancja aspektu jest tworzona przez aplikację w momencie uruchomienia i istnieje aż do jej zakończenia (singleton). Istnieje jednak możliwość wybrania innego cyklu życia dla aspektu: issingleton() singleton (domyślne) perthis(pointcut) nowa instancja dla każdego obiektu do którego odnosi się referencja this w punkcie złączenia pertarget(pointcut) nowa instancja dla każdego obiektu, który jest celem działania punktu złączenia percflow(pointcut) nowa instancja dla każdego wątku inicjowanego w ramach punktu złączenia 27
28 Aspekty Kompletny przykład aspektu: public aspect MyAspect issingleton(){ public pointcut myclasstaskcalled() : call(public void MyClass.foo()); public pointcut anyclassbarcalled(int number) : call(* *.bar(..)) && args(number); before() : myclasstaskcalled(){ } System.out.println("rada before() w MyAspect"); before(int value) : anyclassbarcalled(value) { System.out.println("rada before() w MyAspect"); System.out.println("Parametr " + value); } }
Przekroje statyczne AspectJ może działać na statycznej strukturze aplikacji. Umożliwia to m. in.: dodawanie nowych składowych do klasy dodawanie nowych metod deklarowanie nowych interfejsów deklarowanie nowych zależności dziedziczenia Przykład: public aspect AddRunnable { declare parents : MyClass implements Runnable; public void MyClass.run(){ System.out.println("Implementacja metody Runnable.run()"); } } 29
30 Podsumowanie Możliwość zmiany kodu klasy Javy w trakcie wykonywania programu wprowadza wiele nowych możliwości przy tworzeniu oprogramowania. Zmiany takie można wprowadzać bezpośrednio do kodu ładowanego przez ClassLoader'a lub poprzez różne dostępne narzędzia (np. ASM). Wiele popularnych bibliotek korzysta z tej techniki aby efektywnie realizować zadania (np. Hibernate). Na koncepcji dynamicznej podmiany kodu opiera się jeden z paradygmatów tworzenia oprogramowania programowanie aspektowe.