如何应对Android面试官->Retrofit中注解、反射、动态代理、手写实现

注解 反射 可以查看上一章的讲解 如何应对Android面试官->Java中的注解、反射

代理模式

静态代理

代理模式类图

静态代理主要分三个角色:抽象角色、真实角色、代理角色

抽象角色:指代理角色和真实角色对外提供的公共方法,一般为一个接口;

csharp 复制代码
public interface Message { 
    void message(); 
} 

真实角色:需要实现抽象角色接口,定义了真实角色索要实现的业务逻辑,以便代理角色调用,也是真正的业务逻辑在此;

typescript 复制代码
 public class Alvin implements Message { 
    public void message() { // } 
} 

代理角色:需要实现抽象角色接口,是真实角色的代理,通过真实角色的业务逻辑来实现抽象方法,并可以附加自己的操作,将统一流程控制放到代理角色中处理;

通过代理,把调用者对真实对象的调用隔离了起来;

csharp 复制代码
public class Agent implements Message() { 
    private Message message; 
    public Agent(Message message) { 
        this.message = message; 
    } 
    public void message() { 
        // before(); 
        message.message(); 
        after(); 
    } 
    private void before() { } 
    private void after() { } 
} 

静态代理模式的实际应用

网络请求框架的封装

隔离层就是我们的代理对象,而真实对象就是我们的封装层,当我们发起网络请求的时候,业务并不需要关心具体使用的是哪个网络框架来发起请求,只需要操作隔离层的 HttpRequest 就行,当我们需要用 Volley 的时候,就只需要把我们的 Volley 的封装层实现赋值给隔离层的 HttpRequest 即可,那么就会使用 Volley 发起网络请求,如果要切换到 OkHttp,那么就只需要创建 OKHttp 的实现并赋值给隔离层的 HttpReqeust 即可;

动态代理

通过一个代理类,来实现全部的代理功能;在运行时再创建代理类和其实例,因此显然效率更低。要完成这个场景,需要在运行期动态创建一个Class。JDK提供了Proxy 来完成这件事;

基本使用

抽象角色

arduino 复制代码
interface Api {
    void test(String passName);
}

真实角色

typescript 复制代码
class ApiImpl implements Api {
    @Override    public void test(String passName) {
        System.out.println("真实实现:" + passName);
    }
}

创建真实实例角色

ini 复制代码
ApiImpl impl = new ApiImpl();

JDK动态代理实现

typescript 复制代码
Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] {Api.class}, new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        return Method.invoke(impl, args);
    }
})

实际上, Proxy.newProxyInstance 会创建一个 Class,与静态代理不同,这个 Class 不是由具体的 .java 源文件编译而来,即没有真正的文件,只是在内存中按照 Class 格式生成了一个Class。

动态代理的原理

Android SDK 中的 Proxy.java 中的具体实现

ini 复制代码
final Class<?> [] intfs = interfaces.clone(); // 对接口进行了一个拷贝,获取了一个 Class 对象的集合;
Class<?> cl = getProxyClass0(loader, intfs); // 获取一个 class
final Constructor<?> cons = cl.getConstuctor(constructorParams); // 获取这个 class 的构造函数
cons.newInstance(new Object[]{h}); // 对这个 class 进行实例化

所以:newProxyInstance() 返回的是一个 class 对象;

那么,这个 Class<?> cl 是怎么来的? 它里面的代码是什么样的呢?

那么这个 .class 的实例对象是怎么生成的?

这里要额外提到一点 类的生命周期

class 是由 javac 把 .java 文件编译成 .class,是一个实实在在存在的文件,存放在我们的硬盘中,而 动态代理 帮我们生成了一个 .class 是没有这个文件的,而是直接在内存中 一份实例化对象。

通过 ProxyGenerator.generateProxyClass 生成

ini 复制代码
String var23 = var16 + "$Proxy" + var19;
byte[] var22 = ProxyGenerator.generateProxyClass(var23, var2, var17);
try {    
    return Proxy.defineClass0(var1, var23, var22, 0, var22.length);
} catch (ClassFormatError var14) {    
    throw new IllegalArgumentException(var14.toString());
}

也就是说,我们将 byte[] var22 这个数组写入到一个文件中,就可以看到 动态代理 帮我们生成的 class 实例对象 都有哪些内容

ini 复制代码
String name = Api.class.getName()+"$Proxy0";
//生成代理指定接口的Class数据
byte[] bytes = ProxyGenerator.generateProxyClass(name, new Class[]{Api.class});
FileOutputStream fos = new FileOutputStream("lib/" + name+".class");
fos.write(bytes);fos.close();

生成的文件中可以查看我们的代理类

在初始化时,获得method 备用。而这个代理类中所有方法的实现变为:

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

手写Retrofit实现

Retrofit 核心实现主要分为四部分注解、Retrofit、ServiceMethod、Parameter

Retrofit的核心实现主要是 动态代理、注解解析、构建请求参数、封装Call.Factory发起网络请求。

Retorfit时序图如下:

注解

手写只实现了 @POST、@GET、@QUERY、@FIELD

java 复制代码
@Target(METHOD)
@Retention(RUNTIME)
public @interface POST {    
    String value() default "";
}

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

@Target(PARAMETER)
@Retention(RUNTIME)
public @interface Query {    
    String value();
}

@Target(PARAMETER)
@Retention(RUNTIME)
public @interface Field {    
    String value();
}

动态代理 MyRetrofit

OKHttp 发起网络请求,主要依靠的就是 Call.Factory HttpUrl

Call.Factory 提供了newCall方法用来发起请求,HttpUrl 用来构建请求参数和请求方式

MyRetrofit 也参考 Retorfit 采用 建造者模式实现,主要用来让调用方传递构建的 Call.Factory 和 HttpUrl,以及动态代理创建代理对象,调用 ServiceMethod 用来解析注解并封装请求参数;

kotlin 复制代码
public class MyRetrofit {    
    // 参考 Retrofit 的实现,也增加一层缓存
    final Map<Method, ServiceMethod> serviceMethodCache = new ConcurrentHashMap<>();    
    // 使用 OKhttp 提供的 Call.Factory
    final Call.Factory callFactory;  
    // 使用 OKHttp 提供的 HttpUrl      
    final HttpUrl baseUrl;
    
    MyRetrofit(Call.Factory callFactory, HttpUrl baseUrl) {                this.callFactory = callFactory;        
        this.baseUrl = baseUrl;    
    }
    
    // 参考 Retrofit 的动态代理  
    public <T> T create(final Class<T> service) {        
        return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class[]{service},                
            new InvocationHandler() {                    
            @Override                    
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                        
                //解析这个method 上所有的注解信息                        
                ServiceMethod serviceMethod = loadServiceMethod(method);                        
                //args:                        
                return serviceMethod.invoke(args);                    
            }                
        });    
    }  
      
    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;    
    }
    
    /**     
      * 构建者模式,将一个复杂对象的构建和它的表示分离,可以使使用者不必知道内部组成的细节。     
      */    
    public static final class Builder {        
        private HttpUrl baseUrl;        
        //Okhttp->OkhttClient        
        private okhttp3.Call.Factory callFactory;  
        //null        
        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.");            
            }            
            okhttp3.Call.Factory callFactory = this.callFactory;            
            if (callFactory == null) {                
                callFactory = new OkHttpClient();            
            }            
            return new EnjoyRetrofit(callFactory, baseUrl);        
        }    
    }
}

ServiceMethod

用来解析注解,记录请求类型,参数、完整地址

ini 复制代码
public class ServiceMethod {    
    private final Call.Factory callFactory;    
    private final String relativeUrl;    
    private final boolean hasBody;    
    private final ParameterHandler[] parameterHandler;    
    private FormBody.Builder formBuild;    
    HttpUrl baseUrl;    
    String httpMethod;    
    HttpUrl.Builder urlBuilder;    
    public ServiceMethod(Builder builder) {        
        baseUrl = builder.enjoyRetrofit.baseUrl;        
        callFactory = builder.enjoyRetrofit.callFactory;        
        httpMethod = builder.httpMethod;        
        relativeUrl = builder.relativeUrl;        
        hasBody = builder.hasBody;        
        parameterHandler = builder.parameterHandler;        
        //如果是有请求体,创建一个okhttp的请求体对象        
        if (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());        
        }        
        //获取最终请求地址        
        HttpUrl url;        
        if (urlBuilder == null) {            
            urlBuilder = baseUrl.newBuilder(relativeUrl);        
        }        
        url = urlBuilder.build();        
        //请求体        
        FormBody formBody = null;        
        if (formBuild != null) {            
            formBody = formBuild.build();        
        }        
        Request request = new Request.Builder().url(url).method(httpMethod, formBody).build();        
        return callFactory.newCall(request);    
    }    
    
    // get请求,把 k-v 拼到 url 里面    
    public void addQueryParameter(String key, String value) {        
        if (urlBuilder == null) {            
            urlBuilder = baseUrl.newBuilder(relativeUrl);        
        }        
        urlBuilder.addQueryParameter(key, value);    
    }    

    // Post,把 k-v 放到 请求体中    
    public void addFiledParameter(String key, String value) {        
        formBuild.add(key, value);    
    }    

    public static class Builder {        
        private final MyRetrofit enjoyRetrofit;        
        private final Annotation[] methodAnnotations;        
        private final Annotation[][] parameterAnnotations;        
        ParameterHandler[] parameterHandler;        
        private String httpMethod;        
        private String relativeUrl;        
        private boolean hasBody;        
        
        public Builder(MyRetrofit enjoyRetrofit, Method method) {            
            this.enjoyRetrofit = enjoyRetrofit;            
            //获取方法上的所有的注解            
            methodAnnotations = method.getAnnotations();            
            //获得方法参数的所有的注解 (一个参数可以有多个注解,一个方法又会有多个参数)            
            parameterAnnotations = method.getParameterAnnotations();        
        }        

        public ServiceMethod build() {            
            /**             
              * 1 解析方法上的注解, 只处理POST与GET             
              */            
            for (Annotation methodAnnotation : methodAnnotations) {                
                if (methodAnnotation instanceof POST) {                    
                    //记录当前请求方式                    
                    this.httpMethod = "POST";                    
                    //记录请求url的path                    
                    this.relativeUrl = ((POST) methodAnnotation).value();
                    // 是否有请求体                    
                    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++) {                
                // 一个参数上的所有的注解                
                Annotation[] annotations = parameterAnnotations[i];                
                // 处理参数上的每一个注解                
                for (Annotation annotation : annotations) {                    
                    //todo 可以加一个判断:如果httpMethod是get请求,现在又解析到 Filed 注解,可以提示使用者使用 Query 注解                    
                    if (annotation instanceof Field) {                        
                        //得到注解上的value: 请求参数的key                        
                        String value = ((Field) annotation).value();                        
                        parameterHandler[i] = new ParameterHandler.FiledParameterHandler(value);                    
                    } else if (annotation instanceof Query) {                        
                        String value = ((Query) annotation).value();                        
                        parameterHandler[i] = new ParameterHandler.QueryParameterHandler(value);                    
                    }                
                }            
            }            
            return new ServiceMethod(this);        
        }    
    }
}

ParameterHandler

QUERY、FIELD 请求参数封装,桥接到 HttpUrl 的addQueryParameter formBuild.add

scala 复制代码
public abstract class ParameterHandler {    
    abstract void apply(ServiceMethod serviceMethod, String value);    
    static class QueryParameterHandler extends ParameterHandler {        
        String key;        
        public QueryParameterHandler(String key) {            
            this.key = key;        
        }        
        //serviceMethod: 回调        
        @Override        
        void apply(ServiceMethod serviceMethod, String value) {            
            serviceMethod.addQueryParameter(key,value);        
        }    
    }  
      
    static class FiledParameterHandler extends ParameterHandler {        
        String key;        
        public FiledParameterHandler(String key) {            
            this.key = key;        
        }        
        @Override        
        void apply(ServiceMethod serviceMethod, String value) {            
            serviceMethod.addFiledParameter(key,value);        
        }    
     }
}

简历润色

简历中可写上:深度理解 Retofit 实现原理,理解 注解、反射、动态代理,可手写 Retofit 核心实现

相关推荐
li_liuliu29 分钟前
Android4.4 在系统中添加自己的System Service
android
C4rpeDime3 小时前
自建MD5解密平台-续
android
鲤籽鲲4 小时前
C# Random 随机数 全面解析
android·java·c#
m0_548514778 小时前
2024.12.10——攻防世界Web_php_include
android·前端·php
凤邪摩羯8 小时前
Android-性能优化-03-启动优化-启动耗时
android
凤邪摩羯8 小时前
Android-性能优化-02-内存优化-LeakCanary原理解析
android
喀什酱豆腐9 小时前
Handle
android
m0_7482329210 小时前
Android Https和WebView
android·网络协议·https
m0_7482517211 小时前
Android webview 打开本地H5项目(Cocos游戏以及Unity游戏)
android·游戏·unity
m0_7482546613 小时前
go官方日志库带色彩格式化
android·开发语言·golang