Retrofit中的注解、反射与动态代理

本篇博文所有涉及代码已上传至码云:https://gitee.com/zhangningke/java-basis

代理模式

代理模式是通过给某个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗来讲,代理模式中的代理对象就像我们生活中常见的中介, 比如你想租房,一般在各种租房软件上找房子,联系到的都是中介,而不是房东。

代理模式的目的在于,一方面是通过引用代理对象的方式间接访问目标对象,防止直接访问目标对象给系统代理不必要的复杂性;另一方面是可以通过代理对象对访问进行控制。

代理模式一般包含三个角色:

【抽象角色】指代理角色和真实角色对外提供的公共方法,一般为一个接口,比如租房服务;

【真实角色】需要实现抽象角色接口,定义了真实角色所要实现的业务逻辑,以便供代理角色调用,也就是真正的业务逻辑在此,比如房东可以提供房子给你住。

【代理角色】需要实现抽象角色接口,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作(比如中介会抬高房租再租给你),将统一的流程控制都放到代理角色中处理

静态代理

在使用静态代理时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同的父类。一般来说,代理对象和被代理对象是一对一的关系,当然一个代理对象对应多个被代理对象也是可以的。

/**
 * 抽象角色: 定义了服务的接口
 */
public interface RentService {
	void rent();
}
/**
 * 真实角色: 真正提供房子给你住的房东
 */
public class Landlord implements RentService {
	private String name;
	
	public Landlord(String name) {
		this.name = name;
	}
	
	@Override
	public void rent() {
		System.out.println("一室一厅,价格美丽,先到先得!");
	}
}
/**
 * 代理对象:租房服务经纪人
 * 静态代理的代理对象只能为一个接口服务
 */
public class RentAgent implements RentService{
	private final RentService rentService;
	
	public RentAgent(RentService rentService) {
		this.rentService = rentService;
	}
	
	@Override
	public void rent() {
		preOperation();
		rentService.rent();
		afterSalesService();
	}
	
	private void afterSalesService() {
		System.out.println("合同到期,可帮您转租,请交钱,哈哈哈~");
	}
	
	private void preOperation() {
		System.out.println("布置房间,抬高房价!");
	}
}

 

从上面的案例我们可以发现,使用静态代理时,一对一则会出现静态代理对象多、代码量大,从而导致代码复杂,可维护性差的问题,一对多则代理对象会出现扩展能力差的问题。

动态代理

动态代理顾名思义是在运行时动态帮我们创建代理类和类实例,所以效率肯定会低一些。要完成这个场景,需要运行期动态创建一个Class。JDK提供Proxy来帮我们完成这件事情,基本使用如下:

		// 2.动态代理
		final Operator xiaoguo = new Operator("小郭同学");
		//参数含义:第一个:ClassLoader;第二个:代理的服务接口的Class数组(JDK实现只能代理接口);第三个:InvocationHandler回调接口。
		Object proxyInstance = Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{RentService.class,
			MessageService.class}, new InvocationHandler() {
			/**
			 *
			 * @param o 其实就是被代理的对象
			 * @param method 被代理对象要回调的方法
			 * @param objects 被代理对象要回调的方法包含的参数
			 * @return 其实就是被代理的对象
			 * @throws Throwable 抛异常,比如回调的方法找不到
			 */
			@Override
			public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
				System.out.println("method == " + method);
				if(objects != null){
					for (int i = 0; i < objects.length; i++) {
						System.out.println("第一个参数的内容是 == " + objects[i]);
					}
				}
				//在这里才是真正执行对象方法的地方
				return method.invoke(xiaoguo, objects);;
			}
		});
		
		RentService rentService = (RentService) proxyInstance;
		rentService.rent();
		
		MessageService messageService = (MessageService) proxyInstance;
		messageService.sendMessage("恭喜您中奖6800万!");

事实上,Proxy.newProxyInstance 会创建一个Class,与静态代理不同,这个Class不是由具体的.java源文件编译生成,就是说在磁盘当中不会有真实的Class文件,而是只会在内存中按照Class文件格式生成一个.class文件。

	/**
	 * 把生成的代理类的class文件输出到指定目录,用于查看和分析
	 *
	 * @throws IOException 抛出IO异常
	 */
	private static void proxy() throws IOException {
		String name = MessageService.class.getName() + "$Proxy0";
		//ProxyClassFactory最终通过生成代理指定接口的Class数据
		byte[] bytes = ProxyGenerator.generateProxyClass(name, new Class[]{MessageService.class});
		FileOutputStream fos = new FileOutputStream("agent/" + name + ".class");
		fos.write(bytes);
		fos.close();
	}

 

上图的h其实就是 InvocationHandler 接口,所以我们在使用动态代理时传递的 InvocationHandler 就是一个监听,在代理对象上执行方法,都会由这个监听回调出来。

Retrofit

Retrofit的简单使用

我们在了解一个框架的原理之前,首先要会使用它,下来我们就看看大名鼎鼎的Retrofit是如何为我们所用的。

step1:在官网(https://github.com/square/retrofit)查看retrofit最新版本,当然也可以使用历史版本,然后在我们的项目的build.gradle添加其依赖。

    //Retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.8.1'

step2:定义服务端的接口配置类:

import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.Query;

/**
 * 用于测试Retrofit:
 * 所有的网络请求接口都在这里配置,我们这里规定POST请求的方法参数使用@Field注解,GET请求使用@Query注解。
 */
public interface WeatherApi {
	@POST("/v3/weather/weatherInfo")
	@FormUrlEncoded
	Call<ResponseBody> postWeather(@Field("city") String city, @Field("key") String key);
	
	
	@GET("/v3/weather/weatherInfo")
	Call<ResponseBody> getWeather(@Query("city") String city, @Query("key") String key);
}

step3:创建Retrofit实例对象,并调用create方法返回服务端接口配置类的实例对象。

		OkHttpClient okHttpClient = new OkHttpClient.Builder().callTimeout(10, TimeUnit.SECONDS).build();
		Retrofit retrofit = new Retrofit.Builder()
			.baseUrl("https://restapi.amap.com")
			.callFactory(okHttpClient)// 可以定制OkHttpClient
			.build();
		mWeatherApi = retrofit.create(WeatherApi.class);

step4:访问网络请求,其实真正提供访问网络能力的还是okhttp3.Call.Factory,而OkHttpClient是其唯一实现类,提供了同步访问网络请求的execute方法和异步访问网络请求的enqueue方法:

	/**
	 * 使用Retrofit的Post请求
	 */
	private void getWeatherDataByPost() {
		new Thread(() -> {
			Call<ResponseBody> postWeatherCall = mWeatherApi.postWeather("110101", "ae6c53e2186f33bbf240a12d80672d1b");
			try {
				Response<ResponseBody> response = postWeatherCall.execute();
				if (response.isSuccessful()) {
					String weatherData = response.body() == null ? "" : response.body().string();
					Log.d(TAG, "getWeatherDataByPost: weatherData : " + weatherData);
					refreshUI(weatherData, mTVWeatherDataByPost);
				}
			} catch (IOException e) {
				Log.e(TAG, "onFailure: get weather data failed by post.");
			}
		}).start();
	}
	
	/**
	 * 使用Retrofit的Get请求
	 */
	private void getWeatherDataByGet() {
		Call<ResponseBody> weatherCall = mWeatherApi.getWeather("110101", "ae6c53e2186f33bbf240a12d80672d1b");
		weatherCall.enqueue(new Callback<ResponseBody>() {
			@Override
			public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
				try {
					if (response.isSuccessful()) {
						String weatherData = response.body() == null ? "" : response.body().string();
						Log.d(TAG, "onResponse: weatherData : " + weatherData);
						refreshUI(weatherData, mTVWeatherDataByGet);
					}
				} catch (IOException e) {
					Log.e(TAG, "onFailure: get weather data failed by get,may be IOException.");
				}
			}
			
			@Override
			public void onFailure(Call<ResponseBody> call, Throwable t) {
				Log.e(TAG, "onFailure: get weather data failed by get.");
			}
		});
	}

 PS:记得在清单文件里面加网络权限:

<uses-permission android:name="android.permission.INTERNET" />

实现五毛钱的Retrofit

了解了Retrofit的基本使用,我们打算自己撸一个简易版的Retrofit(不包含适配器和转换器,只提供基本网络访问能力)。

大部分从事安卓开发的同学应用都知道Retrofit是基于注解、反射和动态代理实现的,如果不知道,现在我告诉你,你也知道了>~<

还有基本的网络请求的知识点,比如有 POST、GET等请求方法以及它们的区别。另外我们一定要知道,Retrofit 只是对 OkHttp 做了一层封装,最终的网络访问能力还是 OkHttp 提供的。

开始撸码,我们先定义一个访问服务端的接口:

import com.luffy.annotationdemo.annotation.Field;
import com.luffy.annotationdemo.annotation.GET;
import com.luffy.annotationdemo.annotation.POST;
import com.luffy.annotationdemo.annotation.Query;

import okhttp3.Call;

/**
 * 用于测试我们自己写的MyRetrofit
 * 所有的网络请求接口都在这里配置,我们这里规定POST请求的方法参数使用@Field注解,GET请求使用@Query注解。
 */
public interface MyWeatherApi {
	@POST("/v3/weather/weatherInfo")
	Call postWeather(@Field("city") String city, @Field("key") String key);
	
	@GET("/v3/weather/weatherInfo")
	Call getWeather(@Query("city") String city, @Query("key") String key);
}

方法以及方法参数的注解,以POST为例,其他注解大同小异,具体可查看文首码云链接:

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target(METHOD)
@Retention(RUNTIME)
public @interface POST {
	String value() default "";
}

第二步:我们类比Retrofit的使用,编写如下代码,当然此时写出来的代码好多报红,我们接下来再一一实现相关方法。

		MyRetrofit myRetrofit = new MyRetrofit.Builder().baseUrl("https://restapi.amap.com").build();
		mMyWeatherApi = myRetrofit.create(MyWeatherApi.class);

我们看到new MyRetrofit.Builder(),第一时间应该想到的是构建者模式或者干脆就叫Builder模式。于是我们就撸出了如下代码。

import okhttp3.Call;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;

public class MyRetrofit {
    final Call.Factory callFactory;
    final HttpUrl baseUrl;

    MyRetrofit(Call.Factory callFactory, HttpUrl baseUrl) {
        this.callFactory = callFactory;
        this.baseUrl = baseUrl;
    }
    
    /**
     * 构建者模式,将一个复杂对象的构建和它的表示分离,可以使使用者不必知道内部组成的细节。
     * 注:当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用构造者模式。
     */
    public static final class Builder {
        // 域名和Factory是我们进行网络请求所必须的成员
        private HttpUrl baseUrl;
        // Okhttp->OkhttClient 是 okhttp3.Call.Factory的唯一实现类,如果我们不对callFactory做定制,它就默认是null,所以调用
        // build构建Retrofit时,一定要对其做判空处理。
        private okhttp3.Call.Factory callFactory;

        public Builder callFactory(okhttp3.Call.Factory factory) {
            this.callFactory = factory;
            return this;
        }

        public Builder baseUrl(String baseUrl) {
            this.baseUrl = HttpUrl.get(baseUrl);
            return this;
        }

        public MyRetrofit build() {
            if (baseUrl == null) {
                throw new IllegalStateException("Base URL required.");
            }
            // 如果callFactory为空,给它一个默认的OkHttpClient
            if (callFactory == null) {
                callFactory = new OkHttpClient();
            }
            return new MyRetrofit(callFactory, baseUrl);
        }
    }
}

PS:关于设计模式,可以参考一个大佬的博客:永不磨灭的设计模式 - ShuSheng007

此时,我们写的第一行代码已经OK了,第二行的create还是报红。

我们看到create方法传入接口的Class对象,返回的是接口的 一个实例,我们很容易就想到了动态代理,并且我们定义泛型方法易于扩展。

    /**
     * 使用动态代理,代理我们定义的接口服务
     *
     * @param service 接口服务
     * @param <T>     使用泛型,增加代码的扩展性
     * @return 接口服务的代理对象
     */
    @SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.
    public <T> T create(Class<T> service) {
        return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class[]{service}, new InvocationHandler() {
            private final Object[] emptyArgs = new Object[0];
            
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                /**
                 * 在这里对注解进行解析,得到请求方式和完成的URL
                 */
                // 如果是Object声明的方法,直接调用即可
                if (method.getDeclaringClass() == Object.class) {
                    return method.invoke(this, args);
                }
                // 如果是我们定义的请求网络的方法,需要解析这个method上所有的注解信息
                ServiceMethod serviceMethod = loadServiceMethod(method);
                // args:方法的所有参数
                return serviceMethod.invoke(args == null ? emptyArgs : args);
            }
        });
    }

上面代码中涉及到 loadServiceMethod 方法和 ServiceMethod 类,其实我们是把所有注解解析相关的内容都放在 ServiceMethod 中处理了,并且会用Map把已经解析过的注解内容缓存起来,每次调用接口方法的时候,通过 loadServiceMethod 先从缓存中去取,取不到再新建,loadServiceMethod 类比了DLC(双重加锁校验)的实现。

    /**
     * 先从缓存里面取 ServiceMethod 对象,取不到再去创建
     *
     * @param method 接口中调用的方法
     * @return 返回ServiceMethod 实例对象
     */
    private ServiceMethod loadServiceMethod(Method method) {
        // 先不上锁,避免synchronized的性能损失
        ServiceMethod result = serviceMethodCache.get(method);
        if (result != null) return result;
        // 多线程下,避免重复解析影响性能和功耗
        synchronized (serviceMethodCache) {
            result = serviceMethodCache.get(method);
            if (result == null) {
                result = new ServiceMethod.Builder(this, method).build();
                serviceMethodCache.put(method, result);
            }
        }
        return result;
    }

看到 Builder 我们同样想到构建者模式:

import com.luffy.annotationdemo.annotation.Field;
import com.luffy.annotationdemo.annotation.GET;
import com.luffy.annotationdemo.annotation.POST;
import com.luffy.annotationdemo.annotation.Query;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Objects;

import okhttp3.Call;
import okhttp3.FormBody;
import okhttp3.HttpUrl;
import okhttp3.Request;

/**
 * 用于解析并记录请求类型、参数、完整URL地址
 */
public class ServiceMethod {
	/**
	 * 真正提供网络访问能力的实例对象
	 */
	private final Call.Factory callFactory;
	
	/**
	 * 相对路径,比如: /v3/weather/weatherInfo
	 */
	private final String relativeUrl;
	
	/**
	 * 用于处理接口中的方法参数
	 */
	private final ParameterHandler[] parameterHandler;
	
	/**
	 * 用于创建FormBody
	 */
	private FormBody.Builder formBuild;
	
	/**
	 * 域名
	 */
	HttpUrl baseUrl;
	
	/**
	 * 用于记录请求方式
	 */
	String httpMethod;
	
	/**
	 * 用于构建完整的URL
	 */
	HttpUrl.Builder urlBuilder;
	
	public ServiceMethod(Builder builder) {
		if (builder == null) {
			throw new IllegalArgumentException("builder is null.");
		}
		callFactory = builder.myRetrofit.callFactory;
		relativeUrl = builder.relativeUrl;
		parameterHandler = builder.parameterHandler;
		baseUrl = builder.myRetrofit.baseUrl;
		httpMethod = builder.httpMethod;
		/*
		 * 如果是有请求体,创建一个 okhttp 的请求体对象(POST请求会有请求体)
		 */
		if (builder.hasBody) {
			formBuild = new FormBody.Builder();
		}
	}
	
	public Object invoke(Object[] args) {
		// 1.处理请求的地址与参数
		for (int i = 0; i < parameterHandler.length; i++) {
			ParameterHandler handlers = parameterHandler[i];
			// handler内本来就记录了key,现在给到对应的value
			handlers.apply(this, args[i].toString());
		}
		// 2.获取最终请求地址
		HttpUrl url;
		if (urlBuilder == null) {
			urlBuilder = baseUrl.newBuilder(relativeUrl);
		}
		url = urlBuilder != null ? urlBuilder.build() : null;
		// 3.请求体,只有Post请求的时候 formBuild 不为空,ServiceMethod 构造函数已经有相关逻辑
		FormBody formBody = null;
		if (formBuild != null) {
			formBody = formBuild.build();
		}
		Request request = new Request.Builder().url(Objects.requireNonNull(url)).method(httpMethod, formBody).build();
		return callFactory.newCall(request);
	}
	
	/**
	 * Post请求:把 k-v 放到请求体中
	 *
	 * @param key   服务端规定的key
	 * @param value 客户端传给服务端的值
	 */
	public void addFiledParameter(String key, String value) {
		formBuild.add(key, value);
	}
	
	/**
	 * Get请求:把 k-v 拼到url里面
	 *
	 * @param key   服务端规定的key
	 * @param value 客户端传给服务端的值
	 */
	public void addQueryParameter(String key, String value) {
		if (urlBuilder == null) {
			urlBuilder = baseUrl.newBuilder(relativeUrl);
		}
		Objects.requireNonNull(urlBuilder).addQueryParameter(key, value);
	}
	
	public static class Builder {
		
		private final MyRetrofit myRetrofit;
		private final Annotation[] methodAnnotations;
		private final Annotation[][] parameterAnnotations;
		ParameterHandler[] parameterHandler;
		private String httpMethod;
		private String relativeUrl;
		private boolean hasBody;
		
		public Builder(MyRetrofit myRetrofit, Method method) {
			this.myRetrofit = myRetrofit;
			// 获取方法上的所有的注解
			methodAnnotations = method.getAnnotations();
			// 获得方法参数的所有的注解 (之所以是二维数组,是因为一个参数可以有多个注解,一个方法又会有多个参数)
			parameterAnnotations = method.getParameterAnnotations();
		}
		
		public ServiceMethod build() {
			/*
			 * 1.解析方法上的注解, 只处理POST与GET
			 */
			for (Annotation methodAnnotation : methodAnnotations) {
				if (methodAnnotation.getClass().isAssignableFrom(POST.class)) {
					// 1.1记录当前请求方式
					this.httpMethod = "POST";
					// 1.2记录请求url的path
					this.relativeUrl = ((POST) methodAnnotation).value();
					// 1.3是否有请求体
					this.hasBody = true;
				} else if (methodAnnotation instanceof GET) {
					this.httpMethod = "GET";
					this.relativeUrl = ((GET) methodAnnotation).value();
					this.hasBody = false;
				}
			}
			
			/*
			 * 2 解析方法参数的注解
			 */
			int length = parameterAnnotations.length;
			parameterHandler = new ParameterHandler[length];
			for (int i = 0; i < length; i++) {
				// 2.1拿到第i个参数上的所有的注解
				Annotation[] annotations = parameterAnnotations[i];
				// 2.2处理参数上的每一个注解
				for (Annotation annotation : annotations) {
					if (httpMethod.equals("POST")) {
						if (annotation instanceof Field) {
							//得到注解上的value: 也就是请求参数的key
							String value = ((Field) annotation).value();
							parameterHandler[i] = new ParameterHandler.FiledParameterHandler(value);
						}
						// 这里判断:如果httpMethod是post请求,并且解析到Query注解,可以提示使用者使用Field注解
						else if (annotation instanceof Query) {
							throw new IllegalArgumentException("POST request must use @Field.");
						}
					} else if (httpMethod.equals("GET")) {
						if (annotation instanceof Query) {
							String value = ((Query) annotation).value();
							parameterHandler[i] = new ParameterHandler.QueryParameterHandler(value);
						}
						// 这里判断:如果httpMethod是get请求,并且解析到Field注解,可以提示使用者使用Query注解
						else if (annotation instanceof Field) {
							throw new IllegalArgumentException("GET request must use @Query.");
						}
					}
				}
			}
			return new ServiceMethod(this);
		}
	}
}

注释写的应该还算详细,没搞明白的欢迎评论区与我交流~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值