前言
继Glide篇,RxJava2篇,OkHttp篇后,这次轮到了Retrofit。相比前面几篇,这篇要稍短一些,因为Retrofit库本身代码量就不多,它作为一款上层网络框架,主要作用是包装底层网络框架,完成各种数据的转化与适配工作。虽然代码不多,但Retrofit依然用到了大量的设计模式,具有非常好的扩展性,与RxJava2、Gson、OkHttp等主流库可以无缝对接,这也正是Retrofit的魅力所在。本次的分析要点如下:
- retrofit中的动态代理与完整流程(Proxy、ServiceMethod、OkHttpCall)
- retrofit中的转换器与适配器(Converter、CallAdapter)
Retrofit中的动态代理与完整流程
先从Retrofit的一个简单例子入手
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| Retrofit.Builder builder = new Retrofit.Builder(); Retrofit retrofit = return builder .baseUrl(url) .client(okHttpClient) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create()) .build(); GankApis gankService = retrofit.create(GankApis.class); public interface GankApis { String HOST = "http://gank.io/api/"; @GET("random/data/福利/{num}") Flowable<GankHttpResponse<List<GankItemBean>>> getRandomGirl(@Path("num") int num); }
|
Retrofit的使用还是挺简单的,根据约定的API定义接口,然后通过Builder模式创建Retrofit对象,最后通过create
方法生成接口的一个代理对象,通过这个代理对象就可以调用我们定义好的接口方法获取数据了。Builder模式没有什么好说的,其中addCallAdapter
和Converter
的作用后面再谈,那么重点就在这个create
方法上了,进入源码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| public <T> T create(final Class<T> service) { Utils.validateServiceInterface(service); if (validateEagerly) { eagerlyValidateMethods(service); } return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { private final Platform platform = Platform.get(); @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } if (platform.isDefaultMethod(method)) { return platform.invokeDefaultMethod(method, service, proxy, args); } ServiceMethod<Object, Object> serviceMethod = (ServiceMethod<Object, Object>) loadServiceMethod(method); OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args); return serviceMethod.callAdapter.adapt(okHttpCall); } }); } ServiceMethod<?, ?> loadServiceMethod(Method method) { ServiceMethod<?, ?> result = serviceMethodCache.get(method); if (result != null) return result; synchronized (serviceMethodCache) { result = serviceMethodCache.get(method); if (result == null) { result = new ServiceMethod.Builder<>(this, method).build(); serviceMethodCache.put(method, result); } } return result; }
|
可以看到这里用到了Java自带的Proxy
类,这个类提供了动态代理的支持,newProxyInstance
方法会生成当前接口的代理类,并返回一个该代理类的实例。正是动态代理技术使我们在代理对象的方法触发时有机会添加额外的逻辑,Retrofit也是利用了这一点,把解析注解,生成request,转化response等一系列每次请求都会涉及的重复工作都封装到了这个代理对象中,我们只需要专注于API接口的编写即可,这也是AOP(面向切面编程)思想的一种体现。
每当我们调用代理对象的方法时,都会触发InvocationHandler
的invoke
方法,它会把我们这次调用的方法method和参数args都回调过来,拿到method后首先经过loadServiceMethod
方法将它转化成一个serviceMethod对象,转化时会以method为key优先尝试从缓存中获取serviceMethod,进入ServiceMethod
这个类的源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| final class ServiceMethod<R, T> { ServiceMethod(Builder<R, T> builder) { this.callFactory = builder.retrofit.callFactory(); this.callAdapter = builder.callAdapter; this.baseUrl = builder.retrofit.baseUrl(); this.responseConverter = builder.responseConverter; this.httpMethod = builder.httpMethod; this.relativeUrl = builder.relativeUrl; this.headers = builder.headers; this.contentType = builder.contentType; this.hasBody = builder.hasBody; this.isFormEncoded = builder.isFormEncoded; this.isMultipart = builder.isMultipart; this.parameterHandlers = builder.parameterHandlers; } Request toRequest(@Nullable Object... args) throws IOException { RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers, contentType, hasBody, isFormEncoded, isMultipart); ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers; int argumentCount = args != null ? args.length : 0; if (argumentCount != handlers.length) { throw new IllegalArgumentException("Argument count (" + argumentCount + ") doesn't match expected count (" + handlers.length + ")"); } for (int p = 0; p < argumentCount; p++) { handlers[p].apply(requestBuilder, args[p]); } return requestBuilder.build(); } R toResponse(ResponseBody body) throws IOException { return responseConverter.convert(body); } private void parseMethodAnnotation(Annotation annotation) { if (annotation instanceof DELETE) { parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false); } else if (annotation instanceof GET) { parseHttpMethodAndPath("GET", ((GET) annotation).value(), false); } } private ParameterHandler<?> parseParameterAnnotation( int p, Type type, Annotation[] annotations, Annotation annotation) { if (annotation instanceof Url) { } else if (annotation instanceof Path) { gotPath = true; Path path = (Path) annotation; String name = path.value(); validatePathName(p, name); Converter<?, String> converter = retrofit.stringConverter(type, annotations); return new ParameterHandler.Path<>(name, converter, path.encoded()); } else if (annotation instanceof Query) { } } }
|
为了篇幅省略了大量代码,因为该类中的大部分方法都是parseXXX
用来做当前method及其参数注解的解析工作,并将解析后的结果存储到自己的成员变量中等待后续使用。除此之外还提供了两个重要方法,toRequest
和toResponse
,我们请求时使用的request
和最后得到的response
就是由这两个方法返回的。
在toRequest
中,请求时用到的每个参数都会交给一个ParameterHandler
做处理,它会解析参数的注解(Path,Header,Query,Part等等),并根据注解类型去调用RequestBuilder
提供的不同的方法(addHeader、addQueryParam、addPathParam等等)来逐步完成request的构建。
在toResponse
中则相对简单,直接将原始返回报文response.body经过converter处理成我们需要的类型的对象。
继续回到Retrofit类,在获得serviceMethod后,将它和请求参数一起作为参数创建了一个OkHttpCall对象,OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
如果你之前了解过OkHttp的源码,对Call这个类型一定很熟悉,它是一个已经就绪了的请求任务。OkHttpCall在这里作为一个装饰类,持有真正用做请求的rawCall
,而这个rawCall对象就是通过传入的serviceMethod的toRequest
方法生成的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
| private okhttp3.Call createRawCall() throws IOException { Request request = serviceMethod.toRequest(args); okhttp3.Call call = serviceMethod.callFactory.newCall(request); if (call == null) { throw new NullPointerException("Call.Factory returned null."); } return call; } @Override public void enqueue(final Callback<T> callback) { checkNotNull(callback, "callback == null"); okhttp3.Call call; Throwable failure; synchronized (this) { if (executed) throw new IllegalStateException("Already executed."); executed = true; call = rawCall; failure = creationFailure; if (call == null && failure == null) { try { call = rawCall = createRawCall(); } catch (Throwable t) { failure = creationFailure = t; } } } if (failure != null) { callback.onFailure(this, failure); return; } if (canceled) { call.cancel(); } call.enqueue(new okhttp3.Callback() { @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) throws IOException { Response<T> response; try { response = parseResponse(rawResponse); } catch (Throwable e) { callFailure(e); return; } callSuccess(response); } @Override public void onFailure(okhttp3.Call call, IOException e) { try { callback.onFailure(OkHttpCall.this, e); } catch (Throwable t) { t.printStackTrace(); } } private void callFailure(Throwable e) { try { callback.onFailure(OkHttpCall.this, e); } catch (Throwable t) { t.printStackTrace(); } } private void callSuccess(Response<T> response) { try { callback.onResponse(OkHttpCall.this, response); } catch (Throwable t) { t.printStackTrace(); } } }); } @Override public Response<T> execute() throws IOException { okhttp3.Call call; synchronized (this) { if (executed) throw new IllegalStateException("Already executed."); executed = true; if (creationFailure != null) { if (creationFailure instanceof IOException) { throw (IOException) creationFailure; } else { throw (RuntimeException) creationFailure; } } call = rawCall; if (call == null) { try { call = rawCall = createRawCall(); } catch (IOException | RuntimeException e) { creationFailure = e; throw e; } } } if (canceled) { call.cancel(); } return parseResponse(call.execute()); } Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException { ResponseBody rawBody = rawResponse.body(); ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody); try { T body = serviceMethod.toResponse(catchingBody); return Response.success(body, rawResponse); } catch (RuntimeException e) { catchingBody.throwIfCaught(); throw e; } }
|
上面的代码虽然比较长,但其实只截取了四个比较重要的方法,createRawCall()
生成rawCall,enqueue(Callback<T> callback)
执行异步请求,execute()
执行同步请求,parseResponse(okhttp3.Response rawResponse)
将请求结果经converter处理后返回。OkHttpCall将实际请求交给rawCall来完成,调用了rawCall的相应方法,而同步请求与异步请求已经进入到OkHttp
这个库的源码范畴了,这里就不再深入了,感兴趣的话可以看我之前写的OkHttp源码分析
再次回到Retrofit类,最后将OkHttpCall经过callAdapter
的adapter
方法处理后的结果,作为本次方法调用的返回值返回return serviceMethod.callAdapter.adapt(okHttpCall);
,到此整个流程结束。对于文章最初的例子而言,此刻将得到一个Flowable<GankHttpResponse<List<GankItemBean>>>
对象。
Retrofit中的转换器与适配器
上面虽然走完了整个retrofit的流程,但是有两个关键点还没有细说,其一是toResponse中用到的Converter
,其二是最后用到的CallAdapter
。Retrofit作为一款上层的网络框架(底层通常由OkHttp负责)它最主要职责就是自动的为我们做好数据的转化工作,比如将我们定义的基于注解的接口转化为可供底层网络框架使用的request,将收到的原始response转化为我们希望收到的数据类型,Converter和CallAdapter就是完成转化工作的组件之一。同时,这两个接口设计的十分灵活,易于拓展,可以和当今甚至未来的各种库对接,也体现了Retrofit的优势,下面分别来做分析。
Converter
1 2 3
| public interface Converter<F, T> { T convert(F value) throws IOException; }
|
以上是Converter接口的定义,可见它很直白的是将一个类型转化为另一个类型,但是我们在编写它的实现类时经常会按照以下形式来定义这个方法
1 2
| public RequestBody convert(T value) public T convert(ResponseBody value)
|
即将一个具体对象(通常是bean对象)转化为request消息体,或是将收到的原始response消息体转为一个具体对象。如果不依靠retrofit我们通常会用Gson来手动完成序列化与反序列化工作,retrofit官方扩展库已经贴心的为我们准备好了Gson版本的converter-gson,让Gson的解析工作依靠Converter无缝的插入到Retrofit中。除了Gson以外官方还提供了Jackson,Java8,Protobuf等常见数据类型的转换器。
在开头的例子中,我们在Builder模式中加入了GsonConverterFactory.create()
这个Converter.Factory
接口实现类,进入它的源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public final class GsonConverterFactory extends Converter.Factory { public static GsonConverterFactory create() { return create(new Gson()); } @SuppressWarnings("ConstantConditions") public static GsonConverterFactory create(Gson gson) { if (gson == null) throw new NullPointerException("gson == null"); return new GsonConverterFactory(gson); } private final Gson gson; private GsonConverterFactory(Gson gson) { this.gson = gson; } @Override public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type)); return new GsonResponseBodyConverter<>(gson, adapter); } @Override public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type)); return new GsonRequestBodyConverter<>(gson, adapter); } }
|
这个工厂类为我们提供了创建responseBodyConverter
和requestBodyConverter
的方法,在ServiceMethod
类执行build
方法时会创建converter并保存在自己的成员变量中。
1 2 3 4 5 6 7 8 9
| private Converter<ResponseBody, T> createResponseConverter() { Annotation[] annotations = method.getAnnotations(); try { return retrofit.responseBodyConverter(responseType, annotations); } catch (RuntimeException e) { throw methodError(e, "Unable to create converter for %s", responseType); } }
|
其中Type是在ServiceMethod中解析出的我们的目标类型,然后进入GsonResponseBodyConverter
的源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> { private final Gson gson; private final TypeAdapter<T> adapter; GsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) { this.gson = gson; this.adapter = adapter; } @Override public T convert(ResponseBody value) throws IOException { JsonReader jsonReader = gson.newJsonReader(value.charStream()); try { return adapter.read(jsonReader); } finally { value.close(); } } }
|
在这里获取响应报文中的json并通过gson转到成我们的目标类型对象,然后返回,这也就是之前toResponse
方法中做的实际工作。
通过对GsonConverterFactory
和GsonResponseBodyConverter
的分析,我们也能总结出,当我们需要自定义Converter时的一般流程。
- 定义XXXResponseBodyConverter和XXXRequestBodyConverter,通过Factory中传递过来的type,parameterAnnotations,methodAnnotations这些参数在convert方法中完成我们计划中的类型转换
- 定义XXXConverterFactory,对外提供
create
静态创建方法,重写requestBodyConverter
和responseBodyConverter
方法,创建并返回我们需要的具体converter,并且在创建时传入处理时需要的参数。
CallAdapter
如果说Converter主要是针对requestBody与responseBody来做转换工作的话,CallAdapter就是专门针对Call类型的转换了,看下它的接口定义
1 2 3 4
| public interface CallAdapter<R, T> { Type responseType(); T adapt(Call<R> call); }
|
结合上面的接口,我们由Converter得到了理想的R类型,那么接下来就可以通过CallAdapter再得到理想的T类型,这里以官方扩展库中的adapter-rxjava2为例,首先CallAdapter对象也是在ServiceMethod中由Factory创建的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| private CallAdapter<T, R> createCallAdapter() { Type returnType = method.getGenericReturnType(); if (Utils.hasUnresolvableType(returnType)) { throw methodError( "Method return type must not include a type variable or wildcard: %s", returnType); } if (returnType == void.class) { throw methodError("Service methods cannot return void."); } Annotation[] annotations = method.getAnnotations(); try { return (CallAdapter<T, R>) retrofit.callAdapter(returnType, annotations); } catch (RuntimeException e) { throw methodError(e, "Unable to create call adapter for %s", returnType); } }
|
然后进入Factory的源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
| public final class RxJava2CallAdapterFactory extends CallAdapter.Factory { public static RxJava2CallAdapterFactory create() { return new RxJava2CallAdapterFactory(null, false); } public static RxJava2CallAdapterFactory createAsync() { return new RxJava2CallAdapterFactory(null, true); } @SuppressWarnings("ConstantConditions") public static RxJava2CallAdapterFactory createWithScheduler(Scheduler scheduler) { if (scheduler == null) throw new NullPointerException("scheduler == null"); return new RxJava2CallAdapterFactory(scheduler, false); } private final @Nullable Scheduler scheduler; private final boolean isAsync; private RxJava2CallAdapterFactory(@Nullable Scheduler scheduler, boolean isAsync) { this.scheduler = scheduler; this.isAsync = isAsync; } @Override public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) { Class<?> rawType = getRawType(returnType); if (rawType == Completable.class) { return new RxJava2CallAdapter(Void.class, scheduler, isAsync, false, true, false, false, false, true); } boolean isFlowable = rawType == Flowable.class; boolean isSingle = rawType == Single.class; boolean isMaybe = rawType == Maybe.class; if (rawType != Observable.class && !isFlowable && !isSingle && !isMaybe) { return null; } boolean isResult = false; boolean isBody = false; Type responseType; if (!(returnType instanceof ParameterizedType)) { String name = isFlowable ? "Flowable" : isSingle ? "Single" : isMaybe ? "Maybe" : "Observable"; throw new IllegalStateException(name + " return type must be parameterized" + " as " + name + "<Foo> or " + name + "<? extends Foo>"); } Type observableType = getParameterUpperBound(0, (ParameterizedType) returnType); Class<?> rawObservableType = getRawType(observableType); if (rawObservableType == Response.class) { if (!(observableType instanceof ParameterizedType)) { throw new IllegalStateException("Response must be parameterized" + " as Response<Foo> or Response<? extends Foo>"); } responseType = getParameterUpperBound(0, (ParameterizedType) observableType); } else if (rawObservableType == Result.class) { if (!(observableType instanceof ParameterizedType)) { throw new IllegalStateException("Result must be parameterized" + " as Result<Foo> or Result<? extends Foo>"); } responseType = getParameterUpperBound(0, (ParameterizedType) observableType); isResult = true; } else { responseType = observableType; isBody = true; } return new RxJava2CallAdapter(responseType, scheduler, isAsync, isResult, isBody, isFlowable, isSingle, isMaybe, false); } }
|
上面注释已经标注的很清晰了
- Factory支持指定同步请求或异步请求,或在指定scheduler中请求
- 在get方法中会进行一系列判断,以确定具体的目标类型,为下一步CallAdapter中做转换做准备
然后进入RxJava2CallAdapter源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| @Override public Object adapt(Call<R> call) { Observable<Response<R>> responseObservable = isAsync ? new CallEnqueueObservable<>(call) : new CallExecuteObservable<>(call); Observable<?> observable; if (isResult) { observable = new ResultObservable<>(responseObservable); } else if (isBody) { observable = new BodyObservable<>(responseObservable); } else { observable = responseObservable; } if (scheduler != null) { observable = observable.subscribeOn(scheduler); } if (isFlowable) { return observable.toFlowable(BackpressureStrategy.LATEST); } if (isSingle) { return observable.singleOrError(); } if (isMaybe) { return observable.singleElement(); } if (isCompletable) { return observable.ignoreElements(); } return observable; }
|
在adapt
方法中完成了Call到Object的转换,对本例而言是将Call转换为了Flowable。这里以同步场景为例,call传入了CallEnqueueObservable
,该Observable
将作为事件源把请求结果发送给下游观察者,进入它的源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| final class CallExecuteObservable<T> extends Observable<Response<T>> { private final Call<T> originalCall; CallExecuteObservable(Call<T> originalCall) { this.originalCall = originalCall; } @Override protected void subscribeActual(Observer<? super Response<T>> observer) { Call<T> call = originalCall.clone(); observer.onSubscribe(new CallDisposable(call)); boolean terminated = false; try { Response<T> response = call.execute(); if (!call.isCanceled()) { observer.onNext(response); } if (!call.isCanceled()) { terminated = true; observer.onComplete(); } } catch (Throwable t) { Exceptions.throwIfFatal(t); if (terminated) { RxJavaPlugins.onError(t); } else if (!call.isCanceled()) { try { observer.onError(t); } catch (Throwable inner) { Exceptions.throwIfFatal(inner); RxJavaPlugins.onError(new CompositeException(t, inner)); } } } } private static final class CallDisposable implements Disposable { private final Call<?> call; CallDisposable(Call<?> call) { this.call = call; } @Override public void dispose() { call.cancel(); } @Override public boolean isDisposed() { return call.isCanceled(); } } }
|
- 在这里我们终于看到了
call.execute();
这个在上一节中提到过的实际触发同步请求的方法,它被封装到了subscribeActual
方法中,该方法是当前Observer
被订阅时会触发的方法,可见在该Observable
被订阅时才会发生请求
- 得到请求结果后会通过
observer.onNext(response);
将结果传递给下游观察者
- 在该类中,不仅仅将触发请求与订阅做了绑定,同时还把取消请求与取消订阅做了绑定
到此CallAdapter
也分析完了,官方提供了rxjava、rxjava2、java8等多种类型的Adapter,如果不能满足需求我们也可以自己定义CallAdapter,一样是有套路的:
- 根据目标类型编写
CallAdapter
的实现类,在adapt
方法中完成转化
- 继承
CallAdapter.Factory
编写工厂类,在get
方法中根据获取到的参数做一些判断处理,依照处理结果创建CallAdapter
的实现类对象并返回
最后
Retrofit由于代码量比较少,篇幅相比之前几篇源码分析的文章也要简短不少。但是阅读Retrofit的源码需要对OkHttp和RxJava2的源码有一定的了解,所以我还是建议最后再去分析这款框架。这几款框架也都有共通之处,都为用户预留了拓展接口,想用好这些扩展接口,阅读源码了解其运作原理也是必不可少的:
- 在RxJava2中我们可以自定义Operator(Observable)
- 在Glide中我们可以自定义GlideModule
- 在OkHttp中我们可以自定义Interceptor
- 在Retrofit中我们可以自定义CallAdapter、Converter
通过实现这些自定义接口,我们可以根据需求场景,把自己的业务逻辑无缝插入到这些框架中,既实现了功能又不会破坏代码整体结构,这才是“优雅”的框架。我们在设计框架时,也应该充分借鉴这一点,为用户预留充足的自定义空间,这样才能造出受欢迎的轮子╮( ̄▽ ̄)╭
声明:本站所有文章均为原创或翻译,遵循署名-非商业性使用-禁止演绎 4.0 国际许可协议,如需转载请确保您对该协议有足够了解,并附上作者名(Est)及原贴地址