Mybatis笔记3-映射体系

Mybatis映射体系

Mybatis映射体系

MetaObject

MetaObject应用

查找属性:
忽略大小写,支持驼峰,支持子属性

获取属性值:
基于点获取子属性,如user.name
基于索引获取属性列表值,如users[1].id
基于key获取map值,如user[name]

设置属性
可设置子属性值
持自动创建子属性对象(必须有无参构造器,且不能是集合)

public class MetaObjectTest {

    static class Address {
        private String province;
        private String city;
        private String county;
        private String addressDetail;
        
        public String getProvince() { return province; }
        public void setProvince(String province) { this.province = province; }
        public String getCity() { return city; }
        public void setCity(String city) { this.city = city; }
        public String getCounty() { return county; }
        public void setCounty(String county) { this.county = county; }
        public String getAddressDetail() { return addressDetail; }
        public void setAddressDetail(String addressDetail) { this.addressDetail = addressDetail; }
    }

    private String name;
    private Address address;
    private List<Address> addressList;
    private Map<String, Object> additionParams;
    
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public Address getAddress() { return address; }
    public void setAddress(Address address) { this.address = address; }
    public List<Address> getAddressList() { return addressList; }
    public void setAddressList(List<Address> addressList) { this.addressList = addressList; }
    public Map<String, Object> getAdditionParams() { return additionParams; }
    public void setAdditionParams(Map<String, Object> additionParams) {
        this.additionParams = additionParams;
    }

    public static void main(String[] args) {
        Configuration configuration = new Configuration();
        MetaObjectTest metaObjectTest = new MetaObjectTest();
        // 使用MetaObject解析order对象
        MetaObject metaobject = configuration.newMetaObject(metaObjectTest);

        // 通过MetaObject设置对象属性值
        metaObjectTest.setName("oldName");
        System.out.println(metaObjectTest.getName());
        metaobject.setValue("name", "newName");
        // 通过输出结果,可以看到orderNo被MetaObject设置的值覆盖
        System.out.println(metaObjectTest.getName());

        System.out.println("--->");

        // 通过MetaObject获取对象属性值
        System.out.println("通过MetaObject获取对象属性值:" + metaobject.getValue("name"));

        System.out.println("--->");

        // 通过MetaObject设置嵌套属性的值,它会创建一个空的嵌套对象,并为其属性赋值
        metaobject.setValue("address.addressDetail", "详细地址");
        System.out.println("通过MetaObject自动创建内嵌对象,赋值之后获取 : " 
                           + metaobject.getValue("address.addressDetail"));

        System.out.println("--->");

        System.out.println("通过驼峰命名法获取属性名称 :" 
                           + metaobject.findProperty("address.address_detail", true));

        System.out.println("--->");

        // MetaObject无法自动创建List类型的内嵌对象,需要手动设置,如:
        Address address = new Address();
        address.setProvince("福建省");
        List<Address> addressList = new ArrayList<>();
        addressList.add(address);
        metaobject.setValue("addressList", addressList);
        metaobject.setValue("addressList[0]", address);
        // 访问集合对象成员的方式(也可以访问成员的属性值)
        System.out.println("通过MetaObject获取List类型成员属性 : " 
                           + metaobject.getValue("addressList[0].province"));

        System.out.println("--->");

        // MetaObject同样无法自动创建Map类型的内嵌对象,需要手动设置,如:
        metaobject.setValue("additionParams", new HashMap<>());
        // 设置map属性的键值对,以及访问键值
        metaobject.setValue("additionParams[name]", "名称");
        System.out.println("通过MetaObject获取Map类型成员属性 : " 
                           + metaobject.getValue("additionParams[name]"));
    }
}

MetaObject源码解析

public class MetaObject {
 
    // ...
    
    // 以解析的属性名称name=comments[0].user.name为例
    public Object getValue(String name) {
        // 先通过属性分词器解析全属性名称
        PropertyTokenizer prop = new PropertyTokenizer(name);
        // 存在子属性user.name
        if (prop.hasNext()) {
            // 如果存在子属性,则通过IndexedName再次创建MetaObject
            // IndexedName的值依次为: comments[0]、user
            MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
            if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
                return null;
            } else {
                return metaValue.getValue(prop.getChildren());
            }
        } 
        // 如果不存在子属性,比如解析到name属性时,不存在子属性,通过反射获取属性值(可参考下面BeanWrapper类源码)
        else {
            return objectWrapper.get(prop);
        }
    }
    
    // 以解析comments[0].user.name为例
    public MetaObject metaObjectForProperty(String name) {
        // 这里会先后获取comments[0]、user两个属性的值
        Object value = getValue(name);
        // 将上面通过某个属性获取到的对象,继续将这些对象解析为MetaObject对象
        return MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory);
    }
    
}
PropertyTokenizer

属性名称分词器,它首先会根据"."符号,将属性名称拆分,比如comments[0].user.name通过多次的属性名称分词,会被划分为comments[0]、user、name三部分,再根据这三部分创建各自的MetaObject元对象,进一步解析子属性,源码如下:

public class PropertyTokenizer implements Iterator<PropertyTokenizer> {
    private String name;
    private final String indexedName;
    // index之所以为String类型,是因为出了Collection是数值类型,还有Map类型采用String类型的key
    private String index;
    private final String children;
    
    // 假定入参fullname属性全名为:comments[0].user.name
    // 最终的解析结果为:
    // name = comments
    // indexedName = comments[0]
    // index = 0
    // children = user.name
    public PropertyTokenizer(String fullname) {
        // 首先,获取第一个.的偏移量
        int delim = fullname.indexOf('.');
        // 存在.的情况
        if (delim > -1) {
            // 先将name赋值为第一个.号之前的全部字符,这里为comments[0]
            name = fullname.substring(0, delim);
            // childre赋值为第一个.之后的全部字符串,这里为user.name
            children = fullname.substring(delim + 1);
        } else {
            // 不存在.的字符串,直接将name赋值为整个入参fullname
            name = fullname;
            children = null;
        }
        // 将comments[0]赋值给indexedName
        indexedName = name;
        // 判断name是否存在[符号,存在的话将name赋值为[]符号外的字符串,这里将name重新赋值为comments
        delim = name.indexOf('[');
        if (delim > -1) {
            // index赋值为[]内的下标数值,如果不存在[],index为默认值null
            index = name.substring(delim + 1, name.length() - 1);
            name = name.substring(0, delim);
        }
    }

    // ...
    
    //hasNext判断是否存在子属性
    public boolean hasNext() {
        return children != null;
    }
    
    // 将子属性再次通过属性分词器分析
    public PropertyTokenizer next() {
        return new PropertyTokenizer(children);
    }
}
BeanWrapper

封装目标对象和目标对象类元数据,可以通过它反射获取属性值,这个类只能获取顶层属性的值,比如通过BeanWrapper获取属性名称comments[0].user.name,它仅能返回comments[0]的值。

public class BeanWrapper extends BaseWrapper {
    
    private final Object object;
    private final MetaClass metaClass;
    
    // ...
    
    public Object get(PropertyTokenizer prop) {
        // 通过属性分词器判断该属性是否是一个集合,如果属性分词器记录的index属性为null,表示当前要解析的属性不是集合
        if (prop.getIndex() != null) {
            // 继承父类BaseWrapper的方法(详见下方源码)
            Object collection = resolveCollection(prop, object);
            // 继承父类BaseWrapper的方法(详见下方源码)
            return getCollectionValue(prop, collection);
        } else {
            // 解析非集合类型的属性值(详见下方源码)
            return getBeanProperty(prop, object);
        }
    }
    
    private Object getBeanProperty(PropertyTokenizer prop, Object object) {
        try {
            // 通过MetaClass获取属性值对应的MethodInvoker,内部封装了JDK反射的method对象(详见下方源码)
            // 注意这里是通过属性分词器的name属性获取属性值
            // 当尝试通过BeanWrapper获取属性值,且传入的属性为user.name时,这里的prop.getName()将会获取到user
            //(参看上面PropertyTokenizer如何解析属性名称)
            // 因此其实获取到的时user属性值,无法获取user对象的name属性值
            Invoker method = metaClass.getGetInvoker(prop.getName());
            try {
                // 通过反射获取属性值
                return method.invoke(object, NO_ARGUMENTS);
            } catch (Throwable t) {
                throw ExceptionUtil.unwrapThrowable(t);
            }
        } catch (RuntimeException e) {
            throw e;
        } catch (Throwable t) {
            // ...
        }
    }
    
}
MethodInvoker

封装了类的Setter和Getter,通过它类反射获取属性值或者设置属性值

public class MethodInvoker implements Invoker {
    
    private final Class<?> type;
    private final Method method;
    
    public MethodInvoker(Method method) {
        this.method = method;
        // 兼容Getter和Setter方法,Setter根据入参类型决定,Getter根据出参类型决定
        if (method.getParameterTypes().length == 1) {
            type = method.getParameterTypes()[0];
        } else {
            type = method.getReturnType();
        }
    }
    
    // 反射调用Getter或者Setter方法
    public Object invoke(Object target, Object[] args) 
            throws IllegalAccessException, InvocationTargetException {
        return method.invoke(target, args);
    }
    
    // 返回当前操作的属性类型
    public Class<?> getType() {
        return type;
    }
}
BaseWrapper

为接口ObjectWrapper提供的封装了处理集合公共方法的抽象类

public abstract class BaseWrapper implements ObjectWrapper {
    
    protected static final Object[] NO_ARGUMENTS = new Object[0];
    protected final MetaObject metaObject;
    
    protected BaseWrapper(MetaObject metaObject) {
        this.metaObject = metaObject;
    }
    
    protected Object resolveCollection(PropertyTokenizer prop, Object object) {
        // 属性名称为空直接返回BeanWrapper封装的目标对象Object
        if ("".equals(prop.getName())) {
            return object;
        } else {
            // 若解析的属性是comments[0],将会返回comments属性名对应的对象值
            return metaObject.getValue(prop.getName());
        }
    }
    
    // 从集合中获取某个成员
    protected Object getCollectionValue(PropertyTokenizer prop, Object collection) {
        // 若属性值为Map类型,PropertyTokenizer解析得到的index为key名称
        if (collection instanceof Map) {
            return ((Map) collection).get(prop.getIndex());
        } 
        // 从List、数组类型取值
        else {
            int i = Integer.parseInt(prop.getIndex());
            if (collection instanceof List) {
                return ((List) collection).get(i);
            } else if (collection instanceof Object[]) {
                return ((Object[]) collection)[i];
            } else if (collection instanceof char[]) {
                return ((char[]) collection)[i];
            } else if (collection instanceof boolean[]) {
                return ((boolean[]) collection)[i];
            } else if (collection instanceof byte[]) {
                return ((byte[]) collection)[i];
            } else if (collection instanceof double[]) {
                return ((double[]) collection)[i];
            } else if (collection instanceof float[]) {
                return ((float[]) collection)[i];
            } else if (collection instanceof int[]) {
                return ((int[]) collection)[i];
            } else if (collection instanceof long[]) {
                return ((long[]) collection)[i];
            } else if (collection instanceof short[]) {
                return ((short[]) collection)[i];
            } else {
                // ...
            }
        }
    }
    
    // 为集合属性设置值
    protected void setCollectionValue(PropertyTokenizer prop, Object collection, Object value) {
        // Map类型,以key为index值,value为入参value设置一个Map.Entry
        if (collection instanceof Map) {
            ((Map) collection).put(prop.getIndex(), value);
        } 
        // 为List、数组类型赋值
        else {
            int i = Integer.parseInt(prop.getIndex());
            if (collection instanceof List) {
                ((List) collection).set(i, value);
            } else if (collection instanceof Object[]) {
                ((Object[]) collection)[i] = value;
            } else if (collection instanceof char[]) {
                ((char[]) collection)[i] = (Character) value;
            } else if (collection instanceof boolean[]) {
                ((boolean[]) collection)[i] = (Boolean) value;
            } else if (collection instanceof byte[]) {
                ((byte[]) collection)[i] = (Byte) value;
            } else if (collection instanceof double[]) {
                ((double[]) collection)[i] = (Double) value;
            } else if (collection instanceof float[]) {
                ((float[]) collection)[i] = (Float) value;
            } else if (collection instanceof int[]) {
                ((int[]) collection)[i] = (Integer) value;
            } else if (collection instanceof long[]) {
                ((long[]) collection)[i] = (Long) value;
            } else if (collection instanceof short[]) {
                ((short[]) collection)[i] = (Short) value;
            } else {
                // ...
            }
        }
    }
}

Reflector

Reflector可以解析一个类的所有属性,记录所有的Setter和Getter方法

public class Reflector {
    
    // 解析的类
    private final Class<?> type;
    private final String[] readablePropertyNames;
    private final String[] writeablePropertyNames;
    // 通过类的Method解析
    // 封装所有get开头的方法,key为去除get前缀,首字母小写的方法名称,value为封装Method的MethodInvoker
    // 通过类的Field解析
    // 封装所有属性的赋值方法,key为属性名称,value为属性的赋值方法SetFieldInvoker
    private final Map<String, Invoker> setMethods = new HashMap<String, Invoker>();
    // 封装所有is、get开头的方法,key为去除is、get前缀,首字母小写的方法名称,value为封装Method的MethodInvoker
    // 通过类的Field解析
    // 封装所有属性的赋值方法,key为属性名称,value为属性的赋值方法GetFieldInvoker
    private final Map<String, Invoker> getMethods = new HashMap<String, Invoker>();
    // 封装所有set开头,被认定为Setter方法的属性类型,以及所有Field的类型
    // 如果是List<T>返回泛型类型,如果是T[]同样返回泛型
    private final Map<String, Class<?>> setTypes = new HashMap<String, Class<?>>();
    // 封装所有is、get开头,被认定为Getter方法的属性类型,以及所有Filed的类型
    // 如果是List<T>返回泛型类型,如果是T[]同样返回泛型
    private final Map<String, Class<?>> getTypes = new HashMap<String, Class<?>>();
    // 解析得到的无参构造器
    private Constructor<?> defaultConstructor;
    // 保存所有可以取值、设置的属性名称的并集,key为属性名称全大写字符串,value为属性名称
    private Map<String, String> caseInsensitivePropertyMap = new HashMap<String, String>();
    
    public Reflector(Class<?> clazz) {
        type = clazz;
        // 获取无参构造器
        addDefaultConstructor(clazz);
        // 添加所有合法的Getter
        addGetMethods(clazz);
        // 添加所有合法的Setter
        addSetMethods(clazz);
        // 通过类的Field添加另外一批Getter和Setter方法
        addFields(clazz);
        // 使用数组保存上述通过Method和Filed解析得到的可以获取值的属性名称
        readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
        // 使用数组保存上述通过Method和Filed解析得到的可以设置值的属性名称
        writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);
        // 将所有可以赋值、可以取值的属性名称,转换为大写字符串保存到一个Map中
        for (String propName : readablePropertyNames) {
            caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
        }
        for (String propName : writeablePropertyNames) {
            caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
        }
    }
    
    // 获取默认无参构造器
    private void addDefaultConstructor(Class<?> clazz) {
        // 获取当前类型定义的全部构造器
        Constructor<?>[] consts = clazz.getDeclaredConstructors();
        // 遍历所有构造器,找到入参为0的构造器,并将其设置为可访问的
        for (Constructor<?> constructor : consts) {
            if (constructor.getParameterTypes().length == 0) {
                if (canAccessPrivateMethods()) {
                    try {
                        constructor.setAccessible(true);
                    } catch (Exception e) {
                    }
                }
                if (constructor.isAccessible()) {
                    this.defaultConstructor = constructor;
                }
            }
        }
    }
    
    private void addGetMethods(Class<?> cls) {
        // 保存解析得到同名属性,却有多个方法的冲突Map
        Map<String, List<Method>> conflictingGetters = new HashMap<String, List<Method>>();
        // 获取cls(包括父类、接口定义的)上所有的方法
        Method[] methods = getClassMethods(cls);
        for (Method method : methods) {
            // Getter方法应该没有入参,不记录有参数的方法
            if (method.getParameterTypes().length > 0) {
                continue;
            }
            
            // 如果方法名称是get或者is为前缀,并且前缀之后还有字符,将后面的字符串作为属性名称
            String name = method.getName();
            if ((name.startsWith("get") && name.length() > 3)
                || (name.startsWith("is") && name.length() > 2)) {
                // 删除方法名称前缀get、is、set,并将剩下的第一个字符变为小写
                // 如:getUsername => username
                name = PropertyNamer.methodToProperty(name);
                // 保存得到的method,映射关系为 属性名 -> List<Method> ,避免同一个name获得多个方法
                addMethodConflict(conflictingGetters, name, method);
            }
        }
        // 为每个属性选定唯一一个Getter方法(方法详情略)
        // 规则如下:
        // 遍历每个方法的返回类型,
        // 1. 存在返回类型相同的多个方法
        //      1.1 如果不是boolean类型属性,存在多个方法返回类型一致,抛出异常
        //      1.2 如果是boolean类型属性,以最后一个is开头的方法名称作为该属性的Getter方法
        // 2. 如果多个Getter方法返回类型不一致,以最小的子类作为Getter方法
        // 3. 解析得到的属性名称不能是$开头、serialVersionUID、class这三种形式的名称,不添加Getter方法
        // 4. 将得到的Getter方法,封装为MethodInvoker对象
        resolveGetterConflicts(conflictingGetters);
    }
    
    // ...
    
    private void addSetMethods(Class<?> cls) {
        Map<String, List<Method>> conflictingSetters = new HashMap<String, List<Method>>();
        // 获取cls(包括父类、接口定义的)上所有的方法
        Method[] methods = getClassMethods(cls);
        // 遍历所有以set开头,并且set之后还有字符的方法名称,且方法只有一个入参
        for (Method method : methods) {
            String name = method.getName();
            if (name.startsWith("set") && name.length() > 3) {
                if (method.getParameterTypes().length == 1) {
                    // 删除方法名称前缀get、is、set,并将剩下的第一个字符变为小写
                    // 如:getUsername => username
                    name = PropertyNamer.methodToProperty(name);
                    // 保存得到的method,映射关系为 属性名 -> List<Method> ,避免同一个name获得多个方法
                    addMethodConflict(conflictingSetters, name, method);
                }
            }
        }
        // 为每个属性选定唯一一个Setter方法(方法详情略)
        // 规则如下:
        // 遍历属性名称的所有方法
        // 1. 若通过属性名称查询Getter方法的返回类型和当前方法的入参类型一致,选定该方法作为Setter方法
        // 2. 若通过属性名称查询Getter方法的返回类型和当前方法的入参类型不一致,选取入参类型是最子类的方法
        resolveSetterConflicts(conflictingSetters);
    }
    
    // ...
    
    // 添加当前类属性的访问和赋值方法
    private void addFields(Class<?> clazz) {
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if (canAccessPrivateMethods()) {
                try {
                    field.setAccessible(true);
                } catch (Exception e) {
                }
            }
            if (field.isAccessible()) {
                if (!setMethods.containsKey(field.getName())) {
                    int modifiers = field.getModifiers();
                    // 属性的set方法不能同时被static和final修饰
                    if (!(Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers))) {
                        // 在全局属性setMethods集合中,添加为该属性赋值的SetFieldInvoker对象
                        addSetField(field);
                    }
                }
                // 在全局属性getMethods集合中,添加为该属性赋值的GetFieldInvoker对象
                if (!getMethods.containsKey(field.getName())) {
                    addGetField(field);
                }
            }
        }
        // 进一步解析父类的属性
        if (clazz.getSuperclass() != null) {
            addFields(clazz.getSuperclass());
        }
    }
    
    // ...
    
    /*
     * TODO 这个方法在类中多次使用,为什么不采取一个集合保存解析得到的Method[]??????
     *
     * 这个方法会返回当前类,当前类继承的父类,当前类实现的接口中所有的方法,
     * 之所以不简单的采用Class.getMethods(),是因为也想获取到private访问权限的方法
     */
    private Method[] getClassMethods(Class<?> cls) {
        Map<String, Method> uniqueMethods = new HashMap<String, Method>();
        Class<?> currentClass = cls;
        while (currentClass != null && currentClass != Object.class) {
            // 根据一定规则将方法的明明标记作为key,淘汰所有JVM自己生成的桥接方法(桥接方法用于处理泛型方法)
            // 如果存在方法明明标记一致的情况,考虑存在重载的方法,以最后一个方法为准
            addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods());
            // 同时也去获取接口的方法,因为当前cls入参可能是个抽象类
            Class<?>[] interfaces = currentClass.getInterfaces();
            for (Class<?> anInterface : interfaces) {
                addUniqueMethods(uniqueMethods, anInterface.getMethods());
            }
            // 继续获取父类的方法
            currentClass = currentClass.getSuperclass();
        }
        Collection<Method> methods = uniqueMethods.values();
        return methods.toArray(new Method[methods.size()]);
    }
    
    // ... 
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,919评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,567评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,316评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,294评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,318评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,245评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,120评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,964评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,376评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,592评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,764评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,460评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,070评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,697评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,846评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,819评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,665评论 2 354

推荐阅读更多精彩内容