alljoyn:基于java动态代理的RPC实现原理分析

 alljoyn是由高通开源,allseen组织下,作为IOT的一个开源软件框架。
本文分析它的core部分的远程调用方法的实现过程。
 
以android core sdk的release版本中的simple程序为例子。
(eg alljoyn-14.06.00a-android-sdk-relalljoyn-androidcorealljoyn-14.06.00a-reljavasamplessimpleclient)
 
1. 下面是一个定义为alljoyn接口,并定义了一个远程调用方法Ping.(早期版本的alljyon的RPC实现,需要依赖这个接口定义文件,
这个存在的问题是client, 和service端需要保持接口定义一致,service接口变化后,client不得不跟着更新这个文件,目前alljoyn做了改进,
可以通过将接口定义的元数据信息xml格式文件运行时再发送给client, client可以动态解析出接口(利用busattachment的createinterfacesfromxml))
/*
 * The BusInterface annotation is used to tell the code that this interface is an AllJoyn interface.
 *
 * The 'name' value is used to specify by which name this interface will be known.  If the name is
 * not given the fully qualified name of the Java interface is be used.  In most instances its best
 * to assign an interface name since it helps promote code reuse.
 */
@BusInterface(name = "org.alljoyn.bus.samples.simple.SimpleInterface")
public interface SimpleInterface {

    /*
     * The BusMethod annotation signifies that this function should be used as part of the AllJoyn
     * interface.  The runtime is smart enough to figure out what the input and output of the method
     * is based on the input/output arguments of the Ping method.
     *
     * All methods that use the BusMethod annotation can throw a BusException and should indicate
     * this fact.
     */
    @BusMethod
    String Ping(String inStr) throws BusException;
}

2. 在client.java,使用这个接口的方法发送RPC请求,并接收返回值(Ping方法表面上看没有任何实现,其实已经封装在alljoyn内部,下面会分析到)

                    / * To communicate with an AllJoyn object, we create a ProxyBusObject.
                     * A ProxyBusObject is composed of a name, path, sessionID and interfaces.
                     *
                     * This ProxyBusObject is located at the well-known SERVICE_NAME, under path
                     * "/SimpleService", uses sessionID of CONTACT_PORT, and implements the SimpleInterface.
                     */
                    mProxyObj =  mBus.getProxyBusObject(SERVICE_NAME,
                                                        "/SimpleService",
                                                        sessionId.value,
                                                        new Class<?>[] { SimpleInterface.class });
                    /* We make calls to the methods of the AllJoyn object through one of its interfaces. */
                    mSimpleInterface =  mProxyObj.getInterface(SimpleInterface.class);
                    。。。 。。。
                    if (mSimpleInterface != null) {
                        sendUiMessage(MESSAGE_PING, msg.obj);
                        String reply = mSimpleInterface.Ping((String) msg.obj);
                        sendUiMessage(MESSAGE_PING_REPLY, reply);
                    }
 
3.原理分析
流程如下图所示
在 mBus.getProxyBusObject函数内部(我们可以看到有传递了new Class<?>[] { SimpleInterface.class }),
会new ProxyBusObject对象, 调用下面的构造函数。
    /**
     * Construct a ProxyBusObject.
     *
     * @param busAttachment  The connection the remote object is on.
     * @param busName        Well-known or unique bus name of remote object.
     * @param objPath        Object path of remote object.
     * @param sessionId      The session ID corresponding to the connection to the object.
     * @param busInterfaces  A list of BusInterfaces that this proxy should respond to.
     * @param secure         the security mode for the remote object
     */
    protected ProxyBusObject(BusAttachment busAttachment, String busName, String objPath, int sessionId,
                             Class[] busInterfaces, boolean secure) {
        this.bus = busAttachment;
        this.busName = busName;
        this.objPath = objPath;
        this.flags = 0;
        create(busAttachment, busName, objPath, sessionId, secure);
        replyTimeoutMsecs = 25000;

        //busInterfaces来自mBus.getProxyBusObject调用,传入的new Class<?>[] { SimpleInterface.class }参数值
        //使用java的Proxy类方法,Handler实现了java InvocationHandler 接口,proxy就是代理对象,来控制SimpleInterface具体主题对象Ping方法访问
        proxy = Proxy.newProxyInstance(busInterfaces[0].getClassLoader(), busInterfaces, new Handler());
        try {
            busConnectionLost =
                getClass().getDeclaredMethod("busConnectionLost", String.class);
            busConnectionLost.setAccessible(true);
        } catch (NoSuchMethodException ex) {
            /* This will not happen */
        }
    }
    /**
     * Gets a proxy to an interface of this remote bus object.
     *
     * @param <T> any class implementation of a interface annotated with AllJoyn interface annotations
     * @param intf one of the interfaces supplied when the proxy bus object was
     *             created
     * @return the proxy implementing the interface
     * @see BusAttachment#getProxyBusObject(String, String, int, Class[])
     */
    public <T> T getInterface(Class<T> intf) {
        @SuppressWarnings(value = "unchecked")
        T p = (T) proxy; //proxy被转成SimpleInterface
        return p;
    }
new Handler()实现java InvocationHandler接口, invoke最终Ping方法的触发(不说调用,理由见methodcall注释)
     public Object invoke(Object proxy, Method method, Object[] args) throws BusException {
            /*
             * Some notes on performance.
             *
             * Reflection is very expensive.  So first pass at optimization is to cache the
             * reflection calls that lookup names and annotations the first time the method is
             * invoked.
             *
             * Using a Method as a HashMap key is expensive.  Using method.getName() as the key
             * is less expensive.  But method names may not be unique (they can be overloaded), so
             * need to fall back to Method.equals if more than one method with the same name exists.
             */
            /*
                java动态代理,利用了反射机制,开销较大,所以Handler定义了一个Invocation类,和invocationCache
                private Map<String, List<Invocation>> invocationCache;, 使用方法名作为key, 并且由于被代理的接口
                方法名由于重载可能重名,所以需要用List<Invocation>
            */
            Invocation invocation = null;
            String methodName = method.getName();
            List<Invocation> invocationList = invocationCache.get(methodName);
            if (invocationList != null) {
                if (invocationList.size() == 1) {
                    /* The fast path. */
                    invocation = invocationList.get(0);
                } else {
                    /* The slow path.  Two Java methods exist with the same name for this proxy. */
                    for (Invocation i : invocationList) {
                        if (method.equals(i.method)) {
                            invocation = i;
                            break;
                        }
                    }
                    if (invocation == null) {
                        invocation = new Invocation(method);
                        invocationList.add(invocation);
                    }
                }
            } else {
                /*
                 * The very slow path.  The first time a proxy method is invoked.
                 *
                 * Walk through all the methods looking for ones that match the invoked method name.
                 * This creates a list of all the cached invocation information that we'll use later
                 * on the next method call.
                 */
                /*
                    第一次invoke到该方法后,可以将其缓存到Invocation对象,看下面Invocation类具体实现,
                    会将接口名,方法名,输入,输出参数,返回值类型记录下来
            若无缓存,每次都需要遍历interfaces的方法列表
*/ invocationList = new ArrayList<Invocation>(); for (Class<?> i : proxy.getClass().getInterfaces()) { for (Method m : i.getMethods()) { if (methodName.equals(m.getName())) { Invocation in = new Invocation(m); invocationList.add(in); if (method.equals(in.method)) { invocation = in; } } } } if (invocation == null) { throw new BusException("No such method: " + method); } invocationCache.put(methodName, invocationList); } Object value = null; if (invocation.isMethod) { /*一般java程序执行invoke时,下面执行的会是method.invoke, 并传入被代理对象实例参数, eg. Object object=method.invoke(instanceObject, args); 而alljoyn则不一样,因为执行rpc方法,其实是将传进来的参数值列表序列化,并发送,而 这一过程alljoyn已经封装了统一接口,因此这里invokehandler的实现不需要有一个具体的被代理对象传入, 而只需获取到args, 并调用methodCall进行序列化并发送,等待响应结果返回value。
          所以看不到Ping方法的实现。     
*/ value = methodCall(bus, invocation.interfaceName, invocation.methodName, invocation.inputSig, invocation.genericReturnType, args, replyTimeoutMsecs, flags); }        ... ...
       
return value; } }
原文地址:https://www.cnblogs.com/europelee/p/4746355.html