Wednesday, June 22, 2011

RMI using spring

Let us look at Spring’s support to Remoting.

Spring supports remoting for several different Remote Procedure Call models, including Remote Method Invocation (RMI), Caucho’s Hessian and Burlap, and Spring’s own HTTP invoker.

Spring offers a POJO-based programming model for both your server and client, no matter which remoting solution you choose. This is accomplished using a proxy factory bean that enables you to wire remote services into properties of your other beans as if they were local objects.

The client makes calls to the proxy as if the proxy were providing the service functionality. The proxy communicates with the remote service on behalf of the client. It handles the details of connecting and making remote calls to the remote service.

If the call to the remote service results in a java.rmi.RemoteException, the proxy handles that exception and rethrows it as an unchecked
org.springframework.remoting.RemoteAccessException. Remote exceptions usually signal problems such as network or configuration issues that can’t be gracefully recovered from. Since there’s usually very little that a client can do to gracefully recover from a remote exception, rethrowing a RemoteAccessException makes it optional for the client to handle the exception.

Spring simplifies the RMI model by providing a proxy factory bean that enables you to wire RMI services into your Spring application is if they were local JavaBeans. Spring also provides a remote exporter that makes short work of converting your Spring-managed beans into RMI services.

Spring’s RmiProxyFactoryBean is a factory bean that creates a proxy to an RMI service. RmiProxyFactoryBean produces a proxy object that talks to remote RMI services on behalf of the client. The client talks to the proxy through the service’s interface as if the remote service were just a local POJO.

RmiProxyFactoryBean certainly simplifies the use of RMI services in a Spring application. But that’s only half of an RMI conversation.

Spring provides an easier way to publish RMI services. Instead of writing RMI-specific classes with methods that throw RemoteException, you simply write a POJO that performs the functionality of your service. Spring handles the rest.

For a typical Spring Application we need the following files:

1. An interface that defines the functions.

2. An Implementation that contains properties, its setter and getter methods, functions etc.

3. A XML file called Spring configuration file.

4. Client program that uses the function

Because the service interface doesn’t extend java.rmi.Remote and none of its methods throw java.rmi.RemoteException, this trims the interface down a bit. But more importantly, a client accessing the service through this interface will not have to catch exceptions that they probably won’t be able to deal with. Instead of generating a server skeleton and client stub using rmic and manually adding it to the RMI registry (as you would in conventional RMI), we’ll use Spring’s RmiServiceExporter.

RmiServiceExporter exports any Spring-managed bean as an RMI service. RmiServiceExporter works by wrapping the bean in an adapter class. The adapter class is then bound to the RMI registry and proxies requests to the service class.

Example Code
The simplest way to use RmiServiceExporter to expose the employeeService bean as an RMI service is to configure it in Spring with the following XML:

<bean class="org.springframework.remoting.rmi.RmiServiceExporter">
        <property name="serviceName" value="employee-service"/>
        <property name="service" ref="employeeService"/>
        <property name="serviceInterface" value="rmi.common.EmployeeI"/>
        <property name="registryPort" value="1234"/>
</bean>

Here the employeeService bean is wired into the service property to indicate that RmiServiceExporter is going to export the bean as an RMI service. ServiceName property names the RMI service and the serviceInterface property specifies the interface implemented by the service.

<bean id="employeeService" class="rmi.server.EmployeeImpl">
</bean>

Now let us look at an example...

Let us have simple employee recruitment service exposed through EmployeeI interface which contains methods to add, remove and get number of employees.

public class Employee implements Serializable {
 private String name;
 private String address;
 
 public Employee(String name,String address){
  this.name = name;
  this.address = address;
 }
 
 // getters and setters
}


public interface EmployeeI {
 
 public void addEmployee(Employee employee);
 
 public void removeEmployee(Employee employee);

    public List<Employee> getEmployees();  
    
}

Here is the implementation for EmployeeI interface.

public class EmployeeImpl implements EmployeeI{

    private List<Employee> employees = new ArrayList<Employee>();

    public void addEmployee(Employee employee) {
     employees.add(employee);
    }
    
    public void removeEmployee(Employee employee){
     employees.remove(employee);
    }

    public List<Employee> getEmployees() {
        return employees;
    }
}

On the server side, we need to configure Spring to export the service through RMI. As we discussed earlier we can do it by RmiServiceExporter.
The interface can be accessed by using RmiProxyFactoryBean, or via plain RMI in case of a traditional RMI service. The RmiServiceExporter explicitly supports the exposing of any non-RMI services via RMI invokers. Of course, we first have to set up our service in the Spring container:

<beans>
    <bean id="employeeService" class="rmi.server.EmployeeImpl"/>        
    
    <bean class="org.springframework.remoting.rmi.RmiServiceExporter">
        <property name="serviceName" value="employee-service"/>
        <property name="service" ref="employeeService"/>
        <property name="serviceInterface" value="rmi.common.EmployeeI"/>
        <property name="registryPort" value="1234"/>
    </bean>
</beans>

Now to run the server side service you need Spring context initialization.
public class EmpServerDemo {
 public static void main(String[] args) {
  ApplicationContext ctx = new ClassPathXmlApplicationContext 
                  ("rmi/server/rmi-server-context.xml");
 }
}


Now let us have a look at client side.

To link in the service on the client, we'll create a separate Spring container, containing the simple object and the service linking configuration bits:

<beans>
    <bean id="employeeService"  
             class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
        <property name="serviceUrl" value="rmi://localhost:1234/employee-service"/>
        <property name="serviceInterface" value="rmi.common.EmployeeI"/>
    </bean>
</beans>

You can make client calls through the below code...

public class EmpClientDemo {
 public static void main(String[] args) {
  ApplicationContext ctx = new ClassPathXmlApplicationContext 
                       ("rmi/client/rmi-client-context.xml");
  EmployeeI employee = (EmployeeI) ctx.getBean("employeeService");
  employee.addEmployee(new Employee("Prashant", "address1"));
  employee.addEmployee(new Employee("Sneha", "address2"));
  List<Employee> employees = employee.getEmployees();
  System.out.println("Total number of employees: " + employees.size());
  Iterator<Employee> it = employees.iterator();
  while (it.hasNext()) {
   Employee emp = (Employee) it.next();
   System.out.println(" " + emp);
  }
 }
}

Therefore, the client will not be aware of the fact that the service is running remote and even less about the fact that its method calls are marshaled through RMI. The Spring bean configuration file takes care of these details, so the client code will not be affected if we change the remoting strategy or even if we choose to run the service in-process.

First run the EmpServerDemo in one console to launch the RMI server and then run the EmpClientDemo in another console.

1 comment:

  1. Can Spring RMI be mixed with Plain RMI? Spring service with plain RMI client and vice versa?

    ReplyDelete

Chitika