当前位置: 首页 > news >正文

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 来满足不同的需求。同时,注解的使用也使得代码更加清晰、易读和易于维护,提高了开发效率。

相关文章:

Android Retrofit 框架注解定义与解析模块深度剖析(一)

一、引言 在现代 Android 和 Java 开发中&#xff0c;网络请求是不可或缺的一部分。Retrofit 作为 Square 公司开源的一款强大的类型安全的 HTTP 客户端&#xff0c;凭借其简洁易用的 API 和高效的性能&#xff0c;在开发者社区中广受欢迎。Retrofit 的核心特性之一便是通过注…...

嵌入式学习L6网络编程D3TCP

TCP编程 写代码 第一步socket 绑定 先填充 点分十进制转换成32位整数 client 然后就连接成功了就可以读写数据了 client #include "net.h"int main (void) {int fd -1;struct sockaddr_in sin;/* 1. 创建socket fd */if ((fd socket (AF_INET, SOCK_S…...

【玩转23种Java设计模式】结构型模式篇:享元模式

软件设计模式&#xff08;Design pattern&#xff09;&#xff0c;又称设计模式&#xff0c;是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。 汇总目录链接&…...

超分之DeSRA

Desra: detect and delete the artifacts of gan-based real-world super-resolution models.DeSRA&#xff1a;检测并消除基于GAN的真实世界超分辨率模型中的伪影Xie L, Wang X, Chen X, et al.arXiv preprint arXiv:2307.02457, 2023. 摘要 背景&#xff1a; GAN-SR模型虽然…...

产城融合典范:树莓科技如何助力宜宾数字经济腾飞​

宜宾在推动数字经济发展的征程中&#xff0c;树莓科技扮演着至关重要的角色&#xff0c;堪称产城融合的典范。 树莓科技入驻宜宾后&#xff0c;积极与当地政府合作&#xff0c;以产业发展带动城市建设&#xff0c;以城市功能完善促进产业升级。在产业布局上&#xff0c;树莓科…...

Java数据结构第二十二期:Map与Set的高效应用之道(一)

专栏&#xff1a;Java数据结构秘籍 个人主页&#xff1a;手握风云 目录 一、Map和Set 1.1. 概念 二、搜索树 2.1. 概念 2.2. 查找操作 2.2. 插入操作 2.3. 删除操作 2.4. 性能分析 三、搜索 3.1. 概念及场景 3.2. 模型 四、Map 4.1. Map的说明 3.2. Map的使用 五…...

焊接安全的新纪元:智能监管系统的力量

在现代制造业中&#xff0c;焊接作为一项关键工艺&#xff0c;其安全性直接关系到生产质量和人员安全。为了应对这一挑战&#xff0c;一款创新的焊接联网智能化监管系统应运而生&#xff0c;为焊接行业带来了新的安全保障。 智能监管&#xff0c;安全升级 这款系统通过“一机…...

OpenGL中绘制图形元素的实现(使用visual studio(C++)绘制一个矩形)

目标&#xff1a;使用OpenGL提供的函数绘制矩形、线段、三角形等基本图形元素 所需效果 实验步骤 1、配置OpenGL&#xff08;详情参见OpenGL的配置&#xff09; 2、头文件引入 #include <gl/glut.h> 3、编写方法体 1>矩形实现 //绘制矩形 void DisplayRectangl…...

政安晨【零基础玩转各类开源AI项目】Wan 2.1 本地部署,基于ComfyUI运行,最强文生视频 图生视频,一键生成高质量影片

政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff01; 目录 下载项目 创建虚拟环境 安装项目依赖 尝试运行 依次下载模型 完成 我们今天要使…...

DeepLabv3+改进8:在主干网络中添加SIM注意力机制|助力涨点

🔥【DeepLabv3+改进专栏!探索语义分割新高度】 🌟 你是否在为图像分割的精度与效率发愁? 📢 本专栏重磅推出: ✅ 独家改进策略:融合注意力机制、轻量化设计与多尺度优化 ✅ 即插即用模块:ASPP+升级、解码器 PS:订阅专栏提供完整代码 论文简介 在本文中,我们提出了…...

卷积神经网络(笔记01)

视觉处理三大任务&#xff1a;分类、目标检测、图像分割 CNN网络主要有三部分构成&#xff1a;卷积层&#xff08;Convolutional Layer&#xff09;、池化层&#xff08;Pooling Layer&#xff09;和激活函数 一、解释卷积层中的偏置项是什么&#xff0c;并讨论在神经网络中引…...

从自己电脑的浏览器访问阿里云主机中运行的LLaMA-Factory webui

阿里云主机上LLaMA-Factory的webui在0.0.0.0:7860侦听&#xff0c;无法直接打开&#xff0c;需要通过代理的方法访问。 在LLaMA-Factory目录下创建一个脚本文件run.sh&#xff0c;并加上执行权限&#xff0c;内容如下&#xff1a; #!/bin/shexport GRADIO_SERVER_PORT7860 ex…...

大数据面试之路 (一) 数据倾斜

记录大数据面试历程 数据倾斜 大数据岗位 &#xff0c;数据倾斜面试必问的一个问题。 一、数据倾斜的表现与原因 表现 某个或某几个Task执行时间过长&#xff0c;其他Task快速完成。 Spark/MapReduce作业卡在某个阶段&#xff08;如reduce阶段&#xff09;&#xff0c;日志显…...

文件上传漏洞 upload-labs靶场

&#xff08;这个没删就是还没做完 ; ω ; &#xff09; 目录 Pass-01 前端绕过 关卡分析 绕过&#xff1a;Burpsuite抓包修改或页面禁用js Pass-02 服务器端检测–IMME类型 关卡分析 Content-type 绕过&#xff1a;抓包修改文件的content-type Pass-03 黑名单绕过 关…...

「 DelegateUI 」Ant-d 风格的 Qt Qml UI 套件

写在前面&#xff1a;关于为什么要写一套新的UI框架 一方面&#xff0c;Qt Qml 生态中缺乏一套既遵循现代设计规范(自带的功能少且丑,懂得都懂)&#xff0c;又能深度整合 Qt 生态的开源组件库。 另一方面&#xff0c;Qt Qml 中也有一些其他方案&#xff0c;例如 FluentUI Qml…...

数字人分身开发指南:从概念到实战

一、什么是数字人分身&#xff1f; 想象一下&#xff0c;在电脑或手机屏幕里&#xff0c;一个能跟你聊天、回答问题&#xff0c;甚至还能做表情的虚拟角色。这就是数字人分身&#xff0c;它用上了人工智能技术&#xff0c;让机器也能像人一样交流。无论是在线客服、网络主播还…...

Java小白-管理项目工具Maven(2)初识Maven

一、Maven安装 ①安装jdk1.8或以上版本 ②下载Maven&#xff08;此为3.6.0&#xff09;&#xff1a;地址&#xff1a;Download Apache Maven – Maven 下载地址&#xff1a;Index of /dist/maven/maven-3/3.6.0/binaries ③安装Maven到无中文路径即可 bin&#xff1a;含…...

【附JS、Python、C++题解】Leetcode 面试150题(8)

一、题目 11. 盛最多水的容器 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。你不能倾斜…...

什么是向量数据库向量搜索?

向量数据库 专为高效存储与检索高维向量设计&#xff0c;支持语义搜索、推荐系统等AI场景&#xff0c;如文本/图像嵌入的相似性匹配。 ChromaDB 轻量级开源向量数据库&#xff0c;优势在于易用性&#xff08;快速部署、简洁API&#xff09;和小规模场景&#xff08;本地开发、…...

【WRF-Urban】使用 LCZ 替换 WRF 运行中的 LUCC 数据

使用 LCZ 替换 WRF 运行中的 LUCC 数据 WRF-UCM中的城市类型LCZ的背景介绍完整步骤总结1. 获取 LCZ 数据2. 获取 WRF 运行所需的 LUCC 数据3. 使用 w2w 替换 WRF 的 LUCC 数据4. 运行 WRF 预处理(WPS & REAL)5. 运行 WRF 并优化城市参数化Q1:使用 LCZ 替换 WRF 运行中的…...

centos 7 安装apache服务

四步骤 解包 使用tar -zxvf 对.tar.gz 进行解压 使用tar -jxvf 对.tar.bz2 进行解压 rpm命令使用集合 rpm -qa 查询系统已安装的软件包 rpm -ql查看指定软件包存放的位置 rpm -qi查看指定软件包的属性 rpm -qf查看指定文件或目录是由哪个软件包所安装的 rpm -qpi查看指…...

2025各省市建筑产业和工程建设计划安排

1. 前言 十四届全国人大三次会议3月5日上午9时在人民大会堂开幕&#xff0c;国务院总理李强作政府工作报告。 《2025年政府工作报告》&#xff08;以下简称 “报告”&#xff09;作为统筹国家经济、战略布局与社会发展的蓝图&#xff0c;与建筑业息息相关&#xff0c;为今后的…...

广告营销,会被AI重构吗?

DeepSeek设计&#xff0c;即梦AI绘图&#xff0c;剪映成片。 DeepSeek的热度还在高开疯走。 用户对于各个场景下DS应用的探索也还在持续&#xff0c;各种DS的模式被挖掘出来&#xff0c;超级个体们开始给手下的大模型团队进行分工&#xff0c;实践出各种场景下最佳的排列组合方…...

01 音视频知识学习(视频)

图像基础概念 ◼像素&#xff1a;像素是一个图片的基本单位&#xff0c;pix是英语单词picture的简写&#xff0c;加上英 语单词“元素element”&#xff0c;就得到了“pixel”&#xff0c;简称px&#xff0c;所以“像素”有“图像元素” 之意。 ◼ 分辨率&#xff1a;是指图像…...

深入探究 Ryu REST API

Ryu 4.34 REST API 详细接口说明与示例 Ryu 4.34 的 REST API 提供了对 SDN 网络的核心管理功能&#xff0c;涵盖交换机、流表、端口、拓扑和 QoS 等操作。以下是详细的接口分类、功能说明及 Python 示例代码。 1. 交换机管理 1.1 获取所有交换机 DPID 端点: GET /stats/swi…...

不同AI生成的PHP版雪花算法

OpenAI <?php /*** Snowflake 雪花算法生成器* 生成的 64 位 ID 结构&#xff1a;* 1 位 保留位&#xff08;始终为0&#xff0c;防止负数&#xff09;* 41 位 时间戳&#xff08;毫秒级&#xff0c;当前时间减去自定义纪元&#xff09;* 5 位 数据中心ID* 5 …...

texstudio: 编辑器显示行号+给PDF增加行号

texstudio在编辑器部分增加行号&#xff1a; texstudio默认在编辑器部分不显示行号&#xff0c;如下图&#xff1a; 要实现以下的在编辑部分增加行号&#xff1a; 执行如下操作&#xff1a; 选项-->设置TexStudio-->编辑器-->显示行号-->所有行号选择好后&…...

强化学习基础-马尔可夫决策过程与贝尔曼方程

马尔可夫决策过程 在老虎机问题中&#xff0c;无论智能代理采取什么行动&#xff0c;之后要解决的问题都是一样的。也就是寻找最好的老虎机。但现实生活中的问题是不同的。例如&#xff0c;在围棋游戏中&#xff0c;智能代理落子后&#xff0c;棋盘上的棋子排列会发生变化&…...

爬虫的精准识别:基于 User-Agent 的正则实现

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…...

Scala的初步使用

目录 1. Scala简介2. Scala编写的Hello World2.1 pom.xml中依赖和插件的配置2.2 安装Scala2.12.172.3 安装code-server插件2.4 helloworld.scala2.5 helloworld2.scala2.6 java调用scala object 3. Scala调用Java3.1 例子13.2 例子2 参考 1. Scala简介 Scala是一门多范式的编程…...

【Json RPC框架】框架介绍与环境搭建(Ubuntu 22.04)

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;Json RPC框架 &#x1f337;追光的人&#xff0c;终会万丈光芒 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 ​ JSon RPC框架系列文章Json RPC框架_我们的五年的博…...

python读取word文档 | AI应用开发

python读取word文档 | AI应用开发 RAG中python读取word文档 RAG系统中构建知识库流程中重要的一个步骤是读取外挂的知识文档&#xff0c;为word是其中比较常见的文件。 另一个值得注意的是&#xff0c;RAG在读取文档后需要对文档进行分割&#xff0c;而良好的分割需要有一定结…...

20、组件懒加载

组件懒加载&#xff0c;也被称为异步组件加载&#xff0c;是一种在 Vue 项目中提升性能的重要技术手段。下面从概念、实现原理、使用场景、实现方式几个方面详细介绍&#xff1a; 概念 在传统的 Vue 项目里&#xff0c;当应用启动时&#xff0c;所有的组件代码都会被一次性加…...

打造智能钉钉机器人:借助智谱GLM-4-Flash实现高效智能回复(文末附源码)

文章目录 前言一、准备工作&#xff08;一&#xff09;钉钉机器人&#xff08;二&#xff09;智谱 GLM-4-Flash&#xff08;三&#xff09;内网穿透工具 cpolar&#xff08;四&#xff09;需要准备的工具和环境 二、钉钉机器人的创建与配置步骤1&#xff1a;创建钉钉机器人步骤…...

【故障处理系列--docker卷的挂载】

一位伙伴需求是把容器的目录映射到宿主机且容器目录的内容不被宿主机的空白目录覆盖。我的第一反应是-v 卷的映射&#xff0c;参数是对的&#xff0c;但是用法是错的 1、容器卷的挂载方式 容器把目录映射到宿主机创建volume卷&#xff0c;然后把容器的目录和volume卷绑定 区别…...

兴达易控modbusTCP转profinet接防撞雷达测试

modbusTCP转profinet接防撞雷达测试 随着工业自动化程度的不断提高&#xff0c;现场设备之间的通信需求日益增长。ModbusTCP作为一种广泛应用的工业通信协议&#xff0c;因其简单、可靠的特点&#xff0c;被广泛应用于各种自动化设备中。而Profinet作为工业以太网的一种&#…...

Acknowledgment.nack方法重试消费kafka消息异常

文章目录 问题示例异常 原因nack方法Acknowledgment接口实现类&#xff1a;ConsumerAcknowledgment实现类&#xff1a;ConsumerBatchAcknowledgment 解决方案1 批量消费指定index示例 2 单条消费示例 问题 使用BatchAcknowledgingMessageListener 批量消费Kafka消息&#xff0…...

通过动态获取后端数据判断输入的值打小

eval() 函数在 JavaScript 中是一个非常强大的函数 【1】计算简单公式 很多时候如果需要动态的提供计算的公式&#xff0c;需要写一大段的公式计算逻辑去兼容&#xff0c;可能耗费大量的开发成本。为了快速了解 eval 的用法&#xff0c;直接 ① 打开浏览器&#xff1b;② F1…...

乐维网管平台核心功能解析(一)——告警关联知识

在数字化转型浪潮中&#xff0c;企业IT系统规模呈指数级增长&#xff0c;传统的"人工经验"运维模式已难以应对海量告警处理需求。某银行数据中心曾统计&#xff0c;其日均告警量突破10万条&#xff0c;关键故障的平均定位时间长达3.5小时&#xff0c;直接导致年损失超…...

数据结构_单链表

今天我们要开启链表的学习 &#x1f58b;️&#x1f58b;️&#x1f58b;️ 学了顺序表我们可以知道&#xff1a; &#x1f388;链表其实就是争对顺序表的缺点来设计的&#xff0c;补足的就是顺序表的缺点 &#x1f388;链表在物理上是上一个节点存放的下一个节点的地址 链表 …...

b站视频下载工具软件怎么下载

自行配置FFMPEG环境 请优先选择批量下载&#xff0c;会自处理视频和音频文件。 如果要下载更高质量请登陆。 没有配置FFMPEG下载后会有报错提示&#xff0c;视频音频文件无法合并生成mp4文件 更新批量下载标题&#xff0c;只取视频原标题&#xff0c;B站反爬机制登陆后下载多了…...

如何实现pinia的持久化存储

在 Vue 3 项目中使用 Pinia 进行状态管理时&#xff0c;若要实现持久化存储&#xff0c;可借助 pinia-plugin-persistedstate 插件&#xff0c;该插件能让 Pinia 存储的状态在页面刷新或关闭后依然保留。下面为你详细介绍实现步骤&#xff1a; 1. 安装插件 首先&#xff0c;在…...

webpack介绍

entry与output 入口是 Webpack 开始构建依赖图的起点&#xff0c;Webpack 会从入口文件开始&#xff0c;递归地分析项目的依赖图。输出指定 Webpack 打包后的文件存放位置和文件名。 const path require("path");module.exports {entry: "./src/index.js&qu…...

使用Mermaid语法绘制的C语言程序从Linux移植到Windows的流程图

以下是使用Mermaid语法绘制的C语言程序从Linux移植到Windows的流程图&#xff1a; graph TDA[开始移植] --> B[代码兼容性检查]B --> C[检查系统调用差异\nfork/exec -> CreateProcess]B --> D[检查文件路径格式\n/ vs \\]B --> E[检查依赖库兼容性\nPOSIX vs …...

蓝桥杯嵌入式组第七届省赛题目解析+STM32G431RBT6实现源码

文章目录 1.题目解析1.1 分而治之&#xff0c;藕断丝连1.2 模块化思维导图1.3 模块解析1.3.1 KEY模块1.3.2 ADC模块1.3.3 IIC模块1.3.4 UART模块1.3.5 LCD模块1.3.6 LED模块1.3.7 TIM模块 2.源码3.第七届题目 前言&#xff1a;STM32G431RBT6实现嵌入式组第七届题目解析源码&…...

【spring bean的生命周期】

以下是使用 Mermaid 绘制的 Spring Bean 生命周期流程图&#xff1a; 流程说明 实例化&#xff1a;Spring 容器创建 Bean 的实例。属性赋值&#xff1a;Spring 为 Bean 的属性注入值&#xff08;依赖注入&#xff09;。BeanPostProcessor.postProcessBeforeInitialization&…...

数据类设计_图片类设计之3_半规则图类设计(前端架构基础)

前言 学的东西多了,要想办法用出来.C和C是偏向底层的语言,直接与数据打交道.尝试做一些和数据方面相关的内容 引入 接续上一篇讨论半规则图类型的设计 半规则图的定义 什么是半规则图?笔者看见了一些似乎规则又不是太规则的图形,例如带圆角的矩阵,在页面上找一个圆角框 为了…...

【leetcode hot 100 138】随机链表的复制

解决一&#xff1a;回溯 哈希表 本题要求我们对一个特殊的链表进行深拷贝。如果是普通链表&#xff0c;我们可以直接按照遍历的顺序创建链表节点。而本题中因为随机指针的存在&#xff0c;当我们拷贝节点时&#xff0c;「当前节点的随机指针指向的节点」可能还没创建&#xf…...

如何安全处置旧设备?

每年&#xff0c;数百万台旧设备因老化、故障或被新产品取代而被丢弃&#xff0c;这些设备上存储的数据可能带来安全风险。 如果设备没有被正确删除数据&#xff0c;这些数据往往仍可被恢复。因此&#xff0c;安全处置旧设备至关重要。 旧设备可能包含的敏感数据 旧设备中可能…...

Windows 万兴恢复专家 Wondershare Recoverit-v13.5.7.9-[电脑数据恢复工具]

Windows 万兴恢复专家Wondershare_Recoverit 链接&#xff1a;https://pan.xunlei.com/s/VOL3z608vzAj_IYTvH-F1q7kA1?pwdiu89# 1. 打开Setup.exe进行安装&#xff0c;安装完不要打开软件&#xff0c;记住安装目录 2. 将"Crack"文件夹内的所有文件复制到安装目录 …...