【基于HTTP的远程调用框架 一】深度详解Retrofit2框架概念和使用

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 【基于HTTP的远程调用框架 一】深度详解Retrofit2框架概念和使用

前段时间因为用到了Dubbo的接口,所以学习了一下Zookeeper+Dubbo的RPC调用方式,总结了一篇Blog【SpringBoot学习笔记 十四】SpringBoot+Dubbo+Zookeeper集成开发,最近呢因为有一个合同相关的开发任务,大量使用了Retrofit2,用之前也是半生不熟的仿写,所以还是希望进行一个系统性的学习整体了解下Retrofit2的用法和基本原理,因为Retrofit2是基于OkHttp进行封装的,所以近期会再对OkHttp进行一个系统性学习介绍,还是按照这样的脉络来梳理:

  • 是什么:Retrofit2是什么,基本概念是什么
  • 为什么:为什么选择用Retrofit2,在什么场景下用Retrofit2比较好
  • 怎么用:基本的使用方法,怎么使用
  • 底层原理:是什么样的底层原理和源码支撑Retrofit2的功能

按照这样的线路来进行一个深度理解。

Retrofit2基本概念

第一个问题,是什么?Retrofit2是现在比较流行的网络请求框架,可以理解为OkHttp的加强版,底层封装了OkHttp。准确来说,Retrofit2是一个Restful的Http网络请求框架的封装。因为网络请求工作本质上是由okhttp来完成,而Retrofit负责网络请求接口的封装,整体流程如下图所示:

本质上看,应用通过Retrofit请求网络,实质上是使用Retrofit接口层封装请求参数、Header、Url等信息,之后由OkHttp来完成后续的请求工作。在服务端返回数据后,OkHttp将原始数据交给Retrofit,Retrofit根据用户需求解析。

Retrofit2框架优势

第二个问题,为什么?既然已经有了OkHttp,为什么还要在上边封装一层Retrofit,那就回到了框架的本质,框架的作用是什么?让使用者少干活,只关注核心业务逻辑,那么Retrofit的作用就显而易见了,使用方便简介,让使用者傻瓜式进行web调用,除此之外还有如下几个点:

  • 超级解耦 ,接口定义、接口参数、接口回调不再耦合在一起,Retrofit统统帮封装好了
  • 可以配置不同的httpClient来实现网络请求,如OkHttp、HttpClient
  • 支持同步、异步、Rxjava,最佳实践:Retrofit + OkHttp + RxJava
  • 可以配置不同反序列化工具类来解析不同的数据,如json、xml
  • 请求速度快,使用方便灵活简洁

光看这些优点可能还不是很懂,那么从OkHttp和Retrofit的使用方式对比一下吧:

OkHttp框架使用方式

样板步骤代码如下:

private void testOkHttp() throws IOException {
    //Step1:创建HttpClient对象,也就是构建一个网络类型的实例,一般会将所有的网络请求使用同一个单例对象
    final OkHttpClient client = new OkHttpClient();
    //Step2:构建Request,也就是构建一个具体的网络请求对象,具体的请求url,请求头,请求体
     RequestBody formBody = new FormBody.Builder()
             .add("size", "10")
             .build();
    final Request request = new Request.Builder()
     .url("https://www.baidu.com")
     .post(formBody)
     .build();
    //Step3:构建请求Call,也就是将具体的网络请求与执行请求的实体进行绑定,形成一个具体的正式的可执行实体
    Call call = client.newCall(request);
    //step4 发送网络请求,获取数据,进行后续处理
    call.enqueue(new Callback() {
      @Override
      public void onFailure(Call call, IOException e) {
     }
      @Override
     public void onResponse(Call call, Response response) throws IOException {
          Log.i(TAG,response.toString());
          Log.i(TAG,response.body().string());
     }
   });
 }

OkHttp 是基于Http协议封装的一套请求客户端,虽然它也可以开线程,但根本上它更偏向真正的请求,跟HttpClient的职责是一样的。OkHttp主要负责socket部分的优化,比如多路复用,buffer缓存,数据压缩,但还有问题没有解决:

  1. 用户网络请求的接口配置繁琐,尤其是需要配置请求body,请求头,参数时
  2. 数据解析过程需要用户手动拿到responsbody进行解析,不能复用;
  3. 无法适配自动进行线程的切换

再来看看Retrofit的实现:

Retrofit框架使用方式

样板步骤代码如下:

//step1:创建retrofit对象, 构建一个网络请求的载体对象,和okhttp构建OkhttpClient对象一样,不过retrofit在build的时候有非常多的初始化内容,这些内容可以为后面网络请求提供准备,如准备 现成转换Executor,Gson convert,RxJavaCallAdapter
Retrofit retrofit = new Retrofit.Builder()
       .baseUrl("https://www.baidu.com/")
       .addConverterFactory(GsonConverterFactory.create(new Gson()))
       .build();
//step2:Retrofit的精髓,为统一配置网络请求完成动态代理的设置,参数等都在接口中通过注解体现
ISharedListService sharedListService =  retrofit.create(ISharedListService.class);
//step3:构建具体网络请求对象Request
//1)将接口中的注解翻译成对应的参数;
//2)确定网络请求接口的返回值response类型以及对应的转换器;
//3)将Okhttp的Request封装成为Retrofit的OKhttpCall
Call<SharedListBean> sharedListCall = sharedListService.getSharedList();
//step4:进行网络请求,处理网络请求的数据
sharedListCall.enqueue(new Callback<SharedListBean>() {
  @Override
  public void onResponse(Call<SharedListBean> call, Response<SharedListBean> response{
    if (response.isSuccessful()) {
        System.out.println(response.body().toString());
      }
    }
    @Override
    public void onFailure(Call<SharedListBean> call, Throwable t) {
     t.printStackTrace();
   }
});

整体流程中二者对比如下:

Retrofit2使用实践

第三个问题,怎么用?大致明白为什么我们要使用Retrofit2框架后,我们需要在实践中体会它的好处。

Retrofit2常用注解

Retrofit使用大量注解来简化请求,Retrofit将okhttp请求抽象成java接口,使用注解来配置和描述网络请求参数。接下来可以依照这个示例来进行分析:

/**
     * @Description: 盖章接口
     * @Param: [headers, application, userId, contractSealManualParameter]
     * @Author: tianmaolin
     * @Date: 2021/12/17
     */
    @POST("/tml/contract/contractSealManual")
    Call<CommonContractResponse<String>> contractSealManual(@HeaderMap Map<String, String> headers,
                                                            @Query("application") String application,  @Query("userId") Long userId,
                                                            @Body ContractSealManualParameter contractSealManualParameter);

大概可以分为以下几类:

1 请求方法注解

加在请求的方法上的注解,例如上例中我们看到的@POST("/tml/contract/contractSealManual"),其它用的还有:

2 请求头注解

请求中header中放置的内容,例如上例中的:@HeaderMap Map<String, String> headers,其它用的还有:

3 请求参数注解

请求在参数中防止的参数,例如上例中的:@Query("userId") Long userId或者@Body ContractSealManualParameter contractSealManualParameter,其它用的还有:

注意:接口中的每个方法的参数都要用注解标记,否则会报错。

4 请求和响应格式(标记)注解

这些注解一般是处理有特殊数据传输的请求:

Retrofit2使用步骤

通过以上的了解,不难发现Retrofit将okhttp请求抽象成java接口,采用注解描述和配置网络请求参数,用动态代理将该接口的注解“翻译”成一个Http请求,最后执行Http请求。大致了解了常用注解后,我们来按照上面样板代码中的步骤来进行框架使用。

1 定义通用的Retrofit处理方式

可以让所有的Retrofit基础这个基础的代理实现,将一些请求的共性提炼出来,例如设置请求的读超时时间,写超时时间,添加响应数据Convert等。

public class BaseRetrofitProxy<T> {
    @Resource
    private OkHttpClient okHttpClient;
    @PostConstruct
    public void initClient() {
        okHttpClient = new OkHttpClient.Builder().connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
            .readTimeout(TIMEOUT, TimeUnit.MILLISECONDS).writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
            .addInterceptor(new OkHttpSignInterceptor()
            .build();
    }
    public T getRetrofit(String baseUrl, Class<T> proxy) {
        Retrofit retrofit = new Retrofit.Builder().client(okHttpClient).baseUrl(baseUrl)
            .addConverterFactory(JacksonConverterFactory.create(JsonUtils.getObjectMapper())).build();
        return retrofit.create(proxy);
    }
}

序列化对象可以做如下的设置:

public class JsonUtils {
    private static final String LOCAL_DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    static {
        OBJECT_MAPPER.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        // 忽略空 Bean 转字符串的错误
        OBJECT_MAPPER.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        // 忽略反序列化时,对象不存在对应属性的错误
        OBJECT_MAPPER.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        JavaTimeModule timeModule = new JavaTimeModule();
        timeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ISO_LOCAL_DATE));
        timeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ISO_LOCAL_DATE));
        timeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ISO_LOCAL_TIME));
        timeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ISO_LOCAL_TIME));
        timeModule.addSerializer(LocalDateTime.class,
            new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(LOCAL_DATE_TIME_PATTERN)));
        timeModule.addDeserializer(LocalDateTime.class,
            new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(LOCAL_DATE_TIME_PATTERN)));
        OBJECT_MAPPER.registerModule(timeModule);
        OBJECT_MAPPER.setDateFormat(new SimpleDateFormat(LOCAL_DATE_TIME_PATTERN));
    }
    private JsonUtils() {
    }
    public static ObjectMapper getObjectMapper() {
        return OBJECT_MAPPER;
    }
}

2 定义代理请求接口和接口方法

我们需要创建一个retrofit对象,构建网络请求载体对象,通常一类方法我们使用一个retrofit,我们以请求发送短信接口为例,该例贯穿如下所有步骤:

/**
 * * @Name ContractRetrofitProxy
 * * @Description 短信/邮件 调用接口
 * * @author tianmaolin
 * * @Data 2021/11/18
 */
@RetrofitClient(baseUrl = "${retrofit.message.host}")
public interface MessageRetrofitProxy {
    /**
     * @Description: 发送短信:
     * @Param: [sendMessageParam]
     * @Author: tianmaolin
     * @Date: 2021/11/24
     */
    @POST("/message/send")
    Call<CommonMessageResponse<SendMessageResponse>> sendMessage(@Body SendMessageParam sendMessageParam);
}

以上各部分内容含义如下:

  • @RetrofitClient(baseUrl = "${retrofit.message.host}"):写在配置文件里的,该请求使用的域名
  • @POST("/message/send"):该方法的相对路径
  • Call<CommonMessageResponse<SendMessageResponse>>:Call中的泛型为接收返回值的类型
  • @Body SendMessageParam sendMessageParam:请求的参数

这样一个通过注解定义的网络请求接口就完成了,当然这里的入参和返回值都是需要我们提前进行定义的

3 创建代理对象并发送请求

这个步骤中我们需要使用上述定义好的接口进行代理对象创建,网络接口方法调用:

/**
 * * @Name MessageRetrofitProxyCall
 * * @Description
 * * @author tianmaolin
 * * @Data 2021/11/24
 */
@Slf4j
@Service
public class MessageRetrofitProxyCall extends BaseRetrofitProxy<MessageRetrofitProxy> {
    @Resource
    private MessageRetrofitProxy messageRetrofitProxy;
    @Value("${retrofit.message.host}")
    private String domain;
    //step1: 创建retrofit对象,并进行动态代理设置
    @PostConstruct
    public void init() {
        messageRetrofitProxy = getRetrofit(domain, MessageRetrofitProxy.class);
    }
    /**
     * @Description:远程调用短信发送接口,返回短信发送结果
     * @Param: [sendMessageParam]
     * @return: com.ke.merchant.kernel.rpc.retrofit.message.model.response.SendMessageResponse
     * @Author: tianmaolin
     * @Date: 2021/11/24
     */
    public SendMessageResponse remoteSendMessage(SendMessageParam sendMessageParam) {
        try {
            //step2: 构建具体的网络请求对象
            Call<CommonMessageResponse<SendMessageResponse>> result = messageRetrofitProxy.sendMessage(sendMessageParam);
            LOGGER.info("send message request,sendMessageParam:{}", sendMessageParam);
            //step3: 执行网络请求并获取响应结果
            CommonMessageResponse<SendMessageResponse> response = execute(result);
            LOGGER.info("send message response,sendMessageParam:{}", response);
            if (response != null && ZERO == response.getErrno()) {
                LOGGER.info("send message success in message platform,{}", sendMessageParam.toString());
                return response.getData();
            }
        } catch (Exception e) {
            LOGGER.error("remote send message error ,sendMessageParam:{}", sendMessageParam, e);
        }
        return null;
    }
    /**
     * @Description: 调用执行方法
     * @Param: [call]
     * @return: T
     * @Author: tianmaolin
     * @Date: 2021/11/24
     */
    @Override
    protected <T> T execute(Call<T> call) {
        try {
            Response<T> response = call.execute();
            return response.body();
        } catch (IOException e) {
            throw new ServiceException("MessageRetrofitProxyCall request fail", [添加链接描述](https://tianmaolin.blog.csdn.net/article/details/119702083)e).code("1096").tip("MessageRetrofitProxyCall request fail");
        }
    }
}

看完完整示例,我们再回过头来回顾下Retrofit的好处:

  • 超级解耦 ,接口定义、接口参数、接口回调不再耦合在一起,Retrofit统统帮封装好了:上述的接口定义和参数由MessageRetrofitProxy来实现,执行和回调由MessageRetrofitProxyCall来实现,两个各自是解耦状态
  • 可以配置不同的httpClient来实现网络请求,如OkHttp、HttpClient:在BaseRetrofitProxy中我们可以配置不同的Client
  • 支持同步、异步、Rxjava,最佳实践:Retrofit + OkHttp + RxJava请求的时候如果使用execute就是同步实现,如果使用enqueue就是异步实现
  • 可以配置不同反序列化工具类来解析不同的数据,如json、xml。在BaseRetrofitProxy中我们可以配置不同的解析器
  • 请求速度快,使用方便灵活简洁。方便灵活是感受的很彻底,就像调接口一样进行网络请求。性能方面没有做过实验

总的来说就是调用体验很好,所有的请求数据都是可配的。

相关文章
|
7月前
|
网络协议 Linux iOS开发
推荐:实现RTSP/RTMP/HLS/HTTP协议的轻量级流媒体框架,支持大并发连接请求
推荐:实现RTSP/RTMP/HLS/HTTP协议的轻量级流媒体框架,支持大并发连接请求
295 1
|
7月前
|
算法 API UED
基于Gin框架的HTTP接口限速实践
基于Gin框架的HTTP接口限速实践
167 0
|
缓存 负载均衡 网络协议
60 # http 的基本概念
60 # http 的基本概念
69 0
|
1月前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
122 3
|
2月前
|
前端开发 JavaScript 中间件
前端全栈之路Deno篇(四):Deno2.0如何快速创建http一个 restfulapi/静态文件托管应用及oak框架介绍
Deno 是由 Node.js 创始人 Ryan Dahl 开发的新一代 JavaScript 和 TypeScript 运行时,旨在解决 Node.js 的设计缺陷,具备更强的安全性和内置的 TypeScript 支持。本文介绍了如何使用 Deno 内置的 `Deno.serve` 快速创建 HTTP 服务,并详细讲解了 Oak 框架的安装和使用方法,包括中间件、路由和静态文件服务等功能。Deno 和 Oak 的结合使得创建 RESTful API 变得高效且简便,非常适合快速开发和部署现代 Web 应用程序。
109 2
|
2月前
|
JSON Java fastjson
Java Http 接口对接太繁琐?试试 UniHttp 框架吧
UniHttp 是一个声明式的 HTTP 接口对接框架,旨在简化第三方 HTTP 接口的调用过程。通过注解配置,开发者可以像调用本地方法一样发起 HTTP 请求,无需关注请求的构建和响应处理细节。框架支持多种请求方式和参数类型,提供灵活的生命周期钩子以满足复杂的对接需求,适用于企业级项目的快速开发和维护。GitHub 地址:[UniAPI](https://github.com/burukeYou/UniAPI)。
|
3月前
|
存储 JSON Go
在Gin框架中优雅地处理HTTP请求体中的JSON数据
在Gin框架中优雅地处理HTTP请求体中的JSON数据
|
2月前
|
Java 数据处理 开发者
Java Http 接口对接太繁琐?试试 UniHttp 框架~
【10月更文挑战第10天】在企业级项目开发中,HTTP接口对接是一项常见且重要的任务。传统的编程式HTTP客户端(如HttpClient、Okhttp)虽然功能强大,但往往需要编写大量冗长且复杂的代码,这对于项目的可维护性和可读性都是一个挑战。幸运的是,UniHttp框架的出现为这一问题提供了优雅的解决方案。
93 0
|
2月前
|
资源调度 编译器 Linux
Windows10系统安装Truffle框架,安装失败,提示:error An unexpected error occurred: “https://xxxxx
Windows10系统安装Truffle框架,安装失败,提示:error An unexpected error occurred: “https://xxxxx
95 0
|
7月前
|
缓存 自然语言处理 前端开发
第一章 引言-HTTP协议基础概念和前后端分离架构请求交互概述
第一章 引言-HTTP协议基础概念和前后端分离架构请求交互概述
153 0

热门文章

最新文章