1 Programowane refleksyjne i serializacja 1. Programowanie refleksyjne: przegląd wybranych klas z pakietu java.lang i java.lang.reflect, dynamiczne obiekty proxy. 2. Serializacja interfejs Serializable, pełna kontrola nad serializacją, serializacja do plików XML.
2 Wprowadzenie Programowanie refleksyjne polega na dynamicznym korzystaniu ze struktur, które języka programowania, które nie musiały być zdeterminowane w momencie tworzenia oprogramowania. Najważniejsze klasy języka Java, które umożliwiają programowanie refleksyjne to Class, Field, Method, Array, Constructor. Są one zgrupowane w pakietach java.lang i java.lang.reflect.
3 Klasa Class Class reprezentuje klasę. Każdy obiekt (java.lang.obiect) w Javie zawiera metodę getclass(), która zwraca instancję klasy Class. Podstawowe metody Class: static Class forname(string) zwraca obiekt reprezentujący klasę o podanej nazwie. Jeśli klasa nie była wcześniej używana jest wcześniej ładowana za pomocą odpowiedniego ClassLoader'a. Class.forName("com.mysql.jdbc.Driver"); Object newinstance() tworzy nowy obiekt (instancję) danego typu Object obj = new Object() jest równoważne Object obj = Class.forName("java.lang.Object").newInstance()
Klasa ClassLoader Metoda getclassloader() wywołana na rzecz obiektu Class zwraca obiekt odpowiedzialny za załadowanie zasobów związanych z daną klasą. Obiekty ClassLoader mogą być wykorzystane do dynamicznej zmiany miejsca z którego zostaną załadowane zasoby. class OwnClassLoader extends ClassLoader { public Class findclass(string name) { byte[] b = loadclassdata(name); return defineclass(name, b, 0, b.length); private byte[] loadclassdata(string name) { // załaduj klasę z ustalonej lokalizacji... 4
Zastosowanie własnego ClassLoader'a: Klasa ClassLoader OwnClassLoader cl = new OwnClassLoader(); Class c = cl.loadclass("my.own.myclass"); Object obj = c.newinstance(); MyClass myobj = (MyClass)obj; Istnieją standardowe rozszerzenia podstawowego ClassLoader'a. Są to: SecureClassLoader i URLClassLoader: URL[] urlarray = {new URL("http://www.moje.klasy.pl"), new URL("http://klasy.kolegi.pl") ; URLClassLoader loader = URLClassLoader.newInstance(urlArray); Class c = loader.loadclass("my.own.myclass"); 5
Klasa Class boolean isassignablefrom(class) czy to ta sama klasa, nadklasa czy nadinterfejs dla argumentu. boolean isinstance(object obj) czy obiekt jest instancją klasy lub podklasy. class C1{ class C2 extends C1{ C1 o1 = new C1(); C2 o2 = new C2(); o1.getclass().isassignablefrom(o2.getclass()); o2.getclass().isassignablefrom(o1.getclass()); o1.getclass().isinstance(o2); o2.getclass().isinstance(o1); // true // false // true // false 6
7 Klasa Class Inne metody obiektu Class: boolean isarray() - czy to tablica. boolean isinterface() - czy interfejs. boolean isprimitive() - czy typ prymitywny (int, float, itd.). Constructor[] getconstructors() - zwraca tablicę publicznych konstruktorów. Constructor[] getdeclaredconstructors() - zwraca tablicę wszystkich konstruktorów. Constructor getconstructor(class[]) - zwraca publiczny konstruktorów o zadanych typach argumentów.
8 Klasa Class Field[] getfields() - zwraca tablicę publicznych atrybutów, Field getfield(string) - zwraca publiczny atrybut o podanej nazwie, Class[] getinterfaces() - zwraca interfejsy implementowane przez klasę. Method getmethod(string, Class[]) - zwraca publiczną metodę o podanej nazwie i sygnaturze. Method[] getmethods() - zwraca wszystkie publiczne metody należące do klasy (wraz z metodami implementującymi interfejsy oraz metodami dziedziczonymi z nadklas i nadinterfejsów)
9 Klasa Field Wybrane metody: String getname() - zwraca nazwę atrybutu. Class gettype() - zwraca typ atrybutu. int getmodifiers() - zwraca rodzaj atrybutu (public, private, ). Object get(object) - zwraca wartość atrybutu we wskazanym obiekcie. void set(object obj, Object value) ustawia wartość atrybutu we wskazanym obiekcie.
Klasa Field przykład 1 import java.lang.reflect.field; public class MyClass { public int liczba_naturalna; protected double liczba_double; private String tekst; float liczba_rzeczywista; public static void main(string[] args) { MyClass obj = new MyClass(); Field[] fa = obj.getclass().getdeclaredfields(); for (int i = 0; i < fa.length; i++) { System.out.print(fa[i].getModifiers()); System.out.print(" " + fa[i].gettype().getname()); System.out.println(" " + fa[i].getname()); 10
Klasa Field przykład 1 Wynik działania: 1 int liczba_naturalna 4 double liczba_double 2 java.lang.string tekst 0 float liczba_rzeczywista Liczby zwracane przez getmodifier() mogą być zamienione na odpowiadające im wartości tekstowe za pomocą Modifier.toString(int). Wszystkie możliwe modyfikatory to: abstract, final, interface, native, private, protected, public, static, strictfp, synchronized, transient, volatile. 11
12 Klasa Field przykład 2 MyClass obj = new MyClass(); Field[] fa = obj.getclass().getdeclaredfields(); System.out.println(obj.liczba_naturalna); try { fa[0].set(obj, new Integer(15)); catch (IllegalArgumentException e) { e.printstacktrace(); catch (IllegalAccessException e) { e.printstacktrace(); System.out.println(obj.liczba_naturalna); Wynik działania: 0 15
Klasa Method Wybrane metody: Object invoke(object obj, Object[] args) wywołuje metodę na rzecz obiektu obj. Argumenty wejściowe są przekazywane przez tablicę args. Wartością zwracaną jest wartość zwrócona w wyniku działania metody. Przykład: Method m = Class.forName("MyClass").getDeclaredMethod( "example3", null); m.invoke(null, null); metoda invoke może zwrócić kilka rodzajów wyjątków związanych z dostępem do metody i zgodnością typów argumentów. 13
14 Klasa Method Wady wynikające z urzywania invoke(object obj, Object[] args): typy primitywne (int, float, ) muszą zostać opakowane w odpowiednie klasy (Integer, Float, ) tak, aby można było je przekazać przez tablicę parametrów, brak kontroli (w trakcie kompilacji) typów przekazywanych parametrów, ograniczenie obsługi wyjątków do Throwable. Rozwiązaniem tych problemów jest możliwe dzięki tzw. Dynamic Proxy Class.
Klasa Proxy Program m.invoke(obj); brak kontroli typów Obiekt (klasa) public void metoda(){ Proxy Program proxy.metoda(); obj.metoda(); Obiekt (klasa) public void metoda(){ proxy musi być tworzone dynamicznie! 15
16 Klasa Proxy Klasa Proxy udostępnia metody statyczne służące do tworzenia tzw. dynamicznych klas proxy oraz ich instancji: Utworzenie proxy dla określonego interfejsu (np. MyInterface): InvocationHandler handler = new MyInvocationHandler(); Class proxyclass = Proxy.getProxyClass( MyInterface.class.getClassLoader(), new Class[] { MyInterface.class ); MyInterface mi = (MyInterface) proxyclass.getconstructor( new Class[] { InvocationHandler.class ).newinstance( new Object[] { handler );
17 Klasa Proxy Alternatywne wywołanie: MyInterface mi = (MyInterface) Proxy.newProxyInstance( MyInterface.class.getClassLoader(), // loader dla zasobów new Class[] { MyInterface.class, // tablica interfejsów handler); // obiekt do którego będą przekazywane // wywołania Stworzono obiekt mi, który z zewnątrz wygląda jak klasa implementująca MyInterface, natomiast obsługa metod będzie w rzeczywistości realizowana przez handler.
18 Klasa Proxy Obiekt handler nie musi implementować MyInterface! Musi za to implementować interfejs InvocationHandler, czyli metodę: Object invoke(object proxy, Method method, Object[] args) Wszystkie wywołania metod na obiekcie mi zostaną przekierowane do metody invoke, przy czym pierwszym parametrem będzie obiekt proxy.
Klasa Proxy Pisarz obj = new PisarzImpl(); Method m = obj.getclass().getmethod( "pisz", new Class[] {String.class); m.invoke(obj, new Object[]{"hello world"); class PisarzImpl implements Pisarz { public void pisz(string s){ interface Pisarz { public void pisz(string s); 19
Klasa Proxy Pisarz p = (Pisarz) Proxy.newProxyInstance( Pisarz.class.getClassLoader(), new Class[] { Pisarz.class, new MyHandler()); p.pisz("hello world"); class MyHandler{ private Pisarz obj = new PisarzImpl(); public static Object invoke(object proxy, Method m, Object[] args)){ return m.invoke(obj, args); class PisarzImpl implements Pisarz { public void pisz(string s){ interface Pisarz { public void pisz(string s); 20
21 Serializacja Serializacja polega na zapisaniu stanu aktywnego obiektu. Tak zapisany obiekt może być potem przeniesiony (przesłany) w inne miejsce i tam odtworzony. Aby obiekt mógł być serializowany musi implementować interfejs java.io.serializable. Nie posiada on żadnych metod a jedynie informuje JVM, że obiekt może być zapisywany. Aby umożliwić serializacje podtypów nieserializowalnych klas należy przejąć odpowiedzialność za serializację wszystkich dostępnych pól. Podtyp może przejąć odpowiedzialność tylko wtedy, gdy rozszerzana klasa posiada dostępny, bezargumentowy konstruktor. W przeciwnym razie serializacja zakończy się błędem.
22 Serializacja Klasy wymagające specjalnego traktowania w trakcie serializacji powinny implementować następujące metody: private void writeobject(java.io.objectoutputstream out) throws IOException private void readobject(java.io.objectinputstream in) throws IOException, ClassNotFoundException; Jeśli serializacja ma polegać na zapisie innego obiektu to klasy powinna implementować: dowolny_modyfikator Object writereplace() throws ObjectStreamException; dowolny_modyfikator Object readresolve() throws ObjectStreamException;
23 Serializacja - ObjectOutputStream Aby zapisać lub odczytać klasę należy utworzyć obiekt ObjectOutputStream a następnie skorzystać z metod: void writeobject(object) Object readobject() w celu zapisania lub odczytania obiektu.
24 Serializacja - przykład Zapisywanie: FileOutputStream out = new FileOutputStream("magazyn"); ObjectOutputStream s = new ObjectOutputStream(out); s.writeobject("hello World"); s.writeobject(new Date()); s.flush(); Odczytywanie: FileInputStream in = new FileInputStream("magazyn"); ObjectInputStream s = new ObjectInputStream(in); String today = (String)s.readObject(); Date date = (Date)s.readObject();
Pełna kontrola nad serializacją implementacja writeobject(objectoutputstream) oraz readobject(): private void writeobject(objectoutputstream os) throws IOException{ os.defaultwriteobject(); // własny kod private void readobject(objectinputstream s) throws IOException { s.defaultreadobject(); // własny kod, aktualizacja stanu obiektu implementacja interfejsu Externalizable: public interface Externalizable extends Serializable { public void writeexternal(objectoutput out) throws IOException; public void readexternal(objectinput in) throws IOException, ClassNotFoundException; 25
26 Serializacja a XML Wraz z wersją 1.4 Javy udostępniona została możliwość serializacji obiektów typu JavaBeans do plików tekstowych w formacie XML. Służą do tego klasy z pakietu java.beans: XMLEncoder: XMLEncoder e = new XMLEncoder(new FileOutputStream("jbutton.xml"))); e.writeobject(new JButton("Hello world")); e.close(); XMLDecoder: XMLDecoder d = new XMLDecoder(new FileInputStream("jbutton.xml")); obj = d.readobject(); d.close();
27 Serializacja a XML Zawartość pliku XML: <?xml version="1.0" encoding="utf-8"?> <java version="1.5.0_04" class="java.beans.xmldecoder"> <object class="javax.swing.jbutton"> <string>hello world</string> </object> </java> Ciekawostka: Objętość pliku XML: 189 bajtów, objętość pliku binarnego 4329 bajtów!
28 Podsumowanie Programowanie refleksyjne i serializacja wydatnie zwiększają przydatność języka programowania do tworzenia zaawansowanych, wysoce konfigurowalnych projektów. Serializacja ponadto leży u podstaw wielu technik związanych z szeroko rozumianą wymianą danych a wśród nich przede wszystkim programowania rozproszonego.