What are RESTful APIs?
Summarize the REST ful API in one sentence: When we use the HTTP protocol for data transmission, we should abide by the rules of HTTP, including request method, resource type, Uri format, etc..
Author: Drew_MyINTYRE
Link: https://www.jianshu.com/p/3ec1b6a99612
Why set the request in the form of (interface + annotation)?
迪米特法则
: Also known as the principle of minimum knowledge, that is, to minimize unnecessary dependencies between modules, that is, to reduce the coupling between modules.
门面模式
: A design pattern based on Demeter's law, which aims to control the access of complex modules/systems more uniformly. For example: Now we want to do a function of getting pictures, first get from the local cache, if there is no cache, get from the network and then add it to the local cache, if you don’t do anything, then you have to write the cache logic every time you get a picture, the more you write, the higher the possibility of errors. At this time, the cache function can be encapsulated through the facade mode, and only one entry for obtaining pictures is exposed, so that the caller can use it more conveniently and with higher security. In fact, functional programming is also a product of the facade model.
Why Design Through the Facade Pattern ApiService
?
Retrofit
The general process of making a request with is as follows:
interface ApiService {
/**
* 获取首页数据
*/
@GET("/article/list/{page}/json")
suspend fun getHomeList(@Path("page") pageNo: Int)
: ApiResponse<ArticleBean>
}
//构建Retrofit
val retrofit = Retrofit.Builder().build()
//创建ApiService实例
val apiService =retrofit.create(ApiService::class.java)
//发起请求(这里用的是suspend会自动发起请求,Java中可通过返回的call请求)
apiService.getHomeList(1)
We all know Retrofit
that is just OkHttp
an encapsulation of . If you use it directly , you have to do a lot of tedious work when constructing it. The most terrible thing is that Request may be constructed in multiple places (ViewModel, Repository...), and the more scattered the writing, the more difficult it is to troubleshoot errors OkHttp
. Request
Retrofit attaches all the necessary information required by the Request to the method in the form of annotations (it is still an abstract method, try to remove all redundant information), as a user, only needs to call the corresponding method to realize the request. As for how to parse, construct, and initiate requests, Retrofit will do internal processing, and the caller does not want or need to know, so Retrofit helps the caller shield some useless information through the facade mode, and only exposes the only entry, so that the caller can focus more on business development . This mode is also used in our commonly used Room and GreenDao.
Dynamic proxy is not a tool
The Retrofit build looks like this:
Retrofit.Builder()
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(ApiConstants.BASE_URL)
.build()
Very typical builder mode, you can configure OkHttp
, Gson
, RxJava
and so on, and finally build()
do the build operation through , follow build()
the code:
# Retrofit.class
public Retrofit build() {
// 1. CallAdapter 工厂集合
List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));
// 2. Converter 工厂集合
List<Converter.Factory> converterFactories =
new ArrayList<>(
1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());
converterFactories.add(new BuiltInConverters());
converterFactories.addAll(this.converterFactories);
converterFactories.addAll(platform.defaultConverterFactories());
return new Retrofit(
callFactory,
baseUrl,
unmodifiableList(converterFactories),
unmodifiableList(callAdapterFactories),
callbackExecutor,
validateEagerly);
}
What is a dynamic proxy?
A dynamic proxy can dynamically create an instance of at runtime interface
. The proxy class we Proxy.newProxyInstance
generate through will be intercepted by the method when we call any method of the interface. InvocationHandler#invoke
At the same time, in this method, we can get the parameters passed in, and then do corresponding processing according to the parameter value.
Every time CRUD will do a manual reporting operation, this is obviously a template code, how to solve it? Let's look at the dynamic agent:
interface ToDo {
fun buyHouse()
}
/**
* 中介
*/
class Middlemen(private val any: Any) : InvocationHandler {
override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {
return method?.invoke(any, *(args ?: arrayOfNulls<Any>(0)))
}
}
/**
* 买家
*/
class Buyer : ToDo {
override fun buyHouse() {
print("中介,请帮我买房子")
}
}
// 最后利用代理对象实现购房的动作:
fun main(args: Array<String>) {
val toDo: ToDo = Buyer()
val dynamicMid = Proxy.newProxyInstance(
toDo.javaClass.classLoader, toDo.javaClass.interfaces,
Middlemen(toDo)
) as ToDo
dynamicMid.buyHouse()
}
The most important part of dynamic proxy lies in the realization of proxy object InvocationHandler
and rewriting invoke
method. When the proxy object represents the request of the delegator, no matter how many requirements there are, when the proxy is executed, it will enter invoke()
the method. This is the key point, circled and tested later.
Dynamic proxy gets ApiService
# Retrofit.class
public <T> T create(final Class<T> service) {
validateServiceInterface(service);
return (T)
Proxy.newProxyInstance(
service.getClassLoader(),
new Class<?>[] {service},
new InvocationHandler() {
private final Platform platform = Platform.get();
private final Object[] emptyArgs = new Object[0];
@Override
public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
// ⚠️
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
args = args != null ? args : emptyArgs;
// ⚠️
return platform.isDefaultMethod(method)
? platform.invokeDefaultMethod(method, service, proxy, args)
: loadServiceMethod(method).invoke(args);
}
});
}
invoke() This is a proxy method that invokes
ApiService中
any method and executes it, where the parametersmethod
andargs
representApiService
the corresponding methods and parameters. There is one in the return valueisDefaultMethod
, here if it is the default method of Java8 to execute directly, after all, we only needApiService
the method in the proxy. After repeated screening, the heavy responsibility finally fell onloadServiceMethod
, which is alsoRetrofit
the core method in , let's follow it up:
loadServiceMethod(method)
It mainly preliminarily processes the information in the network request method. When we create api service specific interface, we will add annotations ( , , ...), @GET
parameters @POST
( @PUT
, @Path
... @Query
), etc. This method is to analyze the annotations, parameters, etc. in the interface. After analyzing the interface, a RequestFactory
request factory object is generated, and RequestFactory
one is created using this object CallAdapter
.
CallAdapter
: Adapter, we defineAPI Service
the return value of the method by defaultCall
as type, but sometimes we customize the return type, for example, what should we do when weRxJava
combine with and returnObservable
or type? The role of is to help developers adapt to these return types. You can return any type of data you define through .Single
CallAdapter
CallAdapter#adapt
# Retrofit.class
ServiceMethod<?> loadServiceMethod(Method method) {
ServiceMethod<?> result = serviceMethodCache.get(method);
if (result != null) return result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = ServiceMethod.parseAnnotations(this, method);
serviceMethodCache.put(method, result);
}
}
return result;
}
Creating an object is achieved ServiceMethod
through its static method , followed by this method:parseAnnotations
# HttpServiceMethod.class
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
Retrofit retrofit, Method method, RequestFactory requestFactory) {
boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
boolean continuationWantsResponse = false;
boolean continuationBodyNullable = false;
Annotation[] annotations = method.getAnnotations();
Type adapterType;
//1.获取adapterType,默认为method返回值类型
if (isKotlinSuspendFunction) {
Type[] parameterTypes = method.getGenericParameterTypes();
Type responseType =
Utils.getParameterLowerBound(
0, (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
// Unwrap the actual body type from Response<T>.
responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
continuationWantsResponse = true;
} else {
}
adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
} else {
adapterType = method.getGenericReturnType();
}
//2.创建CallAdapter
CallAdapter<ResponseT, ReturnT> callAdapter =
createCallAdapter(retrofit, method, adapterType, annotations);
Type responseType = callAdapter.responseType();
//3.创建responseConverter
Converter<ResponseBody, ResponseT> responseConverter =
createResponseConverter(retrofit, method, responseType);
okhttp3.Call.Factory callFactory = retrofit.callFactory;
//4.创建HttpServiceMethod类型具体实例
if (!isKotlinSuspendFunction) {
return new HttpServiceMethod.CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
}
//兼容kotlin suspend方法
else if (continuationWantsResponse) {
//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
return (HttpServiceMethod<ResponseT, ReturnT>)
new HttpServiceMethod.SuspendForResponse<>(
requestFactory,
callFactory,
responseConverter,
(CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
} else {
//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
return (HttpServiceMethod<ResponseT, ReturnT>)
new HttpServiceMethod.SuspendForBody<>(
requestFactory,
callFactory,
responseConverter,
(CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
continuationBodyNullable);
}
}
# ServiceMethod.class
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
// ⚠️
RequestFactory requestFactory =
RequestFactory.parseAnnotations(retrofit, method);
Type returnType = method.getGenericReturnType();
...
// ⚠️
return HttpServiceMethod.parseAnnotations(retrofit,
method, requestFactory);
}
# HttpServiceMethod.class
@Override
final @Nullable ReturnT invoke(Object[] args) {
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
return adapt(call, args);
}
OkHttpCall 其实是对 OkHttp 中的realCall 进行了一层包装,realCall 是如何创建的?
First create
OkHttpClient
andRequest
objects,Request
then create anRealCall
object, use it to perform asynchronousenqueue
or synchronousexecute
operations to send requests, and listen to the feedback of request failure or successCallback
.
val okHttpClient = OkHttpClient()
val request: Request = Request.Builder()
.url("https://cn.bing.com/")
.build()
okHttpClient.newCall(request).enqueue(object : Callback{
override fun onFailure(call: Call, e: IOException) {
}
override fun onResponse(call: Call, response: Response) {
}
})
There are three main classes that need to be explained here: OkHttpClient
, Request
and RealCall
.
OkHttpClient
: Equivalent to the configuration center, it can be used to send HTTP requests and read their responses. It has many configurations, such as connectTimeout: the timeout period for establishing a connection (TCP or TLS), readTimeout: the timeout period from initiating a request to reading the response data, Dispatcher: a scheduler, used to schedule network requests initiated by the background, and so on. There are other configurations to view the source code.Request
: A request class that mainly sets the network request Url, request method (GET, POST...), request header, and request body.RealCall
: is returnedRealCall
by the method, and is one of the core classes for executing requests. It is used to connect the application layer and the network layer, that is, combine and to initiate asynchronous and synchronous requests.newCall(Request)
OkHttp
OkHttp
OkHttpClient
Request
loadServiceMethod(method).invoke(args);
, loadServiceMethod(method)
what is returned is HttpServiceMethod
that invoke()
an instance of is created OkHttpCall
, which is actually OkHttp
a series of operations on .
Let's take a look at OkHttpCall
the details of the network request in :
@Override
public void enqueue(final Callback<T> callback) {
Objects.requireNonNull(callback, "callback == null");
okhttp3.Call call;
synchronized (this) {
...
if (call == null && failure == null) {
try {
call = rawCall = createRawCall();
}
}
...
call.enqueue(
new okhttp3.Callback() {
@Override
public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
Response<T> response;
try {
//解析请求返回值
response = parseResponse(rawResponse);
}
...
try {
callback.onResponse(OkHttpCall.this, response);
}
@Override
public void onFailure(okhttp3.Call call, IOException e) {
callFailure(e);
}
private void callFailure(Throwable e) {
try {
callback.onFailure(OkHttpCall.this, e);
}
}
});
}
Analyze the source code above:
1. First, a createRawCall()
method is called internally to create rawCall
, which rawCall
actually refers to Okhttp3
the call of , which is OkHttp
a scheduler for making network request calls;
2. After creating the OkHttp call, start calling enqueue
to make an asynchronous request, and find that the callback that responds in the asynchronous request belongs to okhttp3.Callback
, and the returned results are also all okhttp3.Response
. From here, you can roughly know that retrofit
the network request is actually implemented OkHttp
by .
3. okhttp3.Response
This response is not convenient for developers to use directly, so retrofit
after receiving the result, a new round of parsing is performed on the response result response = parseResponse(rawResponse)
and Response
returned to the developer in the form of an object.
In addition, there is the last line: adapt(call, args)
; Its interior is actually CallAdapted
called by an adapter, as follows:
protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
return callAdapter.adapt(call);
}
adapt
is an abstract method, which passes in the one just created above OkHttpCall
as a parameter. Adapt means adapter, the meaning of adapter, then we can guess, OkHttpCall
is it passed in for the purpose of OkHttpCall
re-adapting? However, OkHttpCall
it is already possible to make network requests, why do you need to use it CallAdapted
for re-adaptation? ⁉️This question should be written down first, and answered later.
From the above analysis, we know that Retrofit
the reason why it is easy to call is because the annotations such as @GET
, @POST
, @Path
and request parameters are parsed internally, so that developers only need to care about whether their newly added request methods meet the specifications. The most important thing to do with the above-mentioned dynamic agent is to create it CallAdapted
. CallAdapted
In fact, it is just an adapter. What you need to know is what it adapts to? Do you still remember that we raised a CallAdapted
question about : let's see how he was created first? ⁉️
abstract class HttpServiceMethod<ResponseT, ReturnT> extends ServiceMethod<ReturnT> {
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
Retrofit retrofit, Method method, RequestFactory requestFactory) {
Type adapterType;
if (isKotlinSuspendFunction) {
Type[] parameterTypes = method.getGenericParameterTypes();
Type responseType =
Utils.getParameterLowerBound(
0, (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
// Unwrap the actual body type from Response<T>.
responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
continuationWantsResponse = true;
}
adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
} else {
adapterType = method.getGenericReturnType();
}
CallAdapter<ResponseT, ReturnT> callAdapter =
createCallAdapter(retrofit, method, adapterType, annotations);
...
Converter<ResponseBody, ResponseT> responseConverter =
createResponseConverter(retrofit, method, responseType);
okhttp3.Call.Factory callFactory = retrofit.callFactory;
if (!isKotlinSuspendFunction) {
return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
}
public CallAdapter<?, ?> nextCallAdapter(
@Nullable CallAdapter.Factory skipPast, Type returnType, Annotation[] annotations) {
int start = callAdapterFactories.indexOf(skipPast) + 1;
for (int i = start, count = callAdapterFactories.size(); i < count; i++) {
CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
if (adapter != null) {
return adapter;
}
}
CallAdapter
According to the types of returnType
and , search from the factory to return the corresponding network request adapter, where returnType refers to the return value type of the method in the network request interface, such as , etc. It refers to the annotation type, such as , , etc.annotations
callAdapterFactories
Call
Observable
annotations
@GET
@POST
An adapter factory is also mentioned here callAdapterFactories
. The difference CallAdapter
is that it is queried from this factory. If there is a search, it must be added. CallAdapter
How is the adapter added to the factory class? ⁉️This callAdapterFactories
variable belongs to Retrofit
the class, and it is found that it is Retrofit
passed in by the constructor, that is, it is Retrofit
assigned a value when it is initialized.
Retrofit
The initialization of is created by a builder pattern. In the method Retrofit
of build()
, the addition of the adapter factory to its adapter is found:
public Retrofit build() {
...
Executor callbackExecutor = this.callbackExecutor;
if (callbackExecutor == null) {
callbackExecutor = platform.defaultCallbackExecutor();
}
List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
//添加适配器callAdapter
callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));
...
return new Retrofit(
callFactory,
baseUrl,
unmodifiableList(converterFactories),
unmodifiableList(callAdapterFactories),
callbackExecutor,
validateEagerly);
}
platform.defaultCallAdapterFactories
It refers to a default adapter factory on the Android platform. When we don't use a custom adapter factory, we add this default factory. The custom adapter factory is mentioned here. In fact, when we use Retrofit
, sometimes it will be RxJava
combined with . For example, when creating Retrofit
, addCallAdapterFactory
it will also RxJava2CallAdapterFactory
be added to callAdapterFactories
.
mRetrofit = new Retrofit.Builder()
.client(okHttpClient)
.baseUrl(baseUrl)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
The purpose of adding the call adapter factory is to support Call
return types of service methods other than , such as support Observable
, Single
return type, etc. When callAdapterFactories
the aggregator adds a default adapter factory, a parameter is also passed in callbackExecutor
, callbackExecutor
which is a default thread scheduler on the Java8 or Android platform. Its function involves a thread switching problem, that is, how to Retrofit
switch the sub-thread to the main thread that needs to be analyzed later? ⁉️
When the above source code was created CallAdapter
, an object was also created at the same time, that is Converter
.
Converter<ResponseBody, ResponseT> responseConverter = createResponseConverter(retrofit, method, responseType);
Converter 数据装换器。它的添加和获取方式和 CallAdapter 类似。在Retrofit 初始化时添加到 List<Converter.Factory> 工厂集合,根据类型 Type 取出不同的 Converter,对返回的网络响应,做出数据的转换,例如转换成实体类。基本逻辑与 CallAdapter 类似。
mRetrofit = new Retrofit.Builder()
.client(mOkHttpClient)
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create()
.build();
总结一下上面讲的:
1. At the beginning, loadServiceMethod
the method is called in the dynamic proxy, and the annotations, parameters, header information, etc. in the interface method are parsed;
2. According to the return type of the interface method, query from the adapter factory collection, generate the corresponding adapter, and CallAdapter
distinguish whether RxJava
it Observable
is Single
or Call
other types . Take out the data converter in the same way ;Retrofit
addCallAdapterFactory()
Converter
3. Using the method generated above CallAdapter
, call invoke
the method to create OkHttpCall
the object, that is, use to make asynchronous or synchronous network requests for the request information OkHttp
, and perform entity class conversion on the response results;
4. After creating , use OkHttpCall
the adapter queried above to call and return the or object of .CallAdapter
adapt
RxJava
Observable
Single
Call
How does Retrofit switch the child thread to the main thread?
When adding the default adapter factory defaultCallAdapterFactories
, it will callbackExecutor
be used as a parameter, then its specific implementation is also in this default adapter factory. Let's see callbackExecutor
what's going on inside.
static final class ExecutorCallbackCall<T> implements Call<T> {
final Executor callbackExecutor;
final Call<T> delegate;
...
@Override
public void enqueue(final Callback<T> callback) {
delegate.enqueue(
new Callback<T>() {
@Override
public void onResponse(Call<T> call, final Response<T> response) {
callbackExecutor.execute(
() -> {
if (delegate.isCanceled()) {
// Emulate OkHttp's behavior of throwing/delivering an IOException on
// cancellation.
callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
} else {
callback.onResponse(ExecutorCallbackCall.this, response);
}
});
}
@Override
public void onFailure(Call<T> call, final Throwable t) {
callbackExecutor.execute(() -> callback.onFailure(ExecutorCallbackCall.this, t));
}
});
}
Learned in the above code, callbackExecutor
that is Executor
, a thread scheduler. enqueue
An asynchronous network request is executed in the implementation of Call , and a thread is also executed in the response delegate.enqueue
of the request . Here is a question, why call another thread in an asynchronous request? We know that is a thread scheduler, so what exactly does it implement internally?onResponse
onFailure
Executor
callbackExecutor
The default callbackExecutor
is created in Retrofit
the initialization of callbackExecutor = platform.defaultCallbackExecutor()
;
static final class Android extends Platform {
@Override
public Executor defaultCallbackExecutor() {
return new MainThreadExecutor();
}
static final class MainThreadExecutor implements Executor {
private final Handler handler = new Handler(Looper.getMainLooper());
@Override
public void execute(Runnable r) {
handler.post(r);
}
}
}
}
platform
It is an Android platform. defaultCallbackExecutor
It actually calls new MainThreadExecutor() internally. It is clear that handler.post(r) uses Handler internally to throw the response to the main thread. This is Retrofit
the core of switching the child thread to the main thread.
Why does Retrofit use dynamic proxy?
What are the advantages of dynamic proxies? Instead of exposing the real principal, different agents are created according to different delegations, and things are done through the agent.
So what shortcomings does Retrofit make up for OkHttp?
When OkHttp is used, is the configuration of request parameters very cumbersome, especially when some forms are submitted, which are smelly and long, and Retrofit makes up for this shortcoming, using @GET, @POST, @Path, @Body and other annotations and some parameters to easily construct requests.
Dynamic proxies come into play when Retrofit creates different interfaces. Whenever different interface methods are executed, the dynamic proxy will intercept the request, analyze the annotations and parameters in the interface, construct different Requests, and finally hand it over to OkHttp for actual execution.
Retrofit combines dynamic proxies without caring about real interface methods, and manages standardized interfaces in a unified manner, parses annotations and parameters in a unified way, and stitches them into requests.
Summarize:
Retrofit
In fact, it is OkHttp
the encapsulation class of , and internal network requests still rely on it OkHttp
, so Retrofit
what has changed after encapsulation?
The interface request is more convenient, and annotations @GET, @POST, @Path, @Body, etc. form a network request;
By default, it helps developers parse responseBody, and you can also customize the parsing strategy;
Retrofit helps developers to switch threads;
Retrofit gives developers more permissions to customize and adapt network requests.
Follow me for more knowledge or contribution