代理模式,在大家的日常开发工作中可能用不到,但是又随处可见,例如Retrofit网络框架就是采用动态代理设计模式实现的。那么代理设计模式能够解决什么问题呢?从架构层面看,代理模式的思想是如何运用的,接下来我们将会对两种代理模式做详细的介绍。
1 静态代理模式
在介绍动态代理模式之前,我们首先看一下静态代理模式。静态代理模式旨在调用层和实现层之间加一层隔离
调用层不需要关心实现层是如何实现的,只需要通知隔离层需要什么,后续的执行就由隔离层分发处理,举个简单的例子。
interface IHttpRequest {
fun post()
fun get()
}
1.1 静态代理模式实现
在网络请求框架的演变过程中,出现过Volley、OkHttp、Retrofit等优秀的开源框架;如果不采用静态代理设计模式,那么每次更换框架,需要将原先的实现全部干掉,换成新的框架实现,这就违背了开闭原则,每次需要修改实现层的代码。
class HttpRequestUtils : IHttpRequest {
override fun post() {
// Log.e("TAG","实现Volley的post请求")
Log.e("TAG","实现OkHttp的post请求")
}
override fun get() {
// Log.e("TAG","实现Volley的get请求")
Log.e("TAG","实现OkHttp的get请求")
}
}
那么静态代理是如何实现的呢?首先针对每一个框架,都有自己的实现,并实现了隔离层接口。
class VolleyRequest : IHttpRequest {
override fun post() {
Log.e("TAG","Volley post")
}
override fun get() {
Log.e("TAG","Volley get")
}
}
class OkHttpRequest : IHttpRequest {
override fun post() {
Log.e("TAG","OkHttpRequest post")
}
override fun get() {
Log.e("TAG","OkHttpRequest get")
}
}
那么代理类的作用就是持有隔离层接口,暴露给调用层一个setRequest方法,可以设置请求的方式
class HttpRequestUtils : IHttpRequest {
private var htIHttpRequest: IHttpRequest? = null
fun setRequest(htIHttpRequest: IHttpRequest){
this.htIHttpRequest = htIHttpRequest
}
override fun post() {
htIHttpRequest?.post()
}
override fun get() {
htIHttpRequest?.get()
}
companion object{
val instance:HttpRequestUtils by lazy {
HttpRequestUtils()
}
}
}
在调用层可以灵活配置需要的网络请求框架,并且可以放在Application中做全局的初始化
HttpRequestUtils.instance.setRequest(VolleyRequest())
HttpRequestUtils.instance.post()
1.2 静态代理的弊端
我们看到,代理类只是持有了一个隔离层接口,如果有2个、3个接口,那么代理类就需要处理多个接口,隔离层会变得臃肿起来。
class HttpRequestUtils : IHttpRequest {
private var htIHttpRequest: IHttpRequest? = null
private var otherRequest: IOtherRequest? = null
fun setOtherRequest(otherRequest: IHttpRequest){
this.otherRequest = otherRequest
}
//...... 如果10个接口就会膨胀下去
fun setRequest(htIHttpRequest: IHttpRequest){
this.htIHttpRequest = htIHttpRequest
}
override fun post() {
htIHttpRequest?.post()
}
override fun get() {
htIHttpRequest?.get()
}
companion object{
val instance:HttpRequestUtils by lazy {
HttpRequestUtils()
}
}
}
或者使用多个代理类,如果有10个接口就需要创建10个代理类,有10个这种HttpRequestUtils代理类,这种情况下,静态代理设计模式就不再适用了,如果我们想一个代理类就管理全部的接口,动态代理模式就出现了。
2 动态代理模式
2.1 动态代理参数
Proxy.newProxyInstance(
classLoader, arrayOf(
IHttpRequest::class.java
)
) { obj, method, objs ->
}
调用Proxy的newProxyInstance方法能够实现动态代理,Proxy是反射中的类,因此我们可以知道动态代理就是通过反射实现的,其中有3个参数:
newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
loader:类加载器
interfaces:要代理的接口集合,也就是说无论多少个接口,都能通过代理实现
InvocationHandler:可以看做是回调,每次方法的调用都会走这个回调
Params:
//proxy – the proxy instance that the method was invoked on
//method – the Method instance corresponding to the interface method invoked on the proxy instance. The declaring class of the Method object will be the //interface that the method was declared in, which may be a superinterface of the //proxy interface that the proxy class inherits the method through.
//args – an array of objects containing the values of the arguments passed in the //method invocation on the proxy instance, or null if interface method takes no //arguments. Arguments of primitive types are wrapped in instances of the //appropriate primitive wrapper class, such as java.lang.Integer or //java.lang.Boolean.
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
之前在Hook AMS的时候也提到过,如果系统源码是Java写的,动态代理的代码最好用Java写,因为Kotlin和Java有些类型还是有差别的,所以之前写Kotlin动态代理老是会有问题,先用Java实现了。
VolleyRequest request = new VolleyRequest();
Object obj = Proxy.newProxyInstance(MyProxy.class.getClassLoader(),
new Class[]{IHttpRequest.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.e("TAG","method"+method);
return method.invoke(request,args);
}
});
((IHttpRequest) obj).post();
回到正题,通过newProxyInstance创建出代理对象之后,可以将该代理对象强转成某个隔离层接口,调用接口方法后,会走InvocationHandler这个回调,在这个回调中,就可以调用具体类的方法。
2022-10-08 12:25:27.168 14116-14116/com.lay.mvi E/TAG: methodpublic abstract void com.lay.mvi.api.IHttpRequest.post()
2022-10-08 12:25:27.168 14116-14116/com.lay.mvi E/TAG: Volley post
所以我们可以看到,在这个方法执行之前,可以做一些处理,例如修改方法的入参,Hook技术就是采用了这点。
2.2 动态代理实现
所以相对于静态代理,动态代理这边同样是一个代理类,但是需要代理不同的接口
public class MyProxy {
private static MyProxy proxy = new MyProxy();
public static MyProxy getInstance() {
return proxy;
}
public <T> T create(Class<T> clazz, Object obj) {
return (T) Proxy.newProxyInstance(MyProxy.class.getClassLoader(),
new Class[]{clazz}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(obj, args);
}
});
}
}
create方法是一个泛型方法,第一个参数传入一个接口类的Class对象,第二个参数可传入接口的具体实现,最终返回的就是一个代理对象。
val create = MyProxy.getInstance().create(IHttpRequest::class.java,VolleyRequest())
create.post()
在调用层传入对应的接口,就能使用一个代理类完成全部接口的代理。
3 采用动态代理实现Retrofit
我们这里不是实现整个Retrofit框架,因为我们知道Retrofit自身并没有网络请求的能力,而是把网络请求交给了OkHttp,Retrofit只是利用注解、动态代理实现了请求接口的封装。
fun getApi(): ApiService {
val builder = OkHttpClient.Builder()
.build()
//Retrofit的封装
val retrofit = Retrofit.Builder()
.baseUrl("http://apis.juhe.cn/simpleWeather/")
.client(builder)
.addConverterFactory(GsonConverterFactory.create())
.build()
//动态代理
return retrofit.create(ApiService::class.java)
}
我们看下create方法的源码,里面正是用到了我们前面使用到的动态代理
public <T> T create(final Class<T> service) {
validateServiceInterface(service);
return (T)
Proxy.newProxyInstance(
service.getClassLoader(),
new Class<?>[] {service},
new InvocationHandler() {
private final Platform platform = Platform.get();
private final Object[] emptyArgs = new Object[0];
@Override
public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
args = args != null ? args : emptyArgs;
return platform.isDefaultMethod(method)
? platform.invokeDefaultMethod(method, service, proxy, args)
: loadServiceMethod(method).invoke(args);
}
});
}
所以ApiService就是一个接口,里面采用注解的形式封装了网络请求的类型、参数等
interface ApiService {
@GET
suspend fun getWeather(
@Url url: String,
@Query("city") city: String,
@Query("key") key: String
): Weather
}
3.1 Retrofit Builder
首先我们先将Retrofit对象的创建定义出来,就是一个简单的建造者设计模式。
public class MyRetrofit {
private String mUrl;
private OkHttpClient client;
private Converter.Factory factory;
public MyRetrofit(String url, OkHttpClient client, Converter.Factory factory) {
this.mUrl = url;
this.client = client;
this.factory = factory;
}
class Builder {
private String baseUrl;
//这部分先用Retrofit自带的
private OkHttpClient client;
private Converter.Factory factory;
public Builder setBaseUrl(String url){
this.baseUrl = url;
return this;
}
public Builder client(OkHttpClient client){
this.client = client;
return this;
}
public Builder addConvertFactory(Converter.Factory factory){
this.factory = factory;
return this;
}
public MyRetrofit build(){
if (baseUrl.isEmpty()) {
throw new IllegalArgumentException("BASE URL must be not empty");
}
return new MyRetrofit(baseUrl, client, factory);
}
}
}
3.2 注解定义
在Retrofit中,定义了很多注解,像@POST、@GET等,其实很多注解我们容易搞混,既然我们自己定义注解,就可以简单明了一些。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) //在方法上使用
public @interface MyGet {
String url() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyPost {
String url();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER) //标记参数
public @interface MyGetParams {
String value();
}
我们定义了两个请求注解@MyGet和@MyPost,用于区分GET请求和POST请求;@MyGetParams注解用于标注GET请求的请求参数,我们就先以GET请求为例。
public interface MyApiService {
@MyGet(url = "query")
Call getWeather(
@MyGetParams("city") String city,
@MyGetParams("key") String key
);
}
3.3 create方法
其实在2.2小节中,我们有写过一个泛型方法就是类似于Retrofit中的create方法,当然这里我们不仅仅是调用一次方法,还需要获取注解。
public <T> T create(Class<T> clazz) {
return (T) Proxy.newProxyInstance(MyRetrofit.class.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
ServiceMethod serviceMethod = loadServiceMethod(method);
return null;
}
});
}
private ServiceMethod loadServiceMethod(Method method) {
ServiceMethod result = methodMap.get(method);
if (result != null) return result;
synchronized (methodMap) {
result = methodMap.get(method);
if (result == null) {
result = new ServiceMethod.Builder()
.setMethod(method)
.setRetrofit(this)
.build();
methodMap.put(method, result);
}
}
return result;
}
create方法还是接收了一个接口类,通过动态代理的方式创建了代理类,首先第一步就是解析注解。因为在项目中,接口可能会被多次请求,不需要每次请求接口的时候都需要解析注解,而是在第一次的时候解析完成存储在HashMap中。
3.3.1 解析方法注解
接下来,我们需要解析注解,注解主要分为方法上的注解和参数中的注解,Method类中都有对应的API可以调用。
public class ServiceMethod {
private MyRetrofit myRetrofit;
private Method method;
public ServiceMethod(MyRetrofit retrofit,Method method){
this.myRetrofit = retrofit;
this.method = method;
//解析注解
processAnnotation(method);
}
//解析注解
private void processAnnotation(Method method) {
}
public static class Builder{
public Builder(){}
private MyRetrofit myRetrofit;
private Method method;
public Builder setRetrofit(MyRetrofit retrofit){
this.myRetrofit = retrofit;
return this;
}
public Builder setMethod(Method method){
this.method = method;
return this;
}
public ServiceMethod build(){
return new ServiceMethod(myRetrofit,method);
}
}
}
首先解析方法注解,调用Method的getDeclaredAnnotations,获取全部的注解,因为在一个方法上可以使用多个注解,因此拿到的是一个数组。
Annotation[] declaredAnnotations = method.getDeclaredAnnotations();
for (Annotation annotation :
declaredAnnotations) {
Log.e("TAG", "注解 ===>" + annotation);
//判断是get还是post请求
this.realUrl = doProcessRequestBridge(annotation);
Log.e("TAG", "请求url ===>" + realUrl);
}
对每个注解类型做判断,如果是GET请求,记录当前Method的请求类型,然后和BaseUrl做参数拼接,记录真正请求的url
private String doProcessRequestBridge(Annotation annotation) {
StringBuffer buffer = new StringBuffer(myRetrofit.getBaseUrl());
if (annotation instanceof MyGet) {
this.requestType = REQUEST.GET;
//如果是get请求,拼接url
String url = ((MyGet) annotation).url();
buffer.append(url);
return buffer.toString();
} else {
this.requestType = REQUEST.POST;
//如果是post请求
String url = ((MyPost) annotation).url();
buffer.append(url);
return buffer.toString();
}
}
3.3.2 解析参数注解
通过getParameterAnnotations方法,能够获取全部参数的注解,这里拿到了是一个二维数组,是因为请求的参数可能会有多个,每个参数还可能承载多个注解
//解析参数注解
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Log.e("TAG", "parameterAnnotations" + parameterAnnotations);
for (int i = 0; i < parameterAnnotations.length; i++) {
Annotation[] parameterAnnotation = parameterAnnotations[i];
//获取参数上的全部注解
for (Annotation annotation :
parameterAnnotation) {
doProcessParamsBridge(annotation);
}
}
//list -- [city, key]
所以首先获取每个参数,然后处理每个参数上的注解,并记录当前方法全部的请求key
private void doProcessParamsBridge(Annotation annotation) {
if (annotation instanceof MyGetParams) {
if (this.requestType == REQUEST.POST) {
throw new IllegalArgumentException("POST请求不能使用@MyGetParams注解");
}
String key = ((MyGetParams) annotation).value();
//记录请求的参数key
keyList.add(key);
}
}
3.3.3 方法执行
当所有的参数执行完成之后,就需要正式执行这个方法了
public <T> T create(Class<T> clazz) {
return (T) Proxy.newProxyInstance(MyRetrofit.class.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
ServiceMethod serviceMethod = loadServiceMethod(method);
return serviceMethod.invoke(args);
}
});
}
所以我们还需要实现一个invoke方法,在这个方法中传入的参数,就是之前注解解析时,与key一一对应的。
public Object invoke(Object[] args) {
if (args.length == 0) {
return null;
}
RequestBody requestBody = null;
String requestType = "GET";
HttpUrl.Builder builder = HttpUrl.parse(this.realUrl).newBuilder();
if (this.requestType == "GET") {
//拼接字符串
StringBuffer buffer = new StringBuffer(this.realUrl);
buffer.append("?");
for (int i = 0; i < keyList.size(); i++) {
String key = keyList.get(i);
builder.addQueryParameter(key,args[i].toString());
}
} else {
//创建请求体
requestBody = new FormBody.Builder().build();
}
Request request = new Request.Builder()
.url(builder.build())
//如果是get请求,不需要RequestBody
.method(requestType,requestBody)
.build();
return myRetrofit.client.newCall(request);
}
这里是采用了HttpUrl.Builder来拼接url,然后创建一个Request请求,使用OkHttp发起一个网络请求,最终返回了请求结果。
3.4 调用
在应用端的调用和Retrofit一致
val builder = OkHttpClient.Builder()
.build()
val retrofit = MyRetrofit.Builder()
.client(builder)
.setBaseUrl("http://apis.juhe.cn/simpleWeather/")
.addConvertFactory(GsonConverterFactory.create())
.build()
retrofit.create(MyApiService::class.java)
.getWeather("北京","申请的聚合数据key")
.enqueue(object : okhttp3.Callback{
override fun onFailure(call: okhttp3.Call, e: IOException) {
Log.e("TAG", "call $call")
}
override fun onResponse(call: okhttp3.Call, response: okhttp3.Response) {
Log.e("TAG", "call ${response.body()?.string()}")
}
})
call {"reason":"查询成功!","result":{"city":"北京","realtime":{"temperature":"12","humidity":"84","info":"阴","wid":"02","direct":"西风","power":"1级","aqi":"92"},"future":[{"date":"2022-10-08","temperature":"9\/16℃","weather":"小雨转多云","wid":{"day":"07","night":"01"},"direct":"西南风转西北风"},{"date":"2022-10-09","temperature":"7\/16℃","weather":"多云转晴","wid":{"day":"01","night":"00"},"direct":"西北风"},{"date":"2022-10-10","temperature":"3\/19℃","weather":"晴","wid":{"day":"00","night":"00"},"direct":"西北风"},{"date":"2022-10-11","temperature":"6\/19℃","weather":"晴","wid":{"day":"00","night":"00"},"direct":"西南风"},{"date":"2022-10-12","temperature":"9\/19℃","weather":"晴转多云","wid":{"day":"00","night":"01"},"direct":"南风转北风"}]},"error_code":0}
此次设计的Retrofit只是做了注解和动态代理设计模式,像类型转换,使用Gson将json字符串转为实体类并没有去做,其实Retrofit核心就是动态代理和注解处理,而请求是OkHttp干的事
GitHub地址:github.com/LLLLLaaayyy…