Carson帶你學設計模式:動態代理模式(Proxy Pattern)

前言

今天Carson來全面總結最常用的設計模式 - 代理模式中的動態代理模式

其他設計模式介紹
這是一份全面 & 詳細的設計模式學習指南
Carson帶你學設計模式:單例模式(Singleton)
Carson帶你學設計模式:簡單工廠模式(SimpleFactoryPattern)
Carson帶你學設計模式:工廠方法模式(Factory Method)
Carson帶你學設計模式:抽象工廠模式(Abstract Factory)
Carson帶你學設計模式:策略模式(Strategy Pattern)
Carson帶你學設計模式:適配器模式(Adapter Pattern)
Carson帶你學設計模式:靜態代理模式(Proxy Pattern)
Carson帶你學設計模式:動態代理模式(Proxy Pattern)
Carson帶你學設計模式:模板方法模式(Template Method)
Carson帶你學設計模式:建造者模式(Builder Pattern)
Carson帶你學設計模式:外觀模式(Facade Pattern)
Carson帶你學設計模式:觀察者模式(Observer)


目錄

示意圖

1. 為什么要使用動態代理

1.1 背景

代理模式中的靜態代理模式存在一些特點:

  • 1個靜態代理 只服務1種類型的目標對象
  • 若要服務多類型的目標對象,則需要為每種目標對象都實現一個靜態代理對象

關于靜態代理模式可以看文章:Carson帶你學設計模式:靜態代理模式(Proxy Pattern)

1.2 沖突

在目標對象較多的情況下,若采用靜態代理,則會出現 靜態代理對象量多、代碼量大,從而導致代碼復雜的問題

1.3 解決方案

采用 動態代理模式


2. 動態代理模式介紹

2.1 實現原理

  • 設計動態代理類(DynamicProxy)時,不需要顯式實現與目標對象類(RealSubject)相同的接口,而是將這種實現推遲到程序運行時由 JVM來實現
  1. 即:在使用時再創建動態代理類 & 實例
  2. 靜態代理則是在代理類實現時就指定與目標對象類(RealSubject)相同的接口
  • 通過Java 反射機制的method.invoke(),通過調用動態代理類對象方法,從而自動調用目標對象的方法

2.2 優點

  • 只需要1個動態代理類就可以解決創建多個靜態代理的問題,避免重復、多余代碼
  • 更強的靈活性
  1. 設計動態代理類(DynamicProxy)時,不需要顯式實現與目標對象類(RealSubject)相同的接口,而是將這種實現推遲到程序運行時由 JVM來實現
  2. 在使用時(調用目標對象方法時)才會動態創建動態代理類 & 實例,不需要事先實例化

2.3 缺點

  • 效率低
    相比靜態代理中 直接調用目標對象方法,動態代理則需要先通過Java反射機制 從而 間接調用目標對象方法
  • 應用場景局限
    因為 Java 的單繼承特性(每個代理類都繼承了 Proxy 類),即只能針對接口 創建 代理類,不能針對類 創建代理類

即只能動態代理 實現了接口的類

2.4 應用場景

  • 基于靜態代理應用場景下,需要代理對象數量較多的情況下使用動態代理
  • AOP 領域
  1. 定義:即 Aspect Oriented Programming = 面向切面編程,是OOP的延續、函數式編程的一種衍生范型
  2. 作用:通過預編譯方式和運行期動態代理實現程序功能的統一維護。
  3. 優點:降低業務邏輯各部分之間的耦合度 、 提高程序的可重用性 & 提高了開發的效率
  4. 具體應用場景:日志記錄、性能統計、安全控制、異常處理等

2.5 與靜態代理模式的區別

示意圖

3. 具體應用

接下來,我將用1個具體實例來對 動態代理模式 進行更深一步的介紹

3.1 實例概況

  • 背景:小成 希望買一臺最新的頂配 Mac 電腦;小何希望買一臺 iPhone
  • 沖突:國內還沒上,只有美國才有
  • 解決方案:尋找一個代購一起進行購買
  1. 即1個代購(動態代理對象)同時 代替 小成 & 小何(目標對象) 去買Mac(間接訪問的操作)
  2. 該代購是代購任何商品 = 什么人有什么需求就會去代購任何東西(動態代理)

3.2 使用步驟

  1. 聲明 調用處理器類
  2. 聲明目標對象類的抽象接口
  3. 聲明目標對象類
  4. 通過動態代理對象,調用目標對象的方法

3.3 步驟詳解

步驟1: 聲明 調用處理器類

DynamicProxy.java

<-- 作用 -->
// 1.  生成 動態代理對象
// 2.  指定 代理對象運行目標對象方法時需要完成的 具體任務
// 注:需實現InvocationHandler接口 = 調用處理器 接口
// 所以稱為 調用處理器類

 public class DynamicProxy implements InvocationHandler {

    // 聲明代理對象
    // 作用:綁定關系,即關聯到哪個接口(與具體的實現類綁定)的哪些方法將被調用時,執行invoke()
    private Object ProxyObject;

    public Object newProxyInstance(Object ProxyObject){
        this.ProxyObject =ProxyObject;
        return Proxy.newProxyInstance(ProxyObject.getClass().getClassLoader(),
                ProxyObject.getClass().getInterfaces(),this);
        // Proxy類 = 動態代理類的主類 
        // Proxy.newProxyInstance()作用:根據指定的類裝載器、一組接口 & 調用處理器 生成動態代理類實例,并最終返回
        // 參數說明:
        // 參數1:指定產生代理對象的類加載器,需要將其指定為和目標對象同一個類加載器
        // 參數2:指定目標對象的實現接口
        // 即要給目標對象提供一組什么接口。若提供了一組接口給它,那么該代理對象就默認實現了該接口,這樣就能調用這組接口中的方法
        // 參數3:指定InvocationHandler對象。即動態代理對象在調用方法時,會關聯到哪個InvocationHandler對象

    }

    //  復寫InvocationHandler接口的invoke()
    //  動態代理對象調用目標對象的任何方法前,都會調用調用處理器類的invoke()
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            // 參數說明:
            // 參數1:動態代理對象(即哪個動態代理對象調用了method()
            // 參數2:目標對象被調用的方法
            // 參數3:指定被調用方法的參數
            throws Throwable {
                System.out.println("代購出門了");
                Object result = null;
                // 通過Java反射機制調用目標對象方法
                result = method.invoke(ProxyObject, args);
        return result;
    }

}


步驟2: 聲明目標對象的抽象接口

Subject.java

public interface Subject {
    // 定義目標對象的接口方法
    // 代購物品
    public  void buybuybuy();

}

步驟3: 聲明目標對象類

Buyer1.java


// 小成,真正的想買Mac的對象 = 目標對象 = 被代理的對象
// 實現抽象目標對象的接口
public class Buyer1 implements Subject  {

    @Override
    public void buybuybuy() {
        System.out.println("小成要買Mac");
    }

}

Buyer2.java

// 小何,真正的想買iPhone的對象 = 目標對象 = 被代理的對象
// 實現抽象目標對象的接口
public class Buyer2 implements Subject  {

    @Override
    public void buybuybuy() {
        System.out.println("小何要買iPhone");
    }

}

步驟4: 通過動態代理對象,調用目標對象的方法
MainActivity.java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 1. 創建調用處理器類對象
        DynamicProxy DynamicProxy = new DynamicProxy();

        // 2. 創建目標對象對象
        Buyer1 mBuyer1 = new Buyer1();

        // 3. 創建動態代理類 & 對象:通過調用處理器類對象newProxyInstance()
        // 傳入上述目標對象對象
        Subject Buyer1_DynamicProxy = (Subject) DynamicProxy.newProxyInstance(mBuyer1);

        // 4. 通過調用動態代理對象方法從而調用目標對象方法
        // 實際上是調用了invoke(),再通過invoke()里的反射機制調用目標對象的方法
        Buyer1_DynamicProxy.buybuybuy();
        // 以上代購為小成代購Mac

        // 以下是代購為小何代購iPhone
        Buyer2 mBuyer2 = new Buyer2();
        Subject Buyer2_DynamicProxy = (Subject) DynamicProxy.newProxyInstance(mBuyer2);
        Buyer2_DynamicProxy.buybuybuy();
    }
}

3.4 測試結果

示意圖

3.5 Demo地址

Carson_Ho的Github地址:動態代理DynamicProxy


4. 源碼分析

  • 在經過上面的實例后,你是否會對以下問題好奇:

    1. 動態代理類 及其對象實例是如何生成的?
    2. 如何通過調用動態代理對象方法,從而調用目標對象方法?
  • 下面,我們順著 步驟4:目標對象 通過 動態代理對象調用方法的使用 來進行動態代理模式的源碼分析

// 步驟4:通過動態代理對象,調用目標對象的方法
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 1. 創建 調用處理器類 對象
        DynamicProxy DynamicProxy = new DynamicProxy();

        // 2. 創建 目標類 對象
        Buyer1 mBuyer1 = new Buyer1();

        // 3. 創建動態代理類 & 對象:通過調用處理器類對象newProxyInstance()
        // 傳入上述目標類對象
        Subject Buyer1_DynamicProxy = (Subject) DynamicProxy.newProxyInstance(mBuyer1);
        // ->>關注1

        // 4. 通過調用動態代理對象方法從而調用目標對象方法
        // ->>關注2
        Buyer1_DynamicProxy.buybuybuy();
        // 以上代購為小成代購Mac

        // 以下是代購為小何代購iPhone
        Buyer2 mBuyer2 = new Buyer2();
        Subject Buyer2_DynamicProxy = (Subject) DynamicProxy.newProxyInstance(mBuyer2);
        Buyer2_DynamicProxy.buybuybuy();
    }
}

此處有兩個重要的源碼分析點:

  • 關注1:創建動態代理類 & 對象:通過調用處理器類對象newProxyInstance()

解決的問題是:動態代理類 及其對象實例是如何生成的?

  • 關注2:通過調用動態代理對象方法從而調用目標對象方法

解決的問題是:如何通過調用動態代理對象方法,從而調用目標對象方法?

下面,我們將主要分析這兩處源碼。

4.1 (關注1)創建動態代理類 & 對象:通過調用處理器類對象newProxyInstance()

// 使用代碼
Subject Buyer1_DynamicProxy = (Subject) DynamicProxy.newProxyInstance(mBuyer1);
  • 即,動態代理類 及其對象實例是如何生成的?
  • 下面,我們直接進入DynamicProxy.newProxyInstance()
<-- 關注1:調用處理器 類的newProxyInstance() -->
// 即步驟1中實現的類:DynamicProxy

// 作用:
// 1.  生成 動態代理對象
// 2.  指定 代理對象運行目標對象方法時需要完成的 具體任務
// 注:需實現InvocationHandler接口 = 調用處理器 接口

 public class DynamicProxy implements InvocationHandler {

    // 聲明代理對象
    private Object ProxyObject;

    public Object newProxyInstance(Object ProxyObject){
        this.ProxyObject =ProxyObject;
        return Proxy.newProxyInstance(ProxyObject.getClass().getClassLoader(),
                ProxyObject.getClass().getInterfaces(),this);
        // Proxy.newProxyInstance()作用:根據指定的類裝載器、一組接口 & 調用處理器 生成動態代理類實例,并最終返回
        // ->>關注2

    }
    
     // 以下暫時忽略,下文會詳細介紹
    //  復寫InvocationHandler接口的invoke()
    //  動態代理對象調用目標對象的任何方法前,都會調用調用處理器類的invoke()
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            // 參數說明:
            // 參數1:動態代理對象(即哪個動態代理對象調用了method()
            // 參數2:目標對象被調用的方法
            // 參數3:指定被調用方法的參數
            throws Throwable {
                System.out.println("代購出門了");
                Object result = null;
                // 通過Java反射機制調用目標對象方法
                result = method.invoke(ProxyObject, args);
        return result;
    }

// 至此,關注1分析完畢,跳出
}

<-- 關注2:newProxyInstance()源碼解析-->
// 作用:根據指定的類裝載器、一組接口 & 調用處理器 生成動態代理類及其對象實例,并最終返回
      
public static Object newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h) throws IllegalArgumentException { 
  // 參數說明:
        // 參數1:指定產生代理對象的類加載器,需要將其指定為和目標對象同一個類加載器
        // 參數2:指定目標對象的實現接口
        // 即要給目標對象提供一組什么接口。若提供了一組接口給它,那么該代理對象就默認實現了該接口,這樣就能調用這組接口中的方法
        // 參數3:指定InvocationHandler對象。即動態代理對象在調用方法時,會關聯到哪個InvocationHandler對象

    ...  
    // 僅貼出核心代碼

    // 1. 通過 為Proxy類指定類加載器對象 & 一組interface,從而創建動態代理類
    // >>關注3
    Class cl = getProxyClass(loader, interfaces); 

    // 2. 通過反射機制獲取動態代理類的構造函數,其參數類型是調用處理器接口類型
    Constructor cons = cl.getConstructor(constructorParams); 

    // 3. 通過動態代理類的構造函數 創建 代理類實例(傳入調用處理器對象
    return (Object) cons.newInstance(new Object[] { h }); 

// 特別注意 
// 1. 動態代理類繼承 Proxy 類 & 并實現了在Proxy.newProxyInstance()中提供的一系列接口(接口數組)
// 2. Proxy 類中有一個映射表
  // 映射關系為:(<ClassLoader>,(<Interfaces>,<ProxyClass>) )
  // 即:1級key = 類加載器,根據1級key 得到 2級key = 接口數組
  // 因此:1類加載器對象 + 1接口數組 = 確定了一個代理類實例
...

// 回到調用關注2的原處
}


<-- 關注3:getProxyClass()源碼分析 -->
// 作用:創建動態代理類

public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces)  
throws IllegalArgumentException { 
    
  ...
  // 僅貼出關鍵代碼
  
<-- 1. 將目標類所實現的接口加載到內存中 -->
    // 遍歷目標類所實現的接口  
    for (int i = 0; i < interfaces.length; i++) {  
          
        // 獲取目標類實現的接口名稱 
        String interfaceName = interfaces[i].getName();  
        Class interfaceClass = null;  
        try {  
        // 加載目標類實現的接口到內存中  
        interfaceClass = Class.forName(interfaceName, false, loader);  
        } catch (ClassNotFoundException e) {  
        }  
        if (interfaceClass != interfaces[i]) {  
        throw new IllegalArgumentException(  
            interfaces[i] + " is not visible from class loader");  
        }  
    }  
       
<-- 2. 生成動態代理類 -->       
        // 根據傳入的接口 & 代理對象 創建動態代理類的字節碼
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(  
            proxyName, interfaces);  

            // 根據動態代理類的字節碼 生成 動態代理類
            proxyClass = defineClass0(loader, proxyName,  
            proxyClassFile, 0, proxyClassFile.length);  
       
        }  
    // 最終返回動態代理類
    return proxyClass;  
    }  

// 回到調用關注3的原處

總結

  • 通過調用處理器類對象的.newProxyInstance()創建動態代理類 及其實例對象(需傳入目標類對象)
  • 具體過程如下:
    1. 通過 為Proy類指定類加載器對象 & 一組接口,從而創建動態代理類的字節碼;再根據類字節碼創建動態代理類
    2. 通過反射機制獲取動態代理類的構造函數(參數類型 = 調用處理器接口類型
    3. 通過動態代理類的構造函數 創建 代理類實例(傳入調用處理器對象

4.2 (關注2)通過調用動態代理對象方法從而調用目標對象方法

即,如何通過調用動態代理對象方法,從而調用目標對象方法?

// 使用代碼
Buyer1_DynamicProxy.buybuybuy();
  • 在關注1中的 DynamicProxy.newProxyInstance()生成了一個動態代理類 及其實例

該動態代理類記為 :$Proxy0

下面我們直接看該類實現及其 buybuybuy()

  • 該方法的邏輯如下:
<-- 動態代理類 $Proxy0 實現-->
// 繼承:Java 動態代理機制的主類:java.lang.reflect.Proxy
// 實現:與目標對象一樣的接口(即上文例子的Subject接口)
public final class $Proxy0 extends Proxy implements Subject {

// 構造函數
public ProxySubject(InvocationHandler invocationhandler)   
    {   
        super(invocationhandler);   
    }  

 // buybuybuy()是目標對象實現接口(Subject)中的方法
 // 即$Proxy0類必須實現
 // 所以在使用動態代理類對象時,才可以調用目標對象的同名方法(即上文的buybuybuy())
 public final void buybuybuy() {
        try {
            super.h.invoke(this, m3, null); 
            // 該方法的邏輯實際上是調用了父類Proxy類的h參數的invoke()
            // h參數即在Proxy.newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h)傳入的第3個參數InvocationHandler對象
            // 即調用了調用處理器的InvocationHandler.invoke()
            // 而復寫的invoke()利用反射機制:Object result=method.invoke(proxied,args)
            // 從而調用目標對象的的方法 ->>關注4
            return;
        } catch (Error e) {
        } catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

<-- 關注4:調用處理器 類復寫的invoke() -->
// 即步驟1中實現的類:DynamicProxy
// 內容很多都分析過了,直接跳到復寫的invoke()中

 public class DynamicProxy implements InvocationHandler {

    private Object ProxyObject;

    public Object newProxyInstance(Object ProxyObject){
        this.ProxyObject =ProxyObject;
        return Proxy.newProxyInstance(ProxyObject.getClass().getClassLoader(),
                ProxyObject.getClass().getInterfaces(),this);
     

    }

    //  復寫InvocationHandler接口的invoke()
    //  動態代理對象調用目標對象的任何方法前,都會調用調用處理器類的invoke()
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            // 參數說明:
            // 參數1:動態代理對象(即哪個動態代理對象調用了method()
            // 參數2:目標對象被調用的方法
            // 參數3:指定被調用方法的參數
            throws Throwable {
                System.out.println("代購出門了");
                Object result = null;
                // 通過Java反射機制調用目標對象方法
                result = method.invoke(ProxyObject, args);
        return result;
    }

總結

  • 動態代理類實現了與目標類一樣的接口,并實現了需要目標類對象需要調用的方法
  • 該方法的實現邏輯 = 調用父類 Proxy類的 h.invoke()

其中h參數 = 在創建動態代理實例中newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h)傳入的第3個參數InvocationHandler對象

  • InvocationHandler.invoke() 中通過反射機制,從而調用目標類對象的方法

4.3 原理總結

我用一張圖總結第4節說的關于動態代理模式的源碼分析。


示意圖

至此,關于代理模式中的動態代理模式的相關知識已經講解完畢。


5. 總結

我用兩張圖總結整篇文章的內容

示意圖
示意圖2
  • 本文主要對動態代理模式進行了全面介紹
  • 接下來我會對每種設計模式進行詳細的分析,歡迎關注Carson_Ho的簡書,不定期分享關于安卓開發的干貨,追求短、平、快,但卻不缺深度

請點贊!因為你的鼓勵是我寫作的最大動力!

相關文章閱讀
這是一份全面 & 詳細的設計模式學習指南
Carson帶你學設計模式:單例模式(Singleton)
Carson帶你學設計模式:簡單工廠模式(SimpleFactoryPattern)
Carson帶你學設計模式:工廠方法模式(Factory Method)
Carson帶你學設計模式:抽象工廠模式(Abstract Factory)
Carson帶你學設計模式:策略模式(Strategy Pattern)
Carson帶你學設計模式:適配器模式(Adapter Pattern)
Carson帶你學設計模式:靜態代理模式(Proxy Pattern)
Carson帶你學設計模式:動態代理模式(Proxy Pattern)
Carson帶你學設計模式:模板方法模式(Template Method)
Carson帶你學設計模式:建造者模式(Builder Pattern)
Carson帶你學設計模式:外觀模式(Facade Pattern)
Carson帶你學設計模式:觀察者模式(Observer)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容