当javassist遇见Spring boot

最近在开发自己的开源项目REACTIVE DUBBO过程中,需要修改Dubbo的一个工具类RpcUtils,通过选型决定用字节码工具javassist对一个静态方法进行魔改。在编码阶段很顺利实现了我想要的效果,但是当打包进行验证时问题出现了。

问题分析

首先来到事发地点

try {
            ClassPool classPool = ClassPool.getDefault();
            CtClass ctClass = classPool.get(RPCUTILS_CLASS_NAME);
            CtMethod ctMethod = ctClass.getDeclaredMethod("getReturnTypes");
            //rename from `getReturnTypes` to `getReturnTypes0`
            ctClass.removeMethod(ctMethod);
            ctMethod.setName("getReturnTypes0");
            ctClass.addMethod(ctMethod);
            //add new `getReturnTypes` method according to RpcUtilsCracker.getReturnTypes
            CtClass ctClass1 = classPool.get(RpcUtilsCracker.class.getName());
            ctMethod = new CtMethod(ctClass1.getDeclaredMethod("getReturnTypes"),ctClass,null);
            ctClass.addMethod(ctMethod);
            ctClass.toClass();
        } catch (NotFoundException|CannotCompileException e) {
            logger.warn("crack RpcUtils failed",e);
        }

这段代码的用途是将RpcUtilsgetReturnTypes方法重命名,并增加自定义的方法。在开发阶段运行正常,然而在使用spring-boot:run运行(或者用Uber jar运行)时会报如下错误:

javassist.NotFoundException: com.alibaba.dubbo.rpc.support.RpcUtils
    at javassist.ClassPool.get(ClassPool.java:452) ~[javassist-3.20.0-GA.jar:na]
    at com.github.cherrythefatbunny.reactive.dubbo.extensions.rpc.support.RpcUtilsCracker.hack(RpcUtilsCracker.java:28) ~[reactive-dubbo-extensions-1.0.2-SNAPSHOT.jar:na]
    at com.github.cherrythefatbunny.reactive.dubbo.boot.ReactiveApplicationContextInitializer.initialize(ReactiveApplicationContextInitializer.java:14) [reactive-dubbo-starter-1.0.2-SNAPSHOT.jar:na]
    at org.springframework.boot.SpringApplication.applyInitializers(SpringApplication.java:649) [spring-boot-2.1.2.RELEASE.jar:2.1.2.RELEASE]
    at org.springframework.boot.SpringApplication.prepareContext(SpringApplication.java:373) [spring-boot-2.1.2.RELEASE.jar:2.1.2.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:314) [spring-boot-2.1.2.RELEASE.jar:2.1.2.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260) [spring-boot-2.1.2.RELEASE.jar:2.1.2.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248) [spring-boot-2.1.2.RELEASE.jar:2.1.2.RELEASE]
    at com.github.cherrythefatbunny.demo.consumer.ConsumerApplication.main(ConsumerApplication.java:12) [classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_121]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_121]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_121]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_121]
    at org.springframework.boot.maven.AbstractRunMojo$LaunchRunner.run(AbstractRunMojo.java:558) [spring-boot-maven-plugin-2.1.2.RELEASE.jar:2.1.2.RELEASE]
    at java.lang.Thread.run(Thread.java:745) [na:1.8.0_121]

报错语句为classPool.get(RPCUTILS_CLASS_NAME),由于这段代码是要修改存量的类,javassist需要首先读class文件。debug发现执行到Object.class.getResource(jarname)时,用IDE启动能返回该类的URL,而Spring boot启动则返回空。

继续跟断点发现,最终是使用类加载器Launcher$AppClassLoader完成加载操作,这样问题定位是出在ClassLoader身上,要想解决这个问题首先要从JVM类加载机制以及Spring boot的启动原理说起。

成功获取Class:


成功获取Class

未能获取Class:


未能获取Class

JVM类加载

类加载器

类加载器用来动态加载Java类到Java虚拟机的内存空间中,分为Bootstrap、Extension和System以及User-Defined。其中Bootstrap负责加载<JAVA_HOME>/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的类;Extension负责加载<JAVA_HOME>/lib/ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类;System负责加载ClassPath下的类。

双亲委派模型

除了Bootstrap,每个类加载器都有父类加载器,当类加载器接收到类加载请求时它会先将请求发给父类加载器处理,如果加载不成功才自己尝试加载。所以通过子类加载器可以找到父类加载器加载的类,反之不可以

JVM ClassLoader

Spring boot启动原理导致的差异

通过IDE(IntelliJ)启动

开发阶段可以通过项目的主函数启动Spring boot,通过启动命令我们发现IDE会自动将依赖加入classpath,这样的启动方式和普通Java项目并无二致,javassist也能顺利找到类RpcUtils

/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/bin/java 
-agentlib:jdwp=transport=dt_socket,address=127.0.0.1:59051,suspend=y,server=n 
-Dvisualvm.id=202724856185364 -XX:TieredStopAtLevel=1 -noverify 
-Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote 
-Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true 
-javaagent:/Users/cherry/Library/Caches/IntelliJIdea2018.3/captureAgent/debugger-agent.jar -Dfile.encoding=UTF-8 
-classpath "/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/charsets.jar:
            ...
            /Users/cherry/IdeaProjects/reactive-dubbo/demo/consumer/target/classes:
            /Users/cherry/IdeaProjects/reactive-dubbo/demo/facade/target/classes:
            ...
            /Users/cherry/.m2/repository/org/springframework/boot/spring-boot-starter/2.1.2.RELEASE/spring-boot-starter-2.1.2.RELEASE.jar:/Users/cherry/.m2/repository/org/springframework/boot/spring-boot/2.1.2.RELEASE/spring-boot-2.1.2.RELEASE.jar:/Users/cherry/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/2.1.2.RELEASE/spring-boot-autoconfigure-2.1.2.RELEASE.jar:/Users/cherry/.m2/repository/org/springframework/boot/spring-boot-starter-logging/2.1.2.RELEASE/spring-boot-starter-logging-2.1.2.RELEASE.jar:/Users/cherry/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar:/Users/cherry/.m2/repository/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar:/Users/cherry/.m2/repository/org/apache/logging/log4j/log4j-to-slf4j/2.11.1/log4j-to-slf4j-2.11.1.jar:/Users/cherry/.m2/repository/org/apache/logging/log4j/log4j-api/2.11.1/log4j-api-2.11.1.jar:/Users/cherry/.m2/repository/org/slf4j/jul-to-slf4j/1.7.25/jul-to-slf4j-1.7.25.jar:/Users/cherry/.m2/repository/javax/annotation/javax.annotation-api/1.3.2/javax.annotation-api-1.3.2.jar:/Users/cherry/.m2/repository/org/springframework/spring-core/5.1.4.RELEASE/spring-core-5.1.4.RELEASE.jar:/Users/cherry/.m2/repository/org/springframework/spring-jcl/5.1.4.RELEASE/spring-jcl-5.1.4.RELEASE.jar:/Users/cherry/.m2/repository/org/yaml/snakeyaml/1.23/snakeyaml-1.23.jar:/Users/cherry/.m2/repository/com/alibaba/spring/spring-context-support/1.0.2/spring-context-support-1.0.2.jar:/Users/cherry/.m2/repository/com/alibaba/boot/dubbo-spring-boot-starter/0.2.1.RELEASE/dubbo-spring-boot-starter-0.2.1.RELEASE.jar:/Users/cherry/.m2/repository/com/alibaba/boot/dubbo-spring-boot-autoconfigure/0.2.1.RELEASE/dubbo-spring-boot-autoconfigure-0.2.1.RELEASE.jar:/Users/cherry/.m2/repository/com/alibaba/dubbo/2.6.5/dubbo-2.6.5.jar:/Users/cherry/.m2/repository/org/springframework/spring-context/5.1.4.RELEASE/spring-context-5.1.4.RELEASE.jar:/Users/cherry/.m2/repository/org/springframework/spring-aop/5.1.4.RELEASE/spring-aop-5.1.4.RELEASE.jar:/Users/cherry/.m2/repository/org/springframework/spring-beans/5.1.4.RELEASE/spring-beans-5.1.4.RELEASE.jar:/Users/cherry/.m2/repository/org/springframework/spring-expression/5.1.4.RELEASE/spring-expression-5.1.4.RELEASE.jar:/Users/cherry/.m2/repository/org/javassist/javassist/3.20.0-GA/javassist-3.20.0-GA.jar:/Users/cherry/.m2/repository/org/jboss/netty/netty/3.2.5.Final/netty-3.2.5.Final.jar:/Users/cherry/.m2/repository/io/netty/netty-all/4.1.31.Final/netty-all-4.1.31.Final.jar:/Users/cherry/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.9.8/jackson-databind-2.9.8.jar:/Users/cherry/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.9.0/jackson-annotations-2.9.0.jar:/Users/cherry/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.9.8/jackson-core-2.9.8.jar:/Users/cherry/.m2/repository/org/springframework/boot/spring-boot-starter-webflux/2.1.2.RELEASE/spring-boot-starter-webflux-2.1.2.RELEASE.jar:/Users/cherry/.m2/repository/org/springframework/boot/spring-boot-starter-json/2.1.2.RELEASE/spring-boot-starter-json-2.1.2.RELEASE.jar:/Users/cherry/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.9.8/jackson-datatype-jdk8-2.9.8.jar:/Users/cherry/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.9.8/jackson-datatype-jsr310-2.9.8.jar:/Users/cherry/.m2/repository/com/fasterxml/jackson/module/jackson-module-parameter-names/2.9.8/jackson-module-parameter-names-2.9.8.jar:/Users/cherry/.m2/repository/org/springframework/boot/spring-boot-starter-reactor-netty/2.1.2.RELEASE/spring-boot-starter-reactor-netty-2.1.2.RELEASE.jar:/Users/cherry/.m2/repository/io/projectreactor/netty/reactor-netty/0.8.4.RELEASE/reactor-netty-0.8.4.RELEASE.jar:/Users/cherry/.m2/repository/io/netty/netty-codec-http/4.1.31.Final/netty-codec-http-4.1.31.Final.jar:/Users/cherry/.m2/repository/io/netty/netty-codec/4.1.31.Final/netty-codec-4.1.31.Final.jar:/Users/cherry/.m2/repository/io/netty/netty-codec-http2/4.1.31.Final/netty-codec-http2-4.1.31.Final.jar:/Users/cherry/.m2/repository/io/netty/netty-handler/4.1.31.Final/netty-handler-4.1.31.Final.jar:/Users/cherry/.m2/repository/io/netty/netty-buffer/4.1.31.Final/netty-buffer-4.1.31.Final.jar:/Users/cherry/.m2/repository/io/netty/netty-transport/4.1.31.Final/netty-transport-4.1.31.Final.jar:/Users/cherry/.m2/repository/io/netty/netty-resolver/4.1.31.Final/netty-resolver-4.1.31.Final.jar:/Users/cherry/.m2/repository/io/netty/netty-handler-proxy/4.1.31.Final/netty-handler-proxy-4.1.31.Final.jar:/Users/cherry/.m2/repository/io/netty/netty-codec-socks/4.1.31.Final/netty-codec-socks-4.1.31.Final.jar:/Users/cherry/.m2/repository/io/netty/netty-transport-native-epoll/4.1.31.Final/netty-transport-native-epoll-4.1.31.Final-linux-x86_64.jar:/Users/cherry/.m2/repository/io/netty/netty-common/4.1.31.Final/netty-common-4.1.31.Final.jar:/Users/cherry/.m2/repository/io/netty/netty-transport-native-unix-common/4.1.31.Final/netty-transport-native-unix-common-4.1.31.Final.jar:/Users/cherry/.m2/repository/org/hibernate/validator/hibernate-validator/6.0.14.Final/hibernate-validator-6.0.14.Final.jar:/Users/cherry/.m2/repository/javax/validation/validation-api/2.0.1.Final/validation-api-2.0.1.Final.jar:/Users/cherry/.m2/repository/org/jboss/logging/jboss-logging/3.3.2.Final/jboss-logging-3.3.2.Final.jar:/Users/cherry/.m2/repository/com/fasterxml/classmate/1.4.0/classmate-1.4.0.jar:/Users/cherry/.m2/repository/org/springframework/spring-web/5.1.4.RELEASE/spring-web-5.1.4.RELEASE.jar:/Users/cherry/.m2/repository/org/springframework/spring-webflux/5.1.4.RELEASE/spring-webflux-5.1.4.RELEASE.jar:/Users/cherry/.m2/repository/org/synchronoss/cloud/nio-multipart-parser/1.1.0/nio-multipart-parser-1.1.0.jar:/Users/cherry/.m2/repository/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar:/Users/cherry/.m2/repository/org/synchronoss/cloud/nio-stream-storage/1.1.3/nio-stream-storage-1.1.3.jar:/Users/cherry/.m2/repository/com/alibaba/dubbo-registry-zookeeper/2.6.5/dubbo-registry-zookeeper-2.6.5.jar:/Users/cherry/.m2/repository/com/alibaba/dubbo-registry-api/2.6.5/dubbo-registry-api-2.6.5.jar:/Users/cherry/.m2/repository/com/alibaba/dubbo-cluster/2.6.5/dubbo-cluster-2.6.5.jar:/Users/cherry/.m2/repository/com/alibaba/dubbo-rpc-api/2.6.5/dubbo-rpc-api-2.6.5.jar:/Users/cherry/.m2/repository/com/alibaba/dubbo-serialization-api/2.6.5/dubbo-serialization-api-2.6.5.jar:/Users/cherry/.m2/repository/com/alibaba/dubbo-container-api/2.6.5/dubbo-container-api-2.6.5.jar:/Users/cherry/.m2/repository/com/alibaba/dubbo-remoting-zookeeper/2.6.5/dubbo-remoting-zookeeper-2.6.5.jar:/Users/cherry/.m2/repository/com/alibaba/dubbo-common/2.6.5/dubbo-common-2.6.5.jar:/Users/cherry/.m2/repository/commons-logging/commons-logging/1.2/commons-logging-1.2.jar:/Users/cherry/.m2/repository/log4j/log4j/1.2.16/log4j-1.2.16.jar:/Users/cherry/.m2/repository/com/alibaba/hessian-lite/3.2.4/hessian-lite-3.2.4.jar:/Users/cherry/.m2/repository/com/alibaba/fastjson/1.2.46/fastjson-1.2.46.jar:/Users/cherry/.m2/repository/com/esotericsoftware/kryo/4.0.1/kryo-4.0.1.jar:/Users/cherry/.m2/repository/com/esotericsoftware/reflectasm/1.11.3/reflectasm-1.11.3.jar:/Users/cherry/.m2/repository/com/esotericsoftware/minlog/1.3.0/minlog-1.3.0.jar:/Users/cherry/.m2/repository/de/javakaffee/kryo-serializers/0.42/kryo-serializers-0.42.jar:/Users/cherry/.m2/repository/de/ruedigermoeller/fst/2.48-jdk-6/fst-2.48-jdk-6.jar:/Users/cherry/.m2/repository/com/cedarsoftware/java-util/1.9.0/java-util-1.9.0.jar:/Users/cherry/.m2/repository/com/cedarsoftware/json-io/2.5.1/json-io-2.5.1.jar:/Users/cherry/.m2/repository/org/apache/zookeeper/zookeeper/3.4.9/zookeeper-3.4.9.jar:/Users/cherry/.m2/repository/org/slf4j/slf4j-log4j12/1.7.25/slf4j-log4j12-1.7.25.jar:/Users/cherry/.m2/repository/jline/jline/0.9.94/jline-0.9.94.jar:/Users/cherry/.m2/repository/io/netty/netty/3.10.5.Final/netty-3.10.5.Final.jar:/Users/cherry/.m2/repository/com/101tec/zkclient/0.2/zkclient-0.2.jar:/Users/cherry/.m2/repository/org/apache/curator/curator-framework/2.12.0/curator-framework-2.12.0.jar:/Users/cherry/.m2/repository/org/apache/curator/curator-client/2.12.0/curator-client-2.12.0.jar:/Users/cherry/.m2/repository/com/google/guava/guava/16.0.1/guava-16.0.1.jar:/Users/cherry/IdeaProjects/reactive-dubbo/reactive-dubbo-starter/target/classes:/Users/cherry/IdeaProjects/reactive-dubbo/reactive-dubbo-extensions/target/classes:/Users/cherry/.m2/repository/org/springframework/boot/spring-boot-autoconfigure-processor/2.1.2.RELEASE/spring-boot-autoconfigure-processor-2.1.2.RELEASE.jar:/Users/cherry/.m2/repository/org/springframework/boot/spring-boot-configuration-processor/2.1.2.RELEASE/spring-boot-configuration-processor-2.1.2.RELEASE.jar:/Users/cherry/.m2/repository/io/projectreactor/reactor-core/3.2.5.RELEASE/reactor-core-3.2.5.RELEASE.jar:/Users/cherry/.m2/repository/org/reactivestreams/reactive-streams/1.0.2/reactive-streams-1.0.2.jar:/Users/cherry/.m2/repository/org/projectlombok/lombok/1.18.4/lombok-1.18.4.jar:/Users/cherry/.m2/repository/org/ow2/asm/asm/5.0.4/asm-5.0.4.jar:/Users/cherry/.m2/repository/org/objenesis/objenesis/2.6/objenesis-2.6.jar:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar" com.github.cherrythefatbunny.demo.consumer.ConsumerApplication

通过jar启动

Spring boot定义了一套打包标准,将依赖的jar都打包成一个Uber jar,启动类变成了Spring boot打包进去的JarLauncherJarLauncher会通过自定义的LaunchedURLClassLoader加载Uber jar内部的lib jar内部的class😂,
由于所寻找的类不在classpath内,因此通过之前的Launcher$AppClassLoader是无法找到的,这正是报错的原因。

── BOOT-INF
│   ├── classes
│   │   ├── application.properties
│   │   └── com
│   │       └── github
│   │           └── cherrythefatbunny
│   │               └── demo
│   │                   └── consumer
│   │                       ├── ConsumerApplication.class
│   │                       └── PersonController.class
│   └── lib
│       ├── asm-5.0.4.jar
│       ...
│       ├── zkclient-0.2.jar
│       └── zookeeper-3.4.9.jar
├── META-INF
│   ├── MANIFEST.MF
│   └── maven
│       └── com.github.cherrythefatbunny.demo
│           └── consumer
│               ├── pom.properties
│               └── pom.xml
└── org
    └── springframework
        └── boot
            └── loader
                ├── ExecutableArchiveLauncher.class
                ├── JarLauncher.class
                ├── LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class
                ├── LaunchedURLClassLoader.class
                ├── Launcher.class
                ...

解决问题

找到了问题的根源是javassist默认使用Object类的Launcher$AppClassLoader,而所依赖的jar都是通过Spring boot的LaunchedURLClassLoader加载的。根据JVM的双亲委派模型可知,使用Launcher$AppClassLoader是无法查找子类加载器LaunchedURLClassLoader所加载类的。幸好javassist为我们留下了扩展的方法,classPool.appendClassPath(new LoaderClassPath(RpcUtilsCracker.class.getClassLoader()));,通过向ClassPool添加一个能访问到类RpcUtils的ClassLoader就可以解决问题。这里我选了代码所在的类RpcUtilsCracker,由于我开发的是一个Spring boot starter,因此该类会被LaunchedURLClassLoader所加载。经过编译打包发现,报错不再出现😎。

总结

Spring boot是Spring生态重要的组成,给程序员带来了极大便利,但不代表这个项目问题就少,而且底层框架不出问题则已,一有问题就让你怀疑人生。之前就曾经遇到过使用Spring boot热部署工具,导致类型判定出现问题。

最近在学习响应式编程,最开始有一定门槛,一旦迈过去就欲罢不能。趁热写了一个小项目REACTIVE DUBBO,Make your Dubbo reactive!

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

推荐阅读更多精彩内容