本篇博文所有涉及代码已上传至码云: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);
}
}
}