Android Retrofit 框架注解定義與解析模塊深度剖析(一)

一、引言

在現代 Android 和 Java 開發中,網絡請求是不可或缺的一部分。Retrofit 作為 Square 公司開源的一款強大的類型安全的 HTTP 客戶端,憑借其簡潔易用的 API 和高效的性能,在開發者社區中廣受歡迎。Retrofit 的核心特性之一便是通過注解來定義 HTTP 請求,這種方式使得代碼更加清晰、易讀且易于維護。本文將深入 Retrofit 框架的源碼,對其注解定義與解析模塊進行全面且細致的分析,揭示其背后的實現原理。

二、Retrofit 框架概述

2.1 Retrofit 的基本工作流程

Retrofit 的主要工作流程可以概括為以下幾個步驟:

  1. 定義服務接口:開發者使用注解定義一個接口,該接口包含了各種 HTTP 請求方法。
  2. 創建 Retrofit 實例:通過 Retrofit.Builder 構建 Retrofit 實例,配置請求的基礎 URL、轉換器工廠、調用適配器工廠等。
  3. 創建服務代理對象:使用 Retrofit 實例創建服務接口的代理對象。
  4. 發起請求:調用服務代理對象的方法發起 HTTP 請求,并處理響應結果。

2.2 注解在 Retrofit 中的作用

注解在 Retrofit 中扮演著至關重要的角色,它們用于描述 HTTP 請求的各個方面,包括請求方法(如 GET、POST 等)、請求路徑、請求參數、請求頭、請求體等。通過注解,開發者可以以一種聲明式的方式定義請求,而無需編寫繁瑣的網絡請求代碼。

三、Retrofit 注解的定義

3.1 HTTP 請求方法注解

3.1.1 @GET 注解

java

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 表示 HTTP GET 請求的注解*/
@Documented
// 該注解只能應用于方法上
@Target(ElementType.METHOD)
// 該注解在運行時保留,以便通過反射獲取
@Retention(RetentionPolicy.RUNTIME)
public @interface GET {/*** 指定請求的相對路徑* @return 請求的相對路徑,默認為空字符串*/String value() default "";
}

@GET 注解用于標記一個方法為 HTTP GET 請求,value 屬性用于指定請求的相對路徑。例如:

java

public interface ApiService {@GET("users/{id}")Call<User> getUser(@Path("id") int userId);
}
3.1.2 其他 HTTP 請求方法注解

除了 @GET 注解,Retrofit 還提供了 @POST@PUT@DELETE@HEAD@OPTIONS@PATCH 等注解,它們的定義方式與 @GET 注解類似,只是用于不同的 HTTP 請求方法。

3.2 請求路徑與參數注解

3.2.1 @Path 注解

java

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 用于替換 URL 中占位符的注解*/
@Documented
// 該注解只能應用于方法的參數上
@Target(ElementType.PARAMETER)
// 該注解在運行時保留
@Retention(RetentionPolicy.RUNTIME)
public @interface Path {/*** 指定 URL 中的占位符名稱* @return 占位符名稱*/String value();/*** 指定是否對參數進行 URL 編碼,默認為 false* @return 是否進行 URL 編碼*/boolean encoded() default false;
}

@Path 注解用于替換 URL 中的占位符,例如在上面的 getUser 方法中,@Path("id") 表示將 userId 參數的值替換到 URL 中的 {id} 占位符處。

3.2.2 @Query@QueryMap 注解

java

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 用于添加查詢參數到 URL 中的注解*/
@Documented
// 該注解只能應用于方法的參數上
@Target(ElementType.PARAMETER)
// 該注解在運行時保留
@Retention(RetentionPolicy.RUNTIME)
public @interface Query {/*** 指定查詢參數的名稱* @return 查詢參數的名稱*/String value();/*** 指定是否對參數進行 URL 編碼,默認為 false* @return 是否進行 URL 編碼*/boolean encoded() default false;
}import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 用于添加多個查詢參數到 URL 中的注解*/
@Documented
// 該注解只能應用于方法的參數上
@Target(ElementType.PARAMETER)
// 該注解在運行時保留
@Retention(RetentionPolicy.RUNTIME)
public @interface QueryMap {/*** 指定是否對參數進行 URL 編碼,默認為 false* @return 是否進行 URL 編碼*/boolean encoded() default false;
}

@Query 注解用于添加單個查詢參數到 URL 中,@QueryMap 注解用于添加多個查詢參數。例如:

java

public interface ApiService {@GET("search")Call<List<Item>> search(@Query("keyword") String keyword, @Query("page") int page);@GET("search")Call<List<Item>> searchWithMap(@QueryMap Map<String, String> queryParams);
}
3.2.3 @Header@HeaderMap 注解

java

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 用于添加請求頭的注解*/
@Documented
// 該注解只能應用于方法的參數上
@Target(ElementType.PARAMETER)
// 該注解在運行時保留
@Retention(RetentionPolicy.RUNTIME)
public @interface Header {/*** 指定請求頭的名稱* @return 請求頭的名稱*/String value();
}import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 用于添加多個請求頭的注解*/
@Documented
// 該注解只能應用于方法的參數上
@Target(ElementType.PARAMETER)
// 該注解在運行時保留
@Retention(RetentionPolicy.RUNTIME)
public @interface HeaderMap {
}

@Header 注解用于添加單個請求頭,@HeaderMap 注解用于添加多個請求頭。例如:

java

public interface ApiService {@GET("data")Call<Data> getData(@Header("Authorization") String token);@GET("data")Call<Data> getDataWithMap(@HeaderMap Map<String, String> headers);
}
3.2.4 @Body 注解

java

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 用于指定請求體的注解*/
@Documented
// 該注解只能應用于方法的參數上
@Target(ElementType.PARAMETER)
// 該注解在運行時保留
@Retention(RetentionPolicy.RUNTIME)
public @interface Body {
}

@Body 注解用于指定請求體,通常用于 POST、PUT 等請求。例如:

java

public interface ApiService {@POST("users")Call<User> createUser(@Body User user);
}
3.2.5 @FormUrlEncoded@Field@FieldMap 注解

java

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 用于標記表單編碼請求的注解*/
@Documented
// 該注解只能應用于方法上
@Target(ElementType.METHOD)
// 該注解在運行時保留
@Retention(RetentionPolicy.RUNTIME)
public @interface FormUrlEncoded {
}import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 用于添加表單字段的注解*/
@Documented
// 該注解只能應用于方法的參數上
@Target(ElementType.PARAMETER)
// 該注解在運行時保留
@Retention(RetentionPolicy.RUNTIME)
public @interface Field {/*** 指定表單字段的名稱* @return 表單字段的名稱*/String value();/*** 指定是否對參數進行 URL 編碼,默認為 false* @return 是否進行 URL 編碼*/boolean encoded() default false;
}import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 用于添加多個表單字段的注解*/
@Documented
// 該注解只能應用于方法的參數上
@Target(ElementType.PARAMETER)
// 該注解在運行時保留
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldMap {/*** 指定是否對參數進行 URL 編碼,默認為 false* @return 是否進行 URL 編碼*/boolean encoded() default false;
}

@FormUrlEncoded 注解用于標記一個方法為表單編碼請求,@Field 注解用于添加單個表單字段,@FieldMap 注解用于添加多個表單字段。例如:

java

public interface ApiService {@FormUrlEncoded@POST("login")Call<LoginResponse> login(@Field("username") String username, @Field("password") String password);@FormUrlEncoded@POST("login")Call<LoginResponse> loginWithMap(@FieldMap Map<String, String> fields);
}
3.2.6 @Multipart@Part@PartMap 注解

java

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 用于標記多部分請求的注解*/
@Documented
// 該注解只能應用于方法上
@Target(ElementType.METHOD)
// 該注解在運行時保留
@Retention(RetentionPolicy.RUNTIME)
public @interface Multipart {
}import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 用于添加多部分請求中的一個部分的注解*/
@Documented
// 該注解只能應用于方法的參數上
@Target(ElementType.PARAMETER)
// 該注解在運行時保留
@Retention(RetentionPolicy.RUNTIME)
public @interface Part {/*** 指定部分的名稱* @return 部分的名稱*/String value() default "";
}import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 用于添加多部分請求中的多個部分的注解*/
@Documented
// 該注解只能應用于方法的參數上
@Target(ElementType.PARAMETER)
// 該注解在運行時保留
@Retention(RetentionPolicy.RUNTIME)
public @interface PartMap {
}

@Multipart 注解用于標記一個方法為多部分請求,@Part 注解用于添加多部分請求中的一個部分,@PartMap 注解用于添加多部分請求中的多個部分。多部分請求常用于文件上傳等場景。例如:

java

public interface ApiService {@Multipart@POST("upload")Call<UploadResponse> uploadFile(@Part("file"; filename="image.jpg"") RequestBody file);@Multipart@POST("upload")Call<UploadResponse> uploadFiles(@PartMap Map<String, RequestBody> files);
}

3.3 其他注解

3.3.1 @Headers 注解

java

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 用于添加多個請求頭的注解*/
@Documented
// 該注解只能應用于方法上
@Target(ElementType.METHOD)
// 該注解在運行時保留
@Retention(RetentionPolicy.RUNTIME)
public @interface Headers {/*** 指定請求頭的數組* @return 請求頭的數組*/String[] value();
}

@Headers 注解用于在方法上添加多個請求頭。例如:

java

public interface ApiService {@Headers({"Content-Type: application/json","Authorization: Bearer token123"})@GET("data")Call<Data> getData();
}

四、Retrofit 注解的解析

4.1 解析入口:ServiceMethod

ServiceMethod 是 Retrofit 中解析注解的核心類,它負責將接口方法上的注解信息解析為實際的 HTTP 請求信息。以下是 ServiceMethod 類的部分源碼:

java

abstract class ServiceMethod<T> {/*** 解析接口方法上的注解,創建 ServiceMethod 實例* @param retrofit Retrofit 實例* @param method 接口方法* @param <T> 響應類型* @return ServiceMethod 實例*/static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {// 創建 RequestFactory.Builder 實例,用于構建 RequestFactoryRequestFactory.Builder requestFactoryBuilder = new RequestFactory.Builder(retrofit, method);// 解析請求方法和路徑相關的注解requestFactoryBuilder.parseMethodAnnotation(method.getAnnotations());// 獲取方法的參數類型Type[] parameterTypes = method.getGenericParameterTypes();// 獲取方法的參數注解Annotation[][] parameterAnnotationsArray = method.getParameterAnnotations();// 遍歷方法的每個參數for (int p = 0; p < parameterTypes.length; p++) {// 解析參數的注解requestFactoryBuilder.parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p]);}// 構建 RequestFactory 實例RequestFactory requestFactory = requestFactoryBuilder.build();// 獲取方法的返回類型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.");}// 創建 CallAdapter 實例,用于將 Call 對象轉換為其他類型CallAdapter<T, ?> callAdapter = createCallAdapter(retrofit, method, returnType, requestFactory);// 獲取響應類型Type responseType = callAdapter.responseType();if (Utils.hasUnresolvableType(responseType)) {throw methodError(method, "Call return type must not include a type variable or wildcard: %s", returnType);}// 創建 Converter 實例,用于將響應數據轉換為指定類型Converter<ResponseBody, T> responseConverter =createResponseConverter(retrofit, method, responseType);// 獲取 OkHttpClient 實例okhttp3.Call.Factory callFactory = retrofit.callFactory();// 創建 ServiceMethod 實例return new HttpServiceMethod<>(callFactory, requestFactory, callAdapter, responseConverter);}/*** 創建 CallAdapter 實例* @param retrofit Retrofit 實例* @param method 接口方法* @param returnType 方法的返回類型* @param requestFactory 請求工廠* @param <T> 響應類型* @return CallAdapter 實例*/private static <T> CallAdapter<T, ?> createCallAdapter(Retrofit retrofit, Method method, Type returnType, RequestFactory requestFactory) {try {// 通過 Retrofit 實例獲取 CallAdapter.Factory 列表,并調用其 get 方法創建 CallAdapter 實例return (CallAdapter<T, ?>) retrofit.callAdapter(returnType, method.getAnnotations());} catch (RuntimeException e) {throw methodError(method, e, "Unable to create call adapter for %s", returnType);}}/*** 創建響應轉換器實例* @param retrofit Retrofit 實例* @param method 接口方法* @param responseType 響應類型* @param <T> 響應類型* @return 響應轉換器實例*/private static <T> Converter<ResponseBody, T> createResponseConverter(Retrofit retrofit, Method method, Type responseType) {try {// 通過 Retrofit 實例獲取 Converter.Factory 列表,并調用其 responseBodyConverter 方法創建響應轉換器實例return retrofit.responseBodyConverter(responseType, method.getAnnotations());} catch (RuntimeException e) {throw methodError(method, e, "Unable to create converter for %s", responseType);}}/*** 抽象方法,用于執行請求* @param args 方法參數* @return 響應結果*/abstract @Nullable T invoke(Object[] args);
}

parseAnnotations 方法是解析的入口,它接收 Retrofit 實例和 Method 實例作為參數,通過以下步驟完成注解解析:

  1. 創建 RequestFactory.Builder 實例,用于構建 RequestFactory
  2. 調用 requestFactoryBuilder.parseMethodAnnotation 方法解析請求方法和路徑相關的注解。
  3. 遍歷方法的每個參數,調用 requestFactoryBuilder.parseParameter 方法解析參數的注解。
  4. 構建 RequestFactory 實例。
  5. 獲取方法的返回類型,并創建 CallAdapter 實例,用于將 Call 對象轉換為其他類型。
  6. 獲取響應類型,并創建 Converter 實例,用于將響應數據轉換為指定類型。
  7. 獲取 OkHttpClient 實例,并創建 ServiceMethod 實例。

4.2 解析請求方法和路徑注解:RequestFactory.Builder.parseMethodAnnotation 方法

java

static final class Builder {// 存儲請求方法(如 GET、POST 等)private String httpMethod;// 存儲請求是否需要請求體private boolean hasBody;// 存儲請求的相對路徑private String relativeUrl;// 存儲請求的請求頭private okhttp3.Headers headers;// 存儲路徑中的占位符名稱private List<String> relativeUrlParamNames;// 標記是否為多部分請求private boolean isMultipart;// 標記是否為表單編碼請求private boolean isFormEncoded;/*** 解析請求方法和路徑相關的注解* @param annotations 方法上的注解數組*/void parseMethodAnnotation(Annotation[] annotations) {for (Annotation annotation : annotations) {if (annotation instanceof HttpMethod) {// 如果注解是 HttpMethod 類型(如 @GET、@POST 等)HttpMethod httpMethodAnnotation = (HttpMethod) annotation;// 獲取請求方法this.httpMethod = httpMethodAnnotation.value();// 判斷是否需要請求體this.hasBody = httpMethodAnnotation.hasBody();String path = httpMethodAnnotation.path();if (!path.isEmpty()) {// 檢查路徑是否以 / 開頭if (path.startsWith("/")) {throw methodError(method, "@%s path must not start with /: %s",httpMethodAnnotation.annotationType().getSimpleName(), path);}this.relativeUrl = path;// 解析路徑中的占位符this.relativeUrlParamNames = parsePathParameters(path);}} else if (annotation instanceof Headers) {// 如果注解是 Headers 類型String[] headersToParse = ((Headers) annotation).value();if (headersToParse.length == 0) {throw methodError(method, "@Headers annotation is empty.");}// 解析請求頭this.headers = parseHeaders(headersToParse);} else if (annotation instanceof Multipart) {// 如果注解是 Multipart 類型if (this.hasBody) {throw methodError(method, "Only one encoding annotation is allowed.");}this.isMultipart = true;this.hasBody = true;} else if (annotation instanceof FormUrlEncoded) {// 如果注解是 FormUrlEncoded 類型if (this.hasBody) {throw methodError(method, "Only one encoding annotation is allowed.");}this.isFormEncoded = true;this.hasBody = true;}}if (httpMethod == null) {throw methodError(method, "HTTP method annotation is required (e.g., @GET, @POST, etc.).");}}/*** 解析路徑中的占位符* @param path 請求路徑* @return 占位符名稱列表*/private static List<String> parsePathParameters(String path) {// 定義正則表達式,用于匹配路徑中的占位符Matcher m = PARAM_URL_REGEX.matcher(path);List<String> patterns = new ArrayList<>();while (m.find()) {String name = m.group(1);if (name == null) {continue;}if (patterns.contains(name)) {throw new IllegalArgumentException("URL path "" + path + "" has duplicate param "" + name + "".");}patterns.add(name);}return patterns;}/*** 解析請求頭* @param headers 請求頭數組* @return OkHttp 的 Headers 實例*/private static okhttp3.Headers parseHeaders(String[] headers) {okhttp3.Headers.Builder builder = new okhttp3.Headers.Builder();for (String header : headers) {int colon = header.indexOf(':');if (colon == -1 || colon == 0 || colon == header.length() - 1) {throw new IllegalArgumentException("Headers value must be in the form "Name: Value". Found: "" + header + """);}String name = header.substring(0, colon).trim();String value = header.substring(colon + 1).trim();builder.add(name, value);}return builder.build();}
}

parseMethodAnnotation 方法遍歷方法上的所有注解,根據注解的類型進行不同的處理:

  1. 如果注解是 HttpMethod 類型(如 @GET@POST 等),獲取請求方法、判斷是否需要請求體,并解析路徑中的占位符。
  2. 如果注解是 Headers 類型,解析請求頭。
  3. 如果注解是 Multipart 類型,標記為多部分請求。
  4. 如果注解是 FormUrlEncoded 類型,標記為表單編碼請求。

4.3 解析參數注解:RequestFactory.Builder.parseParameter 方法

java

static final class Builder {// 存儲參數處理器數組private ParameterHandler<?>[] parameterHandlers;/*** 解析方法參數的注解* @param p 參數索引* @param parameterType 參數類型* @param annotations 參數上的注解數組*/void parseParameter(int p, Type parameterType, @Nullable Annotation[] annotations) {if (annotations == null) {throw parameterError(method, p, "No Retrofit annotation found.");}ParameterHandler<?> result = null;for (Annotation annotation : annotations) {// 創建參數處理器ParameterHandler<?> annotationAction = parseParameterAnnotation(p, parameterType, annotations, annotation);if (annotationAction == null) {continue;}if (result != null) {throw parameterError(method, p, "Multiple Retrofit annotations found, only one allowed.");}result = annotationAction;}if (result == null) {throw parameterError(method, p, "No Retrofit annotation found.");}// 將參數處理器添加到參數處理器數組中parameterHandlers[p] = result;}/*** 解析單個參數注解* @param p 參數索引* @param type 參數類型* @param annotations 參數上的注解數組* @param annotation 當前要解析的注解* @return 參數處理器*/private @Nullable ParameterHandler<?> parseParameterAnnotation(int p, Type type, Annotation[] annotations, Annotation annotation) {if (annotation instanceof Path) {// 如果注解

4.3 解析參數注解:RequestFactory.Builder.parseParameter 方法(續)

java

static final class Builder {// 存儲參數處理器數組private ParameterHandler<?>[] parameterHandlers;/*** 解析方法參數的注解* @param p 參數索引* @param parameterType 參數類型* @param annotations 參數上的注解數組*/void parseParameter(int p, Type parameterType, @Nullable Annotation[] annotations) {if (annotations == null) {throw parameterError(method, p, "No Retrofit annotation found.");}ParameterHandler<?> result = null;for (Annotation annotation : annotations) {// 創建參數處理器ParameterHandler<?> annotationAction = parseParameterAnnotation(p, parameterType, annotations, annotation);if (annotationAction == null) {continue;}if (result != null) {throw parameterError(method, p, "Multiple Retrofit annotations found, only one allowed.");}result = annotationAction;}if (result == null) {throw parameterError(method, p, "No Retrofit annotation found.");}// 將參數處理器添加到參數處理器數組中parameterHandlers[p] = result;}/*** 解析單個參數注解* @param p 參數索引* @param type 參數類型* @param annotations 參數上的注解數組* @param annotation 當前要解析的注解* @return 參數處理器*/private @Nullable ParameterHandler<?> parseParameterAnnotation(int p, Type type, Annotation[] annotations, Annotation annotation) {if (annotation instanceof Path) {// 如果注解是 Path 類型Path path = (Path) annotation;// 檢查路徑中是否包含該占位符if (!relativeUrlParamNames.contains(path.value())) {throw parameterError(method, p, "@Path parameter "" + path.value()+ "" not found in relative URL "" + relativeUrl + """);}// 檢查參數類型是否為基本類型或字符串if (Utils.hasUnresolvableType(type)) {throw parameterError(method, p,"@Path parameter type must not include a type variable or wildcard: %s", type);}// 創建 PathParameterHandler 實例return new ParameterHandler.Path<>(path.value(), path.encoded(),converterFactory.stringConverter(type, annotations, retrofit));} else if (annotation instanceof Query) {// 如果注解是 Query 類型Query query = (Query) annotation;// 檢查參數類型是否為基本類型或字符串if (Utils.hasUnresolvableType(type)) {throw parameterError(method, p,"@Query parameter type must not include a type variable or wildcard: %s", type);}// 創建 QueryParameterHandler 實例return new ParameterHandler.Query<>(query.value(), query.encoded(),converterFactory.stringConverter(type, annotations, retrofit));} else if (annotation instanceof QueryMap) {// 如果注解是 QueryMap 類型if (!Map.class.isAssignableFrom(Utils.getRawType(type))) {throw parameterError(method, p, "@QueryMap parameter type must be Map.");}Type keyType = Utils.getSupertype(type, Map.class, String.class);if (keyType != String.class) {throw parameterError(method, p, "@QueryMap keys must be of type String: %s", type);}Type valueType = Utils.getSupertype(type, Map.class, Object.class);// 創建 QueryMapParameterHandler 實例return new ParameterHandler.QueryMap<>(converterFactory.stringConverter(valueType, annotations, retrofit));} else if (annotation instanceof Header) {// 如果注解是 Header 類型Header header = (Header) annotation;// 檢查參數類型是否為基本類型或字符串if (Utils.hasUnresolvableType(type)) {throw parameterError(method, p,"@Header parameter type must not include a type variable or wildcard: %s", type);}// 創建 HeaderParameterHandler 實例return new ParameterHandler.Header<>(header.value(),converterFactory.stringConverter(type, annotations, retrofit));} else if (annotation instanceof HeaderMap) {// 如果注解是 HeaderMap 類型if (!Map.class.isAssignableFrom(Utils.getRawType(type))) {throw parameterError(method, p, "@HeaderMap parameter type must be Map.");}Type keyType = Utils.getSupertype(type, Map.class, String.class);if (keyType != String.class) {throw parameterError(method, p, "@HeaderMap keys must be of type String: %s", type);}Type valueType = Utils.getSupertype(type, Map.class, Object.class);// 創建 HeaderMapParameterHandler 實例return new ParameterHandler.HeaderMap<>(converterFactory.stringConverter(valueType, annotations, retrofit));} else if (annotation instanceof Body) {// 如果注解是 Body 類型if (isFormEncoded || isMultipart) {throw parameterError(method, p,"@Body parameters cannot be used with form or multi - part encoding.");}// 創建 BodyParameterHandler 實例return new ParameterHandler.Body<>(converterFactory.requestBodyConverter(type, annotations, methodAnnotations, retrofit));} else if (annotation instanceof Field) {// 如果注解是 Field 類型if (!isFormEncoded) {throw parameterError(method, p, "@Field parameters can only be used with form encoding.");}Field field = (Field) annotation;// 檢查參數類型是否為基本類型或字符串if (Utils.hasUnresolvableType(type)) {throw parameterError(method, p,"@Field parameter type must not include a type variable or wildcard: %s", type);}// 創建 FieldParameterHandler 實例return new ParameterHandler.Field<>(field.value(), field.encoded(),converterFactory.stringConverter(type, annotations, retrofit));} else if (annotation instanceof FieldMap) {// 如果注解是 FieldMap 類型if (!isFormEncoded) {throw parameterError(method, p, "@FieldMap parameters can only be used with form encoding.");}if (!Map.class.isAssignableFrom(Utils.getRawType(type))) {throw parameterError(method, p, "@FieldMap parameter type must be Map.");}Type keyType = Utils.getSupertype(type, Map.class, String.class);if (keyType != String.class) {throw parameterError(method, p, "@FieldMap keys must be of type String: %s", type);}Type valueType = Utils.getSupertype(type, Map.class, Object.class);// 創建 FieldMapParameterHandler 實例return new ParameterHandler.FieldMap<>(converterFactory.stringConverter(valueType, annotations, retrofit));} else if (annotation instanceof Part) {// 如果注解是 Part 類型if (!isMultipart) {throw parameterError(method, p, "@Part parameters can only be used with multipart encoding.");}Part part = (Part) annotation;String partName = part.value();if (!"".equals(partName) && !partName.endsWith(";")) {partName = partName + ";";}// 創建 PartParameterHandler 實例return new ParameterHandler.Part<>(partName,converterFactory.requestBodyConverter(type, annotations, methodAnnotations, retrofit));} else if (annotation instanceof PartMap) {// 如果注解是 PartMap 類型if (!isMultipart) {throw parameterError(method, p, "@PartMap parameters can only be used with multipart encoding.");}if (!Map.class.isAssignableFrom(Utils.getRawType(type))) {throw parameterError(method, p, "@PartMap parameter type must be Map.");}Type keyType = Utils.getSupertype(type, Map.class, String.class);if (keyType != String.class) {throw parameterError(method, p, "@PartMap keys must be of type String: %s", type);}Type valueType = Utils.getSupertype(type, Map.class, Object.class);// 創建 PartMapParameterHandler 實例return new ParameterHandler.PartMap<>(converterFactory.requestBodyConverter(valueType, annotations, methodAnnotations, retrofit));}return null;}
}
4.3.1 詳細解析邏輯
  • @Path 注解解析

    • 首先,從 Path 注解中獲取占位符名稱,檢查該占位符是否存在于之前解析得到的 relativeUrlParamNames 列表中。如果不存在,會拋出異常,確保路徑占位符的正確性。
    • 接著,檢查參數類型是否包含不可解析的類型變量或通配符。如果存在,也會拋出異常,因為 @Path 參數類型應該是基本類型或字符串。
    • 最后,創建 ParameterHandler.Path 實例,該實例負責處理路徑占位符的替換。converterFactory.stringConverter 方法用于創建一個將參數類型轉換為字符串的轉換器,以便將參數值正確地替換到路徑中。
  • @Query 注解解析

    • 對于 Query 注解,同樣會檢查參數類型是否包含不可解析的類型變量或通配符。
    • 然后創建 ParameterHandler.Query 實例,該實例會將參數值作為查詢參數添加到 URL 中。converterFactory.stringConverter 用于將參數值轉換為字符串形式。
  • @QueryMap 注解解析

    • 先檢查參數類型是否為 Map 類型,如果不是會拋出異常。
    • 接著檢查 Map 的鍵類型是否為 String 類型,若不是也會拋出異常。
    • 最后創建 ParameterHandler.QueryMap 實例,該實例會將 Map 中的鍵值對作為查詢參數添加到 URL 中。converterFactory.stringConverter 用于將 Map 的值轉換為字符串。
  • @Header 注解解析

    • 檢查參數類型是否包含不可解析的類型變量或通配符。
    • 創建 ParameterHandler.Header 實例,該實例會將參數值作為請求頭添加到請求中。converterFactory.stringConverter 用于將參數值轉換為字符串形式的請求頭值。
  • @HeaderMap 注解解析

    • 檢查參數類型是否為 Map 類型,以及 Map 的鍵類型是否為 String 類型。
    • 創建 ParameterHandler.HeaderMap 實例,該實例會將 Map 中的鍵值對作為請求頭添加到請求中。converterFactory.stringConverter 用于將 Map 的值轉換為字符串形式的請求頭值。
  • @Body 注解解析

    • 檢查當前請求是否為表單編碼或多部分請求,如果是則拋出異常,因為 @Body 參數不能與表單或多部分編碼同時使用。
    • 創建 ParameterHandler.Body 實例,該實例會將參數作為請求體發送。converterFactory.requestBodyConverter 用于將參數類型轉換為 RequestBody 類型,以便進行網絡傳輸。
  • @Field 注解解析

    • 檢查當前請求是否為表單編碼請求,如果不是則拋出異常,因為 @Field 參數只能用于表單編碼請求。
    • 檢查參數類型是否包含不可解析的類型變量或通配符。
    • 創建 ParameterHandler.Field 實例,該實例會將參數值作為表單字段添加到請求體中。converterFactory.stringConverter 用于將參數值轉換為字符串形式的表單字段值。
  • @FieldMap 注解解析

    • 檢查當前請求是否為表單編碼請求,以及參數類型是否為 Map 類型,Map 的鍵類型是否為 String 類型。
    • 創建 ParameterHandler.FieldMap 實例,該實例會將 Map 中的鍵值對作為表單字段添加到請求體中。converterFactory.stringConverter 用于將 Map 的值轉換為字符串形式的表單字段值。
  • @Part 注解解析

    • 檢查當前請求是否為多部分請求,如果不是則拋出異常,因為 @Part 參數只能用于多部分請求。
    • 處理 Part 注解的 value 屬性,確保其格式正確。
    • 創建 ParameterHandler.Part 實例,該實例會將參數作為多部分請求的一部分添加到請求體中。converterFactory.requestBodyConverter 用于將參數類型轉換為 RequestBody 類型。
  • @PartMap 注解解析

    • 檢查當前請求是否為多部分請求,以及參數類型是否為 Map 類型,Map 的鍵類型是否為 String 類型。
    • 創建 ParameterHandler.PartMap 實例,該實例會將 Map 中的鍵值對作為多部分請求的多個部分添加到請求體中。converterFactory.requestBodyConverter 用于將 Map 的值轉換為 RequestBody 類型。

4.4 創建 RequestFactory 實例

在完成所有參數注解的解析后,會調用 RequestFactory.Builderbuild 方法來構建 RequestFactory 實例:

java

RequestFactory build() {return new RequestFactory(this);
}

RequestFactory 類封裝了所有解析得到的請求信息,包括請求方法、請求路徑、請求頭、請求體等,后續會根據這些信息創建實際的 Request 對象。

4.5 創建 CallAdapterConverter 實例

ServiceMethod.parseAnnotations 方法中,還會創建 CallAdapterConverter 實例:

java

// 創建 CallAdapter 實例,用于將 Call 對象轉換為其他類型
CallAdapter<T, ?> callAdapter = createCallAdapter(retrofit, method, returnType, requestFactory);
// 獲取響應類型
Type responseType = callAdapter.responseType();
if (Utils.hasUnresolvableType(responseType)) {throw methodError(method, "Call return type must not include a type variable or wildcard: %s", returnType);
}// 創建 Converter 實例,用于將響應數據轉換為指定類型
Converter<ResponseBody, T> responseConverter =createResponseConverter(retrofit, method, responseType);
4.5.1 CallAdapter 實例創建

createCallAdapter 方法通過 Retrofit 實例的 callAdapter 方法從 CallAdapter.Factory 列表中查找合適的 CallAdapter.Factory,并調用其 get 方法創建 CallAdapter 實例。CallAdapter 的作用是將 Call 對象轉換為其他類型,例如將 Call<Response> 轉換為 Observable<Response> 等,以支持不同的異步編程模型。

4.5.2 Converter 實例創建

createResponseConverter 方法通過 Retrofit 實例的 responseBodyConverter 方法從 Converter.Factory 列表中查找合適的 Converter.Factory,并調用其 responseBodyConverter 方法創建 Converter 實例。Converter 的作用是將 ResponseBody 轉換為指定的響應類型,例如將 JSON 數據轉換為 Java 對象。

4.6 創建 ServiceMethod 實例

最后,根據前面解析得到的信息,創建 ServiceMethod 實例:

java

// 獲取 OkHttpClient 實例
okhttp3.Call.Factory callFactory = retrofit.callFactory();
// 創建 ServiceMethod 實例
return new HttpServiceMethod<>(callFactory, requestFactory, callAdapter, responseConverter);

HttpServiceMethodServiceMethod 的具體實現類,它負責執行實際的 HTTP 請求,并處理響應結果。在 invoke 方法中,會根據 RequestFactory 創建 Request 對象,使用 OkHttpClient 發送請求,然后通過 CallAdapterConverter 處理響應。

java

final class HttpServiceMethod<ResponseT, ReturnT> extends ServiceMethod<ReturnT> {private final okhttp3.Call.Factory callFactory;private final RequestFactory requestFactory;private final CallAdapter<ResponseT, ReturnT> callAdapter;private final Converter<ResponseBody, ResponseT> responseConverter;HttpServiceMethod(okhttp3.Call.Factory callFactory, RequestFactory requestFactory,CallAdapter<ResponseT, ReturnT> callAdapter,Converter<ResponseBody, ResponseT> responseConverter) {this.callFactory = callFactory;this.requestFactory = requestFactory;this.callAdapter = callAdapter;this.responseConverter = responseConverter;}@Override@Nullable ReturnT invoke(Object[] args) {// 創建 OkHttp 的 Request 對象Request request = requestFactory.create(args);// 創建 OkHttp 的 Call 對象okhttp3.Call call = callFactory.newCall(request);// 調用 CallAdapter 的 adapt 方法將 Call 對象轉換為指定類型return callAdapter.adapt(new OkHttpCall<>(request, callFactory, responseConverter));}
}

五、注解解析后的使用

ServiceMethod 實例創建完成后,就可以使用它來發起 HTTP 請求了。在調用服務接口的方法時,實際上是調用 ServiceMethodinvoke 方法:

java

public interface ApiService {@GET("users/{id}")Call<User> getUser(@Path("id") int userId);
}// 創建 Retrofit 實例
Retrofit retrofit = new Retrofit.Builder().baseUrl("https://api.example.com/").addConverterFactory(GsonConverterFactory.create()).build();// 創建服務代理對象
ApiService apiService = retrofit.create(ApiService.class);// 發起請求
Call<User> call = apiService.getUser(1);
call.enqueue(new Callback<User>() {@Overridepublic void onResponse(Call<User> call, Response<User> response) {if (response.isSuccessful()) {User user = response.body();// 處理響應數據} else {// 處理請求失敗}}@Overridepublic void onFailure(Call<User> call, Throwable t) {// 處理請求異常}
});

在上述代碼中,apiService.getUser(1) 實際上調用了 ServiceMethodinvoke 方法,該方法會根據之前解析得到的注解信息創建 Request 對象,使用 OkHttpClient 發送請求,并通過 CallAdapterConverter 處理響應結果。

六、總結

Retrofit 的注解定義與解析模塊是其核心功能之一,通過使用注解,開發者可以以一種簡潔、聲明式的方式定義 HTTP 請求。在解析過程中,Retrofit 利用 Java 的反射機制,在運行時獲取方法和參數上的注解信息,并根據注解類型進行相應的處理。具體步驟包括解析請求方法和路徑注解、解析參數注解、創建 RequestFactoryCallAdapterConverter 實例,最終創建 ServiceMethod 實例來執行實際的 HTTP 請求。這種設計使得 Retrofit 具有高度的靈活性和可擴展性,開發者可以通過自定義 CallAdapter.FactoryConverter.Factory 來滿足不同的需求。同時,注解的使用也使得代碼更加清晰、易讀和易于維護,提高了開發效率。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/71992.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/71992.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/71992.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C# Enumerable類 之 數據分組

總目錄 前言 在 C# 中&#xff0c;System.Linq.Enumerable 類是 LINQ&#xff08;Language Integrated Query&#xff09;的核心組成部分&#xff0c;它提供了一系列靜態方法&#xff0c;用于操作實現了 IEnumerable 接口的集合。通過這些方法&#xff0c;我們可以輕松地對集合…

推理模型對SQL理解能力的評測:DeepSeek r1、GPT-4o、Kimi k1.5和Claude 3.7 Sonnet

引言 隨著大型語言模型&#xff08;LLMs&#xff09;在技術領域的應用日益廣泛&#xff0c;評估這些模型在特定技術任務上的能力變得越來越重要。本研究聚焦于四款領先的推理模型——DeepSeek r1、GPT-4o、Kimi k1.5和Claude 3.7 Sonnet在SQL理解與分析方面的能力&#xff0c;…

IDEA接入阿里云百煉中免費的通義千問[2025版]

安裝deepseek 上一篇文章IDEA安裝deepseek最新教程2025中說明了怎么用idea安裝codeGPT插件&#xff0c;并接入DeepSeek&#xff0c;無奈接入的官方api已經不能使用了&#xff0c;所以我們嘗試從其他地方接入 阿里云百煉https://bailian.console.aliyun.com/ 阿里云百煉?是阿…

實施一套先進的智能攝像頭服務系統。

一、項目背景 隨著物聯網、人工智能和大數據技術的飛速發展&#xff0c;智能攝像頭已成為家庭、企業以及公共安全領域的重要設備。其便捷、高效、智能的特點&#xff0c;使得市場需求日益增長。為了滿足用戶對智能監控的多樣化需求&#xff0c;提供更加全面、可靠的監控服務&a…

linux自啟動服務

在Linux環境中&#xff0c;systemd是一個系統和服務管理器&#xff0c;它為每個服務使用.service文件進行配置。systemctl是用于控制系統服務的主要工具。本文將詳細介紹如何使用systemctl來管理vsftpd服務&#xff0c;以及如何設置服務自啟動。 使用Systemd設置自啟動服務 創…

010-Catch2

Catch2 一、框架簡介 Catch2 是一個基于 C 的現代化單元測試框架&#xff0c;支持 TDD&#xff08;測試驅動開發&#xff09;和 BDD&#xff08;行為驅動開發&#xff09;模式。其核心優勢在于&#xff1a; 單頭文件設計&#xff1a;v2.x 版本僅需包含 catch.hpp 即可使用自然…

數字人分身開發指南:從概念到實戰

一、什么是數字人分身&#xff1f; 想象一下&#xff0c;在電腦或手機屏幕里&#xff0c;一個能跟你聊天、回答問題&#xff0c;甚至還能做表情的虛擬角色。這就是數字人分身&#xff0c;它用上了人工智能技術&#xff0c;讓機器也能像人一樣交流。無論是在線客服、網絡主播還…

Pixelmator Pro for Mac 專業圖像處理軟件【媲美PS的修圖】

介紹 Pixelmator Pro&#xff0c;是一款非常強大、美觀且易于使用的圖像編輯器&#xff0c;專為 Mac 設計。采用單窗口界面、基于機器學習的智能圖像編輯、自動水平檢測&#xff0c;智能快速選擇及更好的修復工具等功能優點。許多非破壞性的專業編輯工具可讓您進行最佳的照片處…

LiveGBS流媒體平臺GB/T28181常見問題-視頻流安全控制HTTP接口鑒權勾選流地址鑒權后401Unauthorized如何播放調用接口流地址校驗

LiveGBS流媒體平臺GB/T28181常見問題頻流安全控制HTTP接口鑒權勾選流地址鑒權后401Unauthorized如何播放調用接口流地址校驗&#xff1f; 1、安全控制1.1、HTTP接口鑒權1.2、流地址鑒權 2、401 Unauthorized2.1、攜帶token調用接口2.1.1、獲取鑒權token2.1.2、調用其它接口2.1.…

C++設計模式-抽象工廠模式:從原理、適用場景、使用方法,常見問題和解決方案深度解析

一、模式基本概念 1.1 定義與核心思想 抽象工廠模式&#xff08;Abstract Factory Pattern&#xff09;是創建型設計模式的集大成者&#xff0c;它通過提供統一的接口來創建多個相互關聯或依賴的對象族&#xff0c;而無需指定具體類。其核心思想體現在兩個維度&#xff1a; …

【prompt實戰】知乎問題解答專家

本文原創作者&#xff1a;姚瑞南 AI-agent 大模型運營專家&#xff0c;先后任職于美團、獵聘等中大廠AI訓練專家和智能運營專家崗&#xff1b;多年人工智能行業智能產品運營及大模型落地經驗&#xff0c;擁有AI外呼方向國家專利與PMP項目管理證書。&#xff08;轉載需經授權&am…

數據結構第八節:紅黑樹(初階)

【本節要點】 紅黑樹概念紅黑樹性質紅黑樹結點定義紅黑樹結構紅黑樹插入操作的分析 一、紅黑樹的概念與性質 1.1 紅黑樹的概念 紅黑樹 &#xff0c;是一種 二叉搜索樹 &#xff0c;但 在每個結點上增加一個存儲位表示結點的顏色&#xff0c;可以是 Red和 Black 。 通過對 任何…

Spring Boot3.3.X整合Mybatis-Plus

前提說明&#xff1a; 項目的springboot版本為&#xff1a;3.3.2 需要整合的mybatis-plus版本&#xff1a;3.5.7 廢話不多說&#xff0c;開始造吧 1.準備好數據庫和表 2.配置全局文件application.properties或者是application.yml&#xff08;配置mapper的映射文件路徑&am…

可視化圖解算法:鏈表指定區間反轉

1. 題目 描述 給你單鏈表的頭指針 head 和兩個整數 left 和 right &#xff0c;其中 left < right 。請你反轉從位置 left 到位置 right 的鏈表節點&#xff0c;返回 反轉后的鏈表 。 示例1 輸入&#xff1a; 輸入&#xff1a;head [1,2,3,4,5], left 2, right 4 輸…

?SQL-遞歸CTE

&#x1f4d6; SQL魔法課堂&#xff1a;CTE「時間折疊術」全解 &#x1f3a9; 第一章&#xff1a;什么是CTE&#xff1f; CTE&#xff08;Common Table Expression&#xff09; 就像 SQL 里的「臨時筆記本」&#x1f4d2;&#xff1a; WITH 臨時筆記本 AS ( SELECT ... FRO…

Cursor 新手入門使用教程

一、Cursor 是什么&#xff1f; Cursor 是一個集成了 GPT-4、Claude 3.5 等先進 LLM&#xff08;大語言模型&#xff09;的類 VSCode 編譯器&#xff0c;可以理解為在 VSCode 中集成了 AI 輔助編程助手。從界面布局來看&#xff0c;Cursor 與 VSCode 基本一致&#xff0c;且使…

如何在Spring Boot中配置和使用MyBatis-Plus

在當今的Java開發中&#xff0c;Spring Boot已經成為了一個非常流行的框架&#xff0c;而MyBatis-Plus則是一個強大的ORM框架&#xff0c;為開發人員提供了更簡便的數據庫操作方式。很多開發者都在使用Spring Boot和MyBatis-Plus的組合來快速構建高效的應用。今天就來聊聊如何在…

【貪心算法3】

力扣1005.k次取反后最大化的數組和 鏈接: link 思路 既然要求最大和&#xff0c;那么不妨先給數組排個序&#xff0c;如果有負數&#xff0c;先處理負數從前往后給數組取反&#xff0c;如果負數處理完后k還有次數&#xff0c;此時數組全是正數了&#xff0c;只需要對第一個元…

自然語言處理中的語音識別技術:從聲波到語義的智能解碼

引言 語音識別&#xff08;Automatic Speech Recognition, ASR&#xff09;是自然語言處理&#xff08;NLP&#xff09;的關鍵分支&#xff0c;旨在將人類語音信號轉化為可處理的文本信息。隨著深度學習技術的突破&#xff0c;語音識別已從實驗室走向日常生活&#xff0c;賦能…

1688店鋪所有商品數據接口詳解

??一、接口概述淘寶開放平臺提供 1688.items.onsale.get/taobao.item_search_shop 接口&#xff0c;可批量獲取店鋪在售商品列表&#xff0c;包含商品 ID、標題、價格、銷量、圖片等核心信息。該接口適用于商品庫管理、競品監控、數據分析等場景 ?二、接口調用流程 前期準…