【项目实践06】【Retrofit2 框架的使用】

文章目录

一、前言

本系列用来记录一些在实际项目中的小东西,并记录在过程中想到一些小东西,因为是随笔记录,所以内容不会过于详细。


本文内容参考自 框架解析2-Retrofit,侵删。

二、背景介绍

最近项目需要使用 coze 的功能,因此在引入 coze 提供的 SDK 时看到其使用了 Retrofit2 内容,简单了解后得知 Retrofit2 封装了网络请求功能。巧就巧在目前正在开发的项目中有一个模块功能需要调用大量三方 API,因此决定使用 Retrofit2 来完成 Http 请求。

三、使用介绍

1. Retrofit2 简介

Retrofit2 是一个适用于 Android 和 Java 的类型安全的 HTTP 客户端框架,由 Square 公司开发。它简化了 Android 应用程序中与网络服务器进行 HTTP 通信的过程,让开发者能够以一种简洁、优雅的方式处理网络请求和响应。以下从几个方面对其进行详细介绍:

  1. 主要特点

    • 简洁易用:使用注解(Annotation)来描述 HTTP 请求,大大减少了样板代码。例如,通过简单的@GET、@POST等注解就可以定义不同类型的 HTTP 请求。
    • 类型安全:Retrofit2 基于接口定义请求,通过动态代理模式生成实现类。由于接口方法的参数和返回值都是明确指定的,所以具有类型安全的特性,减少了运行时错误。
    • 可扩展性:支持多种数据格式的解析和序列化,如 JSON、XML 等。同时,还可以通过自定义转换器(Converter)和拦截器(Interceptor)来满足不同的需求。
    • 高效性能:底层基于 OkHttp,OkHttp 具有高效的网络请求处理能力,包括连接池复用、GZIP 压缩、缓存等机制,使得 Retrofit2 在网络请求方面性能出色。
  2. 核心组件

    • Retrofit:Retrofit 类是整个框架的核心,用于创建网络请求接口的实例。通过Retrofit.Builder来进行配置,如设置 Base URL、添加转换器工厂等。
    • Service Interface:服务接口定义了一系列的 API 方法,每个方法对应一个 HTTP 请求。方法上使用注解来描述请求的类型(如@GET、@POST)、URL 以及请求参数等信息。
    • Converter:负责将 HTTP 响应的内容转换为 Java 对象,以及将 Java 对象转换为 HTTP 请求的内容。Retrofit2 默认支持一些常见的数据格式转换,如 JSON(使用 Gson 库)。如果需要支持其他格式(如 XML),则需要添加相应的转换器工厂,如SimpleXmlConverterFactory。
    • Call:代表一个可执行的 HTTP 请求,通过调用Call.execute()方法可以同步执行请求,返回Response对象;通过调用Call.enqueue(Callback callback)方法可以异步执行请求,在请求完成后通过Callback回调来处理响应结果。

2. Retrofit2 示例

暴露如下两个接口作为示例

java 复制代码
@RestController
@RequestMapping()
public class HelloController {
    @PostMapping("hello")
    public Resp hello(@RequestBody Req req) {
        ThreadUtil.sleep(2000);
        return new Resp("Hello " + req.getName());
    }

    @PostMapping("simpleHello")
    public String simpleHello(@RequestBody Req req) {
        ThreadUtil.sleep(2000);
        return "Hello " + req.getName();
    }
}

Retrofit示例实现如下:可以通过 RetrofitDemoApi 实现 接口远程调用

java 复制代码
@Slf4j
public class RetrofitDemo {

    public static void main(String[] args) throws IOException, InterruptedException {
        final RetrofitDemo retrofit = new RetrofitDemo();
        // 同步请求
        retrofit.sync();
        // 异步请求
        retrofit.async();
        // 挂起主线程,等待异步请求结束
        Thread.sleep(5000);
    }


    /**
     * API 接口
     */
    interface RetrofitDemoApi {
        @POST("hello")
        Call<Resp> hello(@Body Req req);

        @POST("simpleHello")
        Call<String> simpleHello(@Body Req req);
    }

    private void sync() throws IOException {
        Retrofit retrofit = initRetrofit();
        // 创建 API 代理类
        RetrofitDemoApi service = retrofit.create(RetrofitDemoApi.class);
        // 调用 API 接口
        Req req = new Req();
        req.setName("kingfish");

        Call<Resp> helloCall = service.hello(req);
        log.info("hello method 调用结果 : {}", helloCall.execute().body().getMsg());

        Call<String> simpleHelloCall = service.simpleHello(req);
        log.info("simpleHello method 调用结果 : {}", simpleHelloCall.execute().body());
    }

    private void async() throws IOException {
        Retrofit retrofit = initRetrofit();
        // 创建 API 代理类
        RetrofitDemoApi service = retrofit.create(RetrofitDemoApi.class);
        // 调用 API 接口
        Req req = new Req();
        req.setName("kingfish");

        Call<Resp> helloCall = service.hello(req);
        helloCall.enqueue(new Callback<>() {
            @Override
            public void onResponse(Call<Resp> call, Response<Resp> response) {
                log.info("hello method 调用结果 : {}", response.body().getMsg());
            }

            @Override
            public void onFailure(Call<Resp> call, Throwable t) {
                log.error("hello method 调用失败 : {}", t.getMessage());
            }
        });

        Call<Resp> simpleHelloCall = service.hello(req);
        simpleHelloCall.enqueue(new Callback<>() {
            @Override
            public void onResponse(Call<Resp> call, Response<Resp> response) {
                log.info("simpleHello method 调用结果 : {}", response.body().getMsg());
            }

            @Override
            public void onFailure(Call<Resp> call, Throwable t) {
                log.error("simpleHello method 调用失败 : {}", t.getMessage());
            }
        });

    }

    @NotNull
    private Retrofit initRetrofit() {
        return new Retrofit.Builder()
                // 请求地址设置
                .baseUrl("http://localhost:3000/temp-demo/")
                .client(defaultClient(Duration.ofMillis(10000), Duration.ofMillis(10000)))
                // 在 Retrofit2 中,addConverterFactory 方法的主要作用是为 Retrofit 实例添加转换器工厂(Converter Factory),这些转换器工厂用于处理 HTTP 请求体和响应体与 Java 对象之间的相互转换。
                .addConverterFactory(ScalarsConverterFactory.create())
                .addConverterFactory(JacksonConverterFactory.create(defaultObjectMapper()))
                // Retrofit 默认的服务接口方法返回的是 Call 对象,Call 代表一个可执行的 HTTP 请求,
                // 通过它可以进行同步或异步的网络请求操作。但在实际开发中,我们可能希望服务接口方法返回其他类型,
                // 例如 CompletableFuture(Java 8 引入的异步编程工具)、RxJava 的 Observable、Flowable 等,
                // 以更好地与不同的异步编程模型集成。调用适配器工厂就可以帮助我们实现这一功能。
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();
    }

    public static ObjectMapper defaultObjectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        return mapper;
    }

    private static OkHttpClient defaultClient(Duration readTimeout, Duration connectTimeout) {
        return new OkHttpClient.Builder()
                .connectionPool(new ConnectionPool(5, 1, TimeUnit.SECONDS))
                .readTimeout(readTimeout.toMillis(), TimeUnit.MILLISECONDS)
                .connectTimeout(connectTimeout.toMillis(), TimeUnit.MILLISECONDS)
                // 可以添加请求拦截器
//                .addInterceptor(new AuthenticationInterceptor(this.auth)) // 添加拦截器,在请求头中增加 token
//                .addInterceptor(new TimeoutInterceptor()) // 添加拦截器,设置超时时间
//                .addInterceptor(new UserAgentInterceptor()) // 添加拦截器,设置 user-agent
                .build();
    }
}

输出结果如下图:


这里注意:在 Retrofit 中,retrofit2.Retrofit.Builder#addConverterFactory 方法用于添加不同的转换器工厂类,这些工厂类的作用是将 HTTP 响应的数据转换为 Java 对象,或者将 Java 对象转换为 HTTP 请求体。可以添加多个转换器工厂,Retrofit 会按照添加的顺序依次尝试使用这些工厂进行转换,直到找到合适的转换器为止。不同的转换器工厂适用于不同的数据格式,根据服务器返回的数据格式选择合适的转换器工厂非常重要。

  1. 添加顺序很重要:Retrofit 会按照你添加 ConverterFactory 的顺序依次尝试使用它们。在上述示例中,ScalarsConverterFactory 先被添加,所以 Retrofit 会先尝试将响应转换为 String 类型,如果不匹配,再尝试使用 GsonConverterFactory 进行 JSON 转换。

  2. 选择合适的转换器:根据你的 API 服务返回的数据格式,选择合适的 ConverterFactory 进行添加。如果有自定义的数据格式,还可以实现自己的 ConverterFactory。

以下是一些常见的转换工厂类及其作用(需要单独引入相应的依赖):

类名 作用
ScalarsConverterFactory 用于处理基本数据类型(如 String、Integer、Boolean 等)的转换。当服务器返回的是简单的文本数据(如纯字符串、数字等)时,使用这个转换器工厂可以直接将响应数据转换为对应的基本数据类型。
GsonConverterFactory 使用 Google 的 Gson 库将 JSON 数据转换为 Java 对象,或者将 Java 对象转换为 JSON 数据。这是 Retrofit 中最常用的转换器工厂之一,适用于服务器返回 JSON 格式数据的场景。
JacksonConverterFactory 使用 Jackson 库进行 JSON 数据和 Java 对象之间的转换。Jackson 是一个高性能的 JSON 处理库,如果你对性能有较高要求,或者已经在项目中使用了 Jackson,可以选择使用这个转换器工厂。
MoshiConverterFactory 使用 Moshi 库进行 JSON 数据和 Java 对象之间的转换。Moshi 是 Square 公司开发的一个轻量级、快速的 JSON 解析库,它支持 Kotlin 的数据类和不可变对象,使用起来比较方便。
ProtobufConverterFactory 用于处理 Protocol Buffers(简称 Protobuf)格式的数据。Protobuf 是一种高效的二进制数据序列化协议,具有数据体积小、解析速度快等优点。当服务器返回 Protobuf 格式的数据时,可以使用这个转换器工厂将其转换为 Java 对象。

四、源码简析

Retrofit2 时序图如下:( 图源来自 :框架解析2-Retrofit

Retrofit2 的实现其实不难理解,通过 retrofit2.Retrofit#create 来为指定的 API 接口创建代理对象,如下:

java 复制代码
 // create 方法接收一个服务接口的 Class 对象作为参数,返回该接口的一个代理实例。当调用这个代理实例的方法时,会触发 InvocationHandler 的 invoke 方法,在 invoke 方法中根据不同情况处理方法调用
 @SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.
 public <T> T create(final Class<T> service) {
// 验证传入的服务接口是否合法,例如检查接口中定义的方法是否使用了正确的 Retrofit 注解等
   validateServiceInterface(service);
   // 创建dialing对象
   return (T)
       Proxy.newProxyInstance(
           service.getClassLoader(),
           new Class<?>[] {service},
           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 the method is a method from Object then defer to normal invocation.
               // 如果调用的方法是 Object 类的方法(如 toString()、equals() 等),则直接使用反射调用该方法。
               if (method.getDeclaringClass() == Object.class) {
                 return method.invoke(this, args);
               }
               args = args != null ? args : emptyArgs;
               // 检查该方法是否是接口的默认方法。
               // 如果是默认方法,则调用 platform.invokeDefaultMethod 方法来执行默认方法
               // 如果不是默认方法,则调用 loadServiceMethod(method).invoke(args) 方法。
               // loadServiceMethod 方法会根据方法的注解和参数信息生成一个 ServiceMethod 对象,该对象封装了 HTTP 请求的相关信息,然后调用 invoke 方法发起 HTTP 请求。
               return platform.isDefaultMethod(method)
                   ? platform.invokeDefaultMethod(method, service, proxy, args)
                   : loadServiceMethod(method).invoke(args);
             }
           });
 }

 ServiceMethod<?> loadServiceMethod(Method method) {
   // 从缓存中查找对应方法的 ServiceMethod
   ServiceMethod<?> result = serviceMethodCache.get(method);
   if (result != null) return result;
	// 如果缓存没有命中
   synchronized (serviceMethodCache) {
     result = serviceMethodCache.get(method);
     if (result == null) {
       // 解析当前方法,并创建 ServiceMethod 实例放到缓存中
       result = ServiceMethod.parseAnnotations(this, method);
       serviceMethodCache.put(method, result);
     }
   }
   return result;
 }

上面代码可以看到针对每一个 服务方法都会创建一个 ServiceMethod,并调用 ServiceMethod#invoke 方法来完成 Http 调用。而ServiceMethod 是一个抽象类,实现如下:

java 复制代码
abstract class ServiceMethod<T> {
  // 解析 retrofit2 注解信息,完善请求相关内容
  static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
    RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);

    Type returnType = method.getGenericReturnType();
    if (Utils.hasUnresolvableType(returnType)) {
      throw methodError(
          method,
          "Method return type must not include a type variable or wildcard: %s",
          returnType);
    }
    if (returnType == void.class) {
      throw methodError(method, "Service methods cannot return void.");
    }

    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
  }
  // 由子类实现
  abstract @Nullable T invoke(Object[] args);
}

我们这里的实际实现类是 retrofit2.HttpServiceMethod.CallAdapted,CallAdapted#invoke 是由其父类 HttpServiceMethod实现,HttpServiceMethod#invoke 实现如下:

java 复制代码
  @Override
  final @Nullable ReturnT invoke(Object[] args) {
    Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
    // adapt 是抽象方法,由子类(我们这里是 CallAdapted) 实现
    return adapt(call, args);
  }

这里对 OkHttpCall 的入参解释如下:

  • requestFactory:RequestFactory 类型的对象,它负责根据服务接口方法的注解和传入的参数构建 Request 对象,该 Request 对象包含了 HTTP 请求的详细信息,如请求方法(GET、POST 等)、URL、请求头、请求体等。
  • args:调用服务接口方法时传入的参数数组,会被传递给 requestFactory 用于构建请求。
  • callFactory:Call.Factory 类型的对象,通常是 OkHttpClient 实例,它负责创建实际的 HTTP 请求调用。
  • responseConverter:Converter<ResponseBody, ResponseT> 类型的对象,用于将 HTTP 响应的 ResponseBody 转换为 Java 对象,ResponseT 是响应数据的目标类型。

CallAdapted 的实现如下:

java 复制代码
  static final class CallAdapted<ResponseT, ReturnT> extends HttpServiceMethod<ResponseT, ReturnT> {
    private final CallAdapter<ResponseT, ReturnT> callAdapter;

    CallAdapted(
        RequestFactory requestFactory,
        okhttp3.Call.Factory callFactory,
        Converter<ResponseBody, ResponseT> responseConverter,
        CallAdapter<ResponseT, ReturnT> callAdapter) {
      super(requestFactory, callFactory, responseConverter);
      this.callAdapter = callAdapter;
    }

    @Override
    protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
      return callAdapter.adapt(call);
    }
  }

上可以看到 CallAdapted#adapt 会调用 retrofit2.CallAdapter#adapt 方法来完成调用,而这里的 callAdapter 就是 上面构造时的入参 responseConverter。 CallAdapted#adapt 会返回一个 new ExecutorCallbackCall<>(executor, call) 对象,DefaultCallAdapterFactory.ExecutorCallbackCall 部分代码实现如下:

java 复制代码
  static final class ExecutorCallbackCall<T> implements Call<T> {
    final Executor callbackExecutor;
    final Call<T> delegate;

    ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
      this.callbackExecutor = callbackExecutor;
      // 实际类型是 retrofit2.OkHttpCall
      this.delegate = delegate;
    }

    @Override
    public void enqueue(final Callback<T> callback) {
      Objects.requireNonNull(callback, "callback == null");
	  // 异步执行,并执行回调函数
      delegate.enqueue(
          new Callback<T>() {
            @Override
            public void onResponse(Call<T> call, final Response<T> response) {
              callbackExecutor.execute(
                  () -> {
                    if (delegate.isCanceled()) {
                      // Emulate OkHttp's behavior of throwing/delivering an IOException on
                      // cancellation.
                      callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
                    } else {
                      callback.onResponse(ExecutorCallbackCall.this, response);
                    }
                  });
            }

            @Override
            public void onFailure(Call<T> call, final Throwable t) {
              callbackExecutor.execute(() -> callback.onFailure(ExecutorCallbackCall.this, t));
            }
          });
    }
	
	...
	
    @Override
    public Response<T> execute() throws IOException {
      // 同步执行
      return delegate.execute();
    }
    ...
}

从代码中可以看出,这里的调用是交由 delegate(实际类型是 retrofit2.OkHttpCall)来完成,而 retrofit2.OkHttpCall 内部会调用 OkHttp 来完成请求,这里不再赘述。

五、思路延伸

1. OkHttp 中的 Tag

在 OkHttp 中,Tag 是一个可以附加到 Request 或 Call 上的任意对象。它主要用于给请求打上特定的标记,这样在后续的操作中可以方便地对请求进行识别、管理和取消。以下是一些常见的使用场景:

  1. 请求的标识与分类
    你可以给不同类型的请求添加不同的 Tag,以便后续根据 Tag 对请求进行分类和管理。例如,在一个应用中有多种不同业务类型的网络请求,像用户信息请求、商品信息请求等,你可以分别给它们添加不同的 Tag 来区分。

  2. 请求的取消
    可以根据 Tag 来取消一组相关的请求。比如,当用户离开某个页面时,你可以取消该页面发起的所有请求,避免不必要的网络开销。示例如下:

    java 复制代码
    // 自定义拦截器,用于添加 请求Tag,如果有验证还可以再请求中添加身份信息等。
    public class TagInterceptor implements Interceptor {
        private final Object tag;
    
        public TagInterceptor(Object tag) {
            this.tag = tag;
        }
    
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request originalRequest = chain.request();
            Request newRequest = originalRequest.newBuilder()
                   .tag(tag)
                   .build();
            return chain.proceed(newRequest);
        }
    }
    
    
    // 创建 Retrofit 实例
    public class RetrofitClient {
        private static Retrofit retrofit = null;
    
        public static Retrofit getClient(String baseUrl, Object tag) {
            OkHttpClient client = new OkHttpClient.Builder()
            		// 添加自定义拦截器
                   .addInterceptor(new TagInterceptor(tag))
                   .build();
    
            if (retrofit == null) {
                retrofit = new Retrofit.Builder()
                        // 请求地址设置
                        .baseUrl("http://localhost:3000/temp-demo/")
                        .client(client)
                        // 在 Retrofit2 中,addConverterFactory 方法的主要作用是为 Retrofit 实例添加转换器工厂(Converter Factory),这些转换器工厂用于处理 HTTP 请求体和响应体与 Java 对象之间的相互转换。
                        .addConverterFactory(ScalarsConverterFactory.create())
                        .addConverterFactory(JacksonConverterFactory.create())
                        // Retrofit 默认的服务接口方法返回的是 Call 对象,Call 代表一个可执行的 HTTP 请求,
                        // 通过它可以进行同步或异步的网络请求操作。但在实际开发中,我们可能希望服务接口方法返回其他类型,
                        // 例如 CompletableFuture(Java 8 引入的异步编程工具)、RxJava 的 Observable、Flowable 等,
                        // 以更好地与不同的异步编程模型集成。调用适配器工厂就可以帮助我们实现这一功能。
                        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                        .build();
            }
            return retrofit;
        }
    }
    
    public class TagDemo {
    
        // 这里假设定义了一个简单的 API 接口
        interface ExampleApi {
            @GET("data")
            Call<String> getData();
        }
    
        public static void main(String[] args) {
            String baseUrl = "https://example.com/";
            Object tag = "exampleTag";
            Retrofit retrofit = RetrofitClient.getClient(baseUrl, tag);
    
            ExampleApi api = retrofit.create(ExampleApi.class);
            Call<String> call = api.getData();
            call.enqueue(new Callback<String>() {
                @Override
                public void onResponse(Call<String> call, Response<String> response) {
                    if (response.isSuccessful()) {
                        System.out.println("Response: " + response.body());
                    } else {
                        System.out.println("Request failed: " + response.code());
                    }
                }
    
                @Override
                public void onFailure(Call<String> call, Throwable t) {
                    System.out.println("Network error: " + t.getMessage());
                }
            });
    
            // 默认情况下 Call.Factory 就是 OkHttpClient,所以可以将其强转为 OkHttpClient 来获取对应的客户端实例
            OkHttpClient httpClient = (OkHttpClient) retrofit.callFactory();
    
            // 取消带有特定 Tag 的请求
            for (okhttp3.Call runningCall : httpClient.dispatcher().runningCalls()) {
                if (tag.equals(runningCall.request().tag())) {
                    runningCall.cancel();
                }
            }
        }
    }

五、参考内容

  1. 框架解析2-Retrofit
  2. 豆包
相关推荐
计算机小白一个11 分钟前
蓝桥杯 Java B 组之岛屿数量、二叉树路径和(区分DFS与回溯)
java·数据结构·算法·蓝桥杯
菠菠萝宝25 分钟前
【Java八股文】10-数据结构与算法面试篇
java·开发语言·面试·红黑树·跳表·排序·lru
不会Hello World的小苗31 分钟前
Java——链表(LinkedList)
java·开发语言·链表
Allen Bright1 小时前
【Java基础-46.3】Java泛型通配符详解:解锁类型安全的灵活编程
java·开发语言
柃歌1 小时前
【UCB CS 61B SP24】Lecture 7 - Lists 4: Arrays and Lists学习笔记
java·数据结构·笔记·学习·算法
柃歌1 小时前
【UCB CS 61B SP24】Lecture 4 - Lists 2: SLLists学习笔记
java·数据结构·笔记·学习·算法
是姜姜啊!1 小时前
redis的应用,缓存,分布式锁
java·redis·spring
梨落秋溪、2 小时前
输入框元素覆盖冲突
java·服务器·前端
hrrrrb2 小时前
【Java】Java 常用核心类篇 —— 时间-日期API(上)
java·开发语言
小突突突2 小时前
模拟实现Java中的计时器
java·开发语言·后端·java-ee