Segédanyag: Java alkalmazások gyakorlat Készítette: Szabó Attila 2010/2011-2 félév, 11. gyakorlat (az előző 2 gyak közül az egyiken ZH volt, a másik szünet miatt elmaradt)
1
JAR fájl készítés
A JAR (Java Archive) a Java program tömörített (.zip tömörítés), hordozható formája. JAR fájlok a jar alkalmazással készíthetőek. A fontosabb kapcsolók: Parancs
Funkció
jar tf foo.jar
A foo.jar tartalmának listázása
jar xf foo.jar
A foo.jar kicsomagolása
java -jar foo.jar
A foo.jar fő osztályának futtatása
jar cf <mibe>.jar <miket>
Új .jar fájl létrehozása
jar umf <miből> <mibe>.jar
A .jar fájl manifest-jének kiegészítése a miből szövegfájlból
A 'c' kapcsoló új archív fájl létrehozásakor használható; az 'f' kapcsoló jelzi, hogy meg szeretnénk adni az archív nevét (a .zip kiterjesztés konvenció, nem kötelező). Példa (Windows): C:\Javagyak\editor\dir 2010.03.08. 2010.03.08. 2010.03.08. 2010.03.08. 2010.03.08. 2010.03.04.
20:46
. 20:46 .. 20:46 751 PlainEditor$1.class 20:46 435 PlainEditor$2.class 20:46 3 409 PlainEditor.class 12:45 6 591 PlainEditor.java 4 fájl 11 186 bájt 2 könyvtár 13 727 731 712 bájt szabad C:\Javagyak\editor\cd .. C:\Javagyak\jar cf PlainEditor.jar editor C:\Javagyak\jar umf mainclass.txt PlainEditor.jar C:\Javagyak\java -jar PlainEditor.jar
A mainclass.txt tartalma (a fájl végén legyen egy üres sor, mert az utolsó sor nem lesz feldolgozva): Main-Class: editor.PlainEditor
2
Távoli metódushívás Java-ban (RMI)
Java-ban a távoli metódushívás („Remote Method Invocation”, RMI) egy magas szintű eszköz a különböző JVM-ben (esetleg más-más gépen) futó programok kommunikációjára. Az RMI felületet nyújt ahhoz, hogy egy adott virtuális gépen futó program meghívja egy másik virtuális gépben futó programban létező objektum („remote object”) egy metódusát – úgy, mintha ez az objektum jelen lenne a hívó programban. 1
Egy RMI alkalmazás legalább két programból áll. A két programból legalább egy (de akár mindkettő) létrehoz egy objektumot, és elérhetővé teszi az objektumra mutató referenciát. Ezt az objektumot más programok elérhetik (azaz meghívhatják a metódusait), ha szereznek egy megfelelő referenciát. Ehhez a működéshez három feltételt kell biztosítani: 1. nyilvántartást kell vezetni a távolról elérhető objektumokról, 2. meg kell hívni az objektum megfelelő metódusát, illetve 3. a távolról hívható objektumok osztályait elérhetővé kell tenni. A fenti feltételek biztosításához a következő komponensekre van szükség: ● rmiregistry: a távolról elérhető objektumokat egy RMI regiszter tartja nyilván. Az objektumot létrehozó program ide regisztrálja be az objektumra mutató referenciát, illetve a kliens program innen kérheti le azt. ● RMI szerver: az objektumot megosztó program, ami az RMI regiszterbe bejegyzi – a kliens által ismert néven – az objektumra mutató referenciát. ● RMI kliens: a távoli objektum metódusát hívó program, ami az RMI regiszterből lekéri a távoli objektum eléréséhez szükséges referenciát, és meghívja valamelyik metódusát. ● web szerver: a kliens programnak le kell töltenie a távoli objektum osztályát (a kliens program csak egy interfészt ismer, a szerveren lévő megvalósítást futás közben tölti be a JVM). Nem szükséges web szerver akkor, ha a megfelelő osztály implementációja rendelkezésre áll a fájlrendszerben
1. Ábra: Az RMI rendszer részei(forrás: http://java.sun.com/docs/books/tutorial/rmi/overview.html)
Az RMI futásához szükséges komponensek a fenti ábrán láthatóak (a nyilak feliratai a kommunikációs módot adják meg).
2.1
RMI programok felépítése
Az RMI programok felépítésükben csak annyiban különböznek a megszokottaktól, hogy a távolról hívható objektumok osztályainak implementálniuk kell a java.rmi.Remote interfészt egy leszármazottját. (Ezt az interfészt kell a kliensnek ismernie a metódusok meghívásához.) Szükséges továbbá, hogy a leszármazott interfész által előírt összes függvény deklarálja, hogy dobhat java.rmi.RemoteException-t. Az rmidemo.Time interfész: package rmidemo; import java.rmi.Remote; import java.rmi.RemoteException;
2
/** * Pontos ido szolgaltatas interfesze. */ public interface Time extends Remote { /** * Visszater a pontos idovel. */ String getTimeStamp() throws RemoteException; } Az rmidemo.ClockEngine osztály: package rmidemo; import import import import
java.rmi.RemoteException; java.rmi.registry.LocateRegistry; java.rmi.registry.Registry; java.rmi.server.UnicastRemoteObject;
public class ClockEngine implements Time{ //======================================================================== //implemented interfaces //a 'synchronized' biztositja, hogy egyszerre egy kliens hivja a metodust //(az esetleges tobbi kliens addig varakozik) public synchronized String getTimeStamp() throws RemoteException{ //ide jon a pontos ido megvalositasa return new String( "2011.04.28. 012:55:00" ); }
}
//======================================================================== //main public static void main(String[] args) { if (System.getSecurityManager() == null) { System.setSecurityManager(new SecurityManager()); } try { String name = "Time"; //egy egyszeru objektum, amit meg fogunk osztani RMI-n keresztul Time engine = new ClockEngine(); //hozzaferhetove teszi a megadott objektumot a megadott porton Time stub = (Time) UnicastRemoteObject.exportObject(engine, 0); //lekeri az RMI registry-t Registry registry = LocateRegistry.getRegistry(); //regisztralja az objektumot a megadott nevvel registry.rebind(name, stub); System.out.println("ClockEngine bound"); } catch (Exception e) { System.err.println("ClockEngine exception:"); e.printStackTrace(); }
}
Az rmidemo.ClockDisplay osztály: package rmidemo; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class ClockDisplay { //======================================================================== //main
3
public static void main(String args[]) { if( args.length != 1 ){ System.out.println( "Usage: ClockDisplay " ); } if (System.getSecurityManager() == null) { System.setSecurityManager(new SecurityManager()); } try { String name = "Time"; //lekeri a registry-t az elso parameteben megadott geprol Registry registry = LocateRegistry.getRegistry(args[0]); //nev alapjan lekeri a tavoli objektumra mutato referenciat Time time = (Time) registry.lookup(name); //meghivja a tavoli objektum metodusat String currentTime = time.getTimeStamp(); System.out.println("Current time: " + currentTime); } catch (Exception e) { System.err.println("ClockDisplay exception:"); e.printStackTrace(); }
} }
Az inkonzisztens állapot elkerüléséhez a megosztott objektumok implementációjában használjunk synchronized metódust! (Egy objektum szinkronizált metódusának meghívásától annak lefutásáig egyetlen másik szál sem hívhatja az objektum szinkronizált metódusait.) Fontos: a távoli metódushíváskor a kliens gép elküldi a metódus paramétereit a szervernek, és a szerver visszaküldi a metódus visszatérési értékét. A küldés lebonyolításához a paraméter objektumok és a visszatérési érték objektum osztályainak szerializálhatónak kell lenniük! A szerializáció lényege, hogy egy osztály objektumainak állapota „elmenthető” legyen bájt sorozatként. A beépített Java típusok szerializálhatóak ( String, Integer, Double, stb.), és ez a gyakon elég is lesz: ha szükségetek van rá, nézzetek utána hogyan lehet egy tetszőleges osztályt szerializálhatóvá tenni1.
2.2
RMI programok futtatása
A futtatáshoz szükség van néhány JVM paraméterre, és egy policy fájlra. A távolról meghívandó objektumok osztályait el kell helyezni a kliens által hozzáférhető helyen, mondjuk egy jar fájlban. Ezen kívül el kell indítani az rmiregistry programot, ami megtalálható a jre bin könyvtárában. A policy fájl szabályozza a beregisztrált objektumhoz történő hozzáférést: grant { permission java.security.AllPermission; };
A szerver JVM paraméterei: ●
-Djava.security.policy:
az egyenlőségjel után meg kell adni a policy fájl helyét, illetve nevét (a lentebbi példában a fájl az aktuális könyvtárban van, és nincs kiterjesztése!).
●
-Djava.rmi.server.codebase:
az egyenlőségjel után meg kell adni a távoli objektumok implementációinak elérhetőségét (lásd a lenti példát). Ha a jar a neten érhető el, akkor az URL-t kell megadni, ha elérhető lokálisan, akkor a „file:/” előtag után meg kell adni a
1 Pl. itt: http://java.sun.com/developer/technicalArticles/Programming/serialization/
4
hivatkozást. ●
-Djava.rmi.server.hostname:
az egyenlőségjel után meg kell adni a szerver nevét (lásd
a lenti példát). Példa: E:\java -Djava.security.policy=policy -Djava.rmi.server.codebase=file:/E:\workspace\RMIDemo\rmidemo.jar -Djava.rmi.server.hostname=localhost rmidemo.ClockEngine
A kliens JVM paraméterei: az egyenlőségjel után meg kell adni a policy fájl helyét, illetve nevét (a lentebbi példában a fájl az aktuális könyvtárban van, és nincs kiterjesztése!).
●
-Djava.security.policy:
●
-Djava.rmi.server.codebase:
az egyenlőségjel után meg kell adni a távoli objektumok implementációinak elérhetőségét (lásd a lenti példát). Ha a jar a neten érhető el, akkor az URL-t kell megadni, ha elérhető lokálisan, akkor a „file:/” előtag után meg kell adni a hivatkozást.
Példa: E:\java -Djava.security.policy=policy -Djava.rmi.server.codebase=file:/E:\workspace\RMIDemo\rmidemo.jar rmidemo.ClockDisplay localhost
3
Feladatok 1. Írd meg a 8. gyakorlaton bemutatott EchoServer osztályt úgy, hogy nem socket-en keresztül lehet elérni a szolgáltatását, hanem egy megosztott objektum segítségével! Készítsd el a kliens programot is! Használj szinkronizált metódust. 2. Írj egy távolról hívható kalkulátort (csinálj hozzá tesztklienst is)! Az interfész: import java.rmi.Remote; import java.rmi.RemoteException; public interface Calculator extends java.rmi.Remote { public long add(long a, long b) throws java.rmi.RemoteException; public long sub(long a, long b) throws java.rmi.RemoteException; public long mul(long a, long b) throws java.rmi.RemoteException; }
public long div(long a, long b) throws java.rmi.RemoteException;
3. Írj egy távoli naplózó programot! A naplózó tudja rögzíteni az új bejegyzéseket, illetve lekérhető tőle a teljes napló.
5