RMI


Spring includes support for the standard RMI infrastructure as the remoting backend. As much as possible, the usage style is analogous to Hessian, Burlap, and HTTP invoker. Standard RMI-JRMP, included in the JDK, uses Java serialization, like Spring's HTTP invoker. It is a Java-to-Java remoting solution that does not aim for cross-platform remoting.

In contrast to the previously discussed HTTP-based remoting strategies, standard RMI does not work at the HTTP level, but uses a special TCP port for communication: by default, port 1099. Exported services have to be registered with the RMI registry, which can either be started in-process or externally (potentially shared by multiple processes).

Important 

RMI stubs registered with the RMI registry are connected to a specific endpoint. If you restart the server that hosts the RMI endpoints (your Spring-managed services), you need to re-register the stubs — and clients need to look them up again.

Traditional RMI involves using RMI service interfaces, that is, interfaces that derive from java.rmi.Remote, with each method declaring the checked java.rmi.RemoteException. On J2SE 1.4 and earlier, the RMI compiler (rmic) needs to be run for each service implementation to generate the corresponding stub. On J2SE 5.0 and higher, the RMI runtime infrastructure will generate a dynamic proxy on the fly.

Spring supports the traditional style of RMI, but offers — and recommends — RMI invoker as an alternative, using plain Java business interfaces on top of RMI as the remoting infrastructure. There is no need to run the RMI compiler for an RMI invoker service, on J2SE 1.4 and earlier, or on J2SE 5.0.

Spring's RMI support consists of the following main classes:

  • org.springframework.remoting.rmi.RmiProxyFactoryBean: Proxy factory for Spring- compliant proxies that communicate with backend RMI services. To be defined in a Spring bean factory or application context, exposing the proxy object for references (through the FactoryBean mechanism). Proxies will either throw RMI's RemoteException or Spring's generic RemoteAccessException in case of remote invocation failure, depending on the type of service interface used.

  • org.springframework.remoting.rmi.RmiServiceExporter: Exporter that registers a given service — either a traditional RMI service or a plain Java object — with the RMI registry. Can take any existing Spring-managed bean and expose it under a given RMI name in the registry, allowing the target bean to be fully configured and wired via Spring. Creates an in-process RMI registry if none is found at the specified port. The same target bean can easily be exported through other protocols (such as HTTP invoker) at the same time.

These classes are direct equivalents of HttpInvokerProxyFactoryBean and HttpInvokerServiceExporter. While RMI requires a different registry and exposure style, they still share the same configuration style and behavior.

Spring's RMI support will work with standard RMI underneath in the case of a traditional RMI interface; that is, it does not use a special invoker mechanism unless necessary. Any existing RMI service can be accessed via Spring's RmiProxyFactoryBean, even if the target service is not exported via Spring; and any RMI client can access a Spring-managed traditional RMI service that has been exported via Spring's RmiServiceExporter. However, a traditional RMI client will not be able to access a Spring-exported POJO with a plain Java business interface; only a Spring-based client will be able to access such a service.

As with HTTP invoker, client and server codebases are tightly coupled. Java serialization requires strictly matching versions of the involved classes at both ends. If the application versions on the client and server side might differ, use of serialVersionUID is required. (See the JDK's documentation on Java serialization for details.)

Important 

RMI is the traditional option for Java-to-Java remoting. Spring enhances it through the RMI invoker mechanism, which allows it to use a plain Java business interface on top of the RMI backend infrastructure.

However, consider HTTP invoker as a direct alternative. Both are binary and roughly equivalent in terms of efficiency. The serialization mechanism is the same: standard Java serialization. The main difference is that HTTP invoker is more convenient. It can easily go through firewalls, does not require a registry, and does not need to reconnect in case of a server restart.

Nevertheless, RMI's level of setup and maintenance is often acceptable, in particular with Spring's auto-reconnect feature (see the following section). Note that as with HTTP invoker, the class versions at both ends have to be compatible — a consequence of using Java serialization.

Accessing a Service

On the client side, a proxy for a target service can easily be created via a Spring bean definition — analogous to Hessian, Burlap, and HTTP invoker. The configuration needed is the proxy factory class name, the RMI URL of the target service, and the service interface to expose. For example:

<bean  >   <property name="serviceUrl">     <value>rmi://localhost:1099/order</value>   </property>   <property name="serviceInterface">     <value>org.springframework.samples.jpetstore.domain.logic.OrderService</value>   </property> </bean>

The service interface can be the same plain Java business interface used in the earlier examples. It does not need to derive from specific base interfaces or throw special remoting exceptions:

public interface OrderService {       Order getOrder(int orderId); }

Such a proxy is then available for bean references; for example:

<bean  >   <property name="orderService">     <ref bean="rmiProxy"/>   </property> </bean>

The MyOrderServiceClient class is the same as in the earlier example. It would simply expose a bean property of type org.springframework.samples.jpetstore.domain.logic.OrderService here, so the client is tied not to a remote service but rather to a plain Java business interface.

public class MyOrderServiceClient {       private OrderService orderService;       public void setOrderService(OrderService orderService) {     this.orderService = orderService;   }       public void doSomething() {     Order order = this.orderService.getOrder(...);     ...   } }

If the specified service interface is a traditional RMI service interface, RmiProxyFactoryBean will access it as a standard RMI service rather than as a special RMI invoker. This allows access to RMI services that are not exported through Spring, or to services with traditional RMI interfaces that are exported through Spring (for example, for reasons of compatibility). Of course, callers of such a proxy need to deal with the checked RMI RemoteException thrown by each service method.

public interface RemoteOrderService extends java.rmi.Remote {       Order getOrder(int orderId) throws java.rmi.RemoteException; } 

If the specified service interface is a plain Java business interface (for example the OrderService interface shown previously) but the RMI stub received represents a traditional RMI service (for example implementing the RemoteOrderService RMI interface), special behavior will apply. Each invocation on the plain Java interface will be delegated to the corresponding method on the RMI proxy. If the underlying RMI proxy throws an RMI RemoteException, it will automatically be converted to Spring's unchecked RemoteAccessException.

Important 

Spring's RmiProxyFactoryBean can access RMI invoker services or traditional RMI services, through a plain Java business interface or RMI service interface, respectively. The actual mode of operation will be determined by the specified service interface. If both ends are managed by Spring, RMI invoker is recommended because it follows Spring's usual remoting conventions (that is, a plain Java business interface plus the unchecked RemoteAccessException).

As a special feature, RmiProxyFactoryBean is able to access a traditional RMI service through a plain Java business interface, delegating all method calls to corresponding methods on the underlying stub and automatically converting checked RemoteExceptions to unchecked RemoteAccessExceptions. While this involves maintaining two interfaces for the same service, it does allow for accessing existing RMI services in a consistent Spring proxy fashion.

Stub Lookup Strategies

As indicated, RMI stubs are connected to a specific endpoint, rather than just opening a connection to a given target address for each invocation. Consequently, if you restart the server that hosts the RMI endpoints, you need to re-register the stubs — and clients need to look them up again.

While the re-registration of the target service usually happens automatically on restart, stubs held by clients become stale in such a scenario. Clients won't notice this unless they try to call a method on the stub again, which will fail with a connect exception.

To avoid this scenario, Spring's RmiProxyFactoryBean offers a refreshStubOnConnectFailure setting as a bean property: Set this to true to enforce automatic re-lookup of the stub if a call fails with a connect exception.

<bean  >   <property name="serviceUrl">     <value>rmi://localhost:1099/order</value>   </property>   <property name="serviceInterface">     <value>org.springframework.samples.jpetstore.domain.logic.OrderService</value>   </property>   <property name="refreshStubOnConnectFailure">     <value>true</value>   </property> </bean> 

A further issue with stub lookup is that the target RMI server and the RMI registry need to be available at the time of lookup. If the client starts before the server and tries to look up and cache the service stub, client startup will fail — even if the remote service is not needed yet.

To enable lazy lookup of the service stub, set RmiProxyFactoryBean's lookupStubOnStartup flag to false. The stub will then be looked up on first access, that is, when the first method invocation on the proxy comes in; it will be cached from then on. The disadvantage is that there will be no validation that the target service actually exists until the first invocation.

<bean  >   <property name="serviceUrl">     <value>rmi://localhost:1099/order</value>   </property>   <property name="serviceInterface">     <value>org.springframework.samples.jpetstore.domain.logic.OrderService</value>   </property>   <property name="lookupStubOnStartup">     <value>false</value>   </property>   <property name="refreshStubOnConnectFailure">     <value>true</value>   </property> </bean>

To avoid caching of the stub altogether, RmiProxyFactoryBean's cacheStub flag can be set to false. This forces the proxy to perform a stub lookup for each method invocation. While this is clearly not desirable from a performance perspective, it guarantees that the stub will never be stale. However, it is usually sufficient to activate refreshStubOnConnectFailure, which will cache the stub unless a connection failure occurs on invocation.

<bean  >   <property name="serviceUrl">     <value>rmi://localhost:1099/order</value>   </property>   <property name="serviceInterface">     <value>org.springframework.samples.jpetstore.domain.logic.OrderService</value>   </property>   <property name="lookupStubOnStartup">     <value>false</value>   </property>   <property name="cacheStub">     <value>false</value>   </property> </bean> 

Important 

Spring offers a variety of stub lookup strategies. Instead of looking up the stub on startup and caching it for the lifetime of the proxy, there are options to automatically refresh the stub if a connect failure occurs, lazily look up the stub on first access, and/or not cache the stub in the first place.

While the default early lookup and eternal caching is, strictly speaking, the most efficient strategy because it does not require any synchronization on proxy invocation, it is the default mainly for historical reasons. It is usually advisable to turn on refreshStubOnConnectFailure, and also to turn off lookupStubOnStartup. The lifecycles of the client and the server are as decoupled as possible in such a scenario.

Exporting a Service

On the server side, exporting an existing Spring-managed bean as an RMI invoker service is straightforward. A corresponding service exporter must be defined in a Spring context — which can be the middle tier context because it does not have to be a web dispatcher context. RMI is not based on HTTP; therefore no DispatcherServlet is involved.

For example, such an RMI exporter definition could look as follows, exposing the "petStore" bean in the root application context as an RMI invoker service:

<bean  >   <property name="service">     <ref bean="petStore"/>   </property>   <property name="serviceInterface">     <value>org.springframework.samples.jpetstore.domain.logic.OrderService</value>   </property>   <property name="serviceName">     <value>order</value>   </property>   <property name="registryPort">     <value>1099</value>   </property> </bean>

Assuming that the RMI registry is available on localhost, the remote OrderService would then be available as an RMI invoker service at rmi://localhost:1099/order.

In the JPetStore sample application, the RmiServiceExporter definition can be found in the applicationContext.xml file in the WEB-INF directory, alongside resource and business objects. Alternatively, such exporter definitions can be put into a separate bean definition file, building the root application context from multiple files via a corresponding contextConfigLocation entry in web.xml.

If the specified service interface is a traditional RMI service interface, RmiServiceExporter will automatically expose the service as a traditional RMI service rather than as a special RMI invoker. In such a scenario, any client can access the service in standard RMI style (even if not based on Spring).

Important 

RmiServiceExporter will automatically register the service with the RMI registry. If no existing registry is found, a new in-process one will be created. Note that all services in an RMI registry share a single namespace: You need to choose unique service names.

Corresponding to RmiProxyFactoryBean, RmiServiceExporter can expose RMI invoker services or traditional RMI services through a plain Java business interface or RMI service interface, respectively. The actual mode of operation will be determined by the specified service interface.

Overall, Spring makes RMI remoting much less painful than you may remember if you have worked with RMI in the past.

Note that it is not advisable to use the RMI exporter in a process that also runs an EJB container, as RMI uses a shared infrastructure with a central registry, which is likely to cause conflicts if used by both the EJB container and Spring's RMI exporter. Of course, such a setup might nevertheless work, provided that you manage to configure the overall system accordingly to avoid conflicts.

Customization Options

Like HTTP invoker, RMI invoker uses Spring's generic remote invocation mechanism underneath, serializing org.springframework.remoting.support.RemoteInvocation and org.springframework.remoting.support.RemoteInvocationResult objects. This can be leveraged, for example, to include a custom user credential attribute in the invocation, as illustrated in the HTTP invoker section.

As the remote invocation infrastructure is shared with HTTP invoker, a custom RemoteInvocation Factory / RemoteInvocationExecutor pair can be applied to either HTTP invoker or RMI invoker without changes, transferring the additional attributes over the respective protocol.

Important 

Like HTTP invoker, RMI invoker is highly customizable, through the RemoteInvocationFactory and RemoteInvocationExecutor strategies. However, for typical needs, you don't have to worry about any of this. The standard configuration of RmiProxyFactoryBean and RmiServiceExporter should be perfectly sufficient.

RMI-IIOP

Spring also supports RMI-IIOP: RMI access via CORBA's IIOP protocol. RMI-IIOP can be used for accessing backend CORBA services, but also as wire protocol for Java-to-Java remoting. The main support classes are:

  • org.springframework.remoting.rmi.JndiRmiProxyFactoryBean: Proxy factory for Spring-compliant proxies that communicate with backend RMI-IIOP services. To be defined in a Spring bean factory or application context, exposing the proxy object for references (through the FactoryBean mechanism). Proxies will either throw RMI's RemoteException or Spring's generic RemoteAccessException in case of remote invocation failure, depending on the type of service interface used.

  • org.springframework.remoting.rmi.JndiRmiServiceExporter: Exporter that registers a given service — a traditional RMI-IIOP service — with an ORB. Can take any existing Spring- managed bean and expose it under a given JNDI name in the ORB, allowing the target bean to be fully configured and wired via Spring.

These classes are direct equivalents of RmiProxyFactoryBean and RmiServiceExporter. There is no RMI invoker mechanism available here; the target service needs to be a traditional RMI-IIOP service, with a stub generated through the rmic tool with the -iiop option. Furthermore, the exporter is not able to start an in-process registry on demand; instead, an existing ORB needs to be available, for example the orbd executable included in the JDK.

On the client side, either the RMI service interface or a plain Java business interface can be specified. In the case of the latter, each invocation on the plain Java interface will be delegated to the corresponding method on the RMI proxy (analogous to when working with standard RMI). If the underlying RMI proxy throws an RMI RemoteException, it will automatically get converted to Spring's unchecked RemoteAccessException.

JndiRmiProxyFactoryBean supports the same stub lookup strategies as RmiProxyFactoryBean: refreshStubOnConnectFailure, lookupStubOnStartup, and cacheStub. See the section "Stub Lookup Strategies," earlier in this chapter, for details.

EJB containers are required to support RMI-IIOP. As a consequence, Spring's JndiRmiProxyFactoryBean can be used to access remote EJB home objects, for example for stateful session beans. The main difference from a plain JNDI lookup as performed by Spring's org.springframework.jndi.JndiObjectFactoryBean is that the RMI-IIOP access performs narrowing through the javax.rmi.PortableRemoteObject class, as required by well-behaved EJB lookups.



Professional Java Development with the Spring Framework
Professional Java Development with the Spring Framework
ISBN: 0764574833
EAN: 2147483647
Year: 2003
Pages: 188

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net