001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.remoting.rmi; 018 019import java.rmi.AlreadyBoundException; 020import java.rmi.NoSuchObjectException; 021import java.rmi.NotBoundException; 022import java.rmi.Remote; 023import java.rmi.RemoteException; 024import java.rmi.registry.LocateRegistry; 025import java.rmi.registry.Registry; 026import java.rmi.server.RMIClientSocketFactory; 027import java.rmi.server.RMIServerSocketFactory; 028import java.rmi.server.UnicastRemoteObject; 029 030import org.springframework.beans.factory.DisposableBean; 031import org.springframework.beans.factory.InitializingBean; 032 033/** 034 * RMI exporter that exposes the specified service as RMI object with the specified name. 035 * Such services can be accessed via plain RMI or via {@link RmiProxyFactoryBean}. 036 * Also supports exposing any non-RMI service via RMI invokers, to be accessed via 037 * {@link RmiClientInterceptor} / {@link RmiProxyFactoryBean}'s automatic detection 038 * of such invokers. 039 * 040 * <p>With an RMI invoker, RMI communication works on the {@link RmiInvocationHandler} 041 * level, needing only one stub for any service. Service interfaces do not have to 042 * extend {@code java.rmi.Remote} or throw {@code java.rmi.RemoteException} 043 * on all methods, but in and out parameters have to be serializable. 044 * 045 * <p>The major advantage of RMI, compared to Hessian and Burlap, is serialization. 046 * Effectively, any serializable Java object can be transported without hassle. 047 * Hessian and Burlap have their own (de-)serialization mechanisms, but are 048 * HTTP-based and thus much easier to setup than RMI. Alternatively, consider 049 * Spring's HTTP invoker to combine Java serialization with HTTP-based transport. 050 * 051 * <p>Note: RMI makes a best-effort attempt to obtain the fully qualified host name. 052 * If one cannot be determined, it will fall back and use the IP address. Depending 053 * on your network configuration, in some cases it will resolve the IP to the loopback 054 * address. To ensure that RMI will use the host name bound to the correct network 055 * interface, you should pass the {@code java.rmi.server.hostname} property to the 056 * JVM that will export the registry and/or the service using the "-D" JVM argument. 057 * For example: {@code -Djava.rmi.server.hostname=myserver.com} 058 * 059 * @author Juergen Hoeller 060 * @since 13.05.2003 061 * @see RmiClientInterceptor 062 * @see RmiProxyFactoryBean 063 * @see java.rmi.Remote 064 * @see java.rmi.RemoteException 065 * @see org.springframework.remoting.caucho.HessianServiceExporter 066 * @see org.springframework.remoting.caucho.BurlapServiceExporter 067 * @see org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter 068 */ 069public class RmiServiceExporter extends RmiBasedExporter implements InitializingBean, DisposableBean { 070 071 private String serviceName; 072 073 private int servicePort = 0; // anonymous port 074 075 private RMIClientSocketFactory clientSocketFactory; 076 077 private RMIServerSocketFactory serverSocketFactory; 078 079 private Registry registry; 080 081 private String registryHost; 082 083 private int registryPort = Registry.REGISTRY_PORT; 084 085 private RMIClientSocketFactory registryClientSocketFactory; 086 087 private RMIServerSocketFactory registryServerSocketFactory; 088 089 private boolean alwaysCreateRegistry = false; 090 091 private boolean replaceExistingBinding = true; 092 093 private Remote exportedObject; 094 095 private boolean createdRegistry = false; 096 097 098 /** 099 * Set the name of the exported RMI service, 100 * i.e. {@code rmi://host:port/NAME} 101 */ 102 public void setServiceName(String serviceName) { 103 this.serviceName = serviceName; 104 } 105 106 /** 107 * Set the port that the exported RMI service will use. 108 * <p>Default is 0 (anonymous port). 109 */ 110 public void setServicePort(int servicePort) { 111 this.servicePort = servicePort; 112 } 113 114 /** 115 * Set a custom RMI client socket factory to use for exporting the service. 116 * <p>If the given object also implements {@code java.rmi.server.RMIServerSocketFactory}, 117 * it will automatically be registered as server socket factory too. 118 * @see #setServerSocketFactory 119 * @see java.rmi.server.RMIClientSocketFactory 120 * @see java.rmi.server.RMIServerSocketFactory 121 * @see UnicastRemoteObject#exportObject(Remote, int, RMIClientSocketFactory, RMIServerSocketFactory) 122 */ 123 public void setClientSocketFactory(RMIClientSocketFactory clientSocketFactory) { 124 this.clientSocketFactory = clientSocketFactory; 125 } 126 127 /** 128 * Set a custom RMI server socket factory to use for exporting the service. 129 * <p>Only needs to be specified when the client socket factory does not 130 * implement {@code java.rmi.server.RMIServerSocketFactory} already. 131 * @see #setClientSocketFactory 132 * @see java.rmi.server.RMIClientSocketFactory 133 * @see java.rmi.server.RMIServerSocketFactory 134 * @see UnicastRemoteObject#exportObject(Remote, int, RMIClientSocketFactory, RMIServerSocketFactory) 135 */ 136 public void setServerSocketFactory(RMIServerSocketFactory serverSocketFactory) { 137 this.serverSocketFactory = serverSocketFactory; 138 } 139 140 /** 141 * Specify the RMI registry to register the exported service with. 142 * Typically used in combination with RmiRegistryFactoryBean. 143 * <p>Alternatively, you can specify all registry properties locally. 144 * This exporter will then try to locate the specified registry, 145 * automatically creating a new local one if appropriate. 146 * <p>Default is a local registry at the default port (1099), 147 * created on the fly if necessary. 148 * @see RmiRegistryFactoryBean 149 * @see #setRegistryHost 150 * @see #setRegistryPort 151 * @see #setRegistryClientSocketFactory 152 * @see #setRegistryServerSocketFactory 153 */ 154 public void setRegistry(Registry registry) { 155 this.registry = registry; 156 } 157 158 /** 159 * Set the host of the registry for the exported RMI service, 160 * i.e. {@code rmi://HOST:port/name} 161 * <p>Default is localhost. 162 */ 163 public void setRegistryHost(String registryHost) { 164 this.registryHost = registryHost; 165 } 166 167 /** 168 * Set the port of the registry for the exported RMI service, 169 * i.e. {@code rmi://host:PORT/name} 170 * <p>Default is {@code Registry.REGISTRY_PORT} (1099). 171 * @see java.rmi.registry.Registry#REGISTRY_PORT 172 */ 173 public void setRegistryPort(int registryPort) { 174 this.registryPort = registryPort; 175 } 176 177 /** 178 * Set a custom RMI client socket factory to use for the RMI registry. 179 * <p>If the given object also implements {@code java.rmi.server.RMIServerSocketFactory}, 180 * it will automatically be registered as server socket factory too. 181 * @see #setRegistryServerSocketFactory 182 * @see java.rmi.server.RMIClientSocketFactory 183 * @see java.rmi.server.RMIServerSocketFactory 184 * @see LocateRegistry#getRegistry(String, int, RMIClientSocketFactory) 185 */ 186 public void setRegistryClientSocketFactory(RMIClientSocketFactory registryClientSocketFactory) { 187 this.registryClientSocketFactory = registryClientSocketFactory; 188 } 189 190 /** 191 * Set a custom RMI server socket factory to use for the RMI registry. 192 * <p>Only needs to be specified when the client socket factory does not 193 * implement {@code java.rmi.server.RMIServerSocketFactory} already. 194 * @see #setRegistryClientSocketFactory 195 * @see java.rmi.server.RMIClientSocketFactory 196 * @see java.rmi.server.RMIServerSocketFactory 197 * @see LocateRegistry#createRegistry(int, RMIClientSocketFactory, RMIServerSocketFactory) 198 */ 199 public void setRegistryServerSocketFactory(RMIServerSocketFactory registryServerSocketFactory) { 200 this.registryServerSocketFactory = registryServerSocketFactory; 201 } 202 203 /** 204 * Set whether to always create the registry in-process, 205 * not attempting to locate an existing registry at the specified port. 206 * <p>Default is "false". Switch this flag to "true" in order to avoid 207 * the overhead of locating an existing registry when you always 208 * intend to create a new registry in any case. 209 */ 210 public void setAlwaysCreateRegistry(boolean alwaysCreateRegistry) { 211 this.alwaysCreateRegistry = alwaysCreateRegistry; 212 } 213 214 /** 215 * Set whether to replace an existing binding in the RMI registry, 216 * that is, whether to simply override an existing binding with the 217 * specified service in case of a naming conflict in the registry. 218 * <p>Default is "true", assuming that an existing binding for this 219 * exporter's service name is an accidental leftover from a previous 220 * execution. Switch this to "false" to make the exporter fail in such 221 * a scenario, indicating that there was already an RMI object bound. 222 */ 223 public void setReplaceExistingBinding(boolean replaceExistingBinding) { 224 this.replaceExistingBinding = replaceExistingBinding; 225 } 226 227 228 @Override 229 public void afterPropertiesSet() throws RemoteException { 230 prepare(); 231 } 232 233 /** 234 * Initialize this service exporter, registering the service as RMI object. 235 * <p>Creates an RMI registry on the specified port if none exists. 236 * @throws RemoteException if service registration failed 237 */ 238 public void prepare() throws RemoteException { 239 checkService(); 240 241 if (this.serviceName == null) { 242 throw new IllegalArgumentException("Property 'serviceName' is required"); 243 } 244 245 // Check socket factories for exported object. 246 if (this.clientSocketFactory instanceof RMIServerSocketFactory) { 247 this.serverSocketFactory = (RMIServerSocketFactory) this.clientSocketFactory; 248 } 249 if ((this.clientSocketFactory != null && this.serverSocketFactory == null) || 250 (this.clientSocketFactory == null && this.serverSocketFactory != null)) { 251 throw new IllegalArgumentException( 252 "Both RMIClientSocketFactory and RMIServerSocketFactory or none required"); 253 } 254 255 // Check socket factories for RMI registry. 256 if (this.registryClientSocketFactory instanceof RMIServerSocketFactory) { 257 this.registryServerSocketFactory = (RMIServerSocketFactory) this.registryClientSocketFactory; 258 } 259 if (this.registryClientSocketFactory == null && this.registryServerSocketFactory != null) { 260 throw new IllegalArgumentException( 261 "RMIServerSocketFactory without RMIClientSocketFactory for registry not supported"); 262 } 263 264 this.createdRegistry = false; 265 266 // Determine RMI registry to use. 267 if (this.registry == null) { 268 this.registry = getRegistry(this.registryHost, this.registryPort, 269 this.registryClientSocketFactory, this.registryServerSocketFactory); 270 this.createdRegistry = true; 271 } 272 273 // Initialize and cache exported object. 274 this.exportedObject = getObjectToExport(); 275 276 if (logger.isInfoEnabled()) { 277 logger.info("Binding service '" + this.serviceName + "' to RMI registry: " + this.registry); 278 } 279 280 // Export RMI object. 281 if (this.clientSocketFactory != null) { 282 UnicastRemoteObject.exportObject( 283 this.exportedObject, this.servicePort, this.clientSocketFactory, this.serverSocketFactory); 284 } 285 else { 286 UnicastRemoteObject.exportObject(this.exportedObject, this.servicePort); 287 } 288 289 // Bind RMI object to registry. 290 try { 291 if (this.replaceExistingBinding) { 292 this.registry.rebind(this.serviceName, this.exportedObject); 293 } 294 else { 295 this.registry.bind(this.serviceName, this.exportedObject); 296 } 297 } 298 catch (AlreadyBoundException ex) { 299 // Already an RMI object bound for the specified service name... 300 unexportObjectSilently(); 301 throw new IllegalStateException( 302 "Already an RMI object bound for name '" + this.serviceName + "': " + ex.toString()); 303 } 304 c/a> 305 // Registry binding failed: let's unexport the RMI object as well. 306 unexportObjectSilently(); 307 throw ex; 308 } 309 } 310 311 312 /** 313 * Locate or create the RMI registry for this exporter. 314 * @param registryHost the registry host to use (if this is specified, 315 * no implicit creation of a RMI registry will happen) 316 * @param registryPort the registry port to use 317 * @param clientSocketFactory the RMI client socket factory for the registry (if any) 318 * @param serverSocketFactory the RMI server socket factory for the registry (if any) 319 * @return the RMI registry 320 * @throws RemoteException if the registry couldn't be located or created 321 */ 322 protected Registry getRegistry(String registryHost, int registryPort, 323 RMIClientSocketFactory clientSocketFactory, RMIServerSocketFactory serverSocketFactory) 324 throws RemoteException { 325 326 if (registryHost != null) { 327 // Host explicitly specified: only lookup possible. 328 if (logger.isInfoEnabled()) { 329 logger.info("Looking for RMI registry at port '" + registryPort + "' of host [" + registryHost + "]"); 330 } 331 Registry reg = LocateRegistry.getRegistry(registryHost, registryPort, clientSocketFactory); 332 testRegistry(reg); 333 return reg; 334 } 335 336 else { 337 return getRegistry(registryPort, clientSocketFactory, serverSocketFactory); 338 } 339 } 340 341 /** 342 * Locate or create the RMI registry for this exporter. 343 * @param registryPort the registry port to use 344 * @param clientSocketFactory the RMI client socket factory for the registry (if any) 345 * @param serverSocketFactory the RMI server socket factory for the registry (if any) 346 * @return the RMI registry 347 * @throws RemoteException if the registry couldn't be located or created 348 */ 349 protected Registry getRegistry( 350 int registryPort, RMIClientSocketFactory clientSocketFactory, RMIServerSocketFactory serverSocketFactory) 351 throws RemoteException { 352 353 if (clientSocketFactory != null) { 354 if (this.alwaysCreateRegistry) { 355 logger.info("Creating new RMI registry"); 356 return LocateRegistry.createRegistry(registryPort, clientSocketFactory, serverSocketFactory); 357 } 358 if (logger.isInfoEnabled()) { 359 logger.info("Looking for RMI registry at port '" + registryPort + "', using custom socket factory"); 360 } 361 synchronized (LocateRegistry.class) { 362 try { 363 // Retrieve existing registry. 364 Registry reg = LocateRegistry.getRegistry(null, registryPort, clientSocketFactory); 365 testRegistry(reg); 366 return reg; 367 } 368 catch (RemoteException ex) { 369 logger.debug("RMI registry access threw exception", ex); 370 logger.info("Could not detect RMI registry - creating new one"); 371 // Assume no registry found -> create new one. 372 return LocateRegistry.createRegistry(registryPort, clientSocketFactory, serverSocketFactory); 373 } 374 } 375 } 376 377 else { 378 return getRegistry(registryPort); 379 } 380 } 381 382 /** 383 * Locate or create the RMI registry for this exporter. 384 * @param registryPort the registry port to use 385 * @return the RMI registry 386 * @throws RemoteException if the registry couldn't be located or created 387 */ 388 protected Registry getRegistry(int registryPort) throws RemoteException { 389 if (this.alwaysCreateRegistry) { 390 logger.info("Creating new RMI registry"); 391 return LocateRegistry.createRegistry(registryPort); 392 } 393 if (logger.isInfoEnabled()) { 394 logger.info("Looking for RMI registry at port '" + registryPort + "'"); 395 } 396 synchronized (LocateRegistry.class) { 397 try { 398 // Retrieve existing registry. 399 Registry reg = LocateRegistry.getRegistry(registryPort); 400 testRegistry(reg); 401 return reg; 402 } 403 catch (RemoteException ex) { 404 logger.debug("RMI registry access threw exception", ex); 405 logger.info("Could not detect RMI registry - creating new one"); 406 // Assume no registry found -> create new one. 407 return LocateRegistry.createRegistry(registryPort); 408 } 409 } 410 } 411 412 /** 413 * Test the given RMI registry, calling some operation on it to 414 * check whether it is still active. 415 * <p>Default implementation calls {@code Registry.list()}. 416 * @param registry the RMI registry to test 417 * @throws RemoteException if thrown by registry methods 418 * @see java.rmi.registry.Registry#list() 419 */ 420 protected void testRegistry(Registry registry) throws RemoteException { 421 registry.list(); 422 } 423 424 425 /** 426 * Unbind the RMI service from the registry on bean factory shutdown. 427 */ 428 @Override 429 public void destroy() throws RemoteException { 430 if (logger.isInfoEnabled()) { 431 logger.info("Unbinding RMI service '" + this.serviceName + 432 "' from registry" + (this.createdRegistry ? (" at port '" + this.registryPort + "'") : "")); 433 } 434 try { 435 this.registry.unbind(this.serviceName); 436 } 437 catch (NotBoundException ex) { 438 if (logger.isWarnEnabled()) { 439 logger.warn("RMI service '" + this.serviceName + "' is not bound to registry" + 440 (this.createdRegistry ? (" at port '" + this.registryPort + "' anymore") : ""), ex); 441 } 442 } 443 finally { 444 unexportObjectSilently(); 445 } 446 } 447 448 /** 449 * Unexport the registered RMI object, logging any exception that arises. 450 */ 451 private void unexportObjectSilently() { 452 try { 453 UnicastRemoteObject.unexportObject(this.exportedObject, true); 454 } 455 catch (NoSuchObjectException ex) { 456 if (logger.isWarnEnabled()) { 457 logger.warn("RMI object for service '" + this.serviceName + "' is not exported anymore", ex); 458 } 459 } 460 } 461 462}