Retrofit源码阅读

前言

  • 为什么要看Retrofit源码?

    • 因为目前项目使用的Retrofit网络请求。想去详细了解下。
  • 这次阅读重点关注的点通过Create方法追踪工作流程

    • 如何进行线程切换
    • 用到反射如何去优化性能的
    • 怎么解析参数和注解的
    • 它针对Kotlin做哪些处理
  • 本次阅读源码版本依赖

    • implementation 'com.squareup.retrofit2:retrofit:2.9.0'
      implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
      
  • 分析不对的地方请指出。。。

  • 来张图

    retrofit.png

Retrofit 基本使用

  • 创建接口

    用于存放请求地址参数信息

    interface ApiService {
        @GET("users/{user}/repos")
        fun listRepos(@Path("user") user: String?): Call<ResponseBody>?
    }
    
  • 发起请求与Retrofit配置

    class MainActivity : AppCompatActivity(), View.OnClickListener {
        private var mApiService: ApiService? = null
    
        private var mBinding: ActivityMainBinding? = null
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            mBinding = ActivityMainBinding.inflate(layoutInflater)
            setContentView(mBinding?.root)
            mBinding?.tvData?.setOnClickListener(this)
            //retrofit配置
            val retrofit = Retrofit.Builder()
                .baseUrl("https://api.github.com/")
                .build()
           //接口实例
            mApiService = retrofit.create(ApiService::class.java)
        }
    
        override fun onClick(v: View?) {
            requestData()
        }
       
        //发起请求
        private fun requestData() {
            mApiService?.listRepos("brycecui")?.enqueue(object : Callback<ResponseBody> {
                override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
                    Log.e("MainActivity", "onResponse:${response.body().toString()}")
                }
    
                override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
                    Log.e("MainActivity", "onFailure${t.localizedMessage}")
                }
    
            })
        }
    
    }
    

问题分析

Create方法源码

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 the method is a method from Object then defer to normal invocation.
              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);
            }
          });
}
  • 注释①。获取对应平台

    • private static final Platform PLATFORM = findPlatform();
      
      static Platform get() {
        return PLATFORM;
      }
      
      //得到Android平台
      private static Platform findPlatform() {
        return "Dalvik".equals(System.getProperty("java.vm.name"))
            ? new Android() //
            : new Platform(true);
      }
      //省略部分代码
        static final class Android extends Platform {
          Android() {
            super(Build.VERSION.SDK_INT >= 24);
          }
          @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);
            }
          }
        }
      

      会得到一个Android平台。

  • 注释②。会返回false走到loadServiceMethod方法

    • 看下loadServiceMethod源码

      private final Map<Method, ServiceMethod<?>> serviceMethodCache = new ConcurrentHashMap<>();
      ServiceMethod<?> loadServiceMethod(Method method) {
        ServiceMethod<?> result = serviceMethodCache.get(method);
        if (result != null) return result;
      
        synchronized (serviceMethodCache) {
          result = serviceMethodCache.get(method);
          if (result == null) {
            //解析数据得到ServiceMethod
            result = ServiceMethod.parseAnnotations(this, method);
            serviceMethodCache.put(method, result);
          }
        }
        return result;
      }
      

      如果(线程安全的ConcurrentHashMap)缓存中有直接返回。没有解析并储存之后返回ServiceMethod

    • 看下他解析数据的过程源码

      abstract class ServiceMethod<T> {
        static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
          
          RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
      
          //省略部分代码
          
          return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
        }
      
        abstract @Nullable T invoke(Object[] args);
      }
      

      ServiceMethod是一个抽象类。

      • RequestFactory的parseAnnotations源码

        //建造者模式
        static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
          return new Builder(retrofit, method).build();
        }
        
        
      • RequestFactory构造方法

        RequestFactory(Builder builder) {
          method = builder.method;
          baseUrl = builder.retrofit.baseUrl;
          httpMethod = builder.httpMethod;
          relativeUrl = builder.relativeUrl;
          headers = builder.headers;
          contentType = builder.contentType;
          hasBody = builder.hasBody;
          isFormEncoded = builder.isFormEncoded;
          isMultipart = builder.isMultipart;
          parameterHandlers = builder.parameterHandlers;
          isKotlinSuspendFunction = builder.isKotlinSuspendFunction;
        }
        
      • RequestFactory.Builder和它的build方法

         Builder(Retrofit retrofit, Method method) {
              this.retrofit = retrofit;
              this.method = method;
           //通过 反射得到注解、参数等信息。
              this.methodAnnotations = method.getAnnotations();
              this.parameterTypes = method.getGenericParameterTypes();
              this.parameterAnnotationsArray = method.getParameterAnnotations();
            }
        RequestFactory build() {
          for (Annotation annotation : methodAnnotations) {
            parseMethodAnnotation(annotation);
          }
          int parameterCount = parameterAnnotationsArray.length;
          parameterHandlers = new ParameterHandler<?>[parameterCount];
          for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
            parameterHandlers[p] =
                parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
          }
          //省略部分空检查代码
          return new RequestFactory(this);
        }
        
      • 我看看下返回的RequestFactory都有啥。看下图是使用上面简单使用的例子debug的


        retrofit-debug

        baseUrl:就是retrofit传过来的baseUrl

        httpMethod:我们用的GET注解。(用的POST就是POST)我们可以看下如何进行解析的

        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);
          } else if (annotation instanceof HEAD) {
            parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);
          } else if (annotation instanceof PATCH) {
            parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true);
          } else if (annotation instanceof POST) {
            parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
          } else if (annotation instanceof PUT) {
            parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);
          } else if (annotation instanceof OPTIONS) {
            parseHttpMethodAndPath("OPTIONS", ((OPTIONS) annotation).value(), false);
          } else if (annotation instanceof HTTP) {
            HTTP http = (HTTP) annotation;
            parseHttpMethodAndPath(http.method(), http.path(), http.hasBody());
          } else if (annotation instanceof retrofit2.http.Headers) {
            String[] headersToParse = ((retrofit2.http.Headers) annotation).value();
            if (headersToParse.length == 0) {
              throw methodError(method, "@Headers annotation is empty.");
            }
            headers = parseHeaders(headersToParse);
          } else if (annotation instanceof Multipart) {
            if (isFormEncoded) {
              throw methodError(method, "Only one encoding annotation is allowed.");
            }
            isMultipart = true;
          } else if (annotation instanceof FormUrlEncoded) {
            if (isMultipart) {
              throw methodError(method, "Only one encoding annotation is allowed.");
            }
            isFormEncoded = true;
          }
        }
        

        根据使用instanceof 判断是那种注解进行对应的方法解析。也就是parseHttpMethodAndPath方法。

        private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
          //省略httpMethod空异常检查
          this.httpMethod = httpMethod;
          this.hasBody = hasBody;
          if (value.isEmpty()) {
            return;
          }
          // Get the relative URL path and existing query string, if present.
          int question = value.indexOf('?');
          if (question != -1 && question < value.length() - 1) {
            // Ensure the query string does not have any named parameters.
            String queryParams = value.substring(question + 1);
            Matcher queryParamMatcher = PARAM_URL_REGEX.matcher(queryParams);
            if (queryParamMatcher.find()) {
              throw methodError(
                  method,
                  "URL query string \"%s\" must not have replace block. "
                      + "For dynamic query parameters use @Query.",
                  queryParams);
            }
          }
        
          this.relativeUrl = value;
          this.relativeUrlParamNames = parsePathParameters(value);
        }
        /***********************************手动分割线******************************************/
        static Set<String> parsePathParameters(String path) {
              Matcher m = PARAM_URL_REGEX.matcher(path);
              Set<String> patterns = new LinkedHashSet<>();
              while (m.find()) {
                patterns.add(m.group(1));
              }
              return patterns;
            }
        

        parseHttpMethodAndPath 的value是 请求方法注解里面的值。对应示例中是users/{user}/repos。由于没有所以不会走if语句。

        relativeUrl也就等于users/{user}/repos

  • 在看下ServiceMethod返回的HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory)方法

    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;
      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 {
          // TODO figure out if type is nullable or not
          // Metadata metadata = method.getDeclaringClass().getAnnotation(Metadata.class)
          // Find the entry for method
          // Determine if return type is nullable or not
        }
        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);
      Type responseType = callAdapter.responseType();
      
      Converter<ResponseBody, ResponseT> responseConverter = createResponseConverter(retrofit, method, responseType);
    
      okhttp3.Call.Factory callFactory = retrofit.callFactory;
      if (!isKotlinSuspendFunction) {
        return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
      } else if (continuationWantsResponse) {
        //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
        return (HttpServiceMethod<ResponseT, ReturnT>)
            new SuspendForResponse<>(
                requestFactory,
                callFactory,
                responseConverter,
                (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
      } else {
        //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
        return (HttpServiceMethod<ResponseT, ReturnT>)
            new SuspendForBody<>(
                requestFactory,
                callFactory,
                responseConverter,
                (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
                continuationBodyNullable);
      }
    }
    

    isKotlinSuspendFunction用来判断是否使用kotlin协程请求。

    true代表使用挂起函数。那么会创建SuspendForResponse和SuspendForBody。分别是返回值为Ressponse<T>和ResponseBody。

    分别在内部调用了扩展函数。使用协程切换线程

    //返回值为Ressponse使用
    suspend fun <T> Call<T>.awaitResponse(): Response<T> {
      return suspendCancellableCoroutine { continuation ->
        continuation.invokeOnCancellation {
          cancel()
        }
        enqueue(object : Callback<T> {
          override fun onResponse(call: Call<T>, response: Response<T>) {
            continuation.resume(response)
          }
    
          override fun onFailure(call: Call<T>, t: Throwable) {
            continuation.resumeWithException(t)
          }
        })
      }
    }
    //返回值为ResponseBody使用。成功直接返回ResponseBody
    suspend fun <T : Any> Call<T>.await(): T {
      return suspendCancellableCoroutine { continuation ->
        continuation.invokeOnCancellation {
          cancel()
        }
        enqueue(object : Callback<T> {
          override fun onResponse(call: Call<T>, response: Response<T>) {
            if (response.isSuccessful) {
              val body = response.body()
              if (body == null) {
                val invocation = call.request().tag(Invocation::class.java)!!
                val method = invocation.method()
                val e = KotlinNullPointerException("Response from " +
                    method.declaringClass.name +
                    '.' +
                    method.name +
                    " was null but response body type was declared as non-null")
                continuation.resumeWithException(e)
              } else {
                continuation.resume(body)
              }
            } else {
              continuation.resumeWithException(HttpException(response))
            }
          }
    
          override fun onFailure(call: Call<T>, t: Throwable) {
            continuation.resumeWithException(t)
          }
        })
      }
    }
    

    最终会返回一个CallAdapted<>,而CallAdapted<>是继承的HttpServiceMethod。

    static final class CallAdapted<ResponseT, ReturnT> extends HttpServiceMethod<ResponseT, ReturnT> {
      private final CallAdapter<ResponseT, ReturnT> callAdapter;
      CallAdapted(
          RequestFactory requestFactory,
          okhttp3.Call.Factory callFactory,
          Converter<ResponseBody, ResponseT> responseConverter,
          CallAdapter<ResponseT, ReturnT> callAdapter) {
        super(requestFactory, callFactory, responseConverter);
        this.callAdapter = callAdapter;
      }
    
      @Override
      protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
        return callAdapter.adapt(call);
      }
    }
    
                            而loadServiceMethod(method)的invoke(args)方法。会调用CallAdapted的adapte方法。而这个CallAdapted是由HttpServiceMethod的 createCallAdapter方法创建的。也就是retrofit的addCallAdapterFactory方法添加的。示例中我们没有添加所以走得是默认的CallAdapted。在retrofit的Builder类中可以找到
    
List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
//这添加一个平台默认的CallAdapter并且传一个线程池callbackExecutor
callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));
//可以看到defaultCallbackExecutor也是由平台默认的额
 Executor callbackExecutor = this.callbackExecutor;
      if (callbackExecutor == null) {
        callbackExecutor = platform.defaultCallbackExecutor();
      }

我们看下平台platform这俩个默认方法。

List<? extends CallAdapter.Factory> defaultCallAdapterFactories(
    @Nullable Executor callbackExecutor) {
  DefaultCallAdapterFactory executorFactory = new DefaultCallAdapterFactory(callbackExecutor);
  return hasJava8Types
      ? asList(CompletableFutureCallAdapterFactory.INSTANCE, executorFactory)
      : singletonList(executorFactory);
}

继续看DefaultCallAdapterFactory

final class DefaultCallAdapterFactory extends CallAdapter.Factory {
  private final @Nullable Executor callbackExecutor;

  DefaultCallAdapterFactory(@Nullable Executor callbackExecutor) {
    this.callbackExecutor = callbackExecutor;
  }

  @Override
  public @Nullable CallAdapter<?, ?> get(
      Type returnType, Annotation[] annotations, Retrofit retrofit) {
//省略空检查
    final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType);

    final Executor executor =
        Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)
            ? null
            : callbackExecutor;

    return new CallAdapter<Object, Call<?>>() {
      @Override
      public Type responseType() {
        return responseType;
      }

      @Override
      public Call<Object> adapt(Call<Object> call) {
        //当时kotlin协程请求时返回okhttpcall 否则返回ExecutorCallbackCall
        return executor == null ? call : new ExecutorCallbackCall<>(executor, call);
      }
    };
  }

  static final class ExecutorCallbackCall<T> implements Call<T> {
    final Executor callbackExecutor;
    final Call<T> delegate;

    ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
      this.callbackExecutor = callbackExecutor;
      this.delegate = delegate;
    }

    @Override
    public void enqueue(final Callback<T> callback) {
      Objects.requireNonNull(callback, "callback == null");

      //(OkHttp封装类)OkHttpCall的enqueue方法进行请求
      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));
            }
          });
    }

    @Override
    public boolean isExecuted() {
      return delegate.isExecuted();
    }

    @Override
    public Response<T> execute() throws IOException {
      return delegate.execute();
    }

    @Override
    public void cancel() {
      delegate.cancel();
    }

    @Override
    public boolean isCanceled() {
      return delegate.isCanceled();
    }

    @SuppressWarnings("CloneDoesntCallSuperClone") // Performing deep clone.
    @Override
    public Call<T> clone() {
      return new ExecutorCallbackCall<>(callbackExecutor, delegate.clone());
    }

    @Override
    public Request request() {
      return delegate.request();
    }

    @Override
    public Timeout timeout() {
      return delegate.timeout();
    }
  }
}

也就是说我们示例中的mApiService?.listRepos("brycecui")?.enqueue()最终到了这里。delegate他是 Call<T>接口。它的实现类是OkHttpCall。怎么知道不是别的实现类。上面我们通过代码追踪知道了loadserviceMethod.invoke.实际是调用了 抽象类ServiceMethod的实现类HttpServiceMethod的invoke方法。也就是下面这个方法。下面调用了adapt方法并把OkHttpCall传了过去。最终也就是通过DefaultCallAdapterFactory的get方法的adapt创建一个ExecutorCallbackCall传递的。

@Override
final @Nullable ReturnT invoke(Object[] args) {
  Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
  return adapt(call, args);
}

看下 callbackExecutor.execute。线程切换关键所在。

static final class MainThreadExecutor implements Executor {
  private final Handler handler = new Handler(Looper.getMainLooper());
  @Override
  public void execute(Runnable r) {
    handler.post(r);
  }
}

也就是使用handler.post(r);切换到主线程。

总结

  • 如何切换线程

    • kotlin 使用协程
    • 默认使用handler
  • 用到反射如何优化性能的

    • 使用map缓存避免多次反射浪费性能
  • 如何解析参数注解

    • 使用反射解析、正则匹配
  • 针对kotlin做了哪些处理

    • 判断是否使用kotlin并且针对返回值是否只需要body
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容