5.1 introducción a las tecnologías de objetos distribuidos...
TRANSCRIPT
Contenidos
Tutorial de Java RMI
Caso de estudio: diseño e implementación de la capa modelo de MiniBank con Java RMI
Arquitectura en 3 capas
Introducción
Java Remote Method Invocation (RMI)Permite definir e implementar interfaces remotos en el lenguaje Java
Sus operaciones se pueden invocar remotamente, de la misma forma que se invocan las operaciones de interfaces locales
Solución de más alto nivel que el uso directo de sockets o RPCs
Facilidad de desarrollo
Apropiado para construir sistemas cliente/servidor en una intranet, especialmente si cliente y servidor están escritos en Java
RMI también puede funcionar sobre IIOP (CORBA)
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
Un interfaz remoto ha de extender, directa o indirectamente, el interfaz java.rmi.RemoteOperaciones
Los tipos de los parámetros y del resultado han de ser serializablesHan de declarar la excepción java.rmi.RemoteException(además de las propias)
Paso de parámetrosLos objetos locales se pasan por valor (serializados)Los objetos remotos se pasan por referencia
es.udc.fbellas.j2ee.rmitutorial.clock.server.ClockImpl
class ClockImpl implements Clock {
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);
}
}
Stubs y skeletons (1)
<<interface>>Clock
ClockImpl_Stub
Resto aplicacióncliente
Subsistema RMI
<<interface>>java.rmi.server.Skeleton
ClockImpl_Skel ClockImpl
getTimeOfDay
dispatch
Cliente Servidor
Subsistema RMI
getTimeOfDay
Stubs y skeletons (2)
StubClase usada por el cliente en sustitución de la remotaImplementa 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
Los parámetros y el valor de retorno se envían serializados
En términos de patrones, un stub es un Proxy
SkeletonClase usada por el servidorRecibe los mensajes, invoca la operación del objeto que implementa el interfaz remoto y envía el resultado al llamadorEn términos de patrones, un skeleton es un Adapter
Los Stubs y skeletons son transparentes al código
Stubs y skeletons (y 3)Generación de stubs y skeletons
Hasta J2SE 1.4 (inclusive) era preciso usar el compilador de RMI (comando rmic) para generar las clases Stub y SkeletonLa implementación de RMI en J2SE 5.0 hace uso del sistema de introspección de Java (java.lang.reflect)
El sistema de introspección permite dinámicamente averiguar información sobre clases, crear nuevas clases, construir instancias e invocar sus métodosLa implementación de RMI genera dinámicamente (no se genera código) la clase que implementa el stub y obvia la necesidad de tener un skeleton específico => no es necesario usar el compilador de RMI
Recepción y paso de referencias a objetos remotosCuando un cliente invoca una operación remota que devuelve una referencia a un objeto remoto, obtiene una instancia del stub correspondienteCuando un cliente invoca una operación remota en la que pasa una referencia a un objeto remoto, el servidor obtiene una instancia del stub correspondiente
Servicio de nombres
ProblemaEl servidor crea un objeto que implementa el interfaz Clock, ¿ Cómo obtienen los clientes una referencia a ese objeto ?
Servicio de nombresPermite asociar nombres lógicos (ej.: clock) a objetosServidor: asocia un nombre a un objetoCliente: obtiene una referencia al objeto a partir del nombreObjetivo: independencia de ubicació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
ServidorCliente Servicio denombres
sn.bind(“clock”, c)c = sn.lookup(“clock”)
El servicio de nombres de Java RMI
Java RMI define un servicio de nombres muy sencilloEl esquema de nombrado sigue la sintaxis de una URL
//máquina:puerto/nombreDeObjeto, siendo nombreDeObjeto un nombre simple (ej.: clock)máquina y puerto hacen referencia a la máquina en la que corre el servicio de nombres (y no el objeto remoto)Por defecto, máquina = localhost y puerto = 1099
Interfaz java.rmi.registry.RegistryPermite asociar nombres simples a objetos
java.rmi.registry.Registrypublic 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
Aplicación que contiene un objeto que implementa el interfaz java.rmi.registry.RegistryNo es persistentePor 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
ServidorCliente
rmiregistry
r.bind(“clock”, c)c = r.lookup(“clock”) Reg
istr
y
Máquina A Máquina B
java.rmi.Naming (1)
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
Ejemplo:Clock clock = (Clock) Naming.lookup(
“//knopfler/clock”);
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.Serverclass Server {
public static void main (String[] args) {
/** Create an instance of "ClockImpl", export it, obtain a stub* and register the stub in the RMI registry.*/
try {
ClockImpl clockImpl = new ClockImpl();Clock clockStub = (Clock)
UnicastRemoteObject.exportObject(clockImpl, 0);
Naming.rebind("clock", clockStub);System.out.println("Server is working ...");
} catch (Exception e) {e.printStackTrace();
}
}
}
Comentarios (1)
Tipos de objetos remotos en Java RMIUnicast remote objectsActivable objects
Unicast remote objectEl servidor necesita estar constantemente en funcionamientoPermanece siempre en memoria
Activable objectEl servidor se activa la primera vez que se invoca uno de sus métodosEl 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)
Utilidad: implementación de servidores que gestionan una gran cantidad de objetos
Comentarios (y 2)
Lo objetos Clock del ejemplo son de tipo “Unicast Remote Object”
Se “exportan” mediantejava.rmi.server.UnicastRemoteObject.exportObject
Permite que subsistema RMI atienda a peticiones dirigidas hacia ellosDevuelve un stub del objeto ClockNOTA: Existen varias versiones de este método. El ejemplo utiliza una que recibe como argumentos: el objeto y un número de puerto. Para que funcione el sistema dinámico de stubs y skeletons de J2SE 5.0 hay que usar 0 como número de puerto
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];
}
es.udc.fbellas.j2ee.rmitutorial.clock.client.Client (y 2)
try {/* 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
No cumple la propiedad de independencia de ubicación
La URL del objeto lleva el nombre y puerto de la máquina en la que corre el rmiregistry
Si el rmiregistry cambia de máquina o puerto, es necesario recompilar los clientesAlternativamente, 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
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
No es escalable
Política Multi-thread
Invocaciones desde distintas máquinas virtuales sobre un mismo objeto
Se crea un thread por cada invocación en el servidor (“thread-per-request”)
Invocaciones desde una misma máquina virtual sobre un objeto remoto
No se garantiza que se cree un thread por cada invocación en el servidor
ConclusiónLa clase que implementa un interfaz remoto necesita ser thread-safe
Ejecución (1)
ClockServer.jares.udc.fbellas.j2ee.rmitutorial.clock.server.*es.udc.fbellas.j2ee.rmitutorial.clock.rmiinterface.*
ClockClient.jares.udc.fbellas.j2ee.rmitutorial.clock.client.*es.udc.fbellas.j2ee.rmitutorial.clock.rmiinterface.*
ClockRMIInterface.jares.udc.fbellas.j2ee.rmitutorial.clock.rmiinterface.*
Ejecución – ClockServer.sh (2)
#!/bin/sh
# Inclusion of common environment variables.. $J2EE_EXAMPLES_HOME/Scripts/CommonEnvironmentVariables.sh
# Class path.CP=$J2EE_EXAMPLES_HOME/Subsystems/RMITutorial/Build/Jars/ClockServer.jar
# Code base.CODE_BASE="file:$J2EE_EXAMPLES_HOME/Subsystems/RMITutorial/Build/Jars/\ClockRMIInterface.jar"
# Start server.$JAVA_VIRTUAL_MACHINE -classpath $CP \
-Djava.rmi.server.codebase="$CODE_BASE" \es.udc.fbellas.j2ee.rmitutorial.clock.server.Server
Ejecución – ClockClient.sh (3)
#!/bin/sh
# Inclusion of common environment variables.. $J2EE_EXAMPLES_HOME/Scripts/CommonEnvironmentVariables.sh
# ---------------------------------------------------------------------# Optionally can receive the RMI registry address (with the format# host[:port]).# ---------------------------------------------------------------------
# Class path.CP=$J2EE_EXAMPLES_HOME/Subsystems/RMITutorial/Build/Jars/ClockClient.jar
# Start client.$JAVA_VIRTUAL_MACHINE -classpath $CP \
es.udc.fbellas.j2ee.rmitutorial.clock.client.Client "$@"
Ejecución – codebase (4)
El script del servidor invoca a la máquina virtual con la “propiedad del sistema” java.rmi.server.codebase
Permite especificar un conjunto de clases con notación similar a la de un classpath (usando blancos como separador en vez de los caracteres “:”o “;”)Cuando una aplicación recibe un stub de otra (en este caso el rmiregistry recibe un stub del objeto Clock en el punto 1), si no tiene disponible el interfaz que implementa el stub en su classpath, lo carga del classpath especificado en el codebase de la aplicación que se lo envía (en este caso, ClockServer)
ClockServer.sh
ClockClient.sh
rmiregistry
1: Naming.bind(“clock”, c)3: c = Naming.lookup(“clock”)
2: Carga el interfaz Clock del codebase del servidor
4: t = c.getTimeOfDay()
Máquina A Máquina B
Ejecución – codebase (y 5)
NOTALas “propiedades del sistema” son argumentos que se le pasan a la máquina virtual antes del nombre de la clase que se desea ejecutar, usando la notación
-Dnombre=valor
Las propiedades del sistema se pueden recuperar con el método estático
java.lang.System.getProperties()
es.udc.fbellas.j2ee.minibank.model.accountfacade.rmi (1)
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) : AccountChunkVO+ removeAccount(accountIdentifier : Long) : void+ transfer(sourceAccountIdentifier : Long, destinationAccountIdentifier : Long, amount : double) : void+ findAccountOperationsByDate(accountIdentifier : Long, startDate : Calendar, endDate : Calendar, startIndex : int, count : int) : AccountOperationChunkVO
<<Interface>>
Remote(from rmi)
<<Int erface>>
AccountFacadeImpl- plainAccountFacadeDelegate : P lainAccountFacadeDelegate
+ AccountFacadeImpl()
PlainAccountFacadeDelegate(from p lain)
<<use>>
es.udc.fbellas.j2ee.minibank.model.accountfacade.rmi (2)
AccountFacadeImpl
Server
<<static>> + main(args : String[]) : void
S impleDat aSource(from sql)
DataSourceLocator(from sql)
<<instantiate>>
<<instantiate>> <<use>>
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)
RMIAccountFacadeDelegate- accountFacade : AccountFacade
+ RMIAccountFacadeDelegate()
AccountFacadeDelegate(from delegat e)
<<Interface>>
AccountFacade<<Interface>><<use>>
ConfigurationParametersManager(from configuration)
<<use>>
Ahora los objetos Delegate y el Session Facade son distintos
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;
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 2)
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 BD
RMIMiniBank.war
Arquitectura en 3 capasRMIMiniBank.war: vista y controladorRMIAccountFacade.sh: modeloBase de datosNOTA: en general, la separación física entre modelo e interfaz gráfica (vista + controlador) sólo es positiva cuando hay aplicaciones cliente standalone (si la interfaz es web, como en este caso, es mejor que la capa modelo sea local)
Ejecución (y 2)
Clases en RMIMiniBank.war:WEB-INF/lib: jars de Standard TagLibs y Struts, StandardUtil.jar y WebUtil.jar (subsistema Util) y RMIAccountFacadeClient.jar
RMIAccountFacadeClient.jar: sólo las clases del modelo que precisa el cliente (interfaz de la fachada y clases relacionadas)
¡ No contiene DAOs ni la implementación de las operaciones de la fachada !
WEB-INF/classes: todas las clases del controlador y la vista
Clases en RMIAccountFacade.shDriver JDBC para la BDStandardUtil.jarRMIAccountFacade.jar: todas las clases del modelo