众所周知反射对性能会有损耗,反射的好,在于可以在运行期间调用对象的任何方法,访问它的任何属性,但随之而来的就是性能损失,同时丧失了编译时类型检查的好处,执行反射访问所需要的代码也非常笨拙且冗长。
用到反射的框架,会想办法减免重复的反射工作,从而在程序预热之后,降低反射带来的性能影响。例如网络请求框架Retrofit对反射的注解、跨进程通信反射调用方法对反射到的类实例进行了缓存。如题,今天针对Retrofit的反射注解部分对代码的优化逻辑进行分析。
Retrofit为什么会用到反射呢?在使用Retrofit的时候,我们通常会写一个Service接口来定义网络请求方法,类似如下:
public interface IDemoService{
/**
* baseurl/user
*/
@GET("user")
Call<ResponseBody> getData0();
@POST("user/emails")
@FormUrlEncoded
Call<ResponseBody> getPostData1(@Field("email")String userEmail);
/**
* 指定请求路径,路径由参数形式传入
* baseurl/valueOfParamUrl?id=1
*/
@GET
Call<ResponseBody> getUrlData(@Url String url, @Query("id") long id);
/**
* :@Multipart 表示请求实体是一个支持文件上传的表单,需要配合 @Part和@PartMap使用
* :@Part 用于表单字段,适用于文件上传类型,@Part支持:RequestBody、MultipartBody.Part、任意类型
* :@PartMap 用于不确定个数文件上传 主要修饰 Map<String,Object>,Object为上述支持的类型
* @return
*/
@Multipart
@POST("user/followers")
Call<ResponseBody> getPartData(@Part("name") RequestBody name, @Part MultipartBody.Part file);
}
看到这种设计:将一些配置参数放在注解中。后续如果要使用这些注解中的值,必然需要反射注解。Retrofit通过注解来减少开发者的冗余重复代码量,但同时也引入了反射。
定义好这个网络请求接口之后,就可以使用这个服务的代理进行网络请求:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://localhost/")//注意要以/结尾
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
//获得该Service的代理
IDemoService userServiceProxy = retrofit.create(IDemoService.class);
//调用该Service的方法,可以获得一个Retrofit2.Call<>对象,用于发起网络请求
Call<ResponseBody> executorCallbackCall = userServiceProxy.getData0();
executorCallbackCall.enqueue(new Callback<ResponseBody>() {
//返回到这里,默认回调到主线程!
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
这么设计的好处要和OkHttp3的网络请求进行对比:
- 将请求的配置逻辑解耦出来,业务层只需要关注传输的数据。
- 固定模板,使得请求拼接的代码可以复用
一方面让代码更加简洁,让类之间的分工更加精确,易维护易使用。易使用也是因为Retrofit使用了外观设计模式,为OkHttp3的核心功能设计了新的统一的调用方式。框架总是双面剑,开发效率提高了,代码简洁了,性能就相应地打了折扣。Retrofit使用注解的和动态代理的方式,通过Service模板拼接生成对象。
Retrofit使用动态代理生成代理
Retrofit生成代理的方式在文章 Android框架源码分析——从设计模式角度看 Retrofit 核心源码 中说的有点不确切。Retrofit使用的动态代理仍然是标准的动态代理设计模式,需要通过接口生成一个代理类。只不过调用方法的时候,并不是让哪个实体类(委托者)去执行,而是通过解析这个方法上的注解信息,以此生成一个ServiceMethod,再紧接着调用该ServiceMethod对象的 invoke() 方法,生成一个 Retrofit2.Call<> 对象返回出去。先来看到对Service接口的动态代理:
IDemoService userServiceProxy = retrofit.create(IDemoService.class);
Call<ResponseBody> executorCallbackCall = userServiceProxy.getData0("username");
这里可能有点绕,因为这和我们常见的动态代理模式不太一样,我们见到的动态代理通常是代理类执行一个方法后,将执行结果返回。但Retrofit的设计则是返回一个Retrofit2.Call<>对象。这也是因为开发者写的Service接口的作用是提供请求体的配置信息,并不是请求体本身,Retrofit通过动态代理,用户执行代理类的该方法时,其实内部是去解析该方法,将传入参数拼接到请求体,并将解析后生成的 Retrofit2.Call<> 返回给开发者,用于发起实际的请求。
简而言之,Retrofit的代理类的方法不是直接发起请求,而是通过解析方法上的注解和传入参数,生成一个可以用于请求的对象。
Retrofit通过create()方法,将Service接口生成一个代理类:
public <T> T create(final Class<T> service) {
validateServiceInterface(service);
return (T)
//通过动态代理生成代理类
Proxy.newProxyInstance(
//第一个参数为类加载器,一般直接使用委托者或者接口类的类加载器
service.getClassLoader(),
//需要生成代理的接口类,根据接口类定义的方法生成代理
new Class<?>[] {service},
//代理类调用方法的时候,会走到InvocationHandler的invoke()方法,执行真正的逻辑
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 (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
args = args != null ? args : emptyArgs;
//如果这个方法在接口类中被default修饰,就直接调用其本身逻辑,否则就认为是需要实现的接口方法,使用invoke进行方法调用。
return platform.isDefaultMethod(method)
? platform.invokeDefaultMethod(method, service, proxy, args)
: loadServiceMethod(method).invoke(args);
}
});
}
由Proxy.newProxyInstance()需要传入三个参数,第一个就是类的加载器,一般直接使用委托者或者接口类的加载器,第二个是需要代理的接口类,根据接口定义的方法生成代理。最后一个参数是InvocationHandler,生成的代理类中方法的调用都会执行到 invocationHandler.invoke()方法来执行具体的逻辑,代理类中不作任何处理。既然用了动态代理,那么method.invoke()的反射耗时就无法避免了,那么Retrofit对性能可以在哪里下文章呢?先来看到这里的loadServiceMethod() 这个方法进行了Service方法的解析,其中就包括了反射各种注解,这一步是非常耗时的!
//Retrofit
ServiceMethod<?> loadServiceMethod(Method method) {
ServiceMethod<?> result = serviceMethodCache.get(method);
if (result != null) return result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
//解析注解!非常耗时!
result = ServiceMethod.parseAnnotations(this, method);
//解决办法:解析之后放到缓存,下次不用再解析
serviceMethodCache.put(method, result);
}
}
return result;
}
之所以说解析注解耗时,就是因为它通过反射将方法上的各种注解全都解析一遍。看到ServiceMethod.parseAnnotations()方法:
//ServiceMethod.java
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
//解析方法上的注解,生成一个RequestFactory
RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
//反射获取返回值类型,约定返回值不能为void
Type returnType = method.getGenericReturnType();
//检查注解,构造用于发起HTTP请求的对象,其invoke方法返回一个Retrofit2.Call<>对象,具体这个Call对象如何生成,由CallFactory工厂来决定。
return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
}
RequestFactory.parseAnnotations()最终调用到RequestFactory.build()方法,其中反射了方法上的注解,生成了一个RequestFactory对象:
//RequestFactory
RequestFactory build() {
//反射获取annotations
this.methodAnnotations = method.getAnnotations();
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}
//...一系列验证
return new RequestFactory(this);
}
private void parseMethodAnnotation(Annotation annotation) {
if (annotation instanceof DELETE) {
parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
} else if (annotation instanceof GET) {
parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
//...
} else if (annotation instanceof HTTP) {
HTTP http = (HTTP) annotation;
parseHttpMethodAndPath(http.method(), http.path(), http.hasBody());
} else if (annotation instanceof retrofit2.http.Headers) {
String[] headersToParse = ((retrofit2.http.Headers) annotation).value();
}
headers = parseHeaders(headersToParse);
} else if (annotation instanceof Multipart) {
isMultipart = true;
} else if (annotation instanceof FormUrlEncoded) {
isFormEncoded = true;
}
}
不仅构建RequestFactory时反射耗时,HttpServiceMethod.parseAnnotations()中也是用到了反射。总而言之,如果我们每次调用一个Service中定义好的方法,都经过反射注解,拼接请求模板的过程,性能会很差。解决办法就是将这些解析之后的结果缓存起来,用于复用。Retrofit将生成的HttpServiceMethod对象(ServiceMethod的子类)放进了serviceMethodCache这个缓存map中:
public final class Retrofit{
private final Map<Method, ServiceMethod<?>> serviceMethodCache = new ConcurrentHashMap<>();
public <T> T create(final Class<T> service) {
//...
}
ServiceMethod<?> loadServiceMethod(Method method) {
ServiceMethod<?> result = serviceMethodCache.get(method);
if (result != null) return result;
synchronized (serviceMethodCache) {
//先尝试从缓存中获取之前解析过的ServiceMethod
result = serviceMethodCache.get(method);
if (result == null) {
result = ServiceMethod.parseAnnotations(this, method);
//将生成的ServiceMethod缓存到map中
serviceMethodCache.put(method, result);
}
}
return result;
}
}
这样的设计可以拓展到很多地方,如果我们会通过反射建立一个模板对象,这个对象可以被复用,例如Retrofit的这个ServiceMethod对象,它是一个请求的模板对象,下次再调用这个ServiceMethod代理的方法的时候,直接从缓存中(复用池中)复用这个ServiceMethod对象,减免每次都一样的解析注解的步骤。