一、前言
在 Retrofit 知識梳理(1) - 流程分析 中,我們對於Retrofit
的流程進行了簡單的分析,大家談到Retrofit
的時候,往往也會提到動態代理,今天這篇文章,我們就來一起研究一下這一過程的內部實現。
二、內部實現
涉及到動態代理的部分為下面這兩句:
//1.返回代理物件
GitHubService service = retrofit.create(GitHubService.class);
//2.呼叫該代理物件的介面方法
Call<ResponseBody> call = service.listRepos("octocat");
複製程式碼
2.1 生成代理物件
第一步的目的就是通過Retrofit
的create
方法,根據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
物件,就是通過第三個引數所傳入的物件,而當我們呼叫代理類所宣告的方法時,最終會呼叫到該InvocationHandler
的invoke
方法,通過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 示例詳解
按照我們之前的分析,在上面的模擬過程中的第三步,會返回一個代理類,我們來看一下這個代理類具體是什麼:
通過斷點,我們可以看到它不再是我們之前宣告的介面,而是一個代理類$Proxy
,它的內部有一個InvocationHandler
的實現類h
,也就是我們通過newProxyInstance
傳入的例項。
之後,我們呼叫該代理類的介面方法,那麼上面的h
物件的invoke
方法的就被回撥,我們可以從中獲取到傳入的引數、註解等資訊,通過該資訊,我們構造出一個CallObject
物件。
最後,再通過這個CallObject
發起請求:
四、總結
普通的代理模式,往往會在InvocationHandler
的實現類中包含一個介面的實現類,當該InvocationHandler
的invoke
方法被回撥時,再呼叫他所包含的介面實現類進行操作,並在之前和之後進行一些處理的操作。對於標準的動態代理,可以參考 這篇文章。
Retrofit
用到的動態代理,並不能算是嚴格的代理模式。它只是利用了代理模式中invoke
這一中轉過程,來解析介面中的註解宣告,然後通過這些註解宣告來建立一個請求類,最終再通過該請求類來發起請求。
也就是說,Retrofit
所關注的重點在於如何建立invoke
方法所返回的例項,而普通的代理模式則在於控制介面實現類的訪問。