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

Podobne dokumenty