詳解靜態(tài)贩虾、動態(tài)代理以及應(yīng)用場景

一篇不太一樣的代理模式詳解催烘,仔細閱讀,你一定會獲取不一樣的代理見解缎罢,而不是人云亦云伊群。
查看了社區(qū)里關(guān)于代理模式描述,發(fā)現(xiàn)很多博客千篇一律甚至存在共性錯誤策精,寫此文提出自己對代理的見解舰始。

  • 靜態(tài)代理
  • 動態(tài)代理
    • JDK
    • CGLib
  • 靜態(tài)代理 VS 動態(tài)代理
  • 直觀的看到動態(tài)代理的模樣
  • 那種只通過接口就能實現(xiàn)功能的技術(shù)是如何實現(xiàn)的

女朋友問我什么是代理,靜態(tài)代理與動態(tài)代理的區(qū)別是什么蛮寂,各有什么優(yōu)勢呢蔽午?什么場景下適合靜態(tài)代理易茬,什么場景該使用動態(tài)代理呢酬蹋?真的如網(wǎng)上所說的,靜態(tài)代理一無是處嗎抽莱? 作為一個合格的男朋友范抓,必須給她安排上,這就說道說道食铐。

一匕垫、靜態(tài)代理

1.1 靜態(tài)代理架構(gòu)圖

靜態(tài)代理類圖.png

角色:

  1. 接口
  2. 被代理實現(xiàn)類
  3. 代理實現(xiàn)類

核心在于代理對象與被代理對象都需要實現(xiàn)同一個Interface接口,這一點也非常好理解虐呻,代理嘛 就是要代理被代理對象的所有方法象泵。

1.2 代碼案例

代碼比較簡單,使用靜態(tài)代理為一個只有加法功能的計算器在計算前后打印日志:

/**
 * 靜態(tài)代理
 * @author zcy
 * @date 2023/2/11
 * @description 求關(guān)注~
 */
public class StaticProxy {
    /**
     * 接口
     */
    interface Factory {
        int plus(int one, int two);
    }

    /**
     * 被代理對象
     */
    static class PlusFactory implements Factory {
        @Override
        public int plus(int one, int two) {
            return one + two;
        }
    }

    /**
     * 代理對象
     */
    static class ProxyFactory implements Factory {
        private Factory beAgentFactory;

        public ProxyFactory(Factory beAgentFactory) {
            this.beAgentFactory = beAgentFactory;
        }

        @Override
        public int plus(int one, int two) {
            try {
                System.out.println("before plus");
                return beAgentFactory.plus(one, two);
            } finally {
                System.out.println("after plus");
            }
        }
    }

    /**
     * 測試
     *
     * @param args
     */
    public static void main(String[] args) {
        // 被代理對象
        PlusFactory beAgentFactory = new PlusFactory();
        ProxyFactory proxyFactory = new ProxyFactory(beAgentFactory);
        int result = proxyFactory.plus(1, 2);
        System.out.println(result);
    }

}

**// 測試結(jié)果
before plus
after plus
3**

1.3 靜態(tài)代理的缺點 以及對現(xiàn)有博客的抨擊

目前網(wǎng)上關(guān)于靜態(tài)代理的優(yōu)缺點分析都存在著一個共性的錯誤斟叼。我們只有在深刻的理解靜態(tài)代理與其應(yīng)用場景才能發(fā)現(xiàn)這些錯誤描述偶惠。

1.3.1 千篇一律的認知錯誤

在國內(nèi)代碼社區(qū)中,搜索關(guān)于靜態(tài)代理的缺點文章朗涩,幾乎千篇一律的指責(zé)到:程序員要手動為每一個被代理類編寫對應(yīng)的代理類忽孽,如果當(dāng)前系統(tǒng)已經(jīng)有成百上千個類,工作量太大了谢床。

真的一定是這樣嗎兄一?

在上述demo中,ProxyFactory類構(gòu)造函數(shù)會接受一個Factory接口的實現(xiàn)類進行代理识腿。因此就算此處需要新增一個被代理對象出革,理論上也不需要再去做一個代理對象了,因為這些被代理類都是Factory類型渡讼。這算是Java最基礎(chǔ)的知識了骂束!

那么什么場景下费薄,當(dāng)新創(chuàng)建一個被代理類時,一定需要寫一個與之對應(yīng)的代理類呢栖雾?

我們還用上面的demo來看楞抡,上面的demo中代理類只干了一件事:計算的前后分別打印一行日志。假設(shè)我們現(xiàn)在又需要寫一個被代理類:HttpPlusFactory析藕,我們希望在完成加法之后將結(jié)果通過HTTP發(fā)送給其他系統(tǒng)召廷,這時我們原有的代理類ProxyFactory就顯得不夠用了,我們需要新建一個專門的HttpProxyFactory代理才行账胧。

簡而言之竞慢,代理存在的目的是想在不修改原有的代碼為前提實現(xiàn)一個共性需求控汉。即將共性的需求放入代理中實現(xiàn)挽荠,假設(shè)我們有不同的共性需求颅痊,我們才需要抽象出不同的代理對象纽匙。這一段話有點繞森逮,但是我希望你能明白含義嚎京。

1.3.2 靜態(tài)代理真正的缺點

抨擊完國內(nèi)千篇一律的錯誤之后啃擦,我們來談?wù)勳o態(tài)代理有什么不太方便的地方厨埋。

  1. 代理類編寫麻煩:

    這個代理對象與被代理對象一樣要實現(xiàn)同一個接口准脂,如果接口中有100個方法那么代理對象就得實現(xiàn)100個方法劫扒。

  2. 一些只有接口沒有被代理類的場景無法使用靜態(tài)代理:

    靜態(tài)代理中,一定是要存在一個被代理對象狸膏,這對于一些只通過接口就能完成業(yè)務(wù)功能的需求很不友好沟饥,譬如:MyBatis、Feign湾戳、Dubbo贤旷。

二、動態(tài)代理

動態(tài)代理砾脑,對于很多框架的實現(xiàn)非常友好幼驶,其誕生的目的就是讓我們不去寫代理對象(JDK或者CGlib幫助我們自動生成)。

2.1 實現(xiàn)方式

  • JDK

    基于Interface生成實現(xiàn)類完成代理

  • Cglib

    基于Class生成子類完成代理

2.1.1 JDK Demo

/**
 * 動態(tài)代理 - 手動編寫被代理類
 * @author zcy
 * @date 2023/2/11
 * @description 求關(guān)注~
 */
public class DynamicProxy {

    /**
     * JDK動態(tài)代理基于接口
     */
    interface Factory {
        int plus(int one, int two);
    }

    /**
     * 被代理對象
     */
    static class PlusFactory implements Factory {
        @Override
        public int plus(int one, int two) {
            return one + two;
        }
    }

    /**
     * 代理對象
     */
    static class ProxyFactory implements InvocationHandler {

        private Factory beAgentFactory;

        public ProxyFactory(Factory beAgentFactory) {
            this.beAgentFactory = beAgentFactory;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            try {
                System.out.println("before plus");
                return method.invoke(beAgentFactory, args);
            } finally {
                System.out.println("after plus");
            }
        }
    }

    public static void main(String[] args) {
        // 1\. 被代理對象
        Factory beAgentFactory = new PlusFactory();
        // 2\. 生成動態(tài)代理
        ProxyFactory proxyFactory = new ProxyFactory(beAgentFactory);
        Factory proxyInstance = (Factory) Proxy.newProxyInstance(proxyFactory.getClass().getClassLoader(), new Class[]{Factory.class}, proxyFactory);
        // 3.調(diào)用方法
        int plus = proxyInstance.plus(1, 2);
        System.out.println(plus);
    }
}

**// 測試結(jié)果
before plus
after plus
3**

看完這個demo之后拦止,你可能會想县遣,這和靜態(tài)代理有什么區(qū)別呢?還是要有接口汹族、被代理類萧求、代理類三個角色。但你要知道顶瞒,如果Factory接口中如有100個抽象方法夸政,那么代理類中只需要有一個invoke方法即可!這是和靜態(tài)代理中的代理類有著本質(zhì)的差別榴徐,這主要得益于動態(tài)代理中的反射機制守问。

當(dāng)然了匀归,如果只是這樣的話,動態(tài)代理的優(yōu)勢還不足以讓你折服耗帕,我們再來看下面這個例子:

/**
 * 動態(tài)代理 - 不需要手寫被代理類
 * @author zcy
 * @date 2023/2/11
 * @description 求關(guān)注~
 */
public class DynamicProxy {

    /**
     * JDK動態(tài)代理基于接口
     */
    interface Factory {
        int plus(int one, int two);
    }

    static class ProxyFactory implements InvocationHandler {

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            try {
                System.out.println("before plus");
                if (method.getName().equals("plus")) {
                    return (int) args[0] + (int) args[1];
                }
                return null;
            } finally {
                System.out.println("after plus");
            }
        }
    }

    public static void main(String[] args) {
        // 2\. 生成動態(tài)代理
        ProxyFactory proxyFactory = new ProxyFactory();
        Factory proxyInstance = (Factory) Proxy.newProxyInstance(proxyFactory.getClass().getClassLoader(), new Class[]{Factory.class}, proxyFactory);
        // 3.調(diào)用方法
        int plus = proxyInstance.plus(1, 2);
        System.out.println(plus);
    }
}

**// 測試結(jié)果
before plus
after plus
3**

在上面這個例子中穆端,我們直接去掉了被代理對象,而是將業(yè)務(wù)抽象到了代理類中仿便。想一想這還是得益于反射機制吧体啰,這時候你再對比對比靜態(tài)代理的代碼,是不是發(fā)現(xiàn)靜態(tài)代理就沒法這么玩了嗽仪。

2.1.2 Cglib Demo

/**
 * Cglib實現(xiàn)動態(tài)代理
 *
 * @author zcy
 * @date 2023/2/12
 * @description 求關(guān)注~
 */
public class CglibProxy {

    /**
     * 被代理對象
     */
    static class PlusFactory {
        public int plus(int one, int two) {
            return one + two;
        }
    }

    /**
     * Cglib Callback
     */
    static class ProxyCallBack implements MethodInterceptor {
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            try {
                System.out.println("before calculate");
                return methodProxy.invokeSuper(o, objects);
            } finally {
                System.out.println("after calculate");
            }
        }
    }

    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(PlusFactory.class);
        enhancer.setCallback(new ProxyCallBack());
        PlusFactory proxy = (PlusFactory) enhancer.create();
        int result = proxy.plus(1, 2);
        System.out.println(result);
    }

}

可以看到Cglib實現(xiàn)方式中荒勇,重點在于CallBack中,也就是此處的ProxyCallBack闻坚,其內(nèi)部的intercept方法參數(shù)中也有Method沽翔、參數(shù)等信息,因此使用上和JDK Proxy感覺非常相似窿凤。

2.2 JDK Proxy VS Cglib Proxy

  • JDK Proxy是基于接口生成實現(xiàn)類完成代理仅偎,Cglib Proxy是基于Class生成子類完成代理。所有Cglib中被代理類中的方法不能有private卷玉、final修飾哨颂。而JDK Proxy就沒有此限制,因為Java語言中接口中的方法天然不能使用private相种、final進行修飾。
  • 速度品姓,這是網(wǎng)上傳的比較多的一種比較寝并,因為我沒有做過相關(guān)實驗,因此不在此定論腹备。

三衬潦、靜態(tài)代理 VS 動態(tài)代理

3.1 區(qū)別

  • 靜態(tài)代理一定要編寫至少一個被代理類與一個代理類,而動態(tài)代理可以不編寫任何被代理類與代理類
  • 靜態(tài)代理編寫麻煩植酥,因為代理類與被代理類需要實現(xiàn)同一個接口镀岛,如果接口有100個方法,那么就需要實現(xiàn)100個方法友驮,而動態(tài)代理不需要漂羊,動態(tài)代理底層使用反射極大減少了開發(fā)量,將100個方法壓縮成一個invoke方法即可卸留。
靜態(tài)代理VS動態(tài)代理.drawio.png

3.2 使用場景

3.2.1 靜態(tài)代理

適合不存在共性需求的場景走越,比如被代理類中有100個方法,代理對象中自然也有100個方法耻瑟,但是這100個方法沒有共性需求旨指,可能第一個方法是打印日志赏酥,第二個方法需要發(fā)送HTTP… 那么這時候就適合用靜態(tài)代理了(假設(shè)一定需要使用代理的話)。

3.2.2 動態(tài)代理

動態(tài)代理非常適合框架底層谆构,并且共性需求很大裸扶,參考MyBatis、Feign搬素、Dubbo姓言。

四、直觀的看到動態(tài)代理的模樣

為了加深動態(tài)代理的理解蔗蹋,這里以JDK動態(tài)代理為例何荚,將上述2.1.1 JDK Demo中JDK生成的代理類打印出來。

在main方法中添加添加如下代碼猪杭,最后會在項目根路徑下保存生成的動態(tài)代理類:

//JDK1.8及以前的版本
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

//JDK1.8以后的版本
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");

生成的代理類如下:

final class $Proxy0 extends Proxy implements Factory {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int plus(int var1, int var2) throws  {
        try {
                        // 這里的super是Proxy餐塘,里面的h屬性,就是我們實現(xiàn)的InnvocationHandler類
            return (Integer)super.h.invoke(this, m3, new Object[]{var1, var2});
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.example.demo.dynamic.DynamicProxy$Factory").getMethod("plus", Integer.TYPE, Integer.TYPE);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

可以看到JDK生成的代理類是實現(xiàn)了接口皂吮,在實現(xiàn)的方法中調(diào)用了父類Proxy中的InvocationHandlerinvoke方法戒傻,并且將method信息、參數(shù)信息都傳過去蜂筹。

五需纳、那種只通過接口就能實現(xiàn)功能的技術(shù)是如何實現(xiàn)的

目前市面上只通編寫接口就能實現(xiàn)功能的框架有很多,比如:MyBatis艺挪、Feign等不翩。

那么你是否也折服于他們只通過Interface就能完成功能的能力?但只要你熟練掌握動態(tài)代理的使用與原理麻裳,理解這些框架并不難口蝠。

這些框架大致的實現(xiàn)思路為:

  1. 肯定使用JDK動態(tài)代理,因為只有接口津坑,沒有實現(xiàn)類妙蔗;
  2. 依賴Spring的生命周期鉤子,對需要生成動態(tài)代理的接口進行代理疆瑰,并將生成好的代理類放入Spring IOC中以提供后續(xù)業(yè)務(wù)的使用眉反。

這里我們開發(fā)一個Demo,需求是只需編寫Interface接口配合上一些注解完成HTTP發(fā)送穆役。

Demo如下:

-com.example.demo
    -app
          -baseinterface
            -ann
              HttpExecute.java
              HttpService.java
            -proxy
              JDKProxy.java
            -scanner
              ClassScanner.java
            -service
              BaseInterfaceBusiness.java
  1. 定義注解:
@Documented
@Target(value = ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface HttpService {
}
@Documented
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface HttpExecute {
    String url();
}

  1. Spring生命周期鉤子寸五,掃描所有使用@HttpService注解修飾的類,并創(chuàng)建其代理對象并存入IOC容器中
@Component
public class ClassScanner implements BeanDefinitionRegistryPostProcessor {

    private final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
    private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

    private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver);

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        try {
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + "com" + "/" + DEFAULT_RESOURCE_PATTERN;
            Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
            for (Resource resource : resources) {
                if (resource.isReadable()) {
                    MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
                    AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();

                    if (annotationMetadata.getAnnotationTypes().contains(HttpService.class.getName())) {
                        Class<?> aClass;
                        aClass = Class.forName(annotationMetadata.getClassName());
                        if (aClass != null) {
                            configurableListableBeanFactory.
                                    registerSingleton(
                                            toLowerCaseFirstOne(aClass.getSimpleName()),
                                            // 使用JDK動態(tài)創(chuàng)建代理對象
                                            JDKProxy.getInstance(aClass));
                            System.out.println("掃描到的 HttpService 接口" + aClass.getSimpleName());
                        }
                    } else {
                        continue;
                    }
                }
            }
        } catch (IOException e) {
        } catch (Exception e) {
        }
    }

    public static String toLowerCaseFirstOne(String s) {
        if (Character.isLowerCase(s.charAt(0))) {
            return s;
        } else {
            return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();
        }
    }

}

  1. InvocationHandler實現(xiàn)類:
public class JDKProxy implements InvocationHandler {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 對象的所有Object類,直接通過
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        }
        HttpExecute annotation = method.getAnnotation(HttpExecute.class);
        System.out.println("發(fā)送HTTP請求: " + annotation.url());
        return "";
    }

    public static <T> T getInstance(Class<T> interfaces) {
        return (T) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), new Class[]{interfaces}, new JDKProxy());
    }

}

  1. 業(yè)務(wù)應(yīng)用
@HttpService
public interface BaseInterfaceBusiness {

    @HttpExecute(url = "<https://xxxx.com>")
    void reduceInventory();

}

  1. 測試
@SpringBootTest
public class DynamicAppTest {

    @Resource
    private BaseInterfaceBusiness baseInterfaceBusiness;

    @Test
    public void baseInterfaceTest() {
        baseInterfaceBusiness.reduceInventory();
    }

}

// 結(jié)果:
// 發(fā)送HTTP請求: https://xxxx.com

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末孵睬,一起剝皮案震驚了整個濱河市播歼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖秘狞,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叭莫,死亡現(xiàn)場離奇詭異,居然都是意外死亡烁试,警方通過查閱死者的電腦和手機雇初,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來减响,“玉大人靖诗,你說我怎么就攤上這事≈荆” “怎么了刊橘?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長颂鸿。 經(jīng)常有香客問我促绵,道長,這世上最難降的妖魔是什么嘴纺? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任败晴,我火速辦了婚禮,結(jié)果婚禮上栽渴,老公的妹妹穿的比我還像新娘尖坤。我一直安慰自己,他們只是感情好闲擦,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布慢味。 她就那樣靜靜地躺著,像睡著了一般佛致。 火紅的嫁衣襯著肌膚如雪贮缕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天俺榆,我揣著相機與錄音,去河邊找鬼装哆。 笑死罐脊,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蜕琴。 我是一名探鬼主播萍桌,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼凌简!你這毒婦竟也來了上炎?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎藕施,沒想到半個月后寇损,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡裳食,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年矛市,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诲祸。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡浊吏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出救氯,到底是詐尸還是另有隱情找田,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布着憨,位于F島的核電站墩衙,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏享扔。R本人自食惡果不足惜底桂,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望惧眠。 院中可真熱鬧籽懦,春花似錦、人聲如沸氛魁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽秀存。三九已至捶码,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間或链,已是汗流浹背惫恼。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留澳盐,地道東北人祈纯。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像叼耙,于是被迫代替她去往敵國和親腕窥。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345

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