声明:大学生菜鸡学习记录,大佬可划走
一、前置认知&核心定位
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 步
- 创建 Retrofit 实例:指定基础 URL、绑定数据解析器;
- 通过 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% 的开发场景,无需死记,收藏备查即可:
✅ 一、请求方式注解(核心)
| 注解 | 作用 | 示例 |
|---|---|---|
| @GET | GET 请求 | @GET("/user/get") |
| @POST | POST 请求 | @POST("/user/add") |
| @PUT | PUT 请求(更新) | @PUT("/user/update") |
| @DELETE | DELETE 请求(删除) | @DELETE("/user/delete") |
✅ 二、参数注解(核心)
| 注解 | 作用 | 适用场景 |
|---|---|---|
| @Query | URL 拼接参数(key=value) | GET 请求带参数 |
| @Body | 请求体(JSON) | POST/PUT 提交对象 |
| @Path | URL 路径占位符 | @GET("/user/{id}") → 替换 {id} |
| @Header | 请求头参数 | 单个接口的认证、版本号 |
✅ 三、其他常用注解
@FormUrlEncoded:表单提交(application/x-www-form-urlencoded);@Field:表单参数(配合 @FormUrlEncoded 使用)。
六、总结 + 避坑指南
✅ 高频坑点(必避,节省 80% 排错时间)
- ❌ 基础 URL 末尾少加
/:如写成http://localhost:8080(正确),不要写成http://localhost:8080/; - ❌ 实体类无无参构造器 /getter:导致 Gson 解析失败,返回 null;
- ❌ 同步请求在主线程执行:Java 后端无影响,Android 需注意,后端可放心使用;
- ❌ 接口路径写错:如
/user/get写成user/get,导致 404; - ❌ 依赖冲突:不要同时引入多个版本的 Retrofit/OkHttp。
✅ 项目优化建议(进阶方向)
- 全局拦截器:统一处理 Token 过期、日志打印、请求加密;
- 响应体封装:自定义统一响应解析器,简化
response.body()处理; - 异常封装:自定义业务异常类,统一处理接口异常。
“全局拦截器:统一处理 Token 过期、日志打印、请求加密;”这个怎么实现,是要用这个OrderedWebFilter吗
✅ 关键结论(必记)
Retrofit 全局拦截器的实现,核心依赖 OkHttp 的 Interceptor 接口,而非 Spring 的 OrderedWebFilter,二者属于完全不同的技术体系、作用于不同场景,千万不要混淆!
✅ 两者本质区别(彻底理清,不踩坑)
| 技术组件 | 所属框架 | 作用场景 | 核心作用 |
|---|---|---|---|
| OkHttp Interceptor | OkHttp(Retrofit 底层) | ✅ 客户端侧(你的 Java 项目作为「调用方」,发起 HTTP 请求时) | 拦截你项目发出的所有请求 / 响应,处理 Token、加密、日志等 |
| OrderedWebFilter | Spring 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),通过拦截器实现「所有指定接口自动加密」,业务代码无需关心加密逻辑,完全解耦。
实现思路(通用)
- 拦截原始请求,读取请求体中的 JSON 字符串;
- 对 JSON 字符串执行加密(示例用 AES,可替换为项目加密规则);
- 重构加密后的请求体,替换原始请求体;
- 继续执行请求,服务端接收加密内容后解密即可。
完整代码(含加密工具类)
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 状态码。
核心实现流程(行业标准)
- 发起原始请求,获取服务端响应;
- 判断响应状态码:若为
401(未授权)/403(Token过期),触发 Token 刷新逻辑; - 调用「刷新 Token 接口」获取新 Token,更新本地 Token(如内存 / Redis);
- 重构原始请求,替换请求头中的旧 Token 为新 Token;
- 重新发起请求,返回新的响应结果;
- 若 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 分钟掌握)
- ✅ Retrofit 全局拦截器必须用 OkHttp 的
Interceptor,和 Spring 的OrderedWebFilter无任何关系; - ✅ 三大核心功能均基于
Interceptor实现,完全解耦业务代码; - ✅ 拦截器整合到
OkHttpClient,再绑定到 Retrofit,全局生效; - ✅ 推荐执行顺序:
日志拦截器 → Token拦截器 → 加密拦截器; - ✅ Token 过期拦截器需加锁保证线程安全,避免重复刷新。