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 jar tf foo.jar jar xf foo.jar java -jar foo.jar jar cf <mibe>.jar <miket> jar umf <miből> <mibe>.jar Funkció A foo.jar tartalmának listázása A foo.jar kicsomagolása A foo.jar fő osztályának futtatása Új.jar fájl létrehozása 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. 20:46 <DIR>. 2010.03.08. 20:46 <DIR>.. 2010.03.08. 20:46 751 PlainEditor$1.class 2010.03.08. 20:46 435 PlainEditor$2.class 2010.03.08. 20:46 3 409 PlainEditor.class 2010.03.04. 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: import java.rmi.remote; 2
/** * Pontos ido szolgaltatas interfesze. */ public interface Time extends Remote { /** * Visszater a pontos idovel. */ String gettimestamp() throws RemoteException; Az rmidemo.clockengine osztály: import java.rmi.registry.locateregistry; import java.rmi.registry.registry; import 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: 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 <hostname>" ); 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á tenni 1. 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
Példa: 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). 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: Példa: -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 hivatkozást. 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; 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