Retrofit 知識梳理(2) Retrofit 動態代理內部實現

澤毛發表於2017-12-21

一、前言

Retrofit 知識梳理(1) - 流程分析 中,我們對於Retrofit的流程進行了簡單的分析,大家談到Retrofit的時候,往往也會提到動態代理,今天這篇文章,我們就來一起研究一下這一過程的內部實現。

二、內部實現

涉及到動態代理的部分為下面這兩句:

//1.返回代理物件
GitHubService service = retrofit.create(GitHubService.class);
//2.呼叫該代理物件的介面方法
Call<ResponseBody> call = service.listRepos("octocat");
複製程式碼

2.1 生成代理物件

第一步的目的就是通過Retrofitcreate方法,根據GithubService這個介面所宣告的方法來得到一個代理物件:

  @SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.
  public <T> T create(final Class<T> service) {
    //...忽略
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();

          @Override public Object invoke(Object proxy, Method method, Object[] args)
              throws Throwable {
            //...

            //1.得到ServiceMethod物件.
            ServiceMethod<Object, Object> serviceMethod = (ServiceMethod<Object, Object>) loadServiceMethod(method);

            //2.通過serviceMethod和args來構建OkHttpCall物件,args就是“octocat”.
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);

            //3.其實就是呼叫ExecutorCallbackCall.adapt方法.
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }
複製程式碼

可以看到,這裡面最核心的方法就是:

Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
複製程式碼

以上三個形參的含義為:

  • ClassLoader:表示由哪一個ClassLoader來載入這個代理類。
  • Class<?>[]:一個interface物件的陣列,表明將要給被代理的物件提供一組什麼樣的介面來訪問,這個可能比較抽象。從上面的例子來說,我們傳入的是GitHubService.class,而它是一個介面,其方法包含有listRepos,那麼Proxy.newProxyInstance所生成的代理物件也會有這個介面listRepos,所以我們才能在第二步當中呼叫它的方法。
  • InvocationHandler:當呼叫代理物件的介面方法時,最終會回撥到這個InvocationHandler物件的invoke方法中。

在對形參進行了簡要的介紹之後,我們再來看一下其內部的實現:

    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
        if (h == null) {
            throw new NullPointerException();
        }
        //1.根據前兩個引數建立一個代理類.
        Class<?> cl = getProxyClass0(loader, interfaces);
        try {
            //2.例項化該代理類,並將第三個引數作為成員變數.
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            return newInstance(cons, h);
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString());
        }
    }
複製程式碼

可以看到,生成代理物件又可以細分為兩步:

  • 根據類載入器和宣告的介面,首先建立一個代理類,該代理類繼承於Proxy,並且它實現了interfaces陣列中所宣告的介面
  • 例項化第一步中建立的代理類,並將第三個引數所物件的InvocationHandler傳入作為其成員變數h

2.1.1 建立代理類

建立代理類的程式碼比較多,也就是對應於getProxyClass0方法:

private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }
        //最終要建立的代理類
        Class<?> proxyClass = null;
        String[] interfaceNames = new String[interfaces.length];
        Set<Class<?>> interfaceSet = new HashSet<>();
        //1.遍歷傳入的介面陣列
        for (int i = 0; i < interfaces.length; i++) {
            String interfaceName = interfaces[i].getName();
            Class<?> interfaceClass = null;
            try {
                interfaceClass = Class.forName(interfaceName, false, loader);
            } catch (ClassNotFoundException e) {
            }
            if (interfaceClass != interfaces[i]) {
                throw new IllegalArgumentException(
                    interfaces[i] + " is not visible from class loader");
            }
            //必須是介面
            if (!interfaceClass.isInterface()) {
                throw new IllegalArgumentException(
                    interfaceClass.getName() + " is not an interface");
            }
            //不允許重複
            if (interfaceSet.contains(interfaceClass)) {
                throw new IllegalArgumentException(
                    "repeated interface: " + interfaceClass.getName());
            }
            interfaceSet.add(interfaceClass);

            interfaceNames[i] = interfaceName;
        }
        //2.如果之前已經生成過,那麼就不需要再去生成.
        List<String> key = Arrays.asList(interfaceNames);
        Map<List<String>, Object> cache;
        synchronized (loaderToCache) {
            cache = loaderToCache.get(loader);
            if (cache == null) {
                cache = new HashMap<>();
                loaderToCache.put(loader, cache);
            }
        }
        synchronized (cache) {
            do {
                Object value = cache.get(key);
                if (value instanceof Reference) {
                    proxyClass = (Class<?>) ((Reference) value).get();
                }
                if (proxyClass != null) {
                    return proxyClass;
                } else if (value == pendingGenerationMarker) {
                    try {
                        cache.wait();
                    } catch (InterruptedException e) {
                    }
                    continue;
                } else {
                    cache.put(key, pendingGenerationMarker);
                    break;
                }
            } while (true);
        }
        //3.真正的產生操作.
        try {
            String proxyPkg = null;
            for (int i = 0; i < interfaces.length; i++) {
                int flags = interfaces[i].getModifiers();
                if (!Modifier.isPublic(flags)) {
                    String name = interfaces[i].getName();
                    int n = name.lastIndexOf('.');
                    String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                        throw new IllegalArgumentException(
                            "non-public interfaces from different packages");
                    }
                }
            }

            if (proxyPkg == null) {
                proxyPkg = "";
            }

            {
                List<Method> methods = getMethods(interfaces);
                Collections.sort(methods, ORDER_BY_SIGNATURE_AND_SUBTYPE);
                validateReturnTypes(methods);
                List<Class<?>[]> exceptions = deduplicateAndGetExceptions(methods);

                Method[] methodsArray = methods.toArray(new Method[methods.size()]);
                Class<?>[][] exceptionsArray = exceptions.toArray(new Class<?>[exceptions.size()][]);

                final long num;
                synchronized (nextUniqueNumberLock) {
                    num = nextUniqueNumber++;
                }
                String proxyName = proxyPkg + proxyClassNamePrefix + num;

                proxyClass = generateProxy(proxyName, interfaces, loader, methodsArray,
                        exceptionsArray);
            }

            proxyClasses.put(proxyClass, null);

        } finally {
            synchronized (cache) {
                if (proxyClass != null) {
                    cache.put(key, new WeakReference<Class<?>>(proxyClass));
                } else {
                    cache.remove(key);
                }
                cache.notifyAll();
            }
        }
        return proxyClass;
    }
複製程式碼

2.1.2 例項化代理類

當建立完代理類之後,接下來就是進行例項化,這裡例項化呼叫的是引數為InvocationHandler的建構函式:

protected Proxy(InvocationHandler h) {
     this.h = h;
}
複製程式碼

也就是說,最後通過Proxy.newProxyInstance返回的代理類例項,其內部的InvocationHandler物件,就是通過第三個引數所傳入的物件,而當我們呼叫代理類所宣告的方法時,最終會呼叫到該InvocationHandlerinvoke方法,通過invoke方法的引數,我們又可以區分具體呼叫的方法。

2.2 呼叫代理物件的介面方法

通過第一步,我們就可以得到一個代理物件,而當我們呼叫這個代理物件的介面方法之後,該代理物件又會回撥它的基類Proxy中的這個方法:

private static Object invoke(Proxy proxy, Method method, Object[] args) throws Throwable {
    InvocationHandler h = proxy.h;
    return h.invoke(proxy, method, args);
}
複製程式碼

invoke的三個引數解釋為:

  • Proxy:代理物件例項,也就是上面在2.1中所返回的例項物件。
  • Method:代理物件所被呼叫的對應介面方法。
  • Object[]:介面方法所傳入的引數。

三、示例

在第二節當中,我們對於原始碼進行了簡單的走讀,下面,我們以一個具體的示例來強化一下認識:

3.1 例項程式碼

** (1) 介面類**

對應於我們在上面所定義的GitHubService.java

public interface Subject {
    public CallObject getCallObject(String args);
}
複製程式碼

(2) 請求類

對應於最終發起網路請求的類:

public class CallObject {

    private String args;

    public CallObject(String args) {
        this.args = args;
    }

    public void call() {
        Log.d("simulate", "call args=" + args);
    }
}
複製程式碼

(3) 模擬代理過程

public class Simulator {

    public static void simulate() {
        Class mClass = Subject.class;
        Subject proxySubject = (Subject) Proxy.newProxyInstance(
                mClass.getClassLoader(),
                new Class[]{ mClass },
                new InvocationHandler() {

                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        String modifiedArgs = (String) args[0] + " after modified";
                        Log.d("simulate", "invoke");
                        return new CallObject(modifiedArgs);
                    }

                });
        Log.d("simulate", "createProxy finished");
        CallObject callObject = proxySubject.getCallObject("args");
        Log.d("simulate", "getCallObject finished");
        callObject.call();
    }
}
複製程式碼

3.2 示例詳解

按照我們之前的分析,在上面的模擬過程中的第三步,會返回一個代理類,我們來看一下這個代理類具體是什麼:

Retrofit 知識梳理(2)   Retrofit 動態代理內部實現
通過斷點,我們可以看到它不再是我們之前宣告的介面,而是一個代理類$Proxy,它的內部有一個InvocationHandler的實現類h,也就是我們通過newProxyInstance傳入的例項。

之後,我們呼叫該代理類的介面方法,那麼上面的h物件的invoke方法的就被回撥,我們可以從中獲取到傳入的引數、註解等資訊,通過該資訊,我們構造出一個CallObject物件。

最後,再通過這個CallObject發起請求:

Retrofit 知識梳理(2)   Retrofit 動態代理內部實現

四、總結

普通的代理模式,往往會在InvocationHandler的實現類中包含一個介面的實現類,當該InvocationHandlerinvoke方法被回撥時,再呼叫他所包含的介面實現類進行操作,並在之前和之後進行一些處理的操作。對於標準的動態代理,可以參考 這篇文章

Retrofit用到的動態代理,並不能算是嚴格的代理模式。它只是利用了代理模式中invoke這一中轉過程,來解析介面中的註解宣告,然後通過這些註解宣告來建立一個請求類,最終再通過該請求類來發起請求。

也就是說,Retrofit所關注的重點在於如何建立invoke方法所返回的例項,而普通的代理模式則在於控制介面實現類的訪問。

相關文章