前言
Java动态代理的意义:
动态代理实现了可以在原始类和接口还未知的时候,就确定代理类的代理行为,当代理类与原始类脱离直接联系后,就可以很灵活地重用于不同的应用场景中。
动态代理其实质就是依靠反射来实现的。
1. 静态代理和动态代理的区别
关于静态代理和动态代理的区别,网上的说法众说纷纭,其实总结起来就是两个字 ----- 时机 。
静态代理在 编译期 就已经决定好了。而动态代理在 运行期 才决定代理对象是怎么样的,当调用生成代理类的方法时,会触发invoke方法,可以在其中替换或者扩充方法,使其更加灵活。
具体比较如下:
1. 静态代理
interface Person {
void introduce(Context context);
}
public static class Man implements Person {
@Override
public void introduce(Context context) {
Toast.makeText(context, "Man Man Man !!!", Toast.LENGTH_SHORT).show();
}
}
public static class Woman implements Person {
@Override
public void introduce(Context context) {
Toast.makeText(context, "Woman Woman Woman !!!", Toast.LENGTH_SHORT).show();
}
}
2. 动态代理
Class c1 = Man.class;
Person person = (Person) Proxy.newProxyInstance(c1.getClassLoader(),
c1.getInterfaces(),
new MyInvocationHandler(c1));
person.introduce(MainActivity.this);
public class MyInvocationHandler implements InvocationHandler {
Class c1;
public MyInvocationHandler(Class c1) {
this.c1 = c1;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(c1.newInstance(), args);
}
}
2. 动态代理注意点
动态代理的优点是 灵活、适宜扩展 。但是使用动态代理需要有几个注意点
- 无法摆脱仅支持interface代理圈子
- Proxy.newProxyInstance()参数问题
- Proxy.newProxyInstance()的return值只可强转为所对应的接口类
- 若被代理类为接口,则不可直接调用method.invoke()方法
1. 无法摆脱仅支持interface代理圈子
若上述代码中Man类并没有集成Person类,则无法使用动态代理,否则会报如下错误
2. Proxy.newProxyInstance()参数问题
如上图所示,Proxy类中newProxyInstance方法的第二个参数是接口Class类的集合。动态代理中,被代理类可分为两种: 接口、接口实现类 。因此针对这两种不同类型,传递的参数也是不同的。
3. Proxy.newProxyInstance()的return值只可强转为所对应的接口类
如上图所示,若将对应接口Person类使用其实现类Man类替换,则会报如下错误
4. 若被代理类为接口,则不可直接调用method.invoke()方法
若被代理类为接口,则会报如下错误。
应用举例
- Retrofit框架
1. Retrofit简介
Retrofit与Okhttp共同出自于Square公司,Retrofit就是对Okhttp做了一层封装的网络请求框架。
2. Okhttp框架使用
在研究Retrofit框架底层代码实现原理前,先了解下Okhttp的使用。关于Okhttp框架的使用如下图。
具体代码如下
compile 'com.squareup.okhttp3:okhttp:3.8.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.8.0'
Message message = new Message();
/*获取OkHttpClient(Header文件)*/
OkHttpClient client = getClient();
/*POST+键值对*/
//RequestBody body = new FormBody.Builder()
//.add("type", "android").build();
/*POST+Json*/
JSONObject json = new JSONObject();
json.put("type", "android");
RequestBody body = RequestBody.create(JSON, json.toString());
Request request = new Request.Builder()
.url("http://172.18.1.188/banner/fetch")
.post(body)
.build();
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
message.what = 0;
message.obj = response.body().string();
} else {
message.what = 1;
message.obj = response.body().string();
}
myHandler.sendMessage(message);
private OkHttpClient getClient() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request request = original.newBuilder()
.header("Accept", "application/json")
.build();
return chain.proceed(request);
}
})
.addInterceptor(interceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS);
return builder.build();
}
简单解释下整个Okhttp请求组成,主要分为两个类:
- OkHttpClient
- Request
- OkHttpClient: 主要负责 Header头文件、Log信息 和 各种超时时间(连接时间、读/写时间)等信息的封装。
- Request: 主要负责 url、RequestBody(GET请求直接参数直接拼接在url上) 等信息的封装。其中,RequestBody封装参数信息,参数类型大致分为 键值对、Json格式 ,不同类型实例化方式不同。
- Response: 最终产物。通过OkHttpClient.newCall()会生成 Call 对象,再通过Call对象调用execute()方法最终生成 Response对象。
3. Retrofit框架底层实现
Retrofit + Okhttp 框架在Okhttp基础上进行了一层封装。如上图所示,关于 OkHttpClient 的配置依然由外部配置,通过Retrofit提供接口方法将对象传入。只是将 Request 配置由Retrofit内部来封装。
Retrofit框架中的 create 方法是其中的核心方法,源码如下
可以看到create方法唯一参数是一个接口的Class类(非接口实现类的Class类,因为由Proxy.newProxyInstance()方法的二参决定的,否则会报错,具体原因请向上翻),其内部使用的是就是 动态代理 。
由上图所示,Retrofit进行动态代理封装时,所需要的几个重要参数有
- OkHttpClient
- url地址
- 接口地址
- 请求方式类型(GET、POST ......)
- 参数类型(键值对、Json格式 ......)
- 参数
这几个重要参数在工程中体现在
总结:url 和 OkhttpClient 利用Retrofit提供的 baseUrl() 和 client() 两个方法传入。开发者创建自己的Service接口,方法参数中包含网络请求所需要的 参数类型 和 参数 ,方法的注解中包含网络请求的 请求类型 和 接口地址 。
4. 自定义Retrofit框架
在知道了Retrofit框架底层的实现原理,我们来手动的高仿去自定义一个自己的Retrofit框架(因为时间问题,这里只是针对Post请求和键值对参数,来搭建一个非常简陋的框架,模拟下Retrofit底层代码)。
package com.test.administractor.proxy;
import java.lang.reflect.Proxy;
import okhttp3.OkHttpClient;
/**
* Created by Administrator on 2018/1/30 0030.
*/
public class QqjRetrofit {
private static QqjRetrofit qqjRetrofit;
private MyInvocationHandler myInvocationHandler;
public static QqjRetrofit getInstance() {
if (qqjRetrofit == null) {
qqjRetrofit = new QqjRetrofit();
}
return qqjRetrofit;
}
public QqjRetrofit init(String baseUrl, OkHttpClient okHttpClient) {
myInvocationHandler = new MyInvocationHandler(baseUrl, okHttpClient);
return this;
}
public T create(Class service) {
return (T) Proxy.newProxyInstance(service.getClassLoader(),
new Class[]{service},
myInvocationHandler);
}
}
package com.test.administractor.proxy;
import org.json.JSONObject;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.Map;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
/**
* Created by Administrator on 2018/1/30 0030.
*/
public class MyInvocationHandler implements InvocationHandler {
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
private String baseUrl;
private OkHttpClient okHttpClient;
public MyInvocationHandler(String baseUrl, OkHttpClient okHttpClient) {
this.baseUrl = baseUrl;
this.okHttpClient = okHttpClient;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*POST+Json*/
JSONObject json = new JSONObject();
String url = (String) args[0];
Map map = (Map) args[1];
Iterator> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = it.next();
json.put(entry.getKey(), entry.getValue());
}
RequestBody body = RequestBody.create(JSON, json.toString());
Request request = new Request.Builder()
.url(baseUrl + url)
.post(body)
.build();
return okHttpClient.newCall(request);
}
}
主程序调用
private void proxy() {
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();
try {
Map map = new HashMap();
map.put("type", "android");
TestClass testClass = QqjRetrofit.getInstance()
.init("http://172.18.1.188", getClient())
.create(TestClass.class);
Call call = testClass.getMessage("/banner/fetch", map);
Response response = call.execute();
if (response.isSuccessful()) {
} else {
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
private OkHttpClient getClient() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request request = original.newBuilder()
.header("Accept", "application/json")
.build();
return chain.proceed(request);
}
})
.addInterceptor(interceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS);
return builder.build();
}