都知道Retrofit
是通过动态代理来生成代理对象作为网络请求的发起者。
今天就来看下动态代理是怎么操作的。或者说是怎么让一个貌似接口的对象调用它的抽象方法呢?
先来看代码
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 "";
}