¡Hola! Hoy consideraremos un tema bastante interesante: Java RMI. Esto significa invocación de método remoto. Puede usar RMI para permitir que dos programas se comuniquen entre sí, incluso si están en computadoras diferentes. ¿Eso suena genial? :) ¡Y no es tan difícil de hacer! En la lección de hoy, analizaremos los elementos de la interacción RMI y descubriremos cómo configurarla. Lo primero que necesitamos es un cliente y un servidor. Realmente no necesitamos profundizar en la terminología informática. Cuando se trata de RMI, estos son solo dos programas. Uno de ellos incluirá un objeto y el otro llamará a métodos en ese objeto. Llamar a métodos de un objeto que existe en un programa diferente: ¡eso es algo que aún no hemos hecho! ¡Es hora de darle una oportunidad! :) Para evitar atascarse, vamos Mantengamos nuestro programa simple. En general, un servidor realiza algunos cálculos que solicita un cliente. Y así será con nosotros. Nuestro servidor será un programa de calculadora simple. Tendrá un solo método:multiplicar() . Multiplicará dos números enviados por el programa cliente y luego devolverá el resultado. En primer lugar, necesitamos una interfaz:

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Calculator extends Remote {

   int multiply(int x, int y) throws RemoteException;
}
¿Por qué necesitamos una interfaz? Porque RMI se basa en la creación de proxies, que estudió en lecciones anteriores . Como probablemente recordará, trabajamos con proxies a través de interfaces, no de clases. ¡Hay 2 requisitos importantes para nuestra interfaz!
  1. Debe extender la interfaz remota.
  2. Todos sus métodos deben lanzar una excepción remota (el IDE no lo hará automáticamente, ¡debe agregar esto manualmente!).
Ahora necesitamos crear una clase de servidor que implemente nuestra interfaz Calculadora . RMI en la práctica - 2Aquí, también, todo es bastante simple:

import java.rmi.RemoteException;

public class RemoteCalculationServer implements Calculator {

   @Override
   public int multiply(int x, int y) throws RemoteException {
       return x*y;
   }

}
Realmente no hay nada que comentar aquí :) Ahora necesitamos escribir un programa de servidor que configurará y ejecutará nuestro objeto calculadora. Se verá así:

import java.rmi.AlreadyBoundException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

public class ServerMain {

   public static final String UNIQUE_BINDING_NAME = "server.calculator";

   public static void main(String[] args) throws RemoteException, AlreadyBoundException, InterruptedException {

       final RemoteCalculationServer server = new RemoteCalculationServer();

       final Registry registry = LocateRegistry.createRegistry(2732);

       Remote stub = UnicastRemoteObject.exportObject(server, 0);
       registry.bind(UNIQUE_BINDING_NAME, stub);

       Thread.sleep(Integer.MAX_VALUE);

   }
}
Resolvamos esto :) En la primera línea, declaramos alguna variable de cadena:

public static final String UNIQUE_BINDING_NAME = "server.calculator";
Esta cadena es el nombre exclusivo del objeto remoto. Nuestro programa cliente usa este nombre para encontrar nuestro servidor: verá esto más adelante. A continuación, creamos nuestro objeto calculadora:

final RemoteCalculationServer server = new RemoteCalculationServer();
Todo está claro aquí. Lo que viene a continuación es más interesante:

final Registry registry = LocateRegistry.createRegistry(2732);
Este objeto de registro es un registro de objetos remotos. Estos son objetos a los que otros programas pueden acceder de forma remota :) Pasamos el número 2732 al método LocateRegistry.createRegistry() . Este es el número de puerto, un número único que otros programas usarán para encontrar nuestro registro de objetos (nuevamente, verá esto a continuación). Avanzando a lo largo... Veamos qué sucede en la siguiente línea:

Remote stub = UnicastRemoteObject.exportObject(server, 0);
Creamos un stub en esta línea. Un stub encapsula toda la llamada remota. Puede considerar este el elemento más importante de RMI. ¿Qué hace?
  1. Recibe toda la información sobre una llamada remota de algún método.
  2. Si el método tiene parámetros, el código auxiliar los deserializará. ¡Presta atención a este punto! Los argumentos que pasa a los métodos llamados de forma remota deben ser serializables (después de todo, se transmitirán a través de la red). Esto no es un problema para nosotros, solo estamos transmitiendo números. Pero si estás transmitiendo objetos, ¡no olvides este requisito!
  3. Después de eso, el stub llama al método deseado.
Pasamos nuestro objeto de servidor de calculadora al método UnicastRemoteObject.exportObject() . Así es como hacemos posible llamar remotamente a sus métodos. Sólo queda una cosa por hacer:

registry.bind(UNIQUE_BINDING_NAME, stub);
"Registramos" nuestro stub en el registro de objetos remotos con el nombre que inventamos al principio. ¡Ahora el cliente podrá encontrarlo! Tal vez haya notado que pusimos el hilo principal del programa a dormir al final:

Thread.sleep(Integer.MAX_VALUE);
Solo necesitamos que el servidor funcione durante mucho tiempo. En el IDE, lanzaremos simultáneamente dos métodos main() : primero, el método main() del servidor (en la clase ServerMain , que ya hemos escrito), y segundo, el método main() del cliente (en la clase ClientMain , que escribiremos a continuación). Es importante que el programa del servidor no finalice mientras iniciamos el cliente, por lo que simplemente lo ponemos en reposo durante mucho tiempo. En cualquier caso, seguirá ejecutándose :) Ahora podemos ejecutar el método main() de nuestro servidor . Deje que se ejecute y espere a que el programa cliente llame a algún método :) ¡Ahora escribamos el programa cliente! Enviará números a nuestro servidor para la multiplicación.

import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class ClientMain {

   public static final String UNIQUE_BINDING_NAME = "server.calculator";

   public static void main(String[] args) throws RemoteException, NotBoundException {

       final Registry registry = LocateRegistry.getRegistry(2732);

       Calculator calculator = (Calculator) registry.lookup(UNIQUE_BINDING_NAME);

       int multiplyResult = calculator.multiply(20, 30);

       System.out.println(multiplyResult);
   }
}
Parece sencillo. Pero, ¿qué está pasando aquí? Primero, el cliente debe conocer el nombre único del objeto cuyos métodos llamará de forma remota. En consecuencia, en el programa cliente, creamos la cadena final estática pública UNIQUE_BINDING_NAME = "server.calculator"; variable. Luego, en el método main() , obtenemos acceso al registro de objetos remotos. Para hacer esto, debemos llamar al método LocateRegistry.getRegistry() y pasar el número de puerto utilizado para crear nuestro registro en el programa ServerMain (puerto 2732; este número es solo un ejemplo; puede intentar usar un número diferente):

final Registry registry = LocateRegistry.getRegistry(2732);
¡Ahora solo necesitamos obtener el objeto deseado del registro! ¡Esto es fácil, porque conocemos su nombre único!

Calculator calculator = (Calculator) registry.lookup(UNIQUE_BINDING_NAME);
Preste atención a la fundición de tipos. Enviamos el objeto recibido a la interfaz Calculator , no a la clase RemoteCalculationServer . Como dijimos al comienzo de la lección, RMI se basa en un proxy, por lo que las llamadas remotas están disponibles solo para los métodos de una interfaz, no para los métodos de una clase. Finalmente, llamamos de forma remota al método multiplicar() en nuestro objeto y enviamos el resultado a la consola.

int multiplyResult = calculator.multiply(20, 30);
System.out.println(multiplyResult);
El método main() de la clase ServerMain ya se ha estado ejecutando durante mucho tiempo. ¡ Ahora es el momento de ejecutar el método main() en el programa cliente ( ClientMain )! Salida de la consola:

600
¡Eso es todo! Nuestro programa (¡dos programas, en realidad!) hizo lo que se suponía que debía hacer :) Si tiene el tiempo y el deseo, puede animar esto un poco. Por ejemplo, haga que la calculadora admita las cuatro operaciones aritméticas estándar y pase los números no como tipos primitivos, sino como objetos CalculationInstance(int x, int y) . ¡Nos vemos en la próxima lección! :)