JDK1.5以前RMI调用是需要存根与代理的,1.2之后代理类好像看不到了.rmic只会生成存根类.
(1.2之前的JDK,我也没试过,我学习JAVA的时候,1.5就出来了)
开发RMI应用时,在进行bind对象时,会检测远程对象所对应的存根是否存在.这就是常发生的
*_stub.class找不到的问题. STUB用在客户端调用时,Rmi Registry为什么要检测他呢?
这是因为当客户端通过Naming.lookup获取这个远程对象时, Registry会把这个存根对象
或用于生成存根对象meta发给客户端,客户端通过这个存根对象或者通过meta生成存根对象.
进而进行运程对象的方法调用.
JDK1.5之后,有了新变化: Naming.lookup获取不再是这个存根对象,而是一个动态代理类.
这里只简单描述一下过程:
1. 获取的代理对象, 其InvocationHandler为RemoteObjectInvocationHandler:
public class RemoteObjectInvocationHandler extends RemoteObject implements InvocationHandler
2. RemoteObjectInvocationHandler的方法为:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getDeclaringClass() == Object.class) { return invokeObjectMethod(proxy, method, args); } else { return invokeRemoteMethod(proxy, method, args); } }
这里把调用分为两部分:invokeObjectMethod和invokeRemoteMethod.我们主要看
invokeRemoteMethod:
private Object invokeRemoteMethod(Object proxy, Method method, Object[] args) throws Exception { try { if (!(proxy instanceof Remote)) { throw new IllegalArgumentException( "proxy not Remote instance"); } return ref.invoke((Remote) proxy, method, args, getMethodHash(method)); } catch (Exception e) { if (!(e instanceof RuntimeException)) { Class<?> cl = proxy.getClass(); try { method = cl.getMethod(method.getName(), method.getParameterTypes()); } catch (NoSuchMethodException nsme) { throw (IllegalArgumentException) new IllegalArgumentException().initCause(nsme); } Class<?> thrownType = e.getClass(); for (Class<?> declaredType : method.getExceptionTypes()) { if (declaredType.isAssignableFrom(thrownType)) { throw e; } } e = new UnexpectedException("unexpected exception", e); } throw e; } }
主要通过ref.invoke来进行调用,若打开rmic生成的存根类,你会发现调用方法也是一样的。
这个动态代理类,其实就是存根的替代品。这里ref的类为sun/rmi/server/UnicastRef.java
签名为:public class UnicastRef implements RemoteRef, 其invoke方法实现如下:
public Object invoke(Remote obj, Method method, Object[] params, long opnum) throws Exception { if (clientRefLog.isLoggable(Log.VERBOSE)) { clientRefLog.log(Log.VERBOSE, "method: " + method); } if (clientCallLog.isLoggable(Log.VERBOSE)) { logClientCall(obj, method); } Connection conn = ref.getChannel().newConnection(); RemoteCall call = null; boolean reuse = true; /* If the call connection is "reused" early, remember not to * reuse again. */ boolean alreadyFreed = false; try { if (clientRefLog.isLoggable(Log.VERBOSE)) { clientRefLog.log(Log.VERBOSE, "opnum = " + opnum); } // create call context call = new StreamRemoteCall(conn, ref.getObjID(), -1, opnum); // marshal parameters try { ObjectOutput out = call.getOutputStream(); marshalCustomCallData(out); Class<?>[] types = method.getParameterTypes(); for (int i = 0; i < types.length; i++) { marshalValue(types[i], params[i], out); } } catch (IOException e) { clientRefLog.log(Log.BRIEF, "IOException marshalling arguments: ", e); throw new MarshalException("error marshalling arguments", e); } // unmarshal return call.executeCall(); try { Class<?> rtype = method.getReturnType(); if (rtype == void.class) return null; ObjectInput in = call.getInputStream(); /* StreamRemoteCall.done() does not actually make use * of conn, therefore it is safe to reuse this * connection before the dirty call is sent for * registered refs. */ Object returnValue = unmarshalValue(rtype, in); /* we are freeing the connection now, do not free * again or reuse. */ alreadyFreed = true; /* if we got to this point, reuse must have been true. */ clientRefLog.log(Log.BRIEF, "free connection (reuse = true)"); /* Free the call's connection early. */ ref.getChannel().free(conn, true); return returnValue; } catch (IOException e) { clientRefLog.log(Log.BRIEF, "IOException unmarshalling return: ", e); throw new UnmarshalException("error unmarshalling return", e); } catch (ClassNotFoundException e) { clientRefLog.log(Log.BRIEF, "ClassNotFoundException unmarshalling return: ", e); throw new UnmarshalException("error unmarshalling return", e); } finally { try { call.done(); } catch (IOException e) { /* WARNING: If the conn has been reused early, * then it is too late to recover from thrown * IOExceptions caught here. This code is relying * on StreamRemoteCall.done() not actually * throwing IOExceptions. */ reuse = false; } } } catch (RuntimeException e) { /* * Need to distinguish between client (generated by the * invoke method itself) and server RuntimeExceptions. * Client side RuntimeExceptions are likely to have * corrupted the call connection and those from the server * are not likely to have done so. If the exception came * from the server the call connection should be reused. */ if ((call == null) || (((StreamRemoteCall) call).getServerException() != e)) { reuse = false; } throw e; } catch (RemoteException e) { /* * Some failure during call; assume connection cannot * be reused. Must assume failure even if ServerException * or ServerError occurs since these failures can happen * during parameter deserialization which would leave * the connection in a corrupted state. */ reuse = false; throw e; } catch (Error e) { /* If errors occurred, the connection is most likely not * reusable. */ reuse = false; throw e; } finally { /* alreadyFreed ensures that we do not log a reuse that * may have already happened. */ if (!alreadyFreed) { if (clientRefLog.isLoggable(Log.BRIEF)) { clientRefLog.log(Log.BRIEF, "free connection (reuse = " + reuse + ")"); } ref.getChannel().free(conn, reuse); } } }
我们大致看出,在这里完成Socket通信.细节我就不描述了.知道是这么回事就成了。