La idea que sustenta RMI es un ideal recurrente en tecnologías de Orientación a Objetos: hacer que un objeto invoque a otro objeto remoto con independencia de la JVM o el servidor en el que se encuentra, como si fuera un objeto local. Transparencia respecto a la máquina.

Hay dos posibilidades:
¿Cómo es posible que el objeto cliente pueda invocar al objeto remoto? Hay varias cosas que debemos tener, una de ellas es un interfaz para el objeto remoto. Un concepto fundamental es que el cliente sólo puede invocar al objeto remoto a través de su interfaz remota. La interfaz remota es el "escaparate" del objeto remoto, es el modo en que el objeto remoto "exporta" sus servicios. Aquello a lo que el cliente accede está en el interfaz del objeto remoto. Lo que no significa que el objeto remoto no tenga otros métodos, claro que puede tenerlos; pero sólo los métodos que estén en su interfaz son invocados por el cliente.
Las tres capas de RMI:
La capa de aplicación del cliente traduce su invocación a una llamada al objeto Stub, que se traduce a JRMP, después a TCP, hasta llegar al nivel de hardware del cliente, que la envía al hardware donde está el objeto remoto. Aquí se recorre el orden inverso, desde el hardware hasta la capa de aplicación. Esta arquitectura mantiene la flexibilidad. Podemos reemplazar una sin afectar a la eficacia del conjunto. Por ejemplo, podemos usar UDP (User Datagram Protocol) en lugar de TCP.
El objeto stub encapsula al objeto remoto, es algo así como el "mensajero" del objeto remoto. Tiene una identificación del objeto remoto a utilizar y además su interfaz (métodos que son invocados, los parámetros y el tipo de retorno). El stub recibe la llamada al objeto remoto y a continuación:
Nota: para versiones anteriores a 1.2 era preciso tener objetos skeleton, que eran el homólogo de los stub, pero en el lado del servidor. Salvo cuando hagamos referencia a rmic, obviaremos la existencia de skeletons.
El proceso parece complejo pero hay buenas noticias: una buena parte del proceso queda oculto al programador. Por ejemplo, la clase stub es generada automáticamente. Más adelante veremos que el cliente llama al objeto remoto como si fuera un objeto local, "como si no existiera el objeto stub".
Supongamos un problema sencillo. Del lado servidor queremos tener objetos que sean capaces de saludar. Veamos su implementación:
package rmi01.server;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.RemoteException;
/***********************************************************************************
* Implementación de clase exportada
***********************************************************************************/
public class SaludoImpl extends UnicastRemoteObject implements Saludo {
String mensaje;
public SaludoImpl( String mensaje ) throws RemoteException {
this.mensaje = mensaje;
}
public String get() { return mensaje; }
}
Este será el objeto remoto al que accederá el cliente. Su trabajo es sencillo, devuelve la cadena que recibe como argumento del constructor. Hereda de UnicastRemoteObject, que hereda de RemoteServer (clase abstracta). Esta y RemoteStub heredan de RemoteObject, que implementa el interfaz Remote. Este interfaz es implementado por Saludo. Se puede observar que es un interfaz sin métodos, que indica que es un interfaz señalizador o marcador (del mismo modo que el interfaz Serializable señala las clases serializables)
Aunque RemoteServer es abstracta implementa comportamiento para la exportación de clases.
La clase UnicastRemoteObject es concreta y es la más frecuentemente utilizada, para situaciones en las que la instancia del objeto remoto se encuentra en una máquina (no en varias), lo que se conoce como objeto remoto no replicado, y debe estar activa del lado servidor.
El interfaz del objeto remoto es compartida por el cliente y por el servidor. De esta forma el cliente "sabe" lo que puede importar (invocar) y el servidor "sabe" lo que exporta. En nuestro caso la clase utiliza el interfaz Saludo:
package rmi01.server;
import java.rmi.*;
/***********************************************************************************
* Interfaz de clase exportada
***********************************************************************************/
public interface Saludo extends Remote {
public String get() throws RemoteException;
}
Tan sólo nos queda implementar la clase servidora, aquella que va a exportar el objeto remoto:
package rmi01.server;
import java.rmi.*;
import java.rmi.registry.*;
/***********************************************************************************
* Servidor RMI. Exporta objetos del tipo SaludoImpl y muestra los vínculos (bindings)
* Ejecución: antes hay que asegurarse que hay _stub y arrancarar rmiregistry en cmd
* COMO FACILIDAD PARA ARRANCAR EL SERVIDOR VER ARCHIVO arrancarServidor.bat
***********************************************************************************/
public class Servidor {
public static void main(String[] args) {
try {
System.out.println("Servidor iniciado");
SaludoImpl h1 = new SaludoImpl("Hola. Desde Ramiro Lago. Soy el primer objeto.");
Naming.rebind("nombre_saludo1", h1);
SaludoImpl h2 = new SaludoImpl("Buenas. Desde Ramiro Lago. Soy el segundo objeto.");
Naming.rebind("nombre_saludo2", h2);
System.out.println("Servidor activo y objeto remoto a la espera de llamadas");
mostrarBindings("");
} catch (Exception e) {
e.printStackTrace();
}
}
/********************************************************************************
* Muestra los vínculos (bindings) de un registro cuyo nombre es 'nombre_registro'
*******************************************************************************/
public static void mostrarBindings( String nombre_registro ) {
try {
String[] bindings = Naming.list( nombre_registro );
System.out.println( "Vínculos disponibles:");
for ( int i = 0; i < bindings.length; i++ )
System.out.println( bindings[i] );
}
catch (Exception e) {
e.printStackTrace();
}
}
}
El método main() crea los objetos remotos (SaludoImpl) y los exporta mediante Naming.rebind(). Con rebind() se inserta en el registro RMI (rmi registry) un nombre (primer argumento) asociado (vinculado) a un objeto remoto (segundo argumento). Sustituye cualquier vinculación existente por la nueva vinculación. En cambio el método bind() realiza una vinculación, pero si ya existiese una previa con el mismo nombre lanza una java.rmi.AlreadyBoundException.
En resumen, la aplicación servidora registra (exporta) objetos remotos mediante rebind(), pasando como argumentos un nombre o clave única del objeto dentro del registro RMI y el objeto que se registra.
rebind(), al registrar el objeto remoto, hace que su stub este disponible para el cliente.
El método mostrarBinding() lista los objetos registrados (exportados).
Las tareas que debemos realizar son:
C:\DOC\Java_eclipse\rmi01\bin>rmic -v1.2 rmi01.server.SaludoImplEsta orden genera un archivo SaludoImpl_Stub.class. Poner como argumentos -v1.2 evita la creación del skeleton, que resulta inútil para versiones Java a partir de la 1.2. Si no hay interfaz remota, no se podrá crear el stub
Nota: rmic.exe está contenido en la misma carpeta del jdk donde se encuentran java.exe o javac.exe, por ejemplo C:\Sun\SDK\jdk\bin. Lo más cómodo es tener este directorio en la variable path.
start rmiregistry O bien: start rmiregistry -J-Djava.security.policy=archivo.policy
rmiregistry es una herramienta del JDK. Podemos iniciar en la línea de ordenes. Es una aplicación bastante poco simpática: si todo va bien la pantalla se queda detenida y "en blanco" y sólo admite un argumento (el número de puerto, 1099 por defecto). Si hay una excepción, lo normal es que se produzca porque ya estamos utilizando el puerto por una instancia previa de la herramienta:
java.rmi.server.ExportException: Port already in use
La segunda versión de rmiregistry inicia el registro con un archivo de política de seguridad. De estos archivos hablaremos más adelante.
Nota: al igual que en el caso anterior, rmiregistry está contenido en la misma carpeta del jdk o del jre donde se encuentra java.exe, por ejemplo C:\Sun\SDK\jdk\bin. Lo más cómodo es tener este directorio en la variable path.
cho off D: cd \DOC\Java_eclipse\rmi01 echo javac -d bin src\rmi01\server\*.java echo COMPILADO SERVIDOR cd bin rmic rmi01.server.SaludoImpl echo CREADO STUB CON RMIC echo ARRANCANDO RMIREGISTRY Y SERVIDOR: echo on start rmiregistry java rmi01.server.Servidor Nota: el proyecto de ejemplo tiene dos carpetas: src\rmi01\server y src\rmi01\cliente.
Resultado:
javac -d bin src\rmi01\server\*.java COMPILADO SERVIDOR CREADO STUB CON RMIC ARRANCANDO RMIREGISTRY Y SERVIDOR: D:\DOC\Java_eclipse\rmi01\bin>start rmiregistry D:\DOC\Java_eclipse\rmi01\bin>java rmi01.server.Servidor Servidor iniciado Servidor activo y objeto remoto a la espera de llamadas Vínculos disponibles: //:1099/nombre_saludo1 //:1099/nombre_saludo2
Algunos de los posibles errores al iniciar el servidor:
java.rmi.server.ExportException: Port already in use
java.rmi.ConnectException: Connection refused to host
java.rmi.StubNotFoundException: Stub class not found: rmi01.server.SaludoImpl_Stub

El cliente simplemente consigue una referencia al objeto remoto a partir de Naming.lookup( cadena_URL_de_objeto_remoto ). En realidad y para ser más preciso, el cliente RECIBE UN OBJETO STUB, que se almacena EN UNA VARIABLE DEL TIPO DE LA INTERFAZ REMOTA. Por medio de esta variable el cliente accederá al objeto remoto. A lookup() se le envía como argumento la cadena de la URL del objeto, donde "nombre_del_objeto" es el nombre definido en rebind() por el servidor:
rmi://host[:puerto]/nombre_del_objeto
El código fuente aparece a continuación. Empieza pidiendo por teclado la url del host (si pulsamos Enter se asume localhost), a continuación obtiene referencia a dos objetos remotos por medio de Naming.lookup():
package rmi01.cliente;
import java.rmi.*;
import java.io.*;
import rmi01.server.Saludo;
/********************************************************************************
* Lee desde el teclado el host (si es Enter equivale a localhost). A continuación
* accede a dos objetos del tipo remoto 'Saludo'
********************************************************************************/
public class Cliente {
public static void main(String[] args) {
//System.setProperty("java.security.policy", "rmi.policy");
//System.setSecurityManager( new RMISecurityManager() );
//// Leemos desde teclado la url del host
String url;
try {
System.out.println("\nIntroduzca direccion de server (ENTER='rmi://localhost/'), ojo, debe terminar con /:");
BufferedReader entrada = new BufferedReader(new InputStreamReader(System.in));
String str = entrada.readLine();
if (str == null || str.length() == 0)
url = "rmi://localhost/";
else
url = str;
}
catch (IOException e) {
url = "rmi://localhost/";
}
try {
Saludo c1 = (Saludo) Naming.lookup( url + "nombre_saludo1");
System.out.println( c1.get() );
Saludo c2 = (Saludo) Naming.lookup( url + "nombre_saludo2");
System.out.println( c2.get() );
}
catch(Exception e) {
e.printStackTrace();
}
}
}
La ejecución del cliente produce la salida esperada:
Introduzca direccion de server (ENTER='rmi://localhost/'), ojo, debe terminar con /: Hola. Desde Ramiro Lago. Soy el primer objeto. Buenas. Desde Ramiro Lago. Soy el segundo objeto.
Algunos de los posibles errores al iniciar el cliente:
java.rmi.NotBoundException
java.rmi.NotBoundException
java.rmi.ConnectException: Connection refused to host: localhost
El modelo de seguridad de Java incluye un gestor de seguridad que protege a las aplicaciones de la descarga de código no seguro vía invocaciones de método remoto. Tiene como objetivo el control de las clases cargadas dinámicamente en una aplicación cliente. En nuestro ejemplo se puede ver que no trabajamos con un administrador de seguridad RMI, ya que hemos puesto en comentario las líneas correspondientes en el cliente:
public class Cliente {
public static void main(String[] args) {
// System.setProperty("java.security.policy", "rmi.policy");
// System.setSecurityManager( new RMISecurityManager() );
...
Normalmente sólo es útil cuando:
Pero si necesita trabajar con un administración de seguridad puede quitar la línea de comentario de System.setSecurityManager() y podrá ver que se produce un error:
java.security.AccessControlException: access denied (java.net.SocketPermission 127.0.0.1:1099 connect,resolve)
El administrador de seguridad ha denegado acceso al servidor, concretamente ha rechazado la creación de un socket contra el servidor. Hay una solución, crear un archivo de política de seguridad que otorgue los permisos adecuados para abrir la conexión. Le hemos dado el nombre (convencional) de rmi.policy:
grant {
permission java.net.SocketPermission "*:1099-65535", "connect";
};
En este caso damos acceso a cualquier dirección IP, para lo cual usamos el asterisco (*). Si queremos indicar el localhost podemos poner 127.0.0.1. Hemos dado permiso para puertos de 1099 al 65535. El nombre de la acción permitida es "connect".
En segundo lugar no se olvide de quitar el comentario en la línea que indica el archivo de política que vamos a utilizar:
System.setProperty("java.security.policy", "rmi.policy");
Nota: existe otras dos posibilidades, alternativas a usar System.setProperty(), que consiste en:
java -Djava.security.policy=rmi.policy rmi01.cliente.Cliente
policy.url.1=file:${java.home}/lib/security/java.policy
policy.url.2=file:${user.home}/.java.policy
policy.url.3=file:${java.home}/lib/security/rmi.policy
Volvamos al uso de System.setProperty(), ¿Dónde colocar el archivo .policy? La opción más sencilla es ponerlo en el directorio actual (el directorio de ejecución, donde se utiliza el interprete java). En nuestro ejemplo, si ejecuto en la línea de ordenes y desde el directorio bin, entonces es en este directorio donde sitúo el archivo policy:
C:\DOC\Java_eclipse\rmi01\bin>dir El volumen de la unidad C no tiene etiqueta. El número de serie del volumen es: 08EC-6A62 Directorio de C:\DOC\Java_eclipse\rmi01\bin 06/02/2006 15:43 78 rmi.policy 06/02/2006 11:58 <DIR> rmi01 1 archivos 78 bytes 3 dirs 17.458.331.648 bytes libres C:\DOC\Java_eclipse\rmi01\bin>java rmi01.cliente.Cliente Introduzca direccion de server (ENTER='rmi://localhost/'), ojo, debe terminar con /: Hola. Desde Ramiro Lago. Soy el primer objeto. Buenas. Desde Ramiro Lago. Soy el segundo objeto.
Si la ejecución se realiza desde un IDE, lo normal (depende de la configuración) es que el directorio actual sea el del proyecto.
De cara a la implantación tengamos en cuenta donde hay que situar los archivos. En nuestro ejemplo las clases que forman parte del servidor:
Las clases que forman parte del cliente son rmi01.cliente.Cliente.class y aquellas imprescindibles para invocar al objeto remoto (rmi01.cliente.Saludo.class y rmi01.cliente.SaludoImpl_Stub.class). Además hay que incluir en el cliente las clases dependientes (aunque en nuestro ejemplo no hay ninguna) de las ya mencionadas.
Una nota: si tiene un cortafuegos activado puede ocurrir que no le permita conexiones RMI a otras máquinas (depende de la configuración del cortafuegos).