Exposing Remote Method Invocation Services Using Spring Framework
The Spring framework simplifies the exposure of Java Remote Method Invocation (RMI) services through the RmiServiceExporter class. This component automatically manages the creation and registration of the RMI registry, allowing any standard Spring-managed bean to be invoked remotely.
Server-Side Configuration
First, define the contract for the remote operation. This interface will be shared between the server and the client.
package com.example.remote.api;
public interface TimeService {
String getCurrentTimestamp();
}
Next, implement the business logic for this service. Annotating the class with @Service ensures it is detected by component scanning.
package com.example.remote.server;
import com.example.remote.api.TimeService;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Service
public class TimeServiceImpl implements TimeService {
private static final DateTimeFormatter FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override
public String getCurrentTimestamp() {
return LocalDateTime.now().format(FORMATTER);
}
}
To expose this bean over the network, configure an RmiServiceExporter bean. The following configuration sets the service name, the interface to expose, and the specific port for the RMI registry.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.remoting.rmi.RmiServiceExporter;
import com.example.remote.api.TimeService;
@Configuration
public class RmiServerConfig {
@Bean
public RmiServiceExporter rmiExporter(TimeService serviceImplementation) {
RmiServiceExporter exporter = new RmiServiceExporter();
exporter.setService(serviceImplementation);
exporter.setServiceInterface(TimeService.class);
exporter.setServiceName("RemoteTimeService");
exporter.setRegistryPort(1200); // Default is 1099 if not specified
return exporter;
}
}
Client-Side Implementation
On the client side, the application requires the same service interface to generate the proxy.
package com.example.client.api;
public interface TimeService {
String getCurrentTimestamp();
}
Using Spring's RmiProxyFactoryBean, the client can connect to the remote service. The factory bean creates a proxy that implements the business interface and forwards calls to the RMI service URL.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.remoting.rmi.RmiProxyFactoryBean;
import com.example.client.api.TimeService;
@Configuration
public class ClientConfig {
@Bean
public RmiProxyFactoryBean timeServiceProxy() {
RmiProxyFactoryBean proxy = new RmiProxyFactoryBean();
proxy.setServiceUrl("rmi://127.0.0.1:1200/RemoteTimeService");
proxy.setServiceInterface(TimeService.class);
proxy.setLookupStubOnStartup(false);
proxy.setRefreshStubOnConnectFailure(true);
return proxy;
}
}
Finally, a JUnit test verifies the connectivity and functionality of the remote invocation.
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.example.client.api.TimeService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ClientConfig.class)
public class RemoteServiceIntegrationTest {
@Autowired
private TimeService timeService;
@Test
public void validateRemoteCall() {
String response = timeService.getCurrentTimestamp();
System.out.println("Server responded with: " + response);
Assert.assertNotNull(response);
Assert.assertFalse(response.isEmpty());
}
}