For RMI on server-side, do we need to start rmiregistry
program, or just call LocateRegistry.createRegistry
?
If both are possible, what are the advant
They're the same thing... rmiregistry
is a separate program, which you can run from a command line or a script, while LocateRegistry.createRegistry
does the same thing programatically.
In my experience, for "real" servers you will want to use rmiregistry
so that you know it's always running regardless of whether or not the client application is started. createRegistry
is very useful for testing, as you can start and stop the registry from your test as necessary.
If you are writing a standalone java application you would want to start your own rmiregistry but if you are writing a J2EE app that obviously runs inside a J2EE container then you want to "LocateRegistry" as there is already one running on the app server!
If you use Spring to export your RMI services, it automatically starts a registry if one is not already running. See RmiServiceExporter
If we start rmiregistry first, RmiServiceExporter would register itself to the running rmiregistry. In this case, we have to set the system property 'java.rmi.server.codebase' to where the 'org.springframework.remoting.rmi.RmiInvocationWrapper_Stub' class can be found. Otherwise, the RmiServiceExporter would not be started and got the exception " ClassNotFoundException class not found: org.springframework.remoting.rmi.RmiInvocationWrapper_Stub; nested exception is: ..."
If your rmi server, rmi client and rmiregistry can access the same filesystem, you may want the system property to be automatically configured to where the spring.jar can be found on the shared filesystem. The following utility classes and spring configuration show how this can be achieved.
abstract public class CodeBaseResolver {
static public String resolveCodeBaseForClass(Class<?> clazz) {
Assert.notNull(clazz);
final CodeSource codeSource = clazz.getProtectionDomain().getCodeSource();
if (codeSource != null) {
return codeSource.getLocation().toString();
} else {
return "";
}
}
}
public class SystemPropertyConfigurer {
private Map<String, String> systemProperties;
public void setSystemProperties(Map<String, String> systemProperties) {
this.systemProperties = systemProperties;
}
@PostConstruct
void init() throws BeansException {
if (systemProperties == null || systemProperties.isEmpty()) {
return;
}
for (Map.Entry<String, String> entry : systemProperties.entrySet()) {
final String key = entry.getKey();
final String value = SystemPropertyUtils.resolvePlaceholders(entry.getValue());
System.setProperty(key, value);
}
}
}
<bean id="springCodeBase" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="xx.CodeBaseResolver.resolveCodeBaseForClass" />
<property name="arguments">
<list>
<value>org.springframework.remoting.rmi.RmiInvocationWrapper_Stub</value>
</list>
</property>
</bean>
<bean id="springCodeBaseConfigurer" class="xx.SystemPropertyConfigurer"
depends-on="springCodeBase">
<property name="systemProperties">
<map>
<entry key="java.rmi.server.codebase" value-ref="springCodeBase" />
</map>
</property>
</bean>
<bean id="rmiServiceExporter" class="org.springframework.remoting.rmi.RmiServiceExporter" depends-on="springCodeBaseConfigurer">
<property name="serviceName" value="XXX" />
<property name="service" ref="XXX" />
<property name="serviceInterface" value="XXX" />
<property name="registryPort" value="${remote.rmi.port}" />
</bean>
The above example shows how system property be set automatically only when rmi server, rmi client and rmi registry can access the same filesystem. If that is not true or spring codebase is shared via other method (e.g. HTTP), you may modify the CodeBaseResolver to fit your need.