Programowanie dynamiczne.
Transkrypt
Programowanie dynamiczne.
Programowane dynamiczne 1. Ładowanie klas 2. Specyfikacja pliku class ● analiza przykładu: Hello world! ● narzędzie ASM 3. Programowanie aspektowe ● AspectJ 1 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 Plik class public class Main { } /** * @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. 3 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 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 8. 0A 00 04 00 08 – klasa (czwarty element) zawiera metodę (ósmy element) 9. 10. 01 00 0F LineNumberTable 11. 01 00 12 LocalVariableTable 4 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 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 6 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 7 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 8 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 Plik class – metoda <init> Kod metody <init>: 2A B7 00 09 B1 // zmienna lokalna o adresie 0 jest wstawiana na stos 2A: ALOAD_0 B7 00 09: INVOKESPECIAL java/lang/Object.<init>()V; // wywołuje metodę void Object.<init>(); B1: RETURN // zwraca typ void 10 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 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 13 Plik class 00 01 – liczba atrybutów 00 20 – SourceFile 00 00 00 02 – długość atrybutu 00 21 – Main.java KONIEC!!! 14 Plik class – zapis assemblerowy // class version 46.0 (46) // access flags 33 public class Main { // compiled from: Main.java // 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 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/ 16 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); 17 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); 18 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 }); 19 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)); 20 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 }); } } 21 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 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). 23 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 24 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 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); } } 28 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 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. 30