Java RMI进阶之源码篇 - 高飞网
154 人阅读

Java RMI进阶之源码篇

2017-07-28 02:09:46

1. 暴露远程对象

    下面的代码是之前一篇文章中示例的代码,其中第二行,暴露了一个远程对象,返回一个存根

AccountManagerImpl am = new AccountManagerImpl();
IAccountManager amStub = (IAccountManager) UnicastRemoteObject.exportObject(am, 0);

1)首先,进入这个方法看一下,该方法用指定的端口,导出了一个远程对象,使之可以接收方法调用。

    /**
     * Exports the remote object to make it available to receive incoming
     * calls, using the particular supplied port.
     * @param obj the remote object to be exported
     * @param port the port to export the object on
     * @return remote object stub
     * @exception RemoteException if export fails
     * @since 1.2
     */
    public static Remote exportObject(Remote obj, int port)
        throws RemoteException
    {
        return exportObject(obj, new UnicastServerRef(port));
    }

接着调用了私有方法exportObject,该方法将用指定的服务引用导出了这个指定的远程对象(AccountManagerImpl实例

    /**
     * Exports the specified object using the specified server ref.
     */
    private static Remote exportObject(Remote obj, UnicastServerRef sref)
        throws RemoteException
    {
        // if obj extends UnicastRemoteObject, set its ref.
        if (obj instanceof UnicastRemoteObject) {
            ((UnicastRemoteObject) obj).ref = sref;
        }
        return sref.exportObject(obj, null, false);
    }

注意,这个方法里有两个类,都含有Unicast单词,意思是单播(可参考下:单播),即网络通讯中最常用的点对点通讯,相对于多播和广播。

    这里直接创建一个UnicastServerRef对象。最终用TCPEndpoint来创建一个通信端点地址。

然后跟进sref.exportObject,该方法导出远程对象,创建skeleton和stubs。创建的存根对象是基于远程对象的实现类。

    /**
     * Export this object, create the skeleton and stubs for this
     * dispatcher.  Create a stub based on the type of the impl,
     * initialize it with the appropriate remote reference. Create the
     * target defined by the impl, dispatcher (this) and stub.
     * Export that target via the Ref.
     */
    public Remote exportObject(Remote impl, Object data,
                               boolean permanent)
        throws RemoteException
    {
        Class implClass = impl.getClass();
        Remote stub;

        try {
            stub = Util.createProxy(implClass, getClientRef(), forceStubUse);
        } catch (IllegalArgumentException e) {
            throw new ExportException(
                "remote object implements illegal remote interface", e);
        }
        if (stub instanceof RemoteStub) {
            setSkeleton(impl);
        }

        Target target =
            new Target(impl, this, stub, ref.getObjID(), permanent);
        ref.exportObject(target);
        hashToMethod_Map = hashToMethod_Maps.get(implClass);
        return stub;
    }

    这段是核心代码,用远程对象类型(AccountManagerImpl)创建了一个代理,即存根stub。

继续跟进ref.exportObject,会在EndPoint上监听一个端口。


2. 获取注册表

Registry registry = LocateRegistry.getRegistry();

如果没有指定端口,则使用默认的端口1099

    /**
     * Returns a reference to the the remote object <code>Registry</code> for
     * the local host on the default registry port of 1099.
     *
     * @return reference (a stub) to the remote object registry
     * @exception RemoteException if the reference could not be created
     * @since JDK1.1
     */
    public static Registry getRegistry()
        throws RemoteException
    {
        return getRegistry(null, Registry.REGISTRY_PORT);
    }

    这里获取注册表时,也是把Registry作为一个远程对象,与上面的创建过程类似,只不过是用sun.rmi.registry.RegistryImpl类创建了一个代理

    /**
     * Returns a locally created remote reference to the remote object
     * <code>Registry</code> on the specified <code>host</code> and
     * <code>port</code>.  Communication with this remote registry will
     * use the supplied <code>RMIClientSocketFactory</code> <code>csf</code>
     * to create <code>Socket</code> connections to the registry on the
     * remote <code>host</code> and <code>port</code>.
     *
     * @param host host for the remote registry
     * @param port port on which the registry accepts requests
     * @param csf  client-side <code>Socket</code> factory used to
     *      make connections to the registry.  If <code>csf</code>
     *      is null, then the default client-side <code>Socket</code>
     *      factory will be used in the registry stub.
     * @return reference (a stub) to the remote registry
     * @exception RemoteException if the reference could not be created
     * @since 1.2
     */
    public static Registry getRegistry(String host, int port,
                                       RMIClientSocketFactory csf)
        throws RemoteException

sun.rmi.registry.RegistryImpl是注册表类,其中有一个重要的属性,即

private Hashtable<String, Remote> bindings = new Hashtable<String, Remote>(101);

用来绑定远程对象到指定的名字上面。

3. 绑定方法

registry.rebind("accountManager", amStub);

最终后调用RegistryImpl的rebind方法

    /**
     * Rebind the name to a new object, replaces any existing binding.
     * @exception RemoteException If remote operation failed.
     */
    public void rebind(String name, Remote obj)
        throws RemoteException, AccessException
    {
        checkAccess("Registry.rebind");
        bindings.put(name, obj);
    }

4. 发现方法

 Registry registry = LocateRegistry.getRegistry();
 IAccountManager accountManager = (IAccountManager) registry.lookup("accountManager");

这里首先也是获取了一下注册表,而前面提到过,这个registry已经是一个代码,对它的操作,同时也是远程方法调用,之后调用lookup方法,也是通过网络去服务端查询方法的。


// implementation of lookup(String)
    public java.rmi.Remote lookup(java.lang.String $param_String_1)
	throws java.rmi.AccessException, java.rmi.NotBoundException, java.rmi.RemoteException
    {
	try {
	    java.rmi.server.RemoteCall call = ref.newCall((java.rmi.server.RemoteObject) this, operations, 2, interfaceHash);
	    try {
		java.io.ObjectOutput out = call.getOutputStream();
		out.writeObject($param_String_1);
	    } catch (java.io.IOException e) {
		throw new java.rmi.MarshalException("error marshalling arguments", e);
	    }
	    ref.invoke(call);
	    java.rmi.Remote $result;
	    try {
		java.io.ObjectInput in = call.getInputStream();
		$result = (java.rmi.Remote) in.readObject();
	    } catch (java.io.IOException e) {
		throw new java.rmi.UnmarshalException("error unmarshalling return", e);
	    } catch (java.lang.ClassNotFoundException e) {
		throw new java.rmi.UnmarshalException("error unmarshalling return", e);
	    } finally {
		ref.done(call);
	    }
	    return $result;
	} catch (java.lang.RuntimeException e) {
	    throw e;
	} catch (java.rmi.RemoteException e) {
	    throw e;
	} catch (java.rmi.NotBoundException e) {
	    throw e;
	} catch (java.lang.Exception e) {
	    throw new java.rmi.UnexpectedException("undeclared checked exception", e);
	}
    }

注意,这里的ref.newCall((java.rmi.server.RemoteObject) this, operations, 2, interfaceHash);就是调用了Registry的"java.rmi.Remote lookup(java.lang.String)"方法,通过远程调用后,最后落脚到RegistryImpl上:

    /**
     * Returns the remote object for specified name in the registry.
     * @exception RemoteException If remote operation failed.
     * @exception NotBound If name is not currently bound.
     */
    public Remote lookup(String name)
        throws RemoteException, NotBoundException
    {
        synchronized (bindings) {
            Remote obj = bindings.get(name);
            if (obj == null)
                throw new NotBoundException(name);
            return obj;
        }
    }

从注册表中得到这个远程对象,因为这个远程对象是一个存根,而存根封装了网络通讯的细节,那发起一个方法调用时,是怎样的流程?


5. 远程方法调用

IAccount account = accountManager.getAccount();

这里的accountManager已经是一个代理,一个存根了。因此对他的调用,走的是Account_Stub代理的调用


因此,最终调用的是代理对象java.rmi.server.RemoteObjectInvocationHandler的invoke方法

    /**
     * Processes a method invocation made on the encapsulating
     * proxy instance, <code>proxy</code>, and returns the result.
     *
     * <p><code>RemoteObjectInvocationHandler</code> implements this method
     * as follows:
     *
     * <p>If <code>method</code> is one of the following methods, it
     * is processed as described below:
     *
     * <ul>
     *
     * <li>{@link Object#hashCode Object.hashCode}: Returns the hash
     * code value for the proxy.
     *
     * <li>{@link Object#equals Object.equals}: Returns <code>true</code>
     * if the argument (<code>args[0]</code>) is an instance of a dynamic
     * proxy class and this invocation handler is equal to the invocation
     * handler of that argument, and returns <code>false</code> otherwise.
     *
     * <li>{@link Object#toString Object.toString}: Returns a string
     * representation of the proxy.
     * </ul>
     *
     * <p>Otherwise, a remote call is made as follows:
     *
     * <ul>
     * <li>If <code>proxy</code> is not an instance of the interface
     * {@link Remote}, then an {@link IllegalArgumentException} is thrown.
     *
     * <li>Otherwise, the {@link RemoteRef#invoke invoke} method is invoked
     * on this invocation handler's <code>RemoteRef</code>, passing
     * <code>proxy</code>, <code>method</code>, <code>args</code>, and the
     * method hash (defined in section 8.3 of the "Java Remote Method
     * Invocation (RMI) Specification") for <code>method</code>, and the
     * result is returned.
     *
     * <li>If an exception is thrown by <code>RemoteRef.invoke</code> and
     * that exception is a checked exception that is not assignable to any
     * exception in the <code>throws</code> clause of the method
     * implemented by the <code>proxy</code>'s class, then that exception
     * is wrapped in an {@link UnexpectedException} and the wrapped
     * exception is thrown.  Otherwise, the exception thrown by
     * <code>invoke</code> is thrown by this method.
     * </ul>
     *
     * <p>The semantics of this method are unspecified if the
     * arguments could not have been produced by an instance of some
     * valid dynamic proxy class containing this invocation handler.
     *
     * @param proxy the proxy instance that the method was invoked on
     * @param method the <code>Method</code> instance corresponding to the
     * interface method invoked on the proxy instance
     * @param args an array of objects containing the values of the
     * arguments passed in the method invocation on the proxy instance, or
     * <code>null</code> if the method takes no arguments
     * @return the value to return from the method invocation on the proxy
     * instance
     * @throws  Throwable the exception to throw from the method invocation
     * on the proxy instance
     **/
    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);
        }
    }

    而这个方法,会使用RemoteRef,通过网络,传递代理对象(存根),方法名,参数列表,通过远程调用以后,获取结果,返回。调用实现的核心是Object sun.rmi.server.UnicastRef.invoke(Remote obj, Method method, Object[] params, long opnum) throws Exception方法,由于方法过长,取一部分如下:

Connection conn = ref.getChannel().newConnection();

// create call context
call = new StreamRemoteCall(conn, ref.getObjID(), -1, opnum);
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);
	}
}
 // unmarshal return
call.executeCall();
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);

首先获取网络链接,通过opnum(方法的hash值)得到相应的方法call,然后设置参数,最后获取返回值。

还没有评论!
54.198.221.13