En capítulos anteriores hemos visto una introducción a RMI y una explicación del paso de objetos no remotos. Vimos que el paso de objetos no remotos se realizaba por copia. A continuación vamos a preguntarnos lo que ocurre cuando se pasan de una JVM a otra objetos remotos, es decir, objetos que han sido exportados por medio de rebind() o de otra forma. Veremos que el paso de estos objetos es por referencia.
Tenemos un objeto remoto (implementado como AlmacenImpl), cuya interfaz es Almacen. Es muy sencillo, básicamente lo que hace es crear un objeto remoto (ProductoImpl) y almacena el objeto en una referencia del tipo interfaz (Producto).
Nota: podríamos almacenar el producto en una referencia del tipo ProductoImpl, pero si lo pasamos a otra JVM deberíamos hacer un casting al tipo interfaz (Producto). Cuando se trata de objetos remotos, una JVM se comunica con otra por medio de los interfaces remotos de los objetos.El ejemplo es sencillo: tenemos un objeto remoto que "apunta a otro":
package rmi03.server;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.RemoteException;
import java.rmi.Naming;
/********************************************************************************
* Implementación de clase para objetos remotos, que contiene una referencia
* (prodSeleccionado) a otra clase remota (ProductoImpl).
* Observar que la referencia a un objeto remoto es del tipo interfaz (aunque el
* new es de la implementación de la clase).
********************************************************************************/
public class AlmacenImpl extends UnicastRemoteObject implements Almacen {
private Producto prodSeleccionado;
/*******************************************************************************
* Constructor. Exporta prodSeleccionado
******************************************************************************/
public AlmacenImpl() throws RemoteException {
try {
prodSeleccionado = new ProductoImpl( "taza x3", 5.7F );
Naming.rebind( "x3", prodSeleccionado);
}
catch (Exception e) {
e.printStackTrace();
}
}
public Producto getProdSeleccionado() { return prodSeleccionado; }
/*******************************************************************************
* Muestra en la consola del servidor prodSeleccionado.
******************************************************************************/
public void showProdSeleccionado() {
try {
System.out.println( "Soy el almacen. Mi producto seleccionado es: " +
prodSeleccionado.getNombre() + ", " +prodSeleccionado.getPrecio() );
}
catch (RemoteException e) {
e.printStackTrace();
}
}
}
Se puede ver que AlmacenImpl exporta un objeto del tipo ProductoImpl, por ello, decimos que este es un objeto remoto. Lo interesante es que AlmacenImpl devuelve la referencia a este producto (una referencia del tipo interfaz, Producto). Esto lo hace en el método getProdSeleccionado(). Veremos qué es lo que recibe el cliente y que puede hacer el cliente con esta referencia. Pero antes de hablar del cliente tenemos que definir el interfaz Almacen:
package rmi03.server;
import java.rmi.Remote;
import java.rmi.RemoteException;
/***********************************************************************************
* Interfaz de clase exportada
***********************************************************************************/
public interface Almacen extends Remote {
public Producto getProdSeleccionado() throws RemoteException;
public void showProdSeleccionado() throws RemoteException;
}
Como hemos visto, el interfaz debe incluir el método que devuelve el producto a otra JVM. La implementación del producto es muy sencilla:
package rmi03.server;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
/********************************************************************************
* Implementación de clase para objetos remotos
********************************************************************************/
public class ProductoImpl extends UnicastRemoteObject implements Producto {
private String nombre;
private float precio;
public ProductoImpl( String nombre, float precio ) throws RemoteException {
this.nombre = nombre;
this.precio = precio;
}
public String getNombre(){ return nombre; }
public float getPrecio() { return precio; }
public void setNombre( String nombre) { this.nombre = nombre; }
public void setPrecio( float precio ) { this.precio = precio; }
}
Y su interfaz:
package rmi03.server;
import java.rmi.Remote;
import java.rmi.RemoteException;
/***********************************************************************************
* Interfaz de clase exportada
***********************************************************************************/
public interface Producto extends Remote {
public String getNombre() throws RemoteException;
public float getPrecio() throws RemoteException;
public void setNombre( String nombre) throws RemoteException;
public void setPrecio( float precio ) throws RemoteException;
}
Nuestro servidor exporta en main() el objeto del tipo AlmacenImpl (en realidad lo que exporta es el stub, como ya hemos dicho en otras páginas). No necesita crear un ProductoImpl, ya que éste es referenciado por AlmacenImpl:
AlmacenImpl almacen = new AlmacenImpl();
Naming.rebind( "almacen", almacen );
Resumen:
Por tanto AlmacenImpl y ProductoImpl son implementaciones de objetos remotos.
¿Qué ocurre en el cliente? Creo de la manera habitual una referencia a un objeto remoto del tipo Almacen (mediante lookup). Y tengo una referencia del tipo Producto (interfaz), PERO NO LA HE OBTENIDO MEDIANTE LOOKUP, SINO QUE ME LA DEVUELVE EL ALMACEN. Si Producto no fuera un objeto remoto ya sabemos lo que ocurriría: el paso de una JVM a otra se realiza por COPIA. Pero puesto que es un objeto remoto, el paso es POR REFERENCIA, es decir, 'Producto prodSelec' se refiere al objeto remoto, no a una copia de él:
package rmi03.cliente;
import java.rmi.*;
import rmi03.server.Producto;
import rmi03.server.Almacen;
public class ClienteRMI {
public static void main(String[] args) {
String url = "rmi://localhost/";
try {
//// Obtenemos objeto remoto almacen y producto. EL PRODUCTO NO LO
//// OBTENGO POR LOOKUP (JNDI), sino que me lo da el almacen
Almacen alm = (Almacen) Naming.lookup( url + "almacen");
Producto prodSelec = alm.getProdSeleccionado();
//// Mostramos el producto (remoto)
System.out.println("Producto seleccionado: " +
prodSelec.getNombre() + "," + prodSelec.getPrecio());
//// Cambio del producto remoto y lo muestro
prodSelec.setPrecio( 8.2F );
System.out.println("Producto seleccionado y modificado localmente: " +
prodSelec.getNombre() + "," + prodSelec.getPrecio());
//// Mensaje que sale en la consola del servidor
alm.showProdSeleccionado();
}
catch(Exception e) {
e.printStackTrace();
}
}
}
Por tanto los cambios que hago por medio de prodSelec, por ejemplo prodSelec.setPrecio( 8.2F ), se los hago al propio objeto remoto que está en otra JVM. La prueba es que el método del almacén showProdSeleccionado() me muestra (en la consola del servidor) el cambio realizado. No hago referencia a dos objetos (el original y la copia), sino que en todo momento estoy haciendo referencia al mismo objeto remoto.