什么是Retrofit?
Type-safe HTTP client for Android and Java by Square, Inc.
Retrofit是一个RESTful 的 HTTP 网络请求框架的封装,网络请求的工作本质上是 OkHttp 完成,而 Retrofit 仅负责 网络请求接口的封装。
Retrofit将 Http请求 抽象成 Java接口:采用 注解 描述网络请求参数 和配置网络请求参数。
比如下面的Java接口描述了使用GET请求发送网络请求,请求参数是一个QueryString name, 返回值是Call<DtoResult<BookQueryResult>>, 它是一个网络请求,可以调用它的execute方法获得执行结果DtoResult<BookQueryResult>。
引入依赖
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>retrofit</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>converter-scalars</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>converter-jackson</artifactId>
<version>2.9.0</version>
</dependency>
网络请求接口定义
public interface GetRequest_Interface {
@GET("openapi.do?keyfrom=abc&key=2032414398&type=data&doctype=json&version=1.1&q=car")
@FormUrlEncoded
Call<DtoResult<BookQueryResult>> listBooks(@Field("name") String name);
}
发起网络请求
# 使用Retrofit.Builder创建Retrofit 客户端。
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://fanyi.youdao.com/") //设置网络请求的Url地址
.addConverterFactory(GsonConverterFactory.create()) //设置数据解析器
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
创建Retrofit 客户端
// 使用Retrofit 客户端创建 网络请求接口 的实例
GetRequest_Interface request = retrofit.create(GetRequest_Interface.class);
//对 发送请求 进行封装
Call<Reception> callListBook = request.listBooks("redis");
发送同步请求
try {
Response<DtoResult<BookQueryResult>> response = call.execute();
if(response.isSuccessfully()){
DtoResult<BookQueryResult> dtoResult = response.body();
// ...
}
} catch (IOException e) {
log.error(e);
}
发送异步请求
call.enqueue(new Callback<DtoResult<BookQueryResult>>() {
//请求成功时回调
@Override
public void onResponse(Call<DtoResult<BookQueryResult>> call, Response<DtoResult<BookQueryResult>> response) {
//请求处理,输出结果
if(response.isSuccessfully()){
DtoResult<BookQueryResult> dtoResult = response.body();
// ...
}
}
//请求失败时候的回调
@Override
public void onFailure(Call<DtoResult<BookQueryResult>> call, Throwable throwable) {
System.out.println("连接失败");
}
});
重点知识
- call.execute发起同步调用;call.enqueue发起异步调用,在异步回调中可以处理onResponse、onFailure.
- response.isSuccessfully(
code >= 200 && code < 300)
3. 可以通过okhttp的interceptor进行全局拦截,然后统一处理所有可能的response code.注释在解析response可能会失败,比如期望的是json, 服务端网关层可能返回了html. 4. 如果网络请求失败,会产生IOException
call.enqueue(new Callback<List<GitHubRepo>>() {
@Override
public void onResponse(Call<List<GitHubRepo>> call, Response<List<GitHubRepo>> response) {
if (response.isSuccessful()) {
Toast.makeText(ErrorHandlingActivity.this, "server returned data", Toast.LENGTH_SHORT).show();
// todo display the data instead of just a toast
}
else {
Toast.makeText(ErrorHandlingActivity.this, "Server returned an error", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call<List<GitHubRepo>> call, Throwable t) {
if (t instanceof IOException) {
Toast.makeText(ErrorHandlingActivity.this, "this is an actual network failure :( inform the user and possibly retry", Toast.LENGTH_SHORT).show();
// logging probably not necessary
}
else {
Toast.makeText(ErrorHandlingActivity.this, "conversion issue! big problems :(", Toast.LENGTH_SHORT).show();
// todo log to some central bug tracking service
}
}
});
Retrofit的注解详细说明
我们可以将Retrofit的注解分为两类
- 接口方法上的注解
- 接口参数上的注解
@HTTP
public interface GetRequest_Interface {
/**
* method:网络请求的方法(区分大小写)
* path:网络请求地址路径
* hasBody:是否有请求体
*/
@HTTP(method = "GET", path = "blog/{id}", hasBody = false)
Call<ResponseBody> getCall(@Path("id") int id);
// {id} 表示是一个变量
// method 的值 retrofit 不会做处理,所以要自行保证准确
}
@FormUrlEncoded和@Multipart
/**
*表明是一个表单格式的请求(Content-Type:application/x-www-form-urlencoded)
* <code>Field("username")</code> 表示将后面的 <code>String name</code> 中name的取值作为 username 的值
*/
@POST("/form")
@FormUrlEncoded
Call<ResponseBody> testFormUrlEncoded1(@Field("username") String name, @Field("age") int age);
/**
* {@link Part} 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型
* 除 {@link okhttp3.MultipartBody.Part} 以外,其它类型都必须带上表单字段({@link okhttp3.MultipartBody.Part} 中已经包含了表单字段的信息),
*/
@POST("/form")
@Multipart
Call<ResponseBody> testFileUpload1(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file);
- 添加请求头
@Header,@Headers
// @Header
@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)
// @Headers
@Headers("Authorization: authorization")
@GET("user")
Call<User> getUser()
// 以上的效果是一致的。
// 区别在于使用场景和使用方式
// 1. 使用场景:@Header用于添加不固定的请求头,@Headers用于添加固定的请求头
// 2. 使用方式:@Header作用于方法的参数;@Headers作用于方法
- 表单参数:
@Field,@FieldMap
发送 Post请求 时提交请求的表单字段,与 @FormUrlEncoded 注解配合使用
public interface GetRequest_Interface {
/**
*表明是一个表单格式的请求(Content-Type:application/x-www-form-urlencoded)
* <code>Field("username")</code> 表示将后面的 <code>String name</code> 中name的取值作为 username 的值
*/
@POST("/form")
@FormUrlEncoded
Call<ResponseBody> testFormUrlEncoded1(@Field("username") String name, @Field("age") int age);
/**
* Map的key作为表单的键
*/
@POST("/form")
@FormUrlEncoded
Call<ResponseBody> testFormUrlEncoded2(@FieldMap Map<String, Object> map);
}
- URL查询参数
@Query,@QueryMap
用于 @GET 方法的查询参数(Query = Url 中 ‘?’ 后面的 key-value)
如:url = www.println.net/?cate=andro…,其中,Query = cate
配置时只需要在接口方法中增加一个参数即可:
public interface GetRequest_Interface {
@GET("/")
Call<String> cate(@Query("cate") String cate);
@GET("/")
Call<String> cate2(@QueryMap Map<String, Object> cate);
}
- URL路径参数
@Path
public interface GetRequest_Interface {
@GET("users/{user}/repos")
Call<ResponseBody> getBlog(@Path("user") String user );
}
- 上传表单参数
@Part,@PartMap
发送 Post请求 时提交请求的表单字段,与@Field的区别:功能相同,但携带的参数类型更加丰富,包括数据流,所以适用于 有文件上传 的场景,与 @Multipart 注解配合使用
public interface GetRequest_Interface {
/**
* {@link Part} 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型
* 除 {@link okhttp3.MultipartBody.Part} 以外,其它类型都必须带上表单字段({@link okhttp3.MultipartBody.Part} 中已经包含了表单字段的信息),
*/
@POST("/form")
@Multipart
Call<ResponseBody> testFileUpload1(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file);
/**
* PartMap 注解支持一个Map作为参数,支持 {@link RequestBody } 类型,
* 如果有其它的类型,会被{@link retrofit2.Converter}转换,如后面会介绍的 使用{@link com.google.gson.Gson} 的 {@link retrofit2.converter.gson.GsonRequestBodyConverter}
* 所以{@link MultipartBody.Part} 就不适用了,所以文件只能用<b> @Part MultipartBody.Part </b>
*/
@POST("/form")
@Multipart
Call<ResponseBody> testFileUpload2(@PartMap Map<String, RequestBody> args, @Part MultipartBody.Part file);
@POST("/form")
@Multipart
Call<ResponseBody> testFileUpload3(@PartMap Map<String, RequestBody> args);
}
- 设置请求URL
@URL
public interface GetRequest_Interface {
@GET
Call<ResponseBody> testUrlAndQuery(@Url String url, @Query("showAll") boolean showAll);
// 当有URL注解时,@GET传入的URL就可以省略
// 当GET、POST...HTTP等方法中没有设置Url时,则必须使用 {@link Url}提供
}
Retrofit内部实现原理
Retrofit使用JDK动态代理机制为接口创建实现类(通过解析接口方法上的注解、参数注解和返回值动态实现OkHttp请求)。
public interface MyInterface {
void fun1();
void fun2();
}
public static void main(String[] args) {
Class[] cs = {MyInterface.class};
ClassLoader loader = MyInterface.class.getClassLoader();
InvocationHandler h = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("annotation " + annotation.toString());
}
System.out.println("无论你调用代理对象的什么方法,其实都是在调用invoke()...");
return null;
}
};
MyInterface mi = (MyInterface)Proxy.newProxyInstance(loader, cs, h);
mi.fun1();
mi.fun2();
}
### Proxy.newProxyInstance生成的类
class X implements MyInterface {
private InvocationHandler h;
public X(InvocationHandler h) {
this.h = h;
}
public void fun1() {
h.invoke();
}
public void fun2() {
h.invoke();
}
}
扩展点有哪些?
- Request/Response Converter
- converter-scalars: Scalars (primitives, boxed, and String)
- converter-jackson
- converter-simplexml
- converter-gson
- converter-moshi
- converter-protobuf
- converter-wire
- converter-jaxb
- Call Adapter
- adapter-rxjava3
- adapter-rxjava2
- adapter-rxjava
- adapter-java8
- adapter-guava
- adapter-scala
使用了哪些设置模式
- Builder:建造者模式
- Abstract Factory: 抽象工厂模式
- Adapter: 适配器模式
如何将Retrofit集成到SpringBoot项目中
使用retrofit-spring-boot-starter将Retrofit集成到SpringBoot项目中
引入依赖
<dependency>
<groupId>com.github.lianjiatech</groupId>
<artifactId>retrofit-spring-boot-starter</artifactId>
<version>2.3.6</version>
</dependency>
添加Java配置类型
@Configuration
@RetrofitScan("cn.xxx.gymy.app.openapi.service")
public class RetrofitConfiguration {
}
在JavaConfig指向的包(cn.xxx.gymy.app.openapi.service)下面创建@RetrofitClient接口
/**
* 工业码用户认证接口
*/
@RetrofitClient(baseUrl = "${gymy.biz.baseUrl}")
public interface GymyAuthHttpClient {
@FormUrlEncoded
@POST("openApi/v1/user/login")
Call<GymyResultDto<GymyTokenDto>> login(@Field("username") String username, @Field("password") String password);
}
将服务中使用@RetrofitClient接口
@Service
@Slf4j
public class GymyAuthServiceImpl implements GymyAuthService {
@Resource
private GymyAuthHttpClient gymyAuthHttpClient;
@Override
public GymyTokenDto login(String username, String password) throws IOException {
Call<GymyResultDto<GymyTokenDto>> loginCall = gymyAuthHttpClient.login(username, password);
retrofit2.Response<GymyResultDto<GymyTokenDto>> gymyTokenDtoResponse = loginCall.execute();
if (gymyTokenDtoResponse.isSuccessful()
&& gymyTokenDtoResponse.body() != null
&& gymyTokenDtoResponse.body().getData() != null) {
return gymyTokenDtoResponse.body().getData();
}
return null;
}
}
上传文件示例
- RetrofitClient定义
/**
* 企业接口
*
* @author James.H.Fu
* @date 2022/6/10 16:53
*/
@RetrofitClient(baseUrl = "${gymy.biz.erjiBaseUrl}")
public interface ErjiFileHttpClient {
/**
* 文件上传接口
*
* @param token 登录令牌
* @param bodyPart
* @return
*/
@POST("/snms/api/file/upload")
@Multipart
Call<ErjiDtoResult<ErjiFileUploadResultDto>> fileUpload(@Header("Authorization") String token, @Part MultipartBody.Part bodyPart);
}
- 上传服务实现
@Service
@Slf4j
public class ErjiUploadServiceImpl implements ErjiUploadService {
@Resource
private ErjiFileHttpClient erjiFileHttpClient;
@Override
@SneakyThrows
public ErjiFileUploadResultDto upload(String fileName, String fileUrl) {
String token = ErjiTokenContext.getToken();
if (!StringUtils.hasText(fileName)) {
fileName = "file.png";
}
// 下载文件
byte[] bytes = HttpUtil.downloadBytes(fileUrl);
// 上传到二级节点
String encodeFileName = URLEncodeUtil.encode(fileName, StandardCharsets.UTF_8);
okhttp3.RequestBody requestBody = okhttp3.RequestBody.create(MediaType.parse("multipart/form-data"), bytes);
MultipartBody.Part bodyPart = MultipartBody.Part.createFormData("file", encodeFileName, requestBody);
Call<ErjiDtoResult<ErjiFileUploadResultDto>> callFileUpload = erjiFileHttpClient.fileUpload(token, bodyPart);
Response<ErjiDtoResult<ErjiFileUploadResultDto>> responseFileUpload = callFileUpload.execute();
ErjiDtoResult<ErjiFileUploadResultDto> result = responseFileUpload.body();
if (result != null) {
if (!result.isSuccessful()) {
log.error("erji file upload result {}", result);
}
return result.getData();
} else {
log.error("erji file upload response {}, errorBody {}", responseFileUpload, responseFileUpload.errorBody());
return null;
}
}
}
下载文件示例
- 定义接口:如果是较大的文件,建议使用@Streaming注解来实现流式读取
@RetrofitClient(baseUrl = "https://img.ljcdn.com/hc-picture/")
public interface DownloadApi {
@GET("{fileKey}")
@Streaming
Response<ResponseBody> download(@Path("fileKey") String fileKey);
}
- 使用ResponseBody.byteStream()读取数据流
@SpringBootTest(classes = RetrofitTestApplication.class)
@RunWith(SpringRunner.class)
public class DownloadTest {
@Autowired
DownloadApi downLoadApi;
@Test
public void download() throws Exception {
String fileKey = "6302d742-ebc8-4649-95cf-62ccf57a1add";
Response<ResponseBody> response = downLoadApi.download(fileKey);
ResponseBody responseBody = response.body();
// 二进制流
InputStream is = responseBody.byteStream();
// 具体如何处理二进制流,由业务自行控制。这里以写入文件为例
File tempDirectory = new File("temp");
if (!tempDirectory.exists()) {
tempDirectory.mkdir();
}
File file = new File(tempDirectory, UUID.randomUUID().toString());
if (!file.exists()) {
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(file);
byte[] b = new byte[1024];
int length;
while ((length = is.read(b)) > 0) {
fos.write(b, 0, length);
}
is.close();
fos.close();
}
}
使用ThreadLocal 在线程间传递Token
- 定义TokenContext
/**
* 授权上下文
*
*/
public class ErjiTokenContext implements AutoCloseable {
private static final ThreadLocal<String> ctx = new ThreadLocal<>();
public ErjiTokenContext(String token) {
ctx.set(token);
}
@Override
public void close() {
ctx.remove();
}
/**
* 获取授权上下文
*
* @return 授权上下文
*/
public static String getToken() {
return ctx.get();
}
}
- 在调用服务前获取Token并初始化一个TokenContext
@Test
public void testEntConfig() {
// 登录,获取token
String token = erjiAuthService.getToken("admin", "***");
// 将token注入ThreadLocal线程上下文
try (ErjiTokenContext erjiTokenContext = new ErjiTokenContext(token)) {
List<String> entPrefixList = Collections.singletonList("88.***.***");
erjiEntService.entConfig(entPrefixList);
}
}
注意:如果在内部实现中新开了线程,一定要记得在新的线程中重新创建TokenContext
String token = ErjiTokenContext.getToken();
ThreadUtil.execAsync(()->{
try (ErjiTokenContext erjiTokenContext = new ErjiTokenContext(token)) {
这里可以调用其他需要token的http接口
}
});
使用TokenInterceptor动态往请求中注入token
- 继承
BasePathMatchInterceptor编写拦截处理器
@Component
public class TimeStampInterceptor extends BasePathMatchInterceptor {
@Override
public Response doIntercept(Chain chain) throws IOException {
Request request = chain.request();
HttpUrl url = request.url();
long timestamp = System.currentTimeMillis();
HttpUrl newUrl = url.newBuilder()
.addQueryParameter("timestamp", String.valueOf(timestamp))
.build();
Request newRequest = request.newBuilder()
.url(newUrl)
.build();
return chain.proceed(newRequest);
}
}
- 使用
@Intercept注解指定要使用的拦截器
@RetrofitClient(baseUrl = "${test.baseUrl}")
@Intercept(handler = TimeStampInterceptor.class, include = {"/api/**"}, exclude = "/api/test/savePerson")
@Intercept(handler = TimeStamp2Interceptor.class) // 需要多个,直接添加即可
public interface HttpApi {
@GET("person")
Result<Person> getPerson(@Query("id") Long id);
@POST("savePerson")
Result<Person> savePerson(@Body Person person);
}
retrofit-spring-boot-starter的其它特性
自定义httpClient
- 注入okHttpClient:实现接口
SourceOkHttpClientRegistrar并将类注册为Bean。
@Slf4j
@Component
public class CustomSourceOkHttpClientRegistrar implements SourceOkHttpClientRegistrar {
@Override
public void register(SourceOkHttpClientRegistry registry) {
OkHttpClient defaultOkHttpClient = new OkHttpClient.Builder()
.connectTimeout(0, TimeUnit.SECONDS)
.readTimeout(0, TimeUnit.SECONDS)
.writeTimeout(0, TimeUnit.SECONDS)
.callTimeout(0, TimeUnit.SECONDS)
.build();
// 替换默认的SourceOkHttpClient
registry.register(Constants.DEFAULT_SOURCE_OK_HTTP_CLIENT, defaultOkHttpClient);
// 添加新的SourceOkHttpClient
registry.register("testSourceOkHttpClient", new OkHttpClient.Builder()
.addInterceptor(chain -> {
log.info("============使用testSourceOkHttpClient=============");
return chain.proceed(chain.request());
})
.build());
}
}
- 通过
@RetrofitClient.sourceOkHttpClient指定当前接口要使用的OkHttpClient
@RetrofitClient(baseUrl = "${test.baseUrl}", sourceOkHttpClient = "testSourceOkHttpClient")
public interface CustomOkHttpTestApi {
@GET("person")
Result<Person> getPerson(@Query("id") Long id);
}
方法上的扩展注解
- @Logging
- @Retry
- @SentinelDegrade
需要在配置中启用sentinel熔断降级策略并在pom.xml中引入依赖
启用sentinel熔断降级策略并在全局关闭,在需要的方法上使用注解@SentinelDegrade
retrofit:
# 熔断降级配置
degrade:
# 熔断降级类型。默认none,表示不启用熔断降级
degrade-type: sentinel
# 全局sentinel降级配置
global-sentinel-degrade:
# 是否开启
enable: false
# ...其他sentinel全局配置
引入pom依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.6.3</version>
</dependency>
- @InterceptMark: 可用于实现自定义拦截器注解
可扩展的接口
- 通过
ErrorDecoder接口/@RetrofitClient#errorDecoder实现自定义错误解码器 - 通过集成
ServiceInstanceChooser/@RetrofitClient#serviceId/@RetrofitClient#path实现微服务之间的http调用
用户可以自行实现
ServiceInstanceChooser接口,完成服务实例的选取逻辑,并将其配置成Spring Bean。对于Spring Cloud应用,组件提供了SpringCloudServiceInstanceChooser实现,用户只需将其配置成Spring Bean即可。
@Bean
@Autowired
public ServiceInstanceChooser serviceInstanceChooser(LoadBalancerClient loadBalancerClient) {
return new SpringCloudServiceInstanceChooser(loadBalancerClient);
}
- 通过
SourceOkHttpClientRegistrar接口/@RetrofitClient#sourceHttpClient注入自定义 ok http client - 通过集成
BasePathMatchInterceptor类/@Intercept实现拦截器 - 继承
LoggingInterceptor实现日志自定义功能
@Bean
public LoggingInterceptor loggingInterceptor(RetrofitProperties retrofitProperties){
return new AggregateLoggingInterceptor(retrofitProperties.getGlobalLog());
}
- 通过
GlobalInterceptor实现全局拦截 - 通过
NetworkInterceptor实现全局网络拦截 - 通过CallAdapter.Factory/@RetrofitClient.callAdapterFactories实现自定义调用适配器
- 通过Converter.Factory/@RetrofitClient.converterFactories实现自定义数据适配器
Retrofit调用适配器
String:将Response Body适配成String返回。- 基础类型(
Long/Integer/Boolean/Float/Double):将Response Body适配成上述基础类型 - 任意
Java类型: 将Response Body适配成对应的Java对象返回 CompletableFuture<T>: 将Response Body适配成CompletableFuture<T>对象返回Void: 不关注返回类型可以使用VoidResponse<T>: 将Response适配成Response<T>对象返回Call<T>: 不执行适配处理,直接返回Call<T>对象Mono<T>:Project Reactor响应式返回类型Single<T>:Rxjava响应式返回类型(支持Rxjava2/Rxjava3)Completable:Rxjava响应式返回类型,HTTP请求没有响应体(支持Rxjava2/Rxjava3)
@RetrofitClient(baseUrl = "${test.baseUrl}")
public interface HttpApi {
@POST("getString")
String getString(@Body Person person);
@GET("person")
Result<Person> getPerson(@Query("id") Long id);
@GET("person")
CompletableFuture<Result<Person>> getPersonCompletableFuture(@Query("id") Long id);
@POST("savePerson")
Void savePersonVoid(@Body Person person);
@GET("person")
Response<Result<Person>> getPersonResponse(@Query("id") Long id);
@GET("person")
Call<Result<Person>> getPersonCall(@Query("id") Long id);
@GET("person")
Mono<Result<Person>> monoPerson(@Query("id") Long id);
@GET("person")
Single<Result<Person>> singlePerson(@Query("id") Long id);
@GET("ping")
Completable ping();
}
Retrofit数据适配器
通常情况下我们选择的是Scalars和Jackson,其中Scalars代表的是基础数据类型的解析,而Jackson代表的是JSON格式的解析。
遇到过的问题
- Jackson ObjectMapper序列化问题 由于第三方接口返回值和我们通常的约定不一样,导致ReponseBody序列化为Java对象时出现以下问题
com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot coerce empty String ("") to element of `java.util.ArrayList<cn.xxx.gymy.app.openapi.model.dto.gymy.code.GymyCodeDto>` (but could if coercion was enabled using `CoercionConfig`)\
at [Source: (okhttp3.ResponseBody$BomAwareReader); line: 3, column: 9] (through reference chain: cn.xxx.gymy.app.openapi.model.dto.gymy.GymyResultDto["data"])
输入值为
{"code":0,"message":"error","success":false,"data":""}
解决方案:
第一步配置ObjectMapper
@Configuration
public class ObjectMapperConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper;
}
}
第二步在RetrofitScan配置类中增加自定义BeanJacksonConverterFactory
@Configuration
@RetrofitScan("cn.yzw.gymy.app.openapi.service")
public class RetrofitConfiguration {
@Resource
private ObjectMapper objectMapper;
@Bean
public customizeJacksonConverterFactory() {
return JacksonConverterFactory.create(objectMapper);
}
}
- 注意ResponseBody,比如errorBody()只能读一次
Service service = retrofit.create(Service.class);
Response<User> response = service.getUser().execute();
log.info("reponse code={}, message={}, errorBody={}, body={}",
response.code(),
response.message(),
// 第一次读取errorBody,正常
response.errorBody() != null ? response.errorBody().string() : "null",
response.body() != null ? "notNull" : "null");
if (response.errorBody() != null) {
// Look up a converter for the Error type on the Retrofit instance.
Converter<ResponseBody, ErrorBody> errorConverter =
retrofit.responseBodyConverter(ErrorBody.class, new Annotation[0]);
// Convert the error body into our Error type.
// 第二次再读取就会报错了
ErrorBody errorBody = errorConverter.convert(response.errorBody());
if (errorBody != null) {
System.out.println("ERROR: " + errorBody.message);
}
}
- 接口不能返回void, 因为始终是有http reponse返回,如果响应中没值,可能写返回Void值
java.lang.IllegalArgumentException: Service methods cannot return void.
for method GitHubServiceRetrofitClient.getContributorsVoid
扩展知识点
- okhttp:
- http SPDY
- TransmittableThreadLocal
- 响应式编程:projectreactor.io/docs/core/r…
参考资料
- retrofit official site
- Android Retrofit详解
- retrofit-spring-boot-starter
- Consuming APIs with Retrofit
- Retrofit 2 — Catch Server Errors Globally with Response Interceptor
- Retrofit结合RxJava使用指南
- spring-boot项目整合Retrofit最佳实践,最优雅的HTTP客户端工具!
- Retrofit 通过刷新头部Token解决token过期
- 13-springboot-retrofit工具
- Retrofit HttpLoggingInterceptor的使用
- Android网络篇(二)—— Retrofit的基本使用
- 不吹不黑!RxJava 比你想象的更加强大
- RxJava 简介