zoukankan      html  css  js  c++  java
  • 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; } }
  • 相关阅读:
    zyb的面试
    Codeforces Round #514 (Div. 2)
    Maximum GCD(fgets读入)
    Harmonic Number(调和级数+欧拉常数)
    Codeforces Round #516 (Div. 2, by Moscow Team Olympiad)
    Sigma Function (平方数与平方数*2的约数和是奇数)
    Leading and Trailing (数论)
    【贪心】【CF3D】 Least Cost Bracket Sequence
    【套题】qbxt国庆刷题班D1
    【极值问题】【CF1063B】 Labyrinth
  • 原文地址:https://www.cnblogs.com/europelee/p/4746355.html
Copyright © 2011-2022 走看看