5.1 Introducción a las tecnologías de objetos distribuidos con Java RMI Contenidos n n Tutorial de Java RMI Caso de estudio: diseño e implementación de la capa modelo de MiniBank con Java RMI n Arquitectura en 3 capas Introducción n n Java Remote Method Invocation (RMI) Permite definir e implementar interfaces remotos en el lenguaje Java n n Solución de más alto nivel que el uso directo de sockets o RPCs n n Sus operaciones se pueden invocar remotamente, de la misma forma que se invocan las operaciones de interfaces locales Facilidad de desarrollo Apropiado para construir sistemas cliente/servidor en una intranet, especialmente si cliente y servidor están escritos en Java n RMI también puede funcionar sobre IIOP (CORBA) Ejemplo Clock TimeOfDay time = clock.getTimeOfDay() Clock Cliente Servidor Estructura de paquetes del tutorial de Java RMI es.udc.fbellas.j2ee.rmitutorial.clock client rmiinterface server es.udc.fbellas.j2ee.rmitutorial.clock.rmiinterface public interface Clock extends Remote { public TimeOfDay getTimeOfDay() throws RemoteException; } public class TimeOfDay implements Serializable { private int hour; private int minute; private int second; public TimeOfDay(int hour, int minute, int second) { this.hour = hour; this.minute = minute; this.second = second; } // Métodos getXXX/setXXX ... } Interfaces remotos n n Un interfaz remoto ha de extender, directa o indirectamente, el interfaz java.rmi.Remote Operaciones n n n Los tipos de los parámetros y del resultado han de ser serializables Han de declarar la excepción java.rmi.RemoteException (además de las propias) Paso de parámetros n n Los objetos locales se pasan por valor (serializados) Los objetos remotos se pasan por referencia es.udc.fbellas.j2ee.rmitutorial.clock.server.ClockImpl class ClockImpl extends UnicastRemoteObject implements Clock { ClockImpl() throws RemoteException {} public TimeOfDay getTimeOfDay() { int hour = Calendar.getInstance().get(Calendar.HOUR); int minute = Calendar.getInstance().get(Calendar.MINUTE); int second = Calendar.getInstance().get(Calendar.SECOND); return new TimeOfDay(hour, minute, second); } } Implementación de interfaces remotos n Tipos de objetos remotos en Java RMI n n n Unicast remote object n n n Unicast remote objects Activable objects El servidor necesita estar constantemente en funcionamiento Permanece siempre en memoria Activable object n n El servidor se activa la primera vez que se invoca uno de sus métodos El servidor puede decidir desactivarlo cuando ningún cliente lo está utilizando (es decir, sale de memoria, pero no se destruye) y activarlo más tarde (cuando algún cliente vuelve a utilizarlo) n Utilidad: implementación de servidores que gestionan una gran cantidad de objetos Implementación de Unicast Remote Objects n La clase de implementación extiende java.rmi.server.UnicastRemoteObject e n implementa el interfaz remoto Para que el objeto pueda ser accedido remotamente, necesita ser exportado n El constructor sin argumentos de UnicastRemoteObject exporta el objeto y puede lanzar java.rmi.RemoteException n El constructor (con o sin argumentos) de la clase de implementación (que invoca automáticamente al constructor sin argumentos de la clase padre) necesita declarar la excepción java.rmi.RemoteException Stubs y skeletons (1) Resto aplicación cliente getTimeOfDay <<interface>> Clock <<interface>> java.rmi.server.Skeleton ClockImpl_Stub getTimeOfDay ClockImpl_Skel ClockImpl dispatch Subsistema RMI Cliente Subsistema RMI Servidor Stubs y skeletons (2) n Stub n n n Clase usada por el cliente en sustitución de la remota Implementa el interfaz remoto (ej.: Clock) La implementación de cada operación envía un mensaje a la máquina virtual que ejecuta el objeto remoto y recibe el resultado n n n En términos de patrones, un stub es un Proxy Skeleton n n n n Los parámetros y el valor de retorno se envían serializados Clase usada por el servidor Recibe los mensajes, invoca la operación del objeto que implementa el interfaz remoto y envía el resultado al llamador En términos de patrones, un skeleton es un Adapter Los Stubs y skeletons son transparentes al código Stubs y skeletons (y 3) n n Cuando un cliente invoca una operación remota que devuelve una referencia a un objeto remoto, obtiene una instancia del stub correspondiente Generación de stubs y skeletons rmic es.udc.fbellas.j2ee.rmitutorial.clock.server.ClockImpl n Genera n n es.udc.fbellas.j2ee.rmitutorial.clock.server.ClockImpl_Stub es.udc.fbellas.j2ee.rmitutorial.clock.server.ClockImpl_Skel Servicio de nombres n Problema n n El servidor crea un objeto que implementa el interfaz Clock, ¿ Cómo obtienen los clientes una referencia a ese objeto ? Servicio de nombres n n n n Permite asociar nombres lógicos (ej.: clock) a objetos Servidor: asocia un nombre a un objeto Cliente: obtiene una referencia al objeto a partir del nombre Objetivo: independencia de ubicación n Si en el futuro el objeto lo implementa otro servidor, o el servidor se cambia de máquina, no es necesario recompilar los clientes c = sn.lookup(“clock”) Cliente sn.bind(“clock”, c) Servicio de nombres Servidor El servicio de nombres de Java RMI n n Java RMI define un servicio de nombres muy sencillo El esquema de nombrado sigue la sintaxis de una URL n n //máquina:puerto/nombreDeObjeto, siendo nombreDeObjeto un nombre simple (ej.: clock) n máquina y puerto hacen referencia a la máquina en la que n corre el servicio de nombres (y no el objeto remoto) Por defecto, máquina = localhost y puerto = 1099 Interfaz java.rmi.registry.Registry n Permite asociar nombres simples a objetos java.rmi.registry.Registry public interface Registry extends Remote { public static final int REGISTRY_PORT = 1099; public java.rmi.Remote lookup (String name) throws java.rmi.RemoteException, java.rmi.NotBoundException, java.rmi.AccessException; public void bind (String name, Remote obj) throws java.rmi.RemoteException, java.rmi.AlreadyBoundException, java.rmi.AccessException; public void unbind (String name) throws java.rmi.RemoteException, java.rmi.NotBoundException, java.rmi.AccessException; public void rebind (String name, Remote obj) throws java.rmi.RemoteException, java.rmi.AccessException; public String[] list () throws java.rmi.RemoteException, java.rmi.AccessException; } rmiregistry n n Aplicación que contiene un objeto que implementa el interfaz java.rmi.registry.Registry No es persistente Por motivos de seguridad, la implementación de rmiregistry prohíbe que se invoquen los métodos bind, rebind y unbind de su objeto Registry desde otra máquina c = r.lookup(“clock”) Registry n Cliente r.bind(“clock”, c) Servidor rmiregistry Máquina A Máquina B java.rmi.Naming (1) n Clase utilidad con métodos estáticos, análogos a los de java.rmi.registry.Registry, para registrar/contactar con objetos a partir de una URL n Ejemplo: Clock clock = (Clock) Naming.lookup( “//knopfler/clock”); n La implementación de Naming.lookup contacta con un objeto que implementa java.rmi.registry.Registry (utilizando la clase utilidad java.rmi.registry.LocateRegistry), que está en la máquina knopfler, escuchando por el puerto 1099, e invoca la operación lookup(“clock”) java.rmi.Naming (y 2) public final class Naming { public static Remote lookup (String url) throws RemoteException, NotBoundException, java.net.MalformedURLException { ... } public static void bind (String url, Remote obj) throws RemoteException, AlreadyBoundException, java.net.MalformedURLException { ... } public static void unbind (String url) throws RemoteException, NotBoundException, java.net.MalformedURLException { ... } public static void rebind (String url, Remote obj) throws RemoteException, java.net.MalformedURLException { ... } public static String[] list (String name) throws RemoteException, java.net.MalformedURLException { ... } } es.udc.fbellas.j2ee.rmitutorial.clock.server.Server class Server { public static void main (String[] args) { /* Install RMI security manager. */ System.setSecurityManager(new RMISecurityManager()); /* * Create an instance of "ClockImpl", and register it in the * RMI registry. */ try { ClockImpl clock = new ClockImpl(); Naming.rebind("clock", clock); System.out.println("Server is working ..."); } catch (Exception e) { e.printStackTrace(); } } } es.udc.fbellas.j2ee.rmitutorial.clock.client.Client (1) class Client { public static void main (String[] args) { /* * Get RMI registry address, which may be specified as * "hostName[:port]". */ String registryAddress; if (args.length == 0) { registryAddress = "localhost"; } else { registryAddress = args[0]; } try { /* Install RMI security manager. */ System.setSecurityManager(new RMISecurityManager()); es.udc.fbellas.j2ee.rmitutorial.clock.client.Client (y 2) /* Get a reference to the object implementing "Clock". */ String clockURL = "//" + registryAddress + "/clock"; Clock clock = (Clock) Naming.lookup(clockURL); /* Obtain the time of day and print it. */ TimeOfDay timeOfDay = clock.getTimeOfDay(); System.out.println("Time of day: " + timeOfDay); } catch (Exception e) { e.printStackTrace(); } } } Evaluación del servicio de nombres de Java RMI n No cumple la propiedad de independencia de ubicación n La URL del objeto lleva el nombre y puerto de la máquina en la que corre el rmiregistry n n Si el rmiregistry cambia de máquina o puerto, es necesario recompilar los clientes Alternativamente, el nombre y puerto se pueden pasar como parámetros desde la línea de comandos (como en el ejemplo) o leerlos de un fichero de configuración n n Aún así, es preciso cambiar la configuración en los clientes No permite tener una estructura jerárquica de nombrado (ej.: /objects/rmitutorial/clock), con posible federación en servidores n No es escalable Política Multi-thread n Invocaciones desde distintas máquinas virtuales sobre un mismo objeto n n Invocaciones desde una misma máquina virtual sobre un objeto remoto n n Se crea un thread por cada invocación en el servidor (“thread-per-request”) No se garantiza que se cree un thread por cada invocación en el servidor Conclusión n La clase que implementa un interfaz remoto necesita ser thread-safe Ejecución (1) n ClockServer.jar n n n ClockClient.jar n n n es.udc.fbellas.j2ee.rmitutorial.clock.client.* es.udc.fbellas.j2ee.rmitutorial.clock.rmiinterface.* ClockRMIInterface.jar n n es.udc.fbellas.j2ee.rmitutorial.clock.server.* es.udc.fbellas.j2ee.rmitutorial.clock.rmiinterface.* es.udc.fbellas.j2ee.rmitutorial.clock.rmiinterface.* ClockRMIInterfaceStub.jar n es.udc.fbellas.j2ee.rmitutorial.clock.server.ClockImpl_S tub Ejecución – ClockServer.sh (2) #!/bin/sh # Class path. CP=$J2EE_EXAMPLES_HOME/Subsystems/RMITutorial/Build/Jars/\ ClockServer.jar # Code base 1 (file system). CODE_BASE="\ file:$J2EE_EXAMPLES_HOME/Subsystems/RMITutorial/Build/Jars/\ ClockRMIInterface.jar \ file:$J2EE_EXAMPLES_HOME/Subsystems/RMITutorial/Build/Jars/\ ClockRMIInterfaceStub.jar" # Code base 2 (web server). #CODE_BASE="\ #http://knopfler/fbellas/ClockRMIInterface.jar \ #http://knopfler/fbellas/ClockRMIInterfaceStub.jar" # Start server. java -classpath $CP -Djava.security.policy=policy \ -Djava.rmi.server.codebase="$CODE_BASE" \ es.udc.fbellas.j2ee.rmitutorial.clock.server.Server Ejecución – ClockClient.sh (3) #!/bin/sh # # # # --------------------------------------------------------------------Optionally can receive the RMI registry address (with the format host[:port]). --------------------------------------------------------------------- # Class path 1 (with remote interface stub). CP=$J2EE_EXAMPLES_HOME/Subsystems/RMITutorial/Build/Jars/\ ClockClient.jar:$J2EE_EXAMPLES_HOME/Subsystems/RMITutorial/Build/Jars/\ ClockRMIInterfaceStub.jar # Class path 2 (without remote interface stub). #CP=$J2EE_EXAMPLES_HOME/Subsystems/RMITutorial/Build/Jars/\ #ClockClient.jar # Start client. java -classpath $CP -Djava.security.policy=policy \ es.udc.fbellas.j2ee.rmitutorial.clock.client.Client "$@" Ejecución – “class path 1” y “code base 1” (4) Máquina A ClockClient.sh Máquina B 3: c = Naming.lookup(“clock”) 4: t = c.getTimeOfDay() 1: Naming.bind(“clock”, c) rmiregistry ClockServer.sh 2: Carga ClockImpl_Stub del codebase del servidor n n El class path del cliente incluye ClockImpl_Stub Problemas n n Si la implementación de ClockImpl cambia es necesario generar otra vez el stub y actualizar los clientes Si existen varias implementaciones de Clock, es necesario incluir todos los stubs en todos los clientes Ejecución – “class path 2” y “code base 1” (5) Máquina A Máquina B 3: c = Naming.lookup(“clock”) 4: Carga ClockImpl_Stub del codebase del servidor ClockClient.sh 5: t = c.getTimeOfDay() 1: Naming.bind(“clock”, c) rmiregistry ClockServer.sh 2: Carga ClockImpl_Stub del codebase del servidor n n El class path del cliente no incluye ClockImpl_Stub Problemas n Cliente y servidor necesitan compartir el sistema de ficheros en el que residen los ficheros .jar especificados en el codebase n Cliente y servidor en la misma máquina o compartiendo sistema de ficheros vía NFS o similar Ejecución – “class path 2” y “code base 2” (6) Máquina A ClockClient.sh Máquina B 3: c = Naming.lookup(“clock”) rmiregistry 1: Naming.bind(“clock”, c) 5: t = c.getTimeOfDay() ClockServer.sh 2: Carga ClockImpl_Stub del servidor web 4: Carga ClockImpl_Stub del servidor web Servidor web n El codebase reside en un servidor web (o de FTP) Ejecución (y 7) n Cuando el cliente no tiene el stub en su class path (dos últimas soluciones) n Es preciso emplear un gestor de seguridad (RMISecurityManager) n n El modelo de ejecución de Java requiere usar un gestor de seguridad para todas las clases que se cargen al margen del class path local Es preciso especificar un fichero de políticas. Ejemplo: grant { permission java.security.AllPermission; }; n En un entorno no controlado (ej.: Internet) esta política sería peligrosa, pero no en uno controlado (ej.: la intranet de una empresa) es.udc.fbellas.j2ee.minibank.model.accountfacade.rmi (1) <<Interface>> Remote (from rmi) <<Interface>> AccountFacade + createAccount(accountVO : AccountVO) : AccountVO + findAccount(accountIdentifier : Long) : AccountVO + addToAccount(accountIdentifier : Long, amount : double) : void + withdrawFromAccount(accountIdentifier : Long, amont : double) : void + findAccountsByUserIdentifier(userIdentifier : Long, startIndex : int, count : int) : Collection + removeAccount(accountIdentifier : Long) : void + transfer(sourceAccountIdentifier : Long, destinationAccountIdentifier : Long, amount : double) : void + findAccountOperationsByDate(accountIdentifier : Long, startDate : Calendar, endDate : Calendar, startIndex : int, count : int) : Collection AccountFacadeImpl - plainAccountFacadeDelegate : PlainAccountFacadeDelegate + AccountFacadeImpl() <<use>> PlainAccountFacadeDelegate (from plain) es.udc.fbellas.j2ee.minibank.model.accountfacade.rmi (2) AccountFacadeImpl <<instantiate>> Server <<static>> + main(args : String[]) : void <<instantiate>> SimpleDataSource (from sql) n <<use>> DataSourceLocator (from sql) En un caso real, en vez de es.udc.fbellas.j2ee.util.sql.SimpleDataSource, se podría usar una implementación de javax.sql.DataSource que hiciese pool de conexiones (ej.: el pool de Struts) es.udc.fbellas.j2ee.minibank.model.accountfacade.rmi (y 3) <<Interface>> AccountFacadeDelegate (from delegate) RMIAccountFacadeDelegate - accountFacade : AccountFacade <<use>> <<Interface>> AccountFacade + RMIAccountFacadeDelegate() <<use>> ConfigurationParametersManager (from configuration) n Ahora los objetos Delegate y el Session Facade son distintos n El Delegate es un Proxy del Session Facade es.udc.fbellas.j2ee.minibank.model.accountfacade.rmi.RMIAccountFacadeDelegate (1) public class RMIAccountFacadeDelegate implements AccountFacadeDelegate { private final static String REGISTRY_ADDRESS_PARAMETER = "RMIAccountFacadeDelegate/registryAddress"; private AccountFacade accountFacade; static { /* Install RMI security manager if needed. */ if (System.getSecurityManager() == null) { System.setSecurityManager(new RMISecurityManager()); } } es.udc.fbellas.j2ee.minibank.model.accountfacade.rmi.RMIAccountFacadeDelegate (2) public RMIAccountFacadeDelegate() throws InternalErrorException { try { String registryAddress = ConfigurationParametersManager.getParameter( REGISTRY_ADDRESS_PARAMETER); String accountFacadeURL = "//" + registryAddress + "/accountFacade"; accountFacade = (AccountFacade) Naming.lookup(accountFacadeURL); } catch (Exception e) { throw new InternalErrorException(e); } } es.udc.fbellas.j2ee.minibank.model.accountfacade.rmi.RMIAccountFacadeDelegate (y 3) public AccountVO createAccount(AccountVO accountVO) throws InternalErrorException { try { return accountFacade.createAccount(accountVO); } catch (RemoteException e) { throw new InternalErrorException(e); } } // Resto de operaciones => Idem “createAccount”. } // class Ejecución (1) Servidor aplicaciones web RMIAccountFacade.sh RMIMiniBank.war n Arquitectura en 3 capas n n n RMIMiniBank.war: vista y controlador RMIAccountFacade.sh: modelo Base de datos BD Ejecución (y 2) n Clases en RMIMiniBank.war: n WEB-INF/lib: jars de Standard TagLibs y Struts, StandardUtil.jar y WebUtil.jar (subsistema Util), RMIAccountFacadeClient.jar y RMIAccountFacadeClientStub.jar n RMIAccountFacadeClient.jar: sólo las clases del modelo que precisa el cliente (interfaz de la fachada y clases relacionadas) n n n n ¡ No contiene DAOs ni la implementación de las operaciones de la fachada ! RMIAccountFacadeClientStub.jar: el stub del interfaz remoto (se puede eliminar si se desear cargar dinámicamente) WEB-INF/classes: todas las clases del controlador y la vista Clases en RMIAccountFacade.sh n n n Driver JDBC para la BD StandardUtil.jar RMIAccountFacade.jar: todas las clases del modelo