XML处理利器:XStream

XML处理利器:XStream

  • XStream 概述

    XStream是一套简洁易用的开源类库,用于将Java对象序列化为XML或者将XML反序列化为Java对象,是Java对象和XML之间的一个双向转化器。

    XStream主要特点

    1 灵活易用:提供了简单,灵活,易用的统一接口,用户无需了解底层细节

    2 无需映射:大多数对象都可以在无需映射的情况下进行序列化和反序列化的操作。

    3 高速稳定:解析速度快,占用内存少。

    4 灵活转换:转换策略是可以定制的,允许自定义类型存储为指定的XML格式。

    5 易于集成:通过实现特定的接口,可以直接与其他任何树状结构进行序列化与反序列化操作。

  • XStream 架构组成

    1 Convert(转换器)

    当 XStream遇到需要转换的对象时,它会委派给合适的转换器实现。XStream为通用类型提供了多种转换器实现,包括基本数据类型,String,Collections,Arrays,null,Date等。

    2 I/O(输入/输出)

    XStream是通过接口HierarchicalStreamWriter和HierarchicalStreamReader从底层XML数据中抽象而来的,分别用于序列化和反序列化操作。

    3 Context(上下文引用)

    在XStream序列化和反序列化对象时,它会创建两个类 MarshallingContext和UnmarshallingContext,由他们来处理数据并委派合适的转换器。XStream提供了三种上下文的默认实现,它们之间存在细微的差别。默认值可以通过XStream.setMode()方法调整,可选值如下:

    (1)XStream.XPATH_REFERENCES:(默认值),通过XPath引用来标识重复的引用。

    (2)XStream_ID_REFERENCES:使用Id引用来标识重复引用

    (3)XStream_NO_REFERENCES:对象作为树形结构,重复的引用被视为两个不同的对象,循环引用会导致异常发生。这种模式速度快,占用内存少。

    4 Facade(统一入口)

    作为XStream的统一入口点,它将上年所提及的重要组件集成在一起,以统一的接口开放出来。

  • XStream使用

    引入一下jar包

    <dependency>
        <groupId>com.thougthworks.xstream</groupId>
        <artifactId>xstream</artifactId>
        <version>1.4.9</version>
    </dependency>
    

    使用代码如下:

    public class XstreamSample {
    
        private static XStream xStream;
    
        static {
            xStream = new XStream(new DomDriver());
        }
    
        public static User getUser() {
            LoginLog loginLog = new LoginLog();
            loginLog.setIp("192.168.1.91");
            loginLog.setLoginLogId(1);
            loginLog.setLoginDate(new Date());
            User user = new User();
            user.setUserName("wsq");
            user.setUserId(1);
            user.setLogs(Lists.newArrayList(loginLog));
            return user;
        }
    
        public static void main(String args[]) {
            try {
                objectToXml();
                xmlToObject();
    
            } catch (Exception e) {
                System.out.println("转换异常" + e);
            }
    
        }
    
        public static void objectToXml() throws Exception {
            User user = getUser();
            FileOutputStream outputStream = new FileOutputStream("/Users/wsq/Desktop/XstreamSample.xml");
            xStream.toXML(user, outputStream);
        }
    
        public static void xmlToObject() throws Exception {
            FileInputStream outputStream = new FileInputStream("/Users/wsq/Desktop/XstreamSample.xml");
            User user = (User) xStream.fromXML(outputStream);
            for (LoginLog login : user.getLogs()) {
                System.out.println(login);
            }
        }
    
    
        @Data
        public static class User {
            private int userId;
            private String userName;
            private String password;
            private int credits;
            private String lastIp;
            private Date lastVisit;
            private List<LoginLog> logs;
    
        }
    
        @Data
        public static class LoginLog {
            private int loginLogId;
            private int userId;
            private String ip;
            private Date loginDate;
        }
    }
    

    生成的XML文件

    <cn.com.servyou.xqy.gaia.facadeimpl.XstreamSample_-User>
      <userId>1</userId>
      <userName>wsq</userName>
      <credits>0</credits>
      <logs>
        <cn.com.servyou.xqy.gaia.facadeimpl.XstreamSample_-LoginLog>
          <loginLogId>1</loginLogId>
          <userId>0</userId>
          <ip>192.168.1.91</ip>
          <loginDate>2020-06-14 06:50:46.642 UTC</loginDate>
        </cn.com.servyou.xqy.gaia.facadeimpl.XstreamSample_-LoginLog>
      </logs>
    </cn.com.servyou.xqy.gaia.facadeimpl.XstreamSample_-User>
    
    

    使用XStream别名

    在默认情况下,Java对象到XML的映射是Java对象属性名对应XML的元素名,Java类的全名对应XML根元素的名字。在实际应用中,如果XML和Java类都已经存在相应的名字,那么,在进行转换时,需要设置别名进行映射。

    XStream别名配置包含三种情况

    1. 类别名:用alias(String name,Class type).
    2. 类成员别名:用aliasField(String alias,Class definedIn,String fieldName).
    3. 类成员作为属性别名:用aliasAttribute(Class definedIn,String attributeName,String alias).单独命名没有意义,还要通过userAttributeFor(Class definedIn,String fieldName)应用到某个类上。

    从上一个实例生成的XML文件来看,生成的XML 元素结构不是很友好。接下来使用XStream提供的别名机制来修饰生成的XML元素的结构。如下所示:

    修改之前的代码为:

    static {
        xStream = new XStream(new DomDriver());
        xStream.alias("loginLog", LoginLog.class);
        xStream.alias("user", User.class);
        xStream.aliasField("id", User.class, "userId");
        xStream.aliasAttribute(LoginLog.class, "userId", "id");
        xStream.useAttributeFor(LoginLog.class, "userId");
        xStream.addImplicitCollection(User.class, "logs");
    }
    

    生成XML如下:

    <user>
      <id>1</id>
      <userName>wsq</userName>
      <credits>0</credits>
      <loginLog id="0">
        <loginLogId>1</loginLogId>
        <ip>192.168.1.91</ip>
        <loginDate>2020-06-14 07:18:16.818 UTC</loginDate>
      </loginLog>
    </user>
    
  • XStream转换器

    在开发过程中,有时可能需要转换一些自定义类型,此时默认的映射方式可能无法满足需要。不用担心,XStream已经提供了丰富的扩展点,用户可以实现自己的转换器,然后调用registerConvert()方法注册自定义的转换器。实现自定义的转换器很简单,只要实现XStream提供的Convert接口并实现其方法即可。

    上一个实例已经成功运用XStream进行对象与XML的相互转换,并应用XStream提供的别名机制设置生成XML的结构。下面使用XStream提供的转换器扩展接口,对生成的XML文件继续优化。

    首先需要实现Convert接口来编写一个日期转换器,具体代码如下:

    d

    public class DateConvert implements Converter {
    
        private Locale locale;
    
        public DateConvert(Locale locale) {
            super();
            this.locale = locale;
        }
    
        //编写Java对象到 XML的转换逻辑
        @Override
        public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) {
            DateFormat formatter = DateFormat.getDateInstance(DateFormat.DATE_FIELD, this.locale);
            writer.setValue(formatter.format(value));
        }
    
        //编写 XML 到 java的转换逻辑
        @Override
        public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
            GregorianCalendar calendar = new GregorianCalendar();
            DateFormat formatter = DateFormat.getDateInstance(DateFormat.DATE_FIELD, this.locale);
            try {
                calendar.setTime(formatter.parse(reader.getValue()));
            } catch (Exception e) {
                throw new ConversionException(e.getMessage(), e);
            }
            return calendar.getGregorianChange();
        }
    
    
        //判断要转换的类型
        @Override
        public boolean canConvert(Class aClass) {
            return Date.class.isAssignableFrom(aClass);
        }
    }
    

    剩下的事情就是调用XStream#registerConvert()方法注册自定义的日期转换器。

    xStream.registerConverter(new DateConvert(Locale.forLanguageTag("CN")));
    

    XML内容如下:

    <user>
      <id>1</id>
      <userName>wsq</userName>
      <credits>0</credits>
      <loginLog id="0">
        <loginLogId>1</loginLogId>
        <ip>192.168.1.91</ip>
        <loginDate>6/14/20</loginDate>
      </loginLog>
    </user>
    
  • XStream注解

    注解 说明 作用目标
    @XStreamAlias 别名注解 类,字段
    @XStreamAsAttribute 转换成属性 字段
    @XStreamOmitField 忽略字段 字段
    @XStreamConvert 注入转换器 对象
    @XStreamImplicit 隐式集合 集合字段

    XStream不但可以通过编码的方式对XML进行转换,而且支持基于注解的方式进行转换。下面使用XStream提供的注解来改造上面的实例。首先需要对User和LoginLog添加相应的注解

    如下所示:

    public class XstreamSample {
    
        private static XStream xStream;
    
        static {
            xStream = new XStream(new DomDriver());
            //实现该方法,判断要转换的类型
            xStream.processAnnotations(User.class);
            xStream.processAnnotations(LoginLog.class);
            //自动加载注解Bean
            xStream.autodetectAnnotations(true);
        }
    
        public static User getUser() {
            LoginLog loginLog = new LoginLog();
            loginLog.setIp("192.168.1.91");
            loginLog.setLoginLogId(1);
            loginLog.setLoginDate(new Date());
            User user = new User();
            user.setUserName("wsq");
            user.setUserId(1);
            user.setLogs(Lists.newArrayList(loginLog));
            return user;
        }
    
        public static void main(String args[]) {
            try {
                objectToXml();
                xmlToObject();
    
            } catch (Exception e) {
                System.out.println("转换异常" + e);
            }
    
        }
    
        public static void objectToXml() throws Exception {
            User user = getUser();
            FileOutputStream outputStream = new FileOutputStream("/Users/wsq/Desktop/XstreamSample.xml");
            xStream.toXML(user, outputStream);
        }
    
        public static void xmlToObject() throws Exception {
            FileInputStream outputStream = new FileInputStream("/Users/wsq/Desktop/XstreamSample.xml");
            User user = (User) xStream.fromXML(outputStream);
            for (LoginLog login : user.getLogs()) {
                System.out.println(login);
            }
        }
    
    
    
        @Data
        @XStreamAlias("user")
        public static class User {
    
            @XStreamAsAttribute
            @XStreamAlias("id")
            private int userId;
            @XStreamAlias("userName")
            private String userName;
            @XStreamAlias("password")
            private String password;
            @XStreamAlias("credits")
            private int credits;
            @XStreamAlias("lastIp")
            private String lastIp;
            @XStreamConverter(DateConvert.class)
            private Date lastVisit;
            @XStreamImplicit
            private List<LoginLog> logs;
    
        }
    
        @Data
        public static class LoginLog {
            @XStreamAlias("loginLogId")
            private int loginLogId;
            @XStreamAlias("userId")
            private int userId;
            @XStreamAlias("ip")
            private String ip;
            @XStreamConverter(DateConvert.class)
            private Date loginDate;
        }
    }
    

    其中@XStreamAlias为别名注解,一般作用于目标类或字段,如实例中的@XStreamAlias("user"),@XStreamAlias("id")。@XStreamConvert注解用于注入自定义的转换器,如实例中的@XStreamConverter(DateConvert.class),注入一个日期转换器。对于集合类型,可以使用@XStreamImplicit注解来隐藏,其作用与XStream#addImplicitCollection()方法一样。@XStreamAsAttribute注解将Java对象属性映射为XML元素的一个属性。@XStreamOmitField注解标注Java对象的属性将不再出现在XMl中。

  • 流化对象

    XStream为java.io.ObjectInputStream和java.io.ObjectOutputStream提供替换的实现,允许以对象流的方式进行XML序列化和反序列化操作。这对于处理集合对象非常有用(List<USer> users),在内存中只保留一个User对象流。很显然,我们应该使用基于流而非DOM的XML解析器读取XML,以提高性能。

    创建一个输出流,至于怎么输出可以使用多种方法,其原理是一样的。在这里就不得不提HierarchicalStreamWriter。HierarchicalStreamWriter是一个接口,从字面意思上来说它是由层级关系的输出流。XStream默认提供了几个常用的实现类用于输出,如CompactWrite,PrettyPrintWriter。下面应用XStream流化对象来处理XML序列化及反序列化。

    public class XstreamSample {
    
        private static XStream xStream;
    
        static {
            xStream = new XStream(new DomDriver());
            //实现该方法,判断要转换的类型
            xStream.processAnnotations(User.class);
            xStream.processAnnotations(LoginLog.class);
            //自动加载注解Bean
            xStream.autodetectAnnotations(true);
        }
    
        public static User getUser() {
            LoginLog loginLog = new LoginLog();
            loginLog.setIp("192.168.1.91");
            loginLog.setLoginLogId(1);
            loginLog.setLoginDate(new Date());
            User user = new User();
            user.setUserName("wsq");
            user.setUserId(1);
            user.setLogs(Lists.newArrayList(loginLog));
            return user;
        }
    
        public static void main(String args[]) {
            try {
                objectToXml();
                xmlToObject();
    
            } catch (Exception e) {
                System.out.println("转换异常" + e);
            }
    
        }
    
        public static void objectToXml() throws Exception {
            User user = getUser();
            PrintWriter pw = new PrintWriter("/Users/wsq/Desktop/XstreamSample.xml");
    
            PrettyPrintWriter ppw = new PrettyPrintWriter(pw);
    //        CompactWriter cw = new CompactWriter(pw);
            ObjectOutputStream out = xStream.createObjectOutputStream(ppw);
            out.writeObject(user);
            out.close();
        }
    
        public static void xmlToObject() throws Exception {
            FileReader reader = new FileReader("/Users/wsq/Desktop/XstreamSample.xml");
            BufferedReader bufferedReader = new BufferedReader(reader);
            ObjectInputStream input = xStream.createObjectInputStream(bufferedReader);
    
            User user = (User) input.readObject();
            for (LoginLog login : user.getLogs()) {
                System.out.println(login);
            }
        }
    
    
        @Data
        @XStreamAlias("user")
        public static class User {
    
            @XStreamAsAttribute
            @XStreamAlias("id")
            private int userId;
            @XStreamAlias("userName")
            private String userName;
            @XStreamAlias("password")
            private String password;
            @XStreamAlias("credits")
            private int credits;
            @XStreamAlias("lastIp")
            private String lastIp;
            @XStreamConverter(DateConvert.class)
            private Date lastVisit;
            @XStreamImplicit
            private List<LoginLog> logs;
    
        }
    
        @Data
        public static class LoginLog {
            @XStreamAlias("loginLogId")
            private int loginLogId;
            @XStreamAlias("userId")
            private int userId;
            @XStreamAlias("ip")
            private String ip;
            @XStreamConverter(DateConvert.class)
            private Date loginDate;
        }
    }
    
    public class DateConvert implements Converter {
    
        private Locale locale;
    
        public DateConvert() {
            super();
            this.locale = Locale.forLanguageTag("CN");
        }
    
        public DateConvert(Locale locale) {
            super();
            this.locale = locale;
        }
    
        //编写Java对象到 XML的转换逻辑
        @Override
        public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) {
            DateFormat formatter = DateFormat.getDateInstance(DateFormat.DATE_FIELD, this.locale);
            writer.setValue(formatter.format(value));
        }
    
        //编写 XML 到 java的转换逻辑
        @Override
        public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
            GregorianCalendar calendar = new GregorianCalendar();
            DateFormat formatter = DateFormat.getDateInstance(DateFormat.DATE_FIELD, this.locale);
            try {
                calendar.setTime(formatter.parse(reader.getValue()));
            } catch (Exception e) {
                throw new ConversionException(e.getMessage(), e);
            }
            return calendar.getGregorianChange();
        }
    
    
        //判断要转换的类型
        @Override
        public boolean canConvert(Class aClass) {
            return Date.class.isAssignableFrom(aClass);
        }
    }
    
  • 持久化API

    如果需要将一个集合中的所有对象持久化到文件系统中,通常会使用java.io的API将集合的对象逐个输入到文件中。虽然这个方法简单,但过程比较复杂。XStream提供了相关集合类的接口实现类,如 XmlArrayList,XmlSet,XmlSet,用一个简单的方法就可以将集合中的每个对象持久化到文件中。下面应用XmlArrayList来持久化一个集合中的所有对象,代码如下:

    public void persist() {
        List<User> users = Lists.newArrayList(getUser());
        File file = new File("/Users/wsq/Desktop/XstreamSample.xml");
        PersistenceStrategy strategy = new FilePersistenceStrategy(file);
        List list = new XmlArrayList(strategy);
        list.addAll(users);
    }
    

    Xstream为XmlArrayList,XmlSet,XmlMap实现类提供统一的创建接口,在创建XmlArrayList(及相关集合)的时候,需要指定一个持久化策略PersistenceStrategy。XStream提供一个持久化到文件的策略 FilePersistenceStrategy。这个策略将集合中每个对象持久化到指定目录不同的文件中

  • 处理JSON

    目前,在Web开发领域,主要的数据交换格式有XML,JSON,相信每个Web开发者对二者都不会感到mos。

    JSON是一种轻量级的数据交换格式,易于阅读和编写,同时也易于及机器解析和生成。JSON采用完全独立于语言的文本格式,但是也使用来类似高级语言的一些习惯(如 java,C#等),这些特性使JSON成为理想的数据交换语言。虽然目前XML是业界数据交换的标准,在可扩展性,数据类型的描述方面有着明显优势,但是相比JSON这种轻量级的数据交换格式,XML显得有些笨重。

    对于大多数Web应用来说,根本不需要复杂的XML来传输数据,XML宣称的扩展性在此没有多大的优势。大多数AJAX应用直接使用JSON发送个接收数据,以构建动态页面的内容,非常灵活易用。如果使用XML,则程序代码将会复杂不少。当然,在 Web Sercvice领域中,XML目前任然有不可动摇的地位。

    在Java的世界里面有不少处理Java对象与JSON,XML相互转换的组件,如JSON-lib,XMLBeans。但同时支持两种数据格式转换的轻量级组件并不多,XStream组件就是少数中的佼佼者。

    XStream的JettisonMappedXmlDriver和JsonHierarchicalStreamDriver都可以很好地完成Java对象和JSON的相互转换工作。值得注意的是,在使用JettisonMappedXmlDriver时,必须事先将jettison依赖包添加到工程pom中

    <dependency>
        <groupId>org.codehaus.jettison</groupId>
        <artifactId>jettison</artifactId>
        <version>1.3.2</version>
    </dependency>
    

    转换代码如下:

    public class XstreamJsonSample {
    
        private static XStream xStream;
    
        public static void main(String args[]) {
            try {
                toJsonByJettisonMappedXmlDriver();
                toJsonHierarchicalStreamDriver();
            } catch (Exception e) {
                System.err.println("转换JSON 报错" + e);
            }
    
        }
    
    
        //生成没有分隔符的 JSON
        public static void toJsonByJettisonMappedXmlDriver() throws Exception {
            User user = getUser();
            FileOutputStream outputStream = new FileOutputStream("/Users/wsq/Desktop/JettisonMappedXmlDriver.json");
            OutputStreamWriter writer = new OutputStreamWriter(outputStream, Charset.forName("UTF-8"));
            xStream = new XStream(new JettisonMappedXmlDriver());
            xStream.setMode(XStream.NO_REFERENCES);
            xStream.alias("user", User.class);
            xStream.toXML(user, writer);
        }
    
        //生成格式正常的JSON
        public static void toJsonHierarchicalStreamDriver() throws Exception {
            User user = getUser();
            FileOutputStream outputStream = new FileOutputStream("/Users/wsq/Desktop/JsonHierarchicalStreamDriver.json");
            OutputStreamWriter writer = new OutputStreamWriter(outputStream, Charset.forName("UTF-8"));
            xStream = new XStream(new JsonHierarchicalStreamDriver());
            xStream.setMode(XStream.NO_REFERENCES);
            xStream.alias("user", User.class);
            xStream.toXML(user, writer);
        }
    
        public static User getUser() {
            LoginLog loginLog = new LoginLog();
            loginLog.setIp("192.168.1.91");
            loginLog.setLoginLogId(1);
            loginLog.setLoginDate(new Date());
            User user = new User();
            user.setUserName("wsq");
            user.setUserId(1);
            user.setLogs(Lists.newArrayList(loginLog));
            return user;
        }
    
        @Data
        public static class User {
    
    
            private int userId;
            private String userName;
            private String password;
            private int credits;
            private String lastIp;
            private Date lastVisit;
            private List<LoginLog> logs;
    
        }
    
        @Data
        public static class LoginLog {
    
            private int loginLogId;
    
            private int userId;
    
            private String ip;
    
            private Date loginDate;
        }
    }
    

    JSON的转换和XML的转换用法一样,只需要在创建XStream实例时,传递一个XML到JSON映射转换的驱动器如JettisonMappedXmlDriver,JsonHierarchicalStreamDriver即可。使用JettisonMappedXmlDriver驱动生成的是一个连续的没有分隔符的JSON串,而使用JsonHierarchicalStreamDriver驱动器生成一个格式化后的JSON串。如果将JSON转换为对象,则只能使用JettisonMappedXmlDriver驱动。

    生成的JSON如下:

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