Retrofit接口请求开发

57 阅读15分钟

声明:大学生菜鸡学习记录,大佬可划走

一、前置认知&核心定位

1. Retrofit是什么?

Retrofit是Square公司基于OkHttp封装的RESTful风格网络请求框架,核心价值是【将HTTP接口转化为Java接口】,彻底摆脱原生OkHttp的冗余代码,极大简化网络请求开发。

2. 核心优势

  • 注解驱动开发,代码简洁易维护,可读性强
  • 完美支持同步/异步请求
  • 内置数据解释适配(Gson/Jackson),自动完成json-java实体转换
  • 可扩展(拦截器、请求头、超时配置等)

3. 核心依赖关系

Retrofit 底层依赖 OkHttp 实现网络通信,开发时无需单独引入 OkHttp,Retrofit 会自动依赖对应版本。

核心基础 3步搭建

核心依赖

<!-- Retrofit核心依赖 --> 
<dependency> 
<groupId>com.squareup.retrofit2</groupId> 
<artifactId>retrofit</artifactId> 
<version>2.9.0</version> 
</dependency> 
<!-- 核心:Gson数据解析器(自动JSON转Java实体) --> 
<dependency> 
<groupId>com.squareup.retrofit2</groupId>
<artifactId>converter-gson</artifactId>
<version>2.9.0</version> 
</dependency>

✅ 实战示例(覆盖 80% 基础场景:GET 带参数、POST 传 JSON)

java

运行

import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.Query;
import retrofit2.http.Body;

// 示例:对接用户模块接口
public interface UserApi {
    // 1. GET请求:带URL参数(如:http://localhost:8080/user/get?userId=1001)
    // @GET:声明GET请求,参数是「接口相对路径」
    // @Query:声明URL拼接参数(key=value)
    @GET("/user/get")
    Call<UserResponse> getUserById(@Query("userId") Long userId);

    // 2. POST请求:提交JSON体(最常用,如新增/修改数据)
    // @POST:声明POST请求
    // @Body:声明请求体(自动序列化为JSON)
    @POST("/user/add")
    Call<BaseResponse> addUser(@Body UserRequest userRequest);
}

✅ 配套实体类(请求 / 响应)【必须定义】

Retrofit 自动完成 JSON 与实体的转换,需提前定义对应实体类,字段名与接口 JSON 字段一一对应

java

运行

// 1. 用户新增-请求实体(前端传参 → 后端接收)
public class UserRequest {
    private String username;
    private String password;
    private Integer age;
    // 必须有:无参构造器、getter/setter(Lombok可简化)
    public UserRequest() {}
    // getter/setter 省略,实际开发必须加
}

// 2. 用户查询-响应实体(接口返回 → Java接收)
public class UserResponse {
    private Long userId;
    private String username;
    private Integer age;
    // 无参构造器、getter/setter 必须加
}

// 3. 通用响应实体(项目统一返回格式,如{code:200,msg:"成功",data:{}})
public class BaseResponse {
    private Integer code;
    private String msg;
    private Object data;
    // 无参构造器、getter/setter 必须加
}

✅ 第三步:创建 Retrofit 实例 + 发起请求(7 分钟)【最终执行】

✅ 核心 2 步

  1. 创建 Retrofit 实例:指定基础 URL、绑定数据解析器
  2. 通过 Retrofit 生成接口代理对象,调用接口方法发起请求。

✅ 完整代码(同步 + 异步,两种方式全覆盖)

java

运行

import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.Response;
import java.io.IOException;

public class RetrofitClient {
    public static void main(String[] args) throws IOException {
        // ========== 1. 创建Retrofit实例(全局单例,项目中建议抽成工具类) ==========
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://localhost:8080") // 基础URL:固定前缀,接口路径拼接在后面
                .addConverterFactory(GsonConverterFactory.create()) // 绑定Gson解析器
                .build();

        // ========== 2. 生成接口代理对象 ==========
        UserApi userApi = retrofit.create(UserApi.class);

        // ========== 3. 方式1:同步请求(推荐:Java工具类/Service层使用) ==========
        // 调用接口方法,返回Call对象
        Call<UserResponse> call = userApi.getUserById(1001L);
        // 执行同步请求,获取响应结果
        Response<UserResponse> response = call.execute();
        // 判断请求成功,获取响应实体
        if (response.isSuccessful()) {
            UserResponse user = response.body();
            System.out.println("同步请求成功:" + user.getUsername());
        }

        // ========== 4. 方式2:异步请求(推荐:非阻塞场景,如异步任务) ==========
        UserRequest request = new UserRequest();
        request.setUsername("test");
        request.setPassword("123456");
        request.setAge(20);
        
        userApi.addUser(request).enqueue(new retrofit2.Callback<BaseResponse>() {
            @Override
            public void onResponse(Call<BaseResponse> call, Response<BaseResponse> response) {
                // 请求成功回调
                if (response.isSuccessful()) {
                    System.out.println("异步请求成功:" + response.body().getMsg());
                }
            }

            @Override
            public void onFailure(Call<BaseResponse> call, Throwable t) {
                // 请求失败回调(网络异常、超时等)
                System.out.println("异步请求失败:" + t.getMessage());
            }
        });
    }
}

✔️ 关键说明:

  • 同步请求:call.execute()会阻塞当前线程,适合 Java 后端同步业务;
  • 异步请求:call.enqueue()非阻塞,回调方法在子线程执行,适合异步场景;
  • response.isSuccessful():判断 HTTP 状态码为 200-299,代表请求成功。

✅ 核心优化(项目必用):抽成全局单例工具类【重点】

项目中 Retrofit 实例无需重复创建,抽成工具类保证全局单例,提升性能:

java

运行

public class RetrofitUtil {
    // 全局单例Retrofit对象
    private static final Retrofit RETROFIT;
    // 静态代码块,初始化一次
    static {
        RETROFIT = new Retrofit.Builder()
                .baseUrl("http://localhost:8080")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
    }
    // 获取接口代理对象的通用方法
    public static <T> T create(Class<T> serviceClass) {
        return RETROFIT.create(serviceClass);
    }
}

✅ 调用简化(一行获取接口对象)

java

运行

// 直接通过工具类获取接口对象,无需重复创建Retrofit
GoodsApi goodsApi = RetrofitUtil.create(GoodsApi.class);

四、进阶核心:项目必备技能

✅ 技能 1:请求头配置(5 分钟)【Token、认证信息必用】

两种方式,覆盖「单个接口」和「全局所有接口」场景,满足项目权限校验需求。

✅ 方式 1:单个接口添加请求头(@Header 注解)

java

运行

// 示例:查询用户信息,添加Token请求头
@GET("/user/get")
Call<UserResponse> getUserById(@Header("token") String token, @Query("userId") Long userId);

✅ 方式 2:全局添加请求头(拦截器,推荐)【项目首选】

所有接口自动携带请求头,无需逐个添加,适合 Token、版本号等全局参数:

java

运行

// 1. 添加OkHttp依赖(如需自定义拦截器,需手动引入)
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.9.3</version>
</dependency>

// 2. Retrofit初始化时绑定拦截器
OkHttpClient okHttpClient = new OkHttpClient.Builder()
        .addInterceptor(chain -> {
            // 拦截请求,添加全局请求头
            okhttp3.Request request = chain.request().newBuilder()
                    .addHeader("token", "xxx-xxx-xxx") // 全局Token
                    .addHeader("version", "1.0.0") // 版本号
                    .build();
            return chain.proceed(request);
        })
        .build();

// 3. Retrofit绑定自定义OkHttpClient
Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("http://localhost:8080")
        .client(okHttpClient) // 绑定带拦截器的OkHttpClient
        .addConverterFactory(GsonConverterFactory.create())
        .build();

✅ 技能 2:超时配置(5 分钟)【解决请求超时问题】

项目中必须配置超时时间,避免接口阻塞,通过 OkHttpClient 统一配置:

java

运行

OkHttpClient okHttpClient = new OkHttpClient.Builder()
        .connectTimeout(10, java.util.concurrent.TimeUnit.SECONDS) // 连接超时:10秒
        .readTimeout(20, java.util.concurrent.TimeUnit.SECONDS)    // 读取超时:20秒
        .writeTimeout(20, java.util.concurrent.TimeUnit.SECONDS)   // 写入超时:20秒
        .build();

✅ 技能 3:异常统一处理(5 分钟)【项目必做】

捕获网络异常、接口异常、解析异常,避免程序崩溃,核心捕获 3 类异常:

java

运行

try {
    Response<UserResponse> response = userApi.getUserById(1001L).execute();
    if (response.isSuccessful()) {
        UserResponse user = response.body();
    } else {
        // 处理HTTP异常(如404/500)
        System.out.println("接口异常,状态码:" + response.code());
    }
} catch (IOException e) {
    // 处理网络异常(如断网、超时)
    e.printStackTrace();
} catch (Exception e) {
    // 处理其他异常(如JSON解析失败)
    e.printStackTrace();
}

五、高频注解速查表

整理 Retrofit 最常用注解,覆盖 90% 的开发场景,无需死记,收藏备查即可:

✅ 一、请求方式注解(核心)

注解作用示例
@GETGET 请求@GET("/user/get")
@POSTPOST 请求@POST("/user/add")
@PUTPUT 请求(更新)@PUT("/user/update")
@DELETEDELETE 请求(删除)@DELETE("/user/delete")

✅ 二、参数注解(核心)

注解作用适用场景
@QueryURL 拼接参数(key=value)GET 请求带参数
@Body请求体(JSON)POST/PUT 提交对象
@PathURL 路径占位符@GET("/user/{id}") → 替换 {id}
@Header请求头参数单个接口的认证、版本号

✅ 三、其他常用注解

  • @FormUrlEncoded:表单提交(application/x-www-form-urlencoded);
  • @Field:表单参数(配合 @FormUrlEncoded 使用)。

六、总结 + 避坑指南

✅ 高频坑点(必避,节省 80% 排错时间)

  1. ❌ 基础 URL 末尾少加/:如写成http://localhost:8080(正确),不要写成http://localhost:8080/
  2. ❌ 实体类无无参构造器 /getter:导致 Gson 解析失败,返回 null;
  3. ❌ 同步请求在主线程执行:Java 后端无影响,Android 需注意,后端可放心使用;
  4. ❌ 接口路径写错:如/user/get写成user/get,导致 404;
  5. ❌ 依赖冲突:不要同时引入多个版本的 Retrofit/OkHttp。

✅ 项目优化建议(进阶方向)

  1. 全局拦截器:统一处理 Token 过期、日志打印、请求加密;
  2. 响应体封装:自定义统一响应解析器,简化response.body()处理;
  3. 异常封装:自定义业务异常类,统一处理接口异常。

“全局拦截器:统一处理 Token 过期、日志打印、请求加密;”这个怎么实现,是要用这个OrderedWebFilter吗

✅ 关键结论(必记)

Retrofit 全局拦截器的实现,核心依赖 OkHttp 的 Interceptor 接口,而非 Spring 的 OrderedWebFilter,二者属于完全不同的技术体系、作用于不同场景,千万不要混淆!

✅ 两者本质区别(彻底理清,不踩坑)

技术组件所属框架作用场景核心作用
OkHttp InterceptorOkHttp(Retrofit 底层)客户端侧(你的 Java 项目作为「调用方」,发起 HTTP 请求时)拦截你项目发出的所有请求 / 响应,处理 Token、加密、日志等
OrderedWebFilterSpring Web服务端侧(你的 Java 项目作为「服务方」,接收前端请求时)拦截前端发给你的项目的请求,做跨域、权限校验、参数过滤等

✅ 一句话总结

你要实现「Retrofit 调用第三方接口时,统一处理 Token 过期、请求加密、日志打印」→ 必须用 OkHttp 的 Interceptor

✅ 功能 1:日志打印拦截器(官方实现,开箱即用)

OkHttp 提供了现成的日志拦截器 HttpLoggingInterceptor,无需自己手写,支持 4 种日志级别,可灵活控制打印内容,满足开发 / 生产环境不同需求。

核心特性

✅ 打印完整请求信息:URL、请求头、请求体、请求方式;✅ 打印完整响应信息:状态码、响应头、响应体、耗时;✅ 支持日志级别动态切换,生产环境可关闭。

实现代码

java

运行

import okhttp3.logging.HttpLoggingInterceptor;

/**
 * 日志拦截器 - 全局打印请求/响应日志
 */
public class LogInterceptor {
    public static HttpLoggingInterceptor getLogInterceptor() {
        // 1. 创建日志拦截器
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(message -> {
            // 日志打印逻辑(可对接项目日志框架,如SLF4J/Logback)
            System.out.println("Retrofit请求日志:" + message);
        });
        
        // 2. 设置日志级别(4种可选,按需配置)
        // ★ NONE:不打印(生产环境推荐)
        // ★ BASIC:只打印请求方式+URL+状态码+耗时
        // ★ HEADERS:打印请求头+响应头 + BASIC内容
        // ★ BODY:打印请求体+响应体 + HEADERS内容(开发环境推荐)
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        
        return loggingInterceptor;
    }
}

✅ 功能 2:请求加密拦截器(统一加密请求体)

项目中敏感接口(如登录、支付、用户信息)需要对请求体 JSON进行加密(如 AES、RSA),通过拦截器实现「所有指定接口自动加密」,业务代码无需关心加密逻辑,完全解耦。

实现思路(通用)

  1. 拦截原始请求,读取请求体中的 JSON 字符串;
  2. 对 JSON 字符串执行加密(示例用 AES,可替换为项目加密规则);
  3. 重构加密后的请求体,替换原始请求体;
  4. 继续执行请求,服务端接收加密内容后解密即可。

完整代码(含加密工具类)

java

运行

import cn.hutool.crypto.symmetric.AES;
import okhttp3.*;
import okio.Buffer;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

/**
 * 请求加密拦截器 - 全局统一加密敏感请求体
 */
public class RequestEncryptInterceptor implements Interceptor {
    // 加密密钥(项目中建议从配置文件读取,如application.yml)
    private static final String AES_KEY = "1234567890123456"; // AES-128需16位密钥
    private static final AES AES = new AES(AES_KEY.getBytes(StandardCharsets.UTF_8));

    @Override
    public Response intercept(Chain chain) throws IOException {
        // 1. 获取原始请求对象
        Request originalRequest = chain.request();
        
        // 【可选】指定需要加密的接口(如包含/api/encrypt的路径),非敏感接口跳过加密
        if (!originalRequest.url().encodedPath().contains("/api/encrypt")) {
            return chain.proceed(originalRequest);
        }

        // 2. 读取原始请求体的JSON内容
        String originalBody = getRequestBodyString(originalRequest);
        
        // 3. 对原始JSON进行加密(替换为项目实际加密规则)
        String encryptBody = AES.encryptBase64(originalBody);

        // 4. 重构请求体:将加密后的内容作为新的请求体
        RequestBody encryptedRequestBody = RequestBody.create(
                encryptBody,
                MediaType.parse("application/json;charset=utf-8")
        );

        // 5. 构建新的请求,替换请求体后继续执行
        Request encryptedRequest = originalRequest.newBuilder()
                .method(originalRequest.method(), encryptedRequestBody)
                .build();
        
        return chain.proceed(encryptedRequest);
    }

    // 工具方法:读取请求体中的字符串内容
    private String getRequestBodyString(Request request) throws IOException {
        if (request.body() == null) {
            return "";
        }
        Buffer buffer = new Buffer();
        request.body().writeTo(buffer);
        return buffer.readUtf8();
    }
}

✅ 功能 3:Token 过期统一拦截器(核心难点,自动刷新重试)

这是项目中最常用、最重要的拦截器,实现「Token 过期后自动刷新 Token → 重新发起原请求」,业务代码无感知,无需每个接口手动处理 401/403 状态码。

核心实现流程(行业标准)

  1. 发起原始请求,获取服务端响应;
  2. 判断响应状态码:若为401(未授权)/403(Token过期),触发 Token 刷新逻辑;
  3. 调用「刷新 Token 接口」获取新 Token,更新本地 Token(如内存 / Redis);
  4. 重构原始请求,替换请求头中的旧 Token 为新 Token;
  5. 重新发起请求,返回新的响应结果;
  6. 若 Token 刷新失败(如刷新接口返回 401),则抛出异常,引导用户重新登录。

完整代码(生产级,可直接复用)

java

运行

import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Token拦截器 - 全局统一处理Token过期、自动刷新、请求重试
 */
public class TokenInterceptor implements Interceptor {
    // 本地存储的Token(项目中建议用Redis/配置中心,此处用静态变量示例)
    private static String TOKEN = "old_token_xxx";
    // 刷新Token的锁:避免多线程同时刷新Token,造成重复请求
    private static final Lock LOCK = new ReentrantLock();

    @Override
    public Response intercept(Chain chain) throws IOException {
        // 1. 获取原始请求,添加Token请求头
        Request originalRequest = chain.request();
        Request requestWithToken = addTokenToRequest(originalRequest);

        // 2. 执行请求,获取响应
        Response response = chain.proceed(requestWithToken);

        // 3. 判断Token是否过期(服务端约定:401=Token过期/失效)
        if (response.code() == 401) {
            LOCK.lock(); // 加锁,防止多线程重复刷新Token
            try {
                // 双重校验:再次检查Token是否已被其他线程刷新
                if (TOKEN.equals(getTokenFromRequest(requestWithToken))) {
                    // 4. 调用刷新Token接口,获取新Token
                    String newToken = refreshToken();
                    if (newToken == null || newToken.isEmpty()) {
                        // 刷新失败:抛出异常,引导用户重新登录
                        throw new RuntimeException("Token刷新失败,请重新登录!");
                    }
                    // 5. 更新本地Token
                    TOKEN = newToken;
                }
                // 6. 重构请求,替换为新Token,重新发起请求
                Request newRequest = addTokenToRequest(originalRequest);
                response.close(); // 关闭旧响应,避免资源泄漏
                response = chain.proceed(newRequest);
            } finally {
                LOCK.unlock(); // 释放锁
            }
        }
        return response;
    }

    // 工具方法:给请求添加Token请求头
    private Request addTokenToRequest(Request request) {
        return request.newBuilder()
                .header("token", TOKEN) // 项目中Token的请求头名,如Authorization/Bearer Token
                .build();
    }

    // 工具方法:从请求头中获取当前Token
    private String getTokenFromRequest(Request request) {
        return request.header("token");
    }

    // 核心方法:调用「刷新Token接口」获取新Token(替换为项目实际接口)
    private String refreshToken() throws IOException {
        // 此处调用项目的刷新Token接口,返回新的Token
        // 示例:调用Retrofit的刷新Token接口,返回newToken
        return "new_token_xxx_2026";
    }
}

🟡 第三步:整合所有拦截器,全局绑定到 Retrofit(最终落地)

✅ 核心规则

OkHttp 拦截器按添加顺序执行,推荐执行顺序:日志拦截器 → Token拦截器 → 请求加密拦截器,完全适配业务逻辑:

请求阶段:日志打印 → 加 Token 头 → 请求体加密 → 发起请求响应阶段:Token 过期校验 → 日志打印

✅ 完整整合代码(全局单例工具类,项目首选)

java

运行

import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import java.util.concurrent.TimeUnit;

/**
 * Retrofit全局单例工具类 - 整合所有拦截器、超时配置
 */
public class RetrofitGlobalUtil {
    // 全局唯一Retrofit实例
    private static volatile Retrofit RETROFIT;
    // 基础URL(项目中建议从配置文件读取)
    private static final String BASE_URL = "http://localhost:8080";

    // 私有化构造器,禁止外部实例化
    private RetrofitGlobalUtil() {}

    // 获取Retrofit实例(双重校验锁,保证单例)
    public static Retrofit getInstance() {
        if (RETROFIT == null) {
            synchronized (RetrofitGlobalUtil.class) {
                if (RETROFIT == null) {
                    // 1. 构建OkHttpClient,整合所有拦截器+超时配置
                    OkHttpClient okHttpClient = new OkHttpClient.Builder()
                            // ========== 1. 超时配置(必加) ==========
                            .connectTimeout(10, TimeUnit.SECONDS)  // 连接超时
                            .readTimeout(20, TimeUnit.SECONDS)     // 读取超时
                            .writeTimeout(20, TimeUnit.SECONDS)    // 写入超时
                            // ========== 2. 拦截器整合(按顺序添加) ==========
                            .addInterceptor(LogInterceptor.getLogInterceptor()) // 日志拦截器
                            .addInterceptor(new TokenInterceptor())             // Token拦截器
                            .addInterceptor(new RequestEncryptInterceptor())    // 加密拦截器
                            .build();

                    // 2. 构建Retrofit实例,绑定OkHttpClient
                    RETROFIT = new Retrofit.Builder()
                            .baseUrl(BASE_URL)
                            .client(okHttpClient) // 核心:绑定带拦截器的OkHttpClient
                            .addConverterFactory(GsonConverterFactory.create())
                            .build();
                }
            }
        }
        return RETROFIT;
    }

    // 通用方法:获取接口代理对象
    public static <T> T create(Class<T> serviceClass) {
        return getInstance().create(serviceClass);
    }
}

✅ 业务调用(极简,完全无感知拦截器逻辑)

所有接口调用无需任何修改,拦截器会自动生效,完美解耦:

java

运行

// 1. 获取接口代理对象
UserApi userApi = RetrofitGlobalUtil.create(UserApi.class);

// 2. 发起请求(日志、Token、加密 全部由拦截器自动处理)
Call<UserResponse> call = userApi.getUserById(1001L);
Response<UserResponse> response = call.execute();

🟢 关键注意事项(生产级避坑指南)

✅ 1. 拦截器执行顺序的坑

  • 添加顺序 = 执行顺序(请求阶段);
  • 响应阶段执行顺序相反(加密拦截器 → Token 拦截器 → 日志拦截器);
  • 建议固定顺序:日志 → Token → 加密,日志拦截器放最前面,能打印完整的请求 / 响应内容。

✅ 2. Token 刷新的线程安全

  • 必须加锁(如ReentrantLock),避免多线程同时刷新 Token,造成重复请求、Token 冲突;
  • 刷新 Token 接口本身要保证幂等性,防止多次刷新生成多个有效 Token。

✅ 3. 加密的灵活适配

  • 可通过「接口路径 / 请求头」指定需要加密的接口,非敏感接口跳过加密,提升性能;
  • 加密密钥禁止硬编码,项目中建议从配置中心(Nacos/Apollo)或环境变量读取。

✅ 4. 日志的生产环境优化

  • 生产环境日志级别改为NONE/BASIC,避免打印敏感数据(如密码、Token);
  • 对接项目日志框架(SLF4J/Logback),而非System.out.println

✅ 5. 响应体重复读取问题

  • 若在拦截器中多次读取response.body(),会抛出ClosedResponseBodyException
  • 解决方案:通过response.peekBody()读取响应体,不消耗流资源。

🟡 总结(核心知识点复盘,1 分钟掌握)

  1. ✅ Retrofit 全局拦截器必须用 OkHttp 的Interceptor,和 Spring 的OrderedWebFilter无任何关系;
  2. ✅ 三大核心功能均基于Interceptor实现,完全解耦业务代码;
  3. ✅ 拦截器整合到OkHttpClient,再绑定到 Retrofit,全局生效;
  4. ✅ 推荐执行顺序:日志拦截器 → Token拦截器 → 加密拦截器
  5. ✅ Token 过期拦截器需加锁保证线程安全,避免重复刷新。