RMI原理一記遠程調用

JDK1.5之前RMI調用是須要存根與代理的,1.2以後代理類好像看不到了.rmic只會生成存根類.java

(1.2以前的JDK,我也沒試過,我學習JAVA的時候,1.5就出來了)app

開發RMI應用時,在進行bind對象時,會檢測遠程對象所對應的存根是否存在.這就是常發生的ide

*_stub.class找不到的問題. STUB用在客戶端調用時,Rmi Registry爲何要檢測他呢?學習

這是由於當客戶端經過Naming.lookup獲取這個遠程對象時, Registry會把這個存根對象ui

或用於生成存根對象meta發給客戶端,客戶端經過這個存根對象或者經過meta生成存根對象.this

進而進行運程對象的方法調用.代理


JDK1.5以後,有了新變化: Naming.lookup獲取再也不是這個存根對象,而是一個動態代理類.code

這裏只簡單描述一下過程:server


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通訊.細節我就不描述了.知道是這麼回事就成了。

相關文章
相關標籤/搜索