都知道Retrofit
是通過動態代理來生成代理對象作為網絡請求的發起者。
今天就來看下動態代理是怎么操作的?;蛘哒f是怎么讓一個貌似接口的對象調用它的抽象方法呢?
先來看代碼
public static void main(String[] args) {
Factory factory = new Factory();
Bird bird = factory.create(Bird.class);
bird.fly();
}
interface Bird {
void fly();
}
這里代碼通過一個Factory
實例調用create
方法,傳入一個接口的class
對象就可以返回一個接口的實例,可以調用接口中的方法fly()
。
而在我們的靜態代碼中并沒有一個類去實現了這個Bird
接口(完整代碼可以看下方)。那么這個對象到底是從哪里來的呢?
完整代碼如下
public class DynamicProxy {
interface Bird {
void fly();
}
public static void main(String[] args) {
Factory factory = new Factory();
Bird bird = factory.create(Bird.class);
bird.fly();
}
static class Factory implements InvocationHandler {
public <T> T create(Class<T> target) {
return (T) Proxy.newProxyInstance(target.getClassLoader(),
new Class[]{target},
this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("flying...");
return null;
}
}
}
在調用bird.fly()
時,輸出結果為flying...
,很明顯,代碼中就如同開始所說的,并不存在一個實現了Bird
接口的子類,而bird
又實實在在調用了fly()
方法。唯一的可能就是bird
是接口的實例(或者說實現接口的子類的對象)。這里看起來似乎就有些詭異了。
當然編程沒有魔法,這里只是利用到了Java
的動態代理,通過Proxy.newProxyInstance()
方法生成實現了指定接口的子類,然后返回了這個動態生成類的實例對象。
這個子類在調用接口中的方法時,其實調用的是InvocationHandler
的invoke()
方法。在此方法中會有對應參數的回調,可以根據這些參數做出合適的攔截/增強等操作。
要留意的一點是JDK
提供的動態代理,動態生成的子類是繼承自Proxy
類的,,而Java
是不支持多繼承的,所以很顯然。通過動態代理返回的對象必然是以接口形式來接收的,擴展的只有接口和實現接口的子類,對于一些沒有實現接口的類是沒有辦法進行擴展的。(cglib
支持擴展類)
知道了這些也就明白了Retrofit
的動態代理大致是個什么邏輯。
下面仿造Retrofit
通過方法上注解來模擬一次網絡請求吧。
通過在接口中在方法上的注解,確定一些請求的參數。然后創建代理對象,在方法中拼接處完整的Url
, https://github.com/search?q=java
請求網絡,并且在響應頭中的Status
字段打印出來。
public interface Bird {
@Protocol()
@Method()
@Path("search")
@Query("q=java")
@Url("github.com")
String fly();
}
public class Test {
public static void main(String[] args) {
Factory factory = new Factory();
Bird bird = factory.create(Bird.class);
String status = bird.fly();
System.out.println(status);//輸出 Status: 200 OK
}
}
部分代碼
public class Factory implements InvocationHandler {
OkHttpClient httpClient = new OkHttpClient();
public <T> T create(Class<T> target) {
return (T) Proxy.newProxyInstance(target.getClassLoader(),
new Class[]{target},
this);
}
public Object invoke(Object proxy, java.lang.reflect.Method method, Object[] args) throws Throwable {
String get = method.getAnnotation(Method.class).method();
String protocol = method.getAnnotation(Protocol.class).value();
String url = method.getAnnotation(Url.class).value();
String path = method.getAnnotation(Path.class).value();
String query = method.getAnnotation(Query.class).value();
String entire_url = protocol + "://" + url + "/" + path + "?" + query;
System.out.println(entire_url);
Request build = new Request.Builder().url(entire_url).get().build();
return httpClient.newCall(build).execute().headers().get("Status");
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Method {
String method() default "GET";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Url {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Protocol {
String value() default "https";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Path {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Query {
String value() default "";
}