代理模式

一剖淀、概念

1、定義

代理模式給某一個對象提供一個代理對象,并由代理對象控制對原對象的引用却嗡。同時代理對象可以調(diào)用被代理對象的方法舶沛,并對其進行增強〈凹郏可以總結為代理對象 = 增強代碼 + 目標對象(原對象)

2如庭、舉例

疫情期間很多公司破產(chǎn),導致很多勞動者都失業(yè)了撼港,小文也是其中之一坪它。公司拖欠小文的工資一直未能下發(fā)。小文希望通過勞動仲裁來要回屬于自己的工資帝牡,所以和公司打官司往毡。基本的仲裁步驟小文都懂否灾,大概有:準備勞動仲裁申請書卖擅、收集證據(jù)、提交申請墨技、開庭答辯等等惩阶,但是小文是第一次進行勞動仲裁,對這些具體的操作沒什么經(jīng)驗扣汪,同時自己在這期間又有了新的工作断楷,很難抽出時間去進行勞動仲裁。這時小文就掏錢找了一位律師崭别,這位律師就相當于代理對象冬筒。律師替小文準備仲裁申請、提交申請茅主、開庭后幫小文進行辯論闡述等等舞痰,律師不僅僅是按著仲裁需要的步驟做了,而且每一步都做的比小文更好诀姚,這就是對被代理對象方法的增強

image

3响牛、場景

面向切面編程中就用到了代理模式,具體的內(nèi)容后面有機會再詳細介紹吧

4赫段、分類:

靜態(tài)代理: 在編譯時就已經(jīng)實現(xiàn)呀打,編譯完成后代理類是一個實際的class文件。
動態(tài)代理: 在運行時動態(tài)生成的糯笙,即編譯完成后沒有實際的class文件贬丛,而是在運行時動態(tài)生成類字節(jié)碼,并加載到JVM中给涕。

二豺憔、靜態(tài)代理

1额获、代碼

我們用靜態(tài)代理來實現(xiàn)上面例子

定義接口:無論是誰進行勞動仲裁,都需要按著這個流程執(zhí)行

public interface ArbitrationStep {
   /**
    * 準備仲裁申請
    */
   void prepareApp();

   /**
    * 收集證據(jù)
    */
   void collectEvi();

   /**
    * 開庭答辯
    */
   void debate();
}

定義被代理類

public class XiaoWen implements ArbitrationStep {

   @Override
   public void prepareApp() {
      System.out.println("準備仲裁申請焕阿!");
   }

   @Override
   public void collectEvi() {
      System.out.println("收集證據(jù)咪啡!");
   }

   @Override
   public void debate() {
      System.out.println("開庭答辯环础!");
   }
}

定義代理類

可以看到代理類中不僅執(zhí)行了被代理的方法茂契,同時還對其方法進行了一定的增強

public class LawyerProxy implements ArbitrationStep {
   private XiaoWen xiaoWen;

   public LawyerProxy(XiaoWen xiaoWen) {
      this.xiaoWen = xiaoWen;
   }

   @Override
   public void prepareApp() {
      System.out.println("律師聽取小文的想法");
      xiaoWen.prepareApp();
      System.out.println("律師對申請進行修改");
   }

   @Override
   public void collectEvi() {
      xiaoWen.collectEvi();
      System.out.println("律師對證據(jù)進行整理");
   }

   @Override
   public void debate() {
      xiaoWen.debate();
      System.out.println("律師更詳細的闡述");
   }
}

測試類

public class Tribunal {
   public static void main(String[] args) {
      XiaoWen xiaoWen = new XiaoWen();
      System.out.println("小文委托律師幫忙進行勞動仲裁");
      LawyerProxy proxy = new LawyerProxy(xiaoWen);
      System.out.println("-------第一步-------");
      proxy.prepareApp();

      System.out.println("-------第二步-------");
      proxy.collectEvi();

      System.out.println("-------第三步-------");
      proxy.debate();
      System.out.println();

      System.out.println("仲裁結束");
   }
}

運行結果

小文委托律師幫忙進行勞動仲裁
-------第一步-------
律師聽取小文的想法
準備仲裁申請!
律師對申請進行修改
-------第二步-------
收集證據(jù)缸匪!
律師對證據(jù)進行整理
-------第三步-------
開庭答辯褒纲!
律師更詳細的闡述

仲裁結束

2准夷、總結

通過上面的代碼可以總結出靜態(tài)代理的步驟

  • 定義接口
    -- 被代理類和代理類都需要實現(xiàn)該接口
  • 定義被代理類
    -- 實現(xiàn)上面定義的接口
  • 定義代理類
    -- 實現(xiàn)上面定義的接口
    -- 創(chuàng)建被代理對象
    -- 調(diào)用方法時需要調(diào)用被代理對象的方法,同時自己可以對方法進行增加

圖解

3莺掠、存在的問題

如果一個類需要被代理衫嵌,就得去創(chuàng)建一個代理類。如果被代理的類過多彻秆,這樣就需要手動創(chuàng)建很多代理類楔绞。為了解決這個問題,便有了動態(tài)代理

三唇兑、基于Java反射的動態(tài)代理

1酒朵、反射與類加載

在這之前我們需要先了解Java反射類加載
同時還可以參考知乎回答——Java 動態(tài)代理作用是什么?

2扎附、使用反射實現(xiàn)動態(tài)代理

接著上面的勞動仲裁的例子蔫耽,不過這次我們用動態(tài)代理去實現(xiàn)
但是需要用到Proxy類的兩個靜態(tài)方法

  • 獲得動態(tài)代理類Class對象
    -- public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
    -- 參數(shù)分別是類加載器和接口的Class對象;
    -- 這個方法留夜, 會從你傳入的接口Class中匙铡,“拷貝”類結構信息到一個新的Class對象中,但新的Class對象帶有構造器碍粥,是可以創(chuàng)建對象的
  • 獲得動態(tài)代理
    -- public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
    -- 封裝了得到代理類Class對象鳖眼、構造函數(shù)等細節(jié),直接返回了代理對象

接口和被代理類不變

public interface ArbitrationStep {
   /**
    * 準備仲裁申請
    */
   void prepareApp();

   /**
    * 收集證據(jù)
    */
   void collectEvi();

   /**
    * 開庭答辯
    */
   void debate();
}
public class XiaoWen implements ArbitrationStep {

   @Override
   public void prepareApp() {
      System.out.println("準備仲裁申請嚼摩!");
   }

   @Override
   public void collectEvi() {
      System.out.println("收集證據(jù)钦讳!");
   }

   @Override
   public void debate() {
      System.out.println("開庭答辯!");
   }
}

通過反射獲得代理對象:

public class Tribunal2 {
   public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
      // 獲取代理類的Class對象
      Class<?> proxyClazz = Proxy.getProxyClass(ArbitrationStep.class.getClassLoader(), ArbitrationStep.class);

      // 獲得構造函數(shù)
      Constructor<?> constructor = proxyClazz.getConstructor(InvocationHandler.class);

      // 創(chuàng)建代理對象
      ArbitrationStep lawyerProxy = (ArbitrationStep) constructor.newInstance(new InvocationHandler() {
         @Override
         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 創(chuàng)建被代理對象低斋,用于調(diào)用方法
            XiaoWen xiaoWen = new XiaoWen();

            // 通過invoke方法調(diào)用了被代理對象的方法
            Object invoke = method.invoke(xiaoWen, args);

            // 每個步驟都讓律師幫忙處理
            System.out.println("律師幫忙處理!");
            return invoke;
         }
      });

      // 通過代理對象調(diào)用方法
      System.out.println("-------第一步-------");
      lawyerProxy.prepareApp();

      System.out.println("-------第二步-------");
      lawyerProxy.collectEvi();

      System.out.println("-------第三步-------");
      lawyerProxy.debate();
      System.out.println();

      System.out.println("仲裁結束");
   }
}

運行結果

-------第一步-------
準備仲裁申請匪凡!
律師幫忙處理膊畴!
-------第二步-------
收集證據(jù)!
律師幫忙處理病游!
-------第三步-------
開庭答辯唇跨!
律師幫忙處理稠通!

仲裁結束

稍微封裝一下獲得代理對象的方法:

public class Tribunal3 {
   public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
      XiaoWen xiaoWen = new XiaoWen();
      // 獲得代理對象
      ArbitrationStep lawyerProxy = (ArbitrationStep) getProxy(xiaoWen);

      // 通過代理對象調(diào)用方法
      System.out.println("-------第一步-------");
      lawyerProxy.prepareApp();

      System.out.println("-------第二步-------");
      lawyerProxy.collectEvi();

      System.out.println("-------第三步-------");
      lawyerProxy.debate();
      System.out.println();

      System.out.println("仲裁結束");
   }

   // 該方法返回一個代理對象
   public static Object getProxy(Object target) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
      // target為被代理對象,得到其代理類的Class對象
      Class<?> proxyClazz = Proxy.getProxyClass(target.getClass().getClassLoader(), target.getClass().getInterfaces());

      // 獲得構造函數(shù)
      Constructor<?> constructor = proxyClazz.getConstructor(InvocationHandler.class);

      // 獲得代理對象
      Object targetProxy = constructor.newInstance(new InvocationHandler() {
         @Override
         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object invoke = method.invoke(target, args);
            System.out.println("代理類增強方法");
            return invoke;
         }
      });
      return targetProxy;
   }
}

自己編寫了一個getProxy方法买猖,傳入被代理的對象改橘,返回一個代理對象。

通過newProxyInstance()獲得代理對象

上面的getProxy方法返回代理對象的過程是我們自己寫的玉控,通過Proxy類的newProxyInstance()可以直接返回代理對象

public static Object getProxyByProxyMethod(Object target) {
   // 直接返回代理對象
   return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         Object invoke = method.invoke(target, args);
         System.out.println("代理對象增強方法");
         return invoke;
      }
   });
}

看看newProxyInstance幫我們做了些什么工作

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{

    Objects.requireNonNull(h);

    // 下面這一段其實就是getProxyClass方法的內(nèi)部實現(xiàn)
    // --------------------------------------------------------------
    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    /*
     * Look up or generate the designated proxy class.
     */
    // 獲得接口加上構造函數(shù)后的Class對象
    Class<?> cl = getProxyClass0(loader, intfs);
    // ----------------------------------------------------------------

    /*
     * Invoke its constructor with the designated invocation handler.
     */

    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }

        // 獲得構造函數(shù)
        final Constructor<?> cons = cl.getConstructor(constructorParams);

        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }

        // 返回代理對象
        return cons.newInstance(new Object[]{h});

    } catch (IllegalAccessException|InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}

我們自己獲取代理對象的步驟是

  • 通過getProxyClass獲得代理對象的Class對象(接口+對應的構造函數(shù))
  • 通過Class對象調(diào)用得到構造方法
  • 構造方法去創(chuàng)建實例飞主,構造方法傳入InvocationHandler實例,需要實現(xiàn)其invoke方法

newProxyInstance幫我們獲取代理的步驟和上面類似高诺,只不過Class對象是直接通過getProxyClass0(loader, intfs)來獲取的碌识。而我們自己封裝的代碼中,使用的是getProxyClass方法虱而。但是該方法最終還是調(diào)用的getProxyClass0(loader, intfs)來獲取的Class對象

getProxyClass方法

public static Class<?> getProxyClass(ClassLoader loader,
                                     Class<?>... interfaces)
    throws IllegalArgumentException
{
    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    return getProxyClass0(loader, intfs);
}

3筏餐、如何實現(xiàn)方法增強

上面的例子確實將被代理對象的方法增強了,但這是如何實現(xiàn)的呢

InvocationHandler

我們在獲得代理類構造器的時候牡拇,傳入了InvocationHandler

Constructor<?> constructor = proxyClazz.getConstructor(InvocationHandler.class);

InvocationHandler是一個接口魁瞪,內(nèi)部只有一個方法invoke

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

在通過constructor獲取代理對象時,newInstance方法需要傳入一個invocationHandler的實例惠呼,這個實例重寫了invoke方法导俘,就是通過它代理對象增強了被代理對象的方法

Object targetProxy = constructor.newInstance(new InvocationHandler() {
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         // 調(diào)用被代理對象的對應方法
         // target 被代理對象
         // args 方法需要的參數(shù)
         Object invoke = method.invoke(target, args);
         System.out.println("代理類增強方法");
         return invoke;
      }
   });
   return targetProxy;
}

圖解**

總結

調(diào)用代理對象的方法時,實際上調(diào)用的是InvocationHandler的invoke方法罢杉,這個方法內(nèi)部不僅調(diào)用了被代理對象的方法趟畏,還可以增加其他功能

4、為什么必須實現(xiàn)接口

通過

lawyerProxy.getClass().getSuperclass()

可以獲得代理對象的父類滩租,可以看到其父類為:

class java.lang.reflect.Proxy

也就是無論是什么代理類赋秀,都會繼承自Proxy,而Java又是單繼承的律想,所以想要被代理對象與代理對象產(chǎn)生聯(lián)系猎莲,就只能通過接口來實現(xiàn)了

四、基于CGLib的動態(tài)代理

Java動態(tài)代理是面向接口的代理模式技即,如果沒有接口著洼,但是想要去實現(xiàn)動態(tài)代理,就需要用到CGLib來進行動態(tài)代理了

CGLIB原理

cglib是一個java字節(jié)碼的生成工具而叼,它動態(tài)生成一個被代理類的子類身笤,子類重寫被代理的類的所有不是final的方法。在子類中采用方法攔截的技術攔截所有父類方法的調(diào)用葵陵,順勢織入橫切邏輯液荸。

示例

被代理類:

public class HelloServiceImpl {
    public void sayHello(){
        System.out.println("Hello Zhanghao");
    }

    public void sayBey(){
        System.out.println("Bye Zhanghao");
    }
}

實現(xiàn)MethodInterceptor接口生成方法攔截器:

public class HelloMethodInterceptor  implements MethodInterceptor{
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("Before: "  + method.getName());
        Object object = methodProxy.invokeSuper(o, objects);
        System.out.println("After: " + method.getName());
        return object;
    }
}

生成代理類對象并打印在代理類對象調(diào)用方法之后的執(zhí)行結果:

public class Client {
    public static void main(String[] args) {
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/zhanghao/Documents/toy/spring-framework-source-study/");
        Enhancer enhancer = new Enhancer();
        //繼承被代理類
        enhancer.setSuperclass(HelloServiceImpl.class);
        //設置回調(diào)
        enhancer.setCallback(new HelloMethodInterceptor());
        //設置代理類對象
        HelloServiceImpl helloService = (HelloServiceImpl) enhancer.create();
        //在調(diào)用代理類中方法時會被我們實現(xiàn)的方法攔截器進行攔截
        helloService.sayBey();
    }
}
result:
Before: sayBey
Bye Zhanghao
After: sayBey

構建代理類過程

我們可以從上面的代碼示例中看到,代理類是由enhancer.create()創(chuàng)建的脱篙。Enhancer是CGLIB的字節(jié)碼增強器娇钱,可以很方便的對類進行拓展伤柄。

創(chuàng)建代理類的過程:

  • 生成代理類的二進制字節(jié)碼文件;
  • 加載二進制字節(jié)碼文搂,生成Class對象适刀;
  • 通過反射機制獲得實例構造,并創(chuàng)建代理類對象煤蹭。

enhancer.create()實現(xiàn):

/**
     * Generate a new class if necessary and uses the specified
     * callbacks (if any) to create a new object instance.
     * Uses the no-arg constructor of the superclass.
     * @return a new instance
     */
    public Object create() {
        classOnly = false;
        argumentTypes = null;
        return createHelper();
    }

     private Object createHelper() {
        validate();
        if (superclass != null) {
            //設置生成類的名稱
            setNamePrefix(superclass.getName());
        } else if (interfaces != null) {
            //設置生成類的名稱
            setNamePrefix(interfaces[ReflectUtils.findPackageProtected(interfaces)].getName());
        }
        //生成代理類對象(在KEY_FACTORY.newInstance(...)->生成代理類的二進制字節(jié)碼文件以及加載二進制字節(jié)碼)
        return super.create(KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,
                                                    ReflectUtils.getNames(interfaces),
                                                    filter,
                                                    callbackTypes,
                                                    useFactory,
                                                    interceptDuringConstruction,
                                                    serialVersionUID));
    }

cglic一共會自動生成三個字節(jié)碼文件笔喉。其中一個類HelloServiceImpl
EnhancerByCGLIB
d855d4dc
繼承了被代理類 HelloServiceImpl。這個類就是加強的代理類疯兼,其中會生成兩個方法CGLIB sayHello1sayHello()然遏。
其中sayHello():

  public final void sayHello() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if(this.CGLIB$CALLBACK_0 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if(var10000 != null) {
            var10000.intercept(this, CGLIB$sayHello$1$Method, CGLIB$emptyArgs, CGLIB$sayHello$1$Proxy);
        } else {
            super.sayHello();
        }
    }

當代理對象的執(zhí)行sayHello方法時,會首先判斷一下是否存在實現(xiàn)了MethodInterceptor接口的CGLIB$CALLBACK_0;吧彪,如果存在待侵,則將調(diào)用MethodInterceptor中的intercept方法。

與JDK代理對比

JDK代理要求被代理的類必須實現(xiàn)接口姨裸,有很強的局限性秧倾。而CGLIB動態(tài)代理則沒有此類強制性要求。簡單的說傀缩,CGLIB會讓生成的代理類繼承被代理類那先,并在代理類中對代理方法進行強化處理(前置處理、后置處理等)赡艰。但是如果被代理類被final修飾售淡,那么它不可被繼承,即不可被代理慷垮;同樣揖闸,如果被代理類中存在final修飾的方法,那么該方法也不可被代理料身。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載汤纸,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。
  • 序言:七十年代末芹血,一起剝皮案震驚了整個濱河市贮泞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌幔烛,老刑警劉巖啃擦,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異饿悬,居然都是意外死亡令蛉,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門乡恕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來言询,“玉大人,你說我怎么就攤上這事傲宜≡撕迹” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵函卒,是天一觀的道長辆憔。 經(jīng)常有香客問我,道長报嵌,這世上最難降的妖魔是什么虱咧? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮锚国,結果婚禮上腕巡,老公的妹妹穿的比我還像新娘。我一直安慰自己血筑,他們只是感情好绘沉,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著豺总,像睡著了一般车伞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上喻喳,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天另玖,我揣著相機與錄音,去河邊找鬼表伦。 笑死谦去,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的绑榴。 我是一名探鬼主播哪轿,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼翔怎!你這毒婦竟也來了窃诉?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤赤套,失蹤者是張志新(化名)和其女友劉穎飘痛,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體容握,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡宣脉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了剔氏。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片塑猖。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡竹祷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出羊苟,到底是詐尸還是另有隱情塑陵,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布蜡励,位于F島的核電站令花,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏凉倚。R本人自食惡果不足惜兼都,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望稽寒。 院中可真熱鬧扮碧,春花似錦、人聲如沸杏糙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽搔啊。三九已至柬祠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間负芋,已是汗流浹背漫蛔。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留旧蛾,地道東北人莽龟。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像锨天,于是被迫代替她去往敵國和親毯盈。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容