学而不思则罔,思而不学则殆
【Retrofit】Retrofit原理解析之使用篇
Retrofit系列文章
【Retrofit】Retrofit原理解析之使用篇
【Retrofit】Retrofit原理解析之原理篇
【Retrofit】Retrofit原理解析之注解详解篇
【Retrofit】Retrofit原理解析之设计模式总结篇
引言
最近在学习使用Retrofit框架进行网络请求,特整理一下使用总结记录下来!
Retrofit本质是根据OkHttp进行二次封装的网络请求框架。
接下来我们就来讲一下Retrofit具体使用
源码下载小技巧,由于国内网络原因,直接从Github上下载源码可能很慢,当然网络很好的不用考虑了。方法是从Github上克隆一份源码到Gitee上,再从Gitee上clone到本地就会非常快。
//Github
https://github.com/square/retrofit
//Gitee
https://gitee.com/zhangyuwxf/retrofit.git
准备工作,测试的服务端:
用于接收测试客户端发送的请求
//服务端
https://github.com/aJanefish/ZyServer
常见使用
普通GET请求
请求所有的信息
http://localhost:3434/retrofit/all.do
使用curl模拟请求数据,情况如下:
86183@DESKTOP-MKB5ERF MINGW64 ~/Desktop
$ curl http://localhost:3434/retrofit/all.do
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 1571 0 1571 0 0 98187 0 --:--:-- --:--:-- --:--:-- 98187{"code":200,"msg":"OK","des":"all.do","data":[{"id":1,"date":"2020-11-15 10:39:13","author":"fish1","title":"retrofit1","address":"https://gitee.com/zhangyuwxf/retrofit.git","content":"http test1"},{"id":2,"date":"2020-11-15 10:39:13","author":"fish2","title":"retrofit2","address":"https://gitee.com/zhangyuwxf/retrofit.git","content":"http test2"},{"id":3,"date":"2020-11-15 10:39:13","author":"fish3","title":"retrofit3","address":"https://gitee.com/zhangyuwxf/retrofit.git","content":"http test3"},{"id":4,"date":"2020-11-15 10:39:13","author":"fish4","title":"retrofit4","address":"https://gitee.com/zhangyuwxf/retrofit.git","content":"http test4"},{"id":5,"date":"2020-11-15 10:39:13","author":"fish5","title":"retrofit5","address":"https://gitee.com/zhangyuwxf/retrofit.git","content":"http test5"},{"id":6,"date":"2020-11-15 10:39:13","author":"fish6","title":"retrofit6","address":"https://gitee.com/zhangyuwxf/retrofit.git","content":"http test6"},{"id":7,"date":"2020-11-15 10:39:13","author":"fish7","title":"retrofit7","address":"https://gitee.com/zhangyuwxf/retrofit.git","content":"http test7"},{"id":8,"date":"2020-11-15 10:39:13","author":"fish8","title":"retrofit8","address":"https://gitee.com/zhangyuwxf/retrofit.git","content":"http test8"},{"id":9,"date":"2020-11-15 10:39:13","author":"fish9","title":"retrofit9","address":"https://gitee.com/zhangyuwxf/retrofit.git","content":"http test9"},{"id":10,"date":"2020-11-15 10:39:13","author":"fish10","title":"retrofit10","address":"https://gitee.com/zhangyuwxf/retrofit.git","content":"http test10"}]}
使用Retrofit发送请求:
retrofit在使用的过程中,需要定义一个接口对象,我们首先演示一个最简单的get请求,接口如下所示:
interface BlogService {
@GET("retrofit/all.do")
Call<Blog> getAll();
}
//实体类
static class Blog {
private String body;
public Blog(String body) {
this.body = body;
}
@Override
public String toString() {
return "Blog{" +
"body='" + body + '\'' +
'}';
}
}
定义了一个getAll的方法,通过@GET注解标识为get请求,@GET中所填写的value和baseUrl组成完整的路径,baseUrl在构造retrofit对象时给出。
如下构建普通的
public static void main(String[] args) {
//1.生成Retrofit对象,通过构建者模式
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(HttpUrl.get("http://localhost:3434/"))
//自定义了一个数据处理的工厂
.addConverterFactory(DataAdapter.FACTORY)
.build();
//2.生成BlogService对象
BlogService blogService = retrofit.create(BlogService.class);
//3.获取Call对象
Call<Blog> call = blogService.getAll();
try {
//4.同步请求也有异步请求
Response<Blog> response = call.execute();
//5.获取对象
Blog blog = response.body();
System.out.print(blog);
} catch (IOException e) {
e.printStackTrace();
}
}
static final class DataAdapter implements Converter<ResponseBody, Blog> {
static final Converter.Factory FACTORY =
new Converter.Factory() {
@Override
public @Nullable
Converter<ResponseBody, ?> responseBodyConverter(
Type type, Annotation[] annotations, Retrofit retrofit) {
if (type == Blog.class) return new DataAdapter();
return null;
}
};
@Override
public Blog convert(ResponseBody responseBody) throws IOException {
String body = responseBody.string();
//返回的Json字符串没有做处理,直接塞进数据对象中
// FIXME: 2020/11/16 数据处理
return new Blog(body);
}
}
如上,先是通过构建者模式生成Retrofit对象,该对象携带了处理对象,但是此时我们只是设置最简单的BaseUrl和数据处理的Converter.Factory,他的实现很简单,发现使我们需要处理的类型,就返回具体的输数据处理的对象,处理过程先简化,后面可以具体看一下,不是重点,Retrofit本身提供了很多数据处理默认的Converter.Factory,包括常见的Json数据格式的对象,也可以自定义处理数据。
第二步是生成BlogService对象,此处主要是通过动态代理来获取指定接口的实例,通过Proxy.newProxyInstance()实现,更多详细信息可以查看【设计模式】代理模式(Proxy Pattern).
第三步是调用接口方法生成Call对象实例。这里需要指出的是接口必须返回:
接口中的方法必须有返回值,且比如是Call类型
第四步调用call.execute()同步获取数据,既然有同步,也有异步获取数据(call.enqueue()),其实内部是通过封装OkHttp来实现的同步和异步的。
客户端和服务端请求数据如下
GET http://localhost:3434/retrofit/all.do HTTP/1.1
Accept-Encoding: gzip
Connection: keep-alive
Host: localhost:3434
User-Agent: okhttp/3.14.9
Blog{body='{"code":200,"msg":"OK","des":"all.do","data":[{"id":1,"date":"2020-11-15 10:39:13","author":"fish1","title":"retrofit1","address":"https://gitee.com/zhangyuwxf/retrofit.git","content":"http test1"},{"id":2,"date":"2020-11-15 10:39:13","author":"fish2","title":"retrofit2","address":"https://gitee.com/zhangyuwxf/retrofit.git","content":"http test2"},{"id":3,"date":"2020-11-15 10:39:13","author":"fish3","title":"retrofit3","address":"https://gitee.com/zhangyuwxf/retrofit.git","content":"http test3"},{"id":4,"date":"2020-11-15 10:39:13","author":"fish4","title":"retrofit4","address":"https://gitee.com/zhangyuwxf/retrofit.git","content":"http test4"},{"id":5,"date":"2020-11-15 10:39:13","author":"fish5","title":"retrofit5","address":"https://gitee.com/zhangyuwxf/retrofit.git","content":"http test5"},{"id":6,"date":"2020-11-15 10:39:13","author":"fish6","title":"retrofit6","address":"https://gitee.com/zhangyuwxf/retrofit.git","content":"http test6"},{"id":7,"date":"2020-11-15 10:39:13","author":"fish7","title":"retrofit7","address":"https://gitee.com/zhangyuwxf/retrofit.git","content":"http test7"},{"id":8,"date":"2020-11-15 10:39:13","author":"fish8","title":"retrofit8","address":"https://gitee.com/zhangyuwxf/retrofit.git","content":"http test8"},{"id":9,"date":"2020-11-15 10:39:13","author":"fish9","title":"retrofit9","address":"https://gitee.com/zhangyuwxf/retrofit.git","content":"http test9"},{"id":10,"date":"2020-11-15 10:39:13","author":"fish10","title":"retrofit10","address":"https://gitee.com/zhangyuwxf/retrofit.git","content":"http test10"}]}'}
动态的url访问 @PATH
两个测试接口:
http://localhost:3434/retrofit/user/fish1
http://localhost:3434/retrofit/user/fish2
测试访问接口:
86183@DESKTOP-MKB5ERF MINGW64 ~/Desktop
$ curl http://localhost:3434/retrofit/user/fish1
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 203 0 203 0 0 863 0 --:--:-- --:--:-- --:--:-- 863{"code":200,"msg":"OK","des":"path fish1","data":[{"id":1,"date":"2020-11-15 10:39:13","author":"fish1","title":"retrofit1","address":"https://gitee.com/zhangyuwxf/retrofit.git","content":"http test1"}]}
86183@DESKTOP-MKB5ERF MINGW64 ~/Desktop
$ curl http://localhost:3434/retrofit/user/fish2
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 203 0 203 0 0 6548 0 --:--:-- --:--:-- --:--:-- 6548{"code":200,"msg":"OK","des":"path fish2","data":[{"id":2,"date":"2020-11-15 10:39:13","author":"fish2","title":"retrofit2","address":"https://gitee.com/zhangyuwxf/retrofit.git","content":"http test2"}]}
通过Retrofit访问
interface BlogService {
@GET("retrofit/user/{username}")
Call<Blog> getByName(@Path("username") String username);
}
可以看到我们定义了一个getByName方法,方法接收一个username参数,并且我们的@GET注解中使用{username}声明了访问路径,这里你可以把{username}当做占位符,而实际运行中会通过@PATH(“username”)所标注的参数进行替换。
Call<Blog> call = blogService.getByName("fish1");
Call<Blog> call = blogService.getByName("fish2");
测试结果如下:
GET http://localhost:3434/retrofit/user/fish1 HTTP/1.1
Accept-Encoding: gzip
Connection: keep-alive
Host: localhost:3434
User-Agent: okhttp/3.14.9
GET http://localhost:3434/retrofit/user/fish2 HTTP/1.1
Accept-Encoding: gzip
Connection: keep-alive
Host: localhost:3434
User-Agent: okhttp/3.14.9
可以看到,发起请求的时候把我们传入的参数已拼接到path中。
Blog{body='{"code":200,"msg":"OK","des":"path fish1","data":[{"id":1,"date":"2020-11-15 10:39:13","author":"fish1","title":"retrofit1","address":"https://gitee.com/zhangyuwxf/retrofit.git","content":"http test1"}]}'}
Blog{body='{"code":200,"msg":"OK","des":"path fish2","data":[{"id":2,"date":"2020-11-15 10:39:13","author":"fish2","title":"retrofit2","address":"https://gitee.com/zhangyuwxf/retrofit.git","content":"http test2"}]}'}
查询参数的设置@Query
http://localhost:3434/retrofit/query?id=1
http://localhost:3434/retrofit/query?id=2
测试服务器返回数据:
86183@DESKTOP-MKB5ERF MINGW64 ~/Desktop
$ curl http://localhost:3434/retrofit/query?id=1
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 201 0 201 0 0 990 0 --:--:-- --:--:-- --:--:-- 990{"code":200,"msg":"OK","des":"path fish2","data":{"id":1,"date":"2020-11-15 10:39:13","author":"fish1","title":"retrofit1","address":"https://gitee.com/zhangyuwxf/retrofit.git","content":"http test1"}}
86183@DESKTOP-MKB5ERF MINGW64 ~/Desktop
$ curl http://localhost:3434/retrofit/query?id=4
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 201 0 201 0 0 13400 0 --:--:-- --:--:-- --:--:-- 13400{"code":200,"msg":"OK","des":"path fish2","data":{"id":4,"date":"2020-11-15 10:39:13","author":"fish4","title":"retrofit4","address":"https://gitee.com/zhangyuwxf/retrofit.git","content":"http test4"}}
即一般的传参,我们可以通过@Query注解方便的完成,我们再次在接口中添加一个方法:
Query注解中的值是参数k,传入的值是参数v.
interface BlogService {
@GET("retrofit/query")
Call<Blog> query(@Query("id") String id);
}
Call<Blog> call = blogService.query("2");
Call<Blog> call = blogService.query("4");
服务端的数据:
GET http://localhost:3434/retrofit/query?id=2 HTTP/1.1
Accept-Encoding: gzip
Connection: keep-alive
Host: localhost:3434
User-Agent: okhttp/3.14.9
GET http://localhost:3434/retrofit/query?id=4 HTTP/1.1
Accept-Encoding: gzip
Connection: keep-alive
Host: localhost:3434
User-Agent: okhttp/3.14.9
客户端请求结果数据:
Blog{body='{"code":200,"msg":"OK","des":"path fish2","data":{"id":2,"date":"2020-11-15 10:39:13","author":"fish2","title":"retrofit2","address":"https://gitee.com/zhangyuwxf/retrofit.git","content":"http test2"}}'}
Blog{body='{"code":200,"msg":"OK","des":"path fish2","data":{"id":4,"date":"2020-11-15 10:39:13","author":"fish4","title":"retrofit4","address":"https://gitee.com/zhangyuwxf/retrofit.git","content":"http test4"}}'}
通过FormUrlEncoded发送表单
关键代码:
@POST("retrofit/login")
@FormUrlEncoded
Call<Blog> login(@Field("username") String username, @Field("password") String password, @Field("tag") String tag);
通过Field字段添加表单键值对
Call<Blog> call = blogService.login("zhangyu", "123456", "tag+" + System.currentTimeMillis());
最终结果:
服务端:
POST http://localhost:3434/retrofit/login?null HTTP/1.1
Accept-Encoding: gzip
Connection: keep-alive
Content-Length: 56
Content-Type: application/x-www-form-urlencoded
Host: localhost:3434
User-Agent: okhttp/3.14.9
username=zhangyu&password=123456&tag=tag%2B1605573357011
客户端
Blog{body='{"code":200,"msg":"OK","des":"LOGIN","data":"LOGIN:username=zhangyu&password=123456&tag=tag%2B1605573357011"}'}
通过@Body上传对象
@POST("retrofit/add")
Call<Blog> addBlog(@Body Blog user);
需要设置Blog对象转换为RequestBody的工厂
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(HttpUrl.get("http://localhost:3434/"))
.addConverterFactory(FACTORY)
.build();
static final Converter.Factory FACTORY =
new Converter.Factory() {
@Override
public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
if (type == Blog.class) {
return new RequestBodyAdapter();
}
return null;
}
};
static final class RequestBodyAdapter implements Converter<Blog, RequestBody> {
@Override
public RequestBody convert(Blog blog) throws IOException {
return RequestBody.create(null, blog.toString()); //构建RequestBody对象
}
}
通过@Body设置的网络请求需要实现步骤
- 创建Converter.Factory工厂,根据Type类型返回对应的类型转换Converter
- 创建T->RequestBody 的类型转化实例
- 发起请求
发起请求
Call<Blog> call = blogService.addBlog(new Blog("I am clint Blog!"));
测试结果如下:
服务端:
POST http://localhost:3434/retrofit/add?null HTTP/1.1
Accept-Encoding: gzip
Connection: keep-alive
Content-Length: 29
Host: localhost:3434
User-Agent: okhttp/3.14.9
Blog{body='I am clint Blog!'}
客户端:
Blog{body='{"code":200,"msg":"OK","des":"ADD","data":"ADD"}'}
通过Multipart发送文件
@Multipart
@POST("retrofit/register")
Call<Blog> registerUser(@Part MultipartBody.Part part, @Part("username") RequestBody username, @Part("password") RequestBody password);
File file = new File("samples/build.gradle");
RequestBody photoRequestBody = RequestBody.create(MediaType.parse("text/plain"), file);
MultipartBody.Part photo = MultipartBody.Part.createFormData("fileName", "build.gradle", photoRequestBody);
Call<Blog> call = blogService.registerUser(photo, RequestBody.create(null, "abc"), RequestBody.create(null, "123"));
测试结果:
服务端:
POST http://localhost:3434/retrofit/register?null HTTP/1.1
Accept-Encoding: gzip
Connection: keep-alive
Content-Length: 1002
Content-Type: multipart/form-data; boundary=10ee6686-40f8-49fa-8075-379e338a9eac
Host: localhost:3434
User-Agent: okhttp/3.14.9
--10ee6686-40f8-49fa-8075-379e338a9eac
Content-Disposition: form-data; name="fileName"; filename="build.gradle"
Content-Type: text/plain
Content-Length: 495
apply plugin: 'java-library'
dependencies {
implementation project(':retrofit')
implementation project(':retrofit-mock')
implementation project(':retrofit-converters:moshi')
implementation project(':retrofit-converters:gson')
implementation project(':retrofit-converters:simplexml')
implementation project(':retrofit-adapters:rxjava')
implementation deps.mockwebserver
implementation deps.guava
implementation deps.jsoup
compileOnly deps.findBugsAnnotations
}
--10ee6686-40f8-49fa-8075-379e338a9eac
Content-Disposition: form-data; name="username"
Content-Transfer-Encoding: binary
Content-Length: 3
abc
--10ee6686-40f8-49fa-8075-379e338a9eac
Content-Disposition: form-data; name="password"
Content-Transfer-Encoding: binary
Content-Length: 3
123
--10ee6686-40f8-49fa-8075-379e338a9eac--
正文分为三个部分。
客户端
Blog{body='{"code":200,"msg":"OK","des":"REGISTER","data":"REGISTER:--10ee6686-40f8-49fa-8075-379e338a9eac\r\nContent-Disposition: form-data; name=\"fileName\"; filename=\"build.gradle\"\r\nContent-Type: text/plain\r\nContent-Length: 495\r\n\r\napply plugin: 'java-library'\r\n\r\ndependencies {\r\n implementation project(':retrofit')\r\n implementation project(':retrofit-mock')\r\n implementation project(':retrofit-converters:moshi')\r\n implementation project(':retrofit-converters:gson')\r\n implementation project(':retrofit-converters:simplexml')\r\n implementation project(':retrofit-adapters:rxjava')\r\n implementation deps.mockwebserver\r\n implementation deps.guava\r\n implementation deps.jsoup\r\n compileOnly deps.findBugsAnnotations\r\n}\r\n\r\n--10ee6686-40f8-49fa-8075-379e338a9eac\r\nContent-Disposition: form-data; name=\"username\"\r\nContent-Transfer-Encoding: binary\r\nContent-Length: 3\r\n\r\nabc\r\n--10ee6686-40f8-49fa-8075-379e338a9eac\r\nContent-Disposition: form-data; name=\"password\"\r\nContent-Transfer-Encoding: binary\r\nContent-Length: 3\r\n\r\n123\r\n--10ee6686-40f8-49fa-8075-379e338a9eac--\r\n"}'}
多文件上传@PartMap
@Multipart
@POST("retrofit/register")
Call<Blog> registerUser(@PartMap Map<String, RequestBody> params, @Part("password") RequestBody password);
RequestBody requestBody1 = RequestBody.create(MediaType.parse("text/plain"), new File("samples/build.gradle"));
RequestBody requestBody2 = RequestBody.create(MediaType.parse("text/plain"), new File("samples/src/main/java/com/example/test/BodyTest.java"));
HashMap<String, RequestBody> hashMap = new HashMap<>();
hashMap.put("part1", requestBody1);
hashMap.put("part2", requestBody2);
Call<Blog> call = blogService.registerUser(hashMap, RequestBody.create(null, "xyz"));
测试结果:
POST http://localhost:3434/retrofit/register?null HTTP/1.1
Accept-Encoding: gzip
Connection: keep-alive
Content-Length: 3696
Content-Type: multipart/form-data; boundary=b7d6f01e-861c-463e-aef1-fb3e00ad64f4
Host: localhost:3434
User-Agent: okhttp/3.14.9
--b7d6f01e-861c-463e-aef1-fb3e00ad64f4
Content-Disposition: form-data; name="part1"
Content-Transfer-Encoding: binary
Content-Type: text/plain
Content-Length: 495
apply plugin: 'java-library'
dependencies {
implementation project(':retrofit')
implementation project(':retrofit-mock')
implementation project(':retrofit-converters:moshi')
implementation project(':retrofit-converters:gson')
implementation project(':retrofit-converters:simplexml')
implementation project(':retrofit-adapters:rxjava')
implementation deps.mockwebserver
implementation deps.guava
implementation deps.jsoup
compileOnly deps.findBugsAnnotations
}
--b7d6f01e-861c-463e-aef1-fb3e00ad64f4
Content-Disposition: form-data; name="part2"
Content-Transfer-Encoding: binary
Content-Type: text/plain
Content-Length: 2664
package com.example.test;
import okhttp3.HttpUrl;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Converter;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.http.*;
import javax.annotation.Nullable;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
public class BodyTest {
interface BlogService {
@GET("retrofit/all.do")
Call<QueryTest.Blog> getAll();
@GET("retrofit/user/{username}")
Call<QueryTest.Blog> getByName(@Path("username") String username);
@GET("retrofit/query")
Call<QueryTest.Blog> query(@Query("id") String id);
@POST("retrofit/add")
Call<Blog> addBlog(@Body Blog user);
}
private static class Blog {
private String body;
public Blog(String body) {
this.body = body;
}
@Override
public String toString() {
return "Blog{" +
"body='" + body + '\'' +
'}';
}
}
public static void main(String[] args) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(HttpUrl.get("http://localhost:3434/"))
.addConverterFactory(DataAdapter.FACTORY)
.build();
BlogService blogService = retrofit.create(BlogService.class);
try {
// FIXME: 2020/11/17 �?要自定义参数,工厂,把Blog转化为RequestBody
Call<Blog> call = blogService.addBlog(new Blog("I am clint Blog!"));
Response<Blog> response = call.execute();
Blog blog = response.body();
System.out.println(blog);
} catch (IOException e) {
e.printStackTrace();
}
}
static final class DataAdapter implements Converter<ResponseBody, Blog> {
static final Factory FACTORY =
new Factory() {
@Override
public @Nullable
Converter<ResponseBody, ?> responseBodyConverter(
Type type, Annotation[] annotations, Retrofit retrofit) {
if (type == QueryTest.Blog.class) return new QueryTest.DataAdapter();
return null;
}
};
@Override
public Blog convert(ResponseBody responseBody) throws IOException {
String body = responseBody.string();
// FIXME: 2020/11/16 数据处理
return new Blog(body);
}
}
}
--b7d6f01e-861c-463e-aef1-fb3e00ad64f4
Content-Disposition: form-data; name="password"
Content-Transfer-Encoding: binary
Content-Length: 3
xyz
--b7d6f01e-861c-463e-aef1-fb3e00ad64f4--
客户端:
Blog{body='{"code":200,"msg":"OK","des":"REGISTER","data":"REGISTER:--b7d6f01e-861c-463e-aef1-fb3e00ad64f4\r\nContent-Disposition: form-data; name=\"part1\"\r\nContent-Transfer-Encoding: binary\r\nContent-Type: text/plain\r\nContent-Length: 495\r\n\r\napply plugin: 'java-library'\r\n\r\ndependencies {\r\n implementation project(':retrofit')\r\n implementation project(':retrofit-mock')\r\n implementation project(':retrofit-converters:moshi')\r\n implementation project(':retrofit-converters:gson')\r\n implementation project(':retrofit-converters:simplexml')\r\n implementation project(':retrofit-adapters:rxjava')\r\n implementation deps.mockwebserver\r\n implementation deps.guava\r\n implementation deps.jsoup\r\n compileOnly deps.findBugsAnnotations\r\n}\r\n\r\n--b7d6f01e-861c-463e-aef1-fb3e00ad64f4\r\nContent-Disposition: form-data; name=\"part2\"\r\nContent-Transfer-Encoding: binary\r\nContent-Type: text/plain\r\nContent-Length: 2664\r\n\r\npackage com.example.test;\r\n\r\nimport okhttp3.HttpUrl;\r\nimport okhttp3.ResponseBody;\r\nimport retrofit2.Call;\r\nimport retrofit2.Converter;\r\nimport retrofit2.Response;\r\nimport retrofit2.Retrofit;\r\nimport retrofit2.http.*;\r\n\r\nimport javax.annotation.Nullable;\r\nimport java.io.IOException;\r\nimport java.lang.annotation.Annotation;\r\nimport java.lang.reflect.Type;\r\n\r\npublic class BodyTest {\r\n interface BlogService {\r\n @GET(\"retrofit/all.do\")\r\n Call<QueryTest.Blog> getAll();\r\n\r\n @GET(\"retrofit/user/{username}\")\r\n Call<QueryTest.Blog> getByName(@Path(\"username\") String username);\r\n\r\n @GET(\"retrofit/query\")\r\n Call<QueryTest.Blog> query(@Query(\"id\") String id);\r\n\r\n @POST(\"retrofit/add\")\r\n Call<Blog> addBlog(@Body Blog user);\r\n }\r\n\r\n\r\n private static class Blog {\r\n private String body;\r\n\r\n public Blog(String body) {\r\n this.body = body;\r\n }\r\n\r\n @Override\r\n public String toString() {\r\n return \"Blog{\" +\r\n \"body='\" + body + '\\'' +\r\n '}';\r\n }\r\n }\r\n\r\n public static void main(String[] args) {\r\n\r\n Retrofit retrofit = new Retrofit.Builder()\r\n .baseUrl(HttpUrl.get(\"http://localhost:3434/\"))\r\n .addConverterFactory(DataAdapter.FACTORY)\r\n .build();\r\n\r\n BlogService blogService = retrofit.create(BlogService.class);\r\n try {\r\n // FIXME: 2020/11/17 �?要自定义参数,工厂,把Blog转化为RequestBody\r\n Call<Blog> call = blogService.addBlog(new Blog(\"I am clint Blog!\"));\r\n Response<Blog> response = call.execute();\r\n Blog blog = response.body();\r\n System.out.println(blog);\r\n } catch (IOException e) {\r\n e.printStackTrace();\r\n }\r\n\r\n\r\n }\r\n\r\n static final class DataAdapter implements Converter<ResponseBody, Blog> {\r\n static final Factory FACTORY =\r\n new Factory() {\r\n @Override\r\n public @Nullable\r\n Converter<ResponseBody, ?> responseBodyConverter(\r\n Type type, Annotation[] annotations, Retrofit retrofit) {\r\n if (type == QueryTest.Blog.class) return new QueryTest.DataAdapter();\r\n return null;\r\n }\r\n };\r\n\r\n @Override\r\n public Blog convert(ResponseBody responseBody) throws IOException {\r\n String body = responseBody.string();\r\n // FIXME: 2020/11/16 数据处理\r\n return new Blog(body);\r\n }\r\n }\r\n}\r\n\r\n--b7d6f01e-861c-463e-aef1-fb3e00ad64f4\r\nContent-Disposition: form-data; name=\"password\"\r\nContent-Transfer-Encoding: binary\r\nContent-Length: 3\r\n\r\nxyz\r\n--b7d6f01e-861c-463e-aef1-fb3e00ad64f4--\r\n"}'}