zoukankan      html  css  js  c++  java
  • Retrofit源码设计模式解析(上)

    Retrofit通过注解的方法标记HTTP请求参数,支持常用HTTP方法,统一返回值解析,支持异步/同步的请求方式,将HTTP请求对象化,参数化。真正执行网络访问的是Okhttp,Okhttp支持HTTP&HTTP2,因此,使用Retrofit可以支持REST、HTTPS及SPDY。

    行业内分析Retrofit的使用方法的文章已经比较丰富,这里不再赘述,如想了解这部分内容,请参考如下链接。

    用 Retrofit 2 简化 HTTP 请求

    Retrofit 源码解析

    本文主要从设计模式的角度分享对Retrofit源码的一些理解。

    1. 外观模式
    2. 建造者模式
    3. 代理模式
    4. 简单工厂模式
    5. 工厂模式
    6. 抽象工厂模式

    一、外观模式

    在封装某些特定功能的子系统时,外观模式是一种很好的设计规范。即该子系统的外部与内部通信时通过一个统一的对象进行。Retrofit是整个库的一个入口类,Retrofit库的使用基本都是围绕着这个类。外观模式具有高内聚、低耦合的特性,对外提供简单统一的接口,隐蔽了子系统具体的实现、隔离变化。

    Retrofit的外观模式的UML类图如下所示。

    image

    Retrofit对客户端模块(Client1、Client2……)提供统一接口,Retrofit类内部封装了ServiceMethod、CallAdapter和Converter等组件。并且,CallAdapter和Converter都是抽象为接口,用户可以扩展自定义的实现。正如官方文档中的示例:

    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.github.com/")
        .build();

    附:举个栗子,说明下外观模式在封装条形码/二维码扫描功能时的应用。

    Android中的条形码/二维码扫描功能通常会基于zxing库进行封装,定义CaptureActivity,统一提供扫码功能,并返回扫描结果。客户端使用该封装只需两步:首先,通过Intent启动CaptureActivity;然后,在onActivityResult中处理扫描结果。

    Intent intent = new Intent(MainActivity.this, CaptureActivity.class);
    startActivityForResult(intent, REQUESTCODE);
    if (requestCode == REQUESTCODE && resultCode == RESULT_OK) {
        Bundle bundle = data.getExtras();
        String scanResult = bundle.getString(CaptureActivity.RESULT);
        helloWorld.setText(scanResult);
    }

    客户端不需要处理任何跟摄像头控制、调焦、图片处理、条形码解析等相关的问题。CaptureActivity提供了所有扫描相关的功能。

    二、建造者模式

    设计模式分为三种类型:创建型模式、结构型模式和行为型模式。建造者模式属于创建型模式,将构建复杂对象的过程和它的部件解耦,使构建过程和部件的表示隔离。Retrofit内部包含Retrofit.Builder,Retrofit包含的域都能通过Builder进行构建。经典设计模式(《设计模式:可复用面向对象软件的基础》)中建造者模式有四种角色:

    • Product产品类——该类为一般为抽象类,定义Product的公共属性配置;
    • Builder建造类——该类同样为抽象类,规范Product的组建,一般由子类实现具体Product的构建过程;
    • ConcreteBuilder实际建造类——继承自Builder,构建具体的Product;
    • Director组装类——统一组装过程。

    在Retrofit类中,Retrofit直接对应Product,并没有基于抽象Product进行扩展;Retrofit.Builder对应ConcreteBuilder,也没有基于抽象Builder进行扩展,同时省略了Director,并在Retrofit.Builder每个setter方法都返回自身,使得客户端代码可以链式调用,整个构建过程更加简单。

    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.github.com/")
        .build();

    举个栗子:笔者基于Retrofit封装适合自身业务的库时,由于需要配置基础URL、默认超时时间、拦截器以及是否添加转换器等,也采用相同的建造者模式。

    public class NetWork {
    
        // 基础URL设置
        private String baseUrl;
        // 默认超时时间
        private long timeout;
    
        /**
         * NetWork构建者
         */
        public static class Builder {
            // 基础URL设置
            private String baseUrl;
            // 默认超时时间
            private long timeout = 5;
    
            /**
             * baseUrl为必填项
             *
             * @param baseUrl 基础Url
             */
            public Builder(String baseUrl) {
                this.baseUrl = baseUrl;
            }
    
            public Builder timeout(long timeout) {
                this.timeout = timeout;
                return this;
            }
    
            public NetWork build() {
                return new NetWork(this);
            }
        }
    
        /**
         * 构造器
         *
         * @param builder 构造builder
         */
        private NetWork(Builder builder) {
            this.baseUrl = builder.baseUrl;
            this.timeout = builder.timeout;
        }

    三、代理模式

    代理模式属于上述提到的结构型模式。当无法或不想直接访问某个对象,或者访问某个对象比较复杂的时候,可以通过一个代理对象来间接访问,代理对象向客户端提供和真实对象同样的接口功能。经典设计模式中,代理模式有四种角色:

    • Subject抽象主题类——申明代理对象和真实对象共同的接口方法;
    • RealSubject真实主题类——实现了Subject接口,真实执行业务逻辑的地方;
    • ProxySubject代理类——实现了Subject接口,持有对RealSubject的引用,在实现的接口方法中调用RealSubject中相应的方法执行;
    • Cliect客户端类——使用代理对象的类。

    代理模式分为静态代理和动态代理,严格按照上述角色定义编写的代码属于静态代理,即在代码运行前ProxySubject代理类的class编译文件就已存在。Retrofit使用的是动态代理,是通过反射机制来动态生成方法接口的代理对象的。动态代理的实现是通过JDK提供的InvocationHandler接口,实现该接口重写其调用方法invoke。

    public <T> T create(final Class<T> service) {
        Utils.validateServiceInterface(service);
        if (validateEagerly) {
            eagerlyValidateMethods(service);
        }
        return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
            new InvocationHandler() {
                private final Platform platform = Platform.get();
                @Override public Object invoke(Object proxy, Method method, 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);
                }
                if (platform.isDefaultMethod(method)) {
                    return platform.invokeDefaultMethod(method, service, proxy, args);
                }
                ServiceMethod serviceMethod = loadServiceMethod(method);
                OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
                return serviceMethod.callAdapter.adapt(okHttpCall);
            }
        });
    }

    Proxy.newProxyInstance返回接口的动态代理类,InvocationHandler的invoke方法处理method分为三种情况:1)Object的方法,直接返回Object的实现;2)判断是否Java8支持的DefaultMethod;3)创建OkHttpCall,通过ServiceMethod转换为接口的动态代理类。

    使用Retrofit的客户端通过create方法获取自定义HTTP请求的动态代理类,是客户端代码中最重要的部分之一。这里有三个重要组件:

    • ServiceMethod
    • OKHttpCall
    • ServiceMethod.callAdapter

    ServiceMethod用于处理Api Service上定义的注解,参数等,得到这个ServiceMethod之后,传给OkHttpCall,这个OkHttpCall就是对Okhttp的网络请求封装的一个类。

    ServiceMethod loadServiceMethod(Method method) {
        ServiceMethod result;
        synchronized (serviceMethodCache) {
            result = serviceMethodCache.get(method);
            if (result == null) {
                result = new ServiceMethod.Builder(this, method).build();
                serviceMethodCache.put(method, result);
            }
        }
        return result;
    }

    关于同步,这里有两点值得学习:

    1. 采用synchronized将锁加在serviceMethodCache上,而不是加到方法上。(直接synchronized加到方法可能会引起Dos,当然,你可以说客户端不用考虑这种问题);
    2. serviceMethodCache是用于缓存HTTP请求方法的,初始方法采用LinkedHashMap而不是普通的HashMap。
    private final Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();

    LinkedHashMap中有一个字段accessOrder,表示是否按照访问顺序进行重排序。默认为false,表示按插入时间顺序排序,如果设置为true,则进行重排序。最近最少访问的元素会被放到队尾,最先删除,而最常访问的元素,则放到队头,最后删除。

    把ServiceMethod传给OkHttpCall实际上就是把网络接口所需要的URL,参数等条件传给了OkHttpCall,就可以进行网络请求了。ServiceMethod中如果没有配置CallAdapter,则使用默认的DefaultCallAdapterFactory, 得到的结果是Call<?>。

    回到正题,你可能已经发现,create方法采用的代理模式和正常的代理模式并不一样,正常的代理模式只是对真实对象的一层控制,这个真实对象是实现对应的接口的,而这里并没有真实的对象,它把方法调用最终全部转发到OKHttp了。

    四、简单工厂模式

    本文后面的部分将集中在简单工厂模式、工厂模式和抽象工厂模式,它们都属于创建型模式,其主要功能都是将对象的实例化部分抽取出来。简单工厂模式也称为静态工厂模式,包含三种角色:

    • Factory工厂角色——负责实现创建所有实例的内部逻辑;
    • Product抽象产品角色——创建的所有对象的父类,负责描述所有实例所共有的公共接口;
    • ConcreteProduct具体产品角色——继承自Product,负责具体产品的创建。

    简单工厂模式是一种很常见、很简单的设计模式,以Platform类为例,其首先包含静态域PLATFORM,并通过静态返回供客户端调用。

    private static final Platform PLATFORM = findPlatform();
    
    static Platform get() {
        return PLATFORM;
    }

    findPlatform其实就是一个静态工厂方法,根据Class.forName是否抛出ClassNotFoundException来判断不同的平台。

    private static Platform findPlatform() {
        try {
            Class.forName("android.os.Build");
            if (Build.VERSION.SDK_INT != 0) {
                return new Android();
            }
        } catch (ClassNotFoundException ignored) {
        }
        try {
            Class.forName("java.util.Optional");
            return new Java8();
        } catch (ClassNotFoundException ignored) {
        }
        try {
            Class.forName("org.robovm.apple.foundation.NSObject");
            return new IOS();
        } catch (ClassNotFoundException ignored) {
        }
        return new Platform();
    }

    而Android、Java8、IOS相当于ConcreteProduct的角色,继承自抽象产品类Platform。

    Java8:

    static class Java8 extends Platform {}

    Android:

    static class Android extends Platform {}

    IOS:

    static class IOS extends Platform {}

    五、工厂模式

    上述简单工厂模式中的工厂方法类只有一个Factory,仍以Platform为例,如果在增加一种平台:Windows Phone,那么就需要修改findPlatform方法,添加Windows Phone类的创建。

    这种修改模式不符合“开闭原则”,即对扩展开放,对修改封闭。本着可扩展的原则,抽象Factory类的公共部分为抽象类,然后不同的平台工厂继承自抽象的Factory。需要不用的平台就调用不同的工厂方法,这就是工厂模式。即对简单工厂中的工厂类进行抽象:

    • Factory抽象工厂类——负责工厂类的公共部分;
    • ConcreteFactory具体工厂类——继承自Factory,实现不同特性的工厂。

    如果按照工厂模式,通过PlatformFactory类抽象工厂方法,那么大概会是这样:

    public abstract class PlatformFactory {
        abstract Platform findPlatform();
    }

    Android、Java8、IOS或者可能新增的Windows Phone工厂继承自PlatformFactory。

    public class AndroidFactory extends PlatformFactory {
    
        @Override
        Platform findPlatform() {
            try {
                Class.forName("android.os.Build");
                if (Build.VERSION.SDK_INT != 0) {
                    return new Android();
                }
            } catch (ClassNotFoundException ignored) {
            }
            return new Platform();
        }
    }

    客户端需要不同的平台对象就调用不同的工厂,但客户端如果调用错误,比如在Android上调用了IOS的工厂,那么就会得到一个Platform的实例,这并不符合要求,这种写法增加了客户端的难度,同时,需要引入抽象层,增加多个具体工厂类,维护成本也更大。

    所以,在使用工厂设计模式时,一定需要衡量利弊,在特定的场景选择最合适的设计模式。那么,Retrofit中使用工厂模式的经典例子又是什么呢?CallAdapter!

    public interface CallAdapter<T> {
    
        Type responseType();
        <R> T adapt(Call<R> call);
     
        abstract class Factory {
            public abstract CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit);
    
            protected static Type getParameterUpperBound(int index, ParameterizedType type) {
                return Utils.getParameterUpperBound(index, type);
            }
    
            protected static Class<?> getRawType(Type type) {
                return Utils.getRawType(type);
            }
        }
    }

    CallAdapter是什么呢?见名知义,对Call进行适配,这里涉及到适配器模式,下节会着重说明。这里关注CallAdapter.Factory,CallAdapter.Factory对应上述角色中的Factory抽象工厂类,包含两个静态工具方法getParameterUpperBound、getRawType和抽象方法get。

    get方法返回不同类型的CallAdapter,RxJavaCallAdapterFactory返回CallAdapter<Observable<?>>,DefaultCallAdapterFactory返回CallAdapter<Call<?>>。

    public final class RxJavaCallAdapterFactory extends CallAdapter.Factory {
    
        // 省略代码
        @Override
        public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
        
            // 省略代码
            CallAdapter<Observable<?>> callAdapter = getCallAdapter(returnType, scheduler);
            // 省略代码
            return callAdapter;
        }
    
        private CallAdapter<Observable<?>> getCallAdapter(Type returnType, Scheduler scheduler) {
            // 省略代码
        }
    }

    如果需要增加新的CallAdapter,继承自CallAdapter.Factory,覆盖get方法即可。符合面向对象软件设计的“开闭原则”。

    六、抽象工厂模式

    在配置Retrofit时,除了上述提到的CallAdapter,还需要addConverterFactory。Retrofit调用Okhttp时,将请求内容由T转换为okhttp3.RequestBody,将返回内容由okhttp3.ResponseBody转换为T,Converter是就是负责转换的类。Retrofit官方文档中给出了多种不同实现的转换器类,如下:

    Gson: com.squareup.retrofit2:converter-gson
    Jackson: com.squareup.retrofit2:converter-jackson
    Moshi: com.squareup.retrofit2:converter-moshi
    Protobuf: com.squareup.retrofit2:converter-protobuf
    Wire: com.squareup.retrofit2:converter-wire
    Simple XML: com.squareup.retrofit2:converter-simplexml
    Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars

    其中包含JSON转换的Gson、Jackson,负责PB解析的Protobuf,负责XML解析的Simple XML,这些类具有相同点,均继承自Converter.Factory。

    public interface Converter<F, T> {
        T convert(F value) throws IOException;
    
        abstract class Factory {
            public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, 
                   Retrofit retrofit) {
                return null;
            }
    
            public Converter<?, RequestBody> requestBodyConverter(Type type, 
                   Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
                return null;
            }
    
            public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
                return null;
            }
        }
    }

    这里注意下Converter.Factory与CallAdapter.Factory的区别,CallAdapter.Factory只有一个抽象的get方法返回CallAdapter<?>,Converter.Factory有三个方法,分别返回Converter<ResponseBody, ?>、Converter<?, RequestBody>和Converter<?, String>。

    相比于工厂模式,具体工厂负责生产具体的产品,每一个具体工厂对应一种具体产品,工厂方法也具有唯一性,一般情况下,一个具体工厂中只有一个工厂方法或者一组重载的工厂方法。但是有时候我们需要一个工厂可以提供多个产品对象,而不是单一的产品对象,例如:上述Converter.Factory需要同时提供请求内容和返回内容的转换类,这时,就需要考虑抽象工厂模式。抽象工厂模式同样包含四种角色:

    • AbstractFactory:抽象工厂
    • ConcreteFactory:具体工厂
    • AbstractProduct:抽象产品
    • Product:具体产品

    标准抽象工厂模式的UML图如下:(图片来自互联网)

    AbatractFactory

    这里以GsonConverterFactory为例进行说明。GsonConverterFactory对应ConcreteFactory具体工厂,表示Gson转换类的工厂,GsonConverterFactory继承自AbstractFactory抽象工厂——Converter.Factory,重写了requestBodyConverter方法和responseBodyConverter方法,相当于上图中的createProductA和createProductB。

    public final class GsonConverterFactory extends Converter.Factory {
    
        // 省略代码
        @Override
        public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
            Retrofit retrofit) {
            TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
            return new GsonResponseBodyConverter<>(gson, adapter);
        }
    
        @Override
        public Converter<?, RequestBody> requestBodyConverter(Type type,
            Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
            TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
            return new GsonRequestBodyConverter<>(gson, adapter);
        }
    }

    这里GsonRequestBodyConverter对应ProductA,GsonResponseBodyConverter对应ProductB。

    final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
        // 省略代码
    }
    final class GsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
        // 省略代码
    }

    Converter<T, RequestBody>对应抽象产品AbstractProductA,Converter<ResponseBody, T>对应抽象产品AbstractProductB。

    最后思考下:为什么Converter.Factory需要用抽象工厂模式,用工厂模式可以吗?如果用工厂模式,客户端需要怎么配置?

  • 相关阅读:
    go语言基础知识
    用vim写go代码——vim-go插件
    Java开发用H2数据库
    css控制文本对齐
    Linux用awk处理文本数据
    Linux修改文件编码
    Linux查看文本文件编码
    go语言学习笔记
    Druid
    spring cloud学习--eureka 02
  • 原文地址:https://www.cnblogs.com/younghao/p/6078156.html
Copyright © 2011-2022 走看看