copyProperties源碼全解析-理解Introspector(內(nèi)省)機(jī)制

很多項(xiàng)目中都使用了VO该溯、DTO、DO别惦、PO等模型設(shè)計(jì)狈茉,在每一層進(jìn)行參數(shù)傳遞之后,免不了會(huì)進(jìn)行大量的對(duì)象屬性之間的拷貝掸掸,此時(shí)我們會(huì)使用到BeanUtils這種工具類(lèi)氯庆,使用copyProperties進(jìn)行便捷的拷貝,代碼如下:

實(shí)體類(lèi)定義
package com.brianxia.reflection.instropector;

/**
 * @author brianxia
 * @version 1.0
 * @date 2021/3/17 19:28
 */
public class Source {

    private String name;
    private int age;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
package com.brianxia.reflection.instropector;

/**
 * @author brianxia
 * @version 1.0
 * @date 2021/3/17 19:28
 */
public class Target {

    private String name;

    private long age;

    public long getAge() {
        return age;
    }

    public void setAge(long age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Target{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
引入相應(yīng)的依賴(lài)

這里我們選擇使用Spring和commons提供的BeanUtil

<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.2.13.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.9.3</version>
        </dependency>
    </dependencies>
方法調(diào)用測(cè)試
  Source source = new Source();
  source.setName("張三");
  source.setAge(18);
  Target target = new Target();
  //Spring
  BeanUtils.copyProperties(source,target);
  //commons
  BeanUtils.copyProperties(target,source);

源碼剖析

  • Spring實(shí)現(xiàn)


    image.png

    這是Spring的方法定義扰付,這里有四個(gè)參數(shù)堤撵,Spring對(duì)于復(fù)制屬性做了一些額外的處理:

source – 源bean
target – 目標(biāo)bean
editable – 可以限制只拷貝父類(lèi)或者接口中定義的屬性
ignoreProperties – 可變參數(shù)列表,排除某些特定的屬性

我們來(lái)看下Spring是如何實(shí)現(xiàn)的:

Class<?> actualEditable = target.getClass();
        if (editable != null) {
            if (!editable.isInstance(target)) {
                throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
                        "] not assignable to Editable class [" + editable.getName() + "]");
            }
            actualEditable = editable;
        }

如果傳遞了editable參數(shù)羽莺,那么就以editable的Class為準(zhǔn)实昨,獲取屬性。

PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);

通過(guò)內(nèi)省盐固,獲取屬性的信息荒给。內(nèi)省在后文中詳細(xì)描述。我們?cè)敿?xì)來(lái)看getPropertyDescriptors這個(gè)方法:

image.png

這里使用了一種CachedIntrospectionResults用來(lái)緩存曾經(jīng)使用過(guò)的內(nèi)省之后的數(shù)據(jù)刁卜,否則每次進(jìn)行copy都需要重新獲取屬性信息志电,性能太低,所以使用進(jìn)行了緩存優(yōu)化蛔趴。CachedIntrospectionResults定義了兩種ConcurrentHashMap存放屬性信息:
image.png

Spring會(huì)先從strongClassCache中獲取挑辆,獲取不到再去softClassCache中獲取,如果都沒(méi)有獲取到孝情,則進(jìn)行創(chuàng)建鱼蝉。創(chuàng)建是在CachedIntrospectionResults的構(gòu)造方法中,其實(shí)創(chuàng)建的過(guò)程就是將目標(biāo)類(lèi)的所有屬性的PropertyDescriptor進(jìn)行了緩存箫荡,注意: 如果有父類(lèi)的話(huà)蚀乔,父類(lèi)的屬性也會(huì)緩存起來(lái)。然后會(huì)將class作為key菲茬,將創(chuàng)建的CachedIntrospectionResults作為value吉挣,默認(rèn)緩存到strongClassCache屬性中(作為強(qiáng)引用)。源類(lèi)也一樣將PropertyDescriptor緩存到CachedIntrospectionResults中婉弹。因此大大提升了性能睬魂。

static CachedIntrospectionResults forClass(Class<?> beanClass) throws BeansException {
        CachedIntrospectionResults results = strongClassCache.get(beanClass);
        if (results != null) {
            return results;
        }
        results = softClassCache.get(beanClass);
        if (results != null) {
            return results;
        }

        results = new CachedIntrospectionResults(beanClass);
        ConcurrentMap<Class<?>, CachedIntrospectionResults> classCacheToUse;

        if (ClassUtils.isCacheSafe(beanClass, CachedIntrospectionResults.class.getClassLoader()) ||
                isClassLoaderAccepted(beanClass.getClassLoader())) {
            classCacheToUse = strongClassCache;
        }
        else {
            if (logger.isDebugEnabled()) {
                logger.debug("Not strongly caching class [" + beanClass.getName() + "] because it is not cache-safe");
            }
            classCacheToUse = softClassCache;
        }

        CachedIntrospectionResults existing = classCacheToUse.putIfAbsent(beanClass, results);
        return (existing != null ? existing : results);
    }

這里有一個(gè)方法需要注意:ClassUtils.isCacheSafe,這個(gè)方法會(huì)檢查給定的beanClass是否由給定的classloader或者此classloader的祖先加載的(雙親委派的原理)镀赌。
所以第一次加載同一個(gè)類(lèi)的屬性會(huì)比較慢氯哮,后續(xù)使用緩存就不用重復(fù)加載了。
回到拷貝代碼部分商佛,接下來(lái)就是比較常規(guī)的使用屬性信息獲取Read和Write方法喉钢,其實(shí)也就是獲取setter和getter方法姆打,使用反射進(jìn)行調(diào)用:

        for (PropertyDescriptor targetPd : targetPds) {
            Method writeMethod = targetPd.getWriteMethod();
            if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
                PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
                if (sourcePd != null) {
                    Method readMethod = sourcePd.getReadMethod();
                    if (readMethod != null &&
                            ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
                        try {
                            if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                                readMethod.setAccessible(true);
                            }
                            Object value = readMethod.invoke(source);
                            if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                writeMethod.setAccessible(true);
                            }
                            writeMethod.invoke(target, value);
                        }
                        catch (Throwable ex) {
                            throw new FatalBeanException(
                                    "Could not copy property '" + targetPd.getName() + "' from source to target", ex);
                        }
                    }
                }
            }
        }

這里Spring對(duì)于非public的方法進(jìn)行了setAccessible處理,使之有處理權(quán)限肠虽。但是Spring比較大的問(wèn)題是裝箱類(lèi)型LongInteger等互相轉(zhuǎn)換無(wú)法做到:

ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())

這個(gè)方法會(huì)判斷set和get的對(duì)象是否有繼承關(guān)系幔戏,如果沒(méi)有繼承關(guān)系,就直接返回税课。而方法內(nèi)部只是簡(jiǎn)簡(jiǎn)單單處理了基本數(shù)據(jù)類(lèi)型和裝箱類(lèi)型的關(guān)系闲延,并未對(duì)數(shù)據(jù)進(jìn)行特殊的處理。

 public static boolean isAssignable(Class<?> lhsType, Class<?> rhsType) {
        Assert.notNull(lhsType, "Left-hand side type must not be null");
        Assert.notNull(rhsType, "Right-hand side type must not be null");
        if (lhsType.isAssignableFrom(rhsType)) {
            return true;
        } else {
            Class resolvedWrapper;
            if (lhsType.isPrimitive()) {
                resolvedWrapper = (Class)primitiveWrapperTypeMap.get(rhsType);
                return lhsType == resolvedWrapper;
            } else {
                resolvedWrapper = (Class)primitiveTypeToWrapperMap.get(rhsType);
                return resolvedWrapper != null && lhsType.isAssignableFrom(resolvedWrapper);
            }
        }
    }

整體來(lái)說(shuō)韩玩,Spring對(duì)于copyProperties的實(shí)現(xiàn)過(guò)于簡(jiǎn)單垒玲,僅僅增加了ignore忽略和editable限制父類(lèi)拷貝等基礎(chǔ)功能,但未對(duì)復(fù)雜的數(shù)據(jù)類(lèi)型轉(zhuǎn)換做出特殊處理找颓。接下來(lái)我們來(lái)看commons的實(shí)現(xiàn)合愈。

  • commons實(shí)現(xiàn)
    從整體結(jié)構(gòu)上來(lái)說(shuō),commons的實(shí)現(xiàn)做了很多的特殊處理提升性能击狮。


    image.png

    它將數(shù)據(jù)類(lèi)型分成了三種佛析,DynaBean(姑且稱(chēng)之為萬(wàn)能Bean,自行查詢(xún)下資料)帘不、Map、JavaBean杨箭。
    DynaBean使用較少寞焙,我們先說(shuō)Map的實(shí)現(xiàn):

  @SuppressWarnings("unchecked")
            final
            // Map properties are always of type <String, Object>
            Map<String, Object> propMap = (Map<String, Object>) orig;
            for (final Map.Entry<String, Object> entry : propMap.entrySet()) {
                final String name = entry.getKey();
                if (getPropertyUtils().isWriteable(dest, name)) {
                    copyProperty(dest, name, entry.getValue());
                }
            }

直接從Map中遍歷Entry,然后將Entry的內(nèi)容寫(xiě)入到目標(biāo)對(duì)象中互婿。而JavaBean的處理如下:

            final PropertyDescriptor[] origDescriptors =
                getPropertyUtils().getPropertyDescriptors(orig);
            for (PropertyDescriptor origDescriptor : origDescriptors) {
                final String name = origDescriptor.getName();
                if ("class".equals(name)) {
                    continue; // No point in trying to set an object's class
                }
                if (getPropertyUtils().isReadable(orig, name) &&
                    getPropertyUtils().isWriteable(dest, name)) {
                    try {
                        final Object value =
                            getPropertyUtils().getSimpleProperty(orig, name);
                        copyProperty(dest, name, value);
                    } catch (final NoSuchMethodException e) {
                        // Should not happen
                    }
                }
            }

與Spring類(lèi)似捣郊,內(nèi)部維護(hù)了兩個(gè)WeakFastHashMap緩存屬性信息,WeakFastHashMap的設(shè)計(jì)很巧妙慈参,借鑒了CopyOnWrite的思想呛牲,查詢(xún)數(shù)據(jù)時(shí)無(wú)需加鎖,它會(huì)選擇clone一份新的數(shù)據(jù)進(jìn)行修改驮配,在clone出來(lái)的數(shù)據(jù)上進(jìn)行修改娘扩,然后再替換原來(lái)的數(shù)據(jù)。比如如下代碼:


 @Override
    public V get(final Object key) {
        if (fast) {
            return (map.get(key));
        } else {
            synchronized (map) {
                return (map.get(key));
            }
        }
    }

 @Override
    public void putAll(final Map<? extends K, ? extends V> in) {
        if (fast) {
            synchronized (this) {
                final Map<K, V> temp =  cloneMap(map);
                temp.putAll(in);
                map = temp;
            }
        } else {
            synchronized (map) {
                map.putAll(in);
            }
        }
    }

commons對(duì)于數(shù)據(jù)類(lèi)型轉(zhuǎn)換壮锻,有專(zhuān)門(mén)的函數(shù)convertForCopy來(lái)進(jìn)行處理琐旁。

 protected Object convert(final Object value, final Class<?> type) {
        final Converter converter = getConvertUtils().lookup(type);
        if (converter != null) {
            log.trace("        USING CONVERTER " + converter);
            return converter.convert(type, value);
        } else {
            return value;
        }
    }

首先根據(jù)數(shù)據(jù)類(lèi)型找到對(duì)應(yīng)的converter,比如IntegerConverter[UseDefault=true, UseLocaleFormat=false]猜绣,使用裝飾者模式進(jìn)行增強(qiáng)灰殴。根據(jù)具體轉(zhuǎn)換出的類(lèi)型,使用Number進(jìn)行處理掰邢。

    @Override
    protected <T> T convertToType(final Class<T> targetType, final Object value) throws Throwable {

        final Class<?> sourceType = value.getClass();
        // Handle Number
        if (value instanceof Number) {
            return toNumber(sourceType, targetType, (Number)value);
        }

        // Handle Boolean
        if (value instanceof Boolean) {
            return toNumber(sourceType, targetType, ((Boolean)value).booleanValue() ? ONE : ZERO);
        }

        // Handle Date --> Long
        if (value instanceof Date && Long.class.equals(targetType)) {
            return targetType.cast(new Long(((Date)value).getTime()));
        }

        // Handle Calendar --> Long
        if (value instanceof Calendar  && Long.class.equals(targetType)) {
            return targetType.cast(new Long(((Calendar)value).getTime().getTime()));
        }

        // Convert all other types to String & handle
        final String stringValue = value.toString().trim();
        if (stringValue.length() == 0) {
            return handleMissing(targetType);
        }

        // Convert/Parse a String
        Number number = null;
        if (useLocaleFormat) {
            final NumberFormat format = getFormat();
            number = parse(sourceType, targetType, stringValue, format);
        } else {
            if (log().isDebugEnabled()) {
                log().debug("    No NumberFormat, using default conversion");
            }
            number = toNumber(sourceType, targetType, stringValue);
        }

        // Ensure the correct number type is returned
        return toNumber(sourceType, targetType, number);

    }

比如此例中Long轉(zhuǎn)換成Integer類(lèi)型牺陶,只需要調(diào)用toNumber即可伟阔。針對(duì)Long型進(jìn)行特殊的處理。

   // Long
        if (targetType.equals(Long.class)) {
            return targetType.cast(new Long(value.longValue()));
        }

總結(jié):commons的實(shí)現(xiàn)要比Spring功能更加強(qiáng)大掰伸,不僅使用了具備COW技術(shù)的緩存大大增強(qiáng)并發(fā)讀取能力皱炉,同時(shí)對(duì)數(shù)據(jù)轉(zhuǎn)換做了嚴(yán)格的處理。

內(nèi)省

最后我們來(lái)說(shuō)一下Java中的內(nèi)省(Introspector)機(jī)制碱工。Introspector與反射類(lèi)似娃承,主要是對(duì)Java Bean屬性、方法等的一種處理方法怕篷。

  • PropertyDescriptor類(lèi):
    PropertyDescriptor類(lèi)表示JavaBean類(lèi)通過(guò)存儲(chǔ)器導(dǎo)出一個(gè)屬性历筝。主要方法:
      1. getPropertyType(),獲得屬性的Class對(duì)象;
      2. getReadMethod()廊谓,獲得用于讀取屬性值的方法梳猪;getWriteMethod(),獲得用于寫(xiě)入屬性值的方法;
      3. hashCode()蒸痹,獲取對(duì)象的哈希值;
      4. setReadMethod(Method readMethod)春弥,設(shè)置用于讀取屬性值的方法;
      5. setWriteMethod(Method writeMethod),設(shè)置用于寫(xiě)入屬性值的方法叠荠。

  • Introspector類(lèi):

將JavaBean中的屬性封裝起來(lái)進(jìn)行操作匿沛。在程序把一個(gè)類(lèi)當(dāng)做JavaBean來(lái)看,就是調(diào)用Introspector.getBeanInfo()方法榛鼎,得到的BeanInfo對(duì)象封裝了把這個(gè)類(lèi)當(dāng)做JavaBean看的結(jié)果信息逃呼,即屬性的信息。

getPropertyDescriptors()者娱,獲得屬性的描述抡笼,可以采用遍歷BeanInfo的方法,來(lái)查找黄鳍、設(shè)置類(lèi)的屬性推姻。

手寫(xiě)一個(gè)copyProperties

package com.brianxia.reflection.instropector;

import org.springframework.beans.BeanUtils;

import javax.xml.ws.spi.Invoker;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author brianxia
 * @version 1.0
 * @date 2021/3/17 19:22
 */
public class InstropectorDemo {

    public static Map<Class,Map<String,PropertyDescriptor>> sourcePd = new ConcurrentHashMap<>();

    public synchronized static void createPd(Object source) throws IntrospectionException {
        Class clazz = source.getClass();
        if(!sourcePd.containsKey(clazz)){
            sourcePd.put(clazz,new HashMap<>());
        }
        Map putData = sourcePd.get(clazz);
        //獲取BeanInfo
        BeanInfo beanInfo = Introspector.getBeanInfo(source.getClass());
        //獲取屬性描述信息
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();

        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
            putData.put(propertyDescriptor.getName(),propertyDescriptor);
        }
    }

    public static PropertyDescriptor getPd(String name,Class clazz) {
        if(!sourcePd.containsKey(clazz)){
            return null;
        }

        return sourcePd.get(clazz).get(name);
    }

    /**
     *
     * @param source  the source bean
     * @param target  the target bean
     */
    public static void copyProperties(Object source,Object target) throws IntrospectionException, InvocationTargetException, IllegalAccessException {
        //獲取BeanInfo
        BeanInfo beanInfo = Introspector.getBeanInfo(target.getClass());
        //獲取屬性描述信息
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();

        Class<?> aClass = source.getClass();
        //創(chuàng)建source的描述信息map
        createPd(source);

        //遍歷屬性描述信息,進(jìn)行copy
        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {

            String name = propertyDescriptor.getName();
            PropertyDescriptor sourcePd = getPd(name,aClass);
            //如果source沒(méi)有對(duì)應(yīng)屬性框沟,直接continue
            if(sourcePd == null){
                continue;
            }

            //獲取getter和setter方法
            Method writeMethod = propertyDescriptor.getWriteMethod();
            Method readMethod = sourcePd.getReadMethod();

            //授予權(quán)限 private也可以訪問(wèn)
            if(writeMethod != null && readMethod != null){
                if(!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())){
                    writeMethod.setAccessible(true);
                }

                if(!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())){
                    readMethod.setAccessible(true);
                }

                //復(fù)制屬性
                Object invoke = readMethod.invoke(source);
                writeMethod.invoke(target,invoke);
            }
        }
    }

    public static void main(String[] args) throws IntrospectionException, InvocationTargetException, IllegalAccessException {
        Source source = new Source();
        source.setName("張三");
        source.setAge(18L);
        Target target = new Target();

        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            //target.setAge(source.getAge());
           // target.setName(source.getName());
            copyProperties(source,target);
            //BeanUtils.copyProperties(source,target);
            //org.apache.commons.beanutils.BeanUtils.copyProperties(target,source);
        }
        long end = System.currentTimeMillis();
        System.out.println(end - start);
        System.out.println(target);

    }
}

此案例中類(lèi)型轉(zhuǎn)換并未特別處理藏古,大家可以根據(jù)commons的實(shí)現(xiàn)自行處理簡(jiǎn)單的轉(zhuǎn)換。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末忍燥,一起剝皮案震驚了整個(gè)濱河市校翔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌灾前,老刑警劉巖防症,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蔫敲,警方通過(guò)查閱死者的電腦和手機(jī)饲嗽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)奈嘿,“玉大人貌虾,你說(shuō)我怎么就攤上這事∪褂蹋” “怎么了尽狠?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)叶圃。 經(jīng)常有香客問(wèn)我袄膏,道長(zhǎng),這世上最難降的妖魔是什么掺冠? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任沉馆,我火速辦了婚禮,結(jié)果婚禮上德崭,老公的妹妹穿的比我還像新娘斥黑。我一直安慰自己,他們只是感情好眉厨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布锌奴。 她就那樣靜靜地躺著,像睡著了一般憾股。 火紅的嫁衣襯著肌膚如雪鹿蜀。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天荔燎,我揣著相機(jī)與錄音耻姥,去河邊找鬼销钝。 笑死有咨,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蒸健。 我是一名探鬼主播座享,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼似忧!你這毒婦竟也來(lái)了渣叛?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤盯捌,失蹤者是張志新(化名)和其女友劉穎淳衙,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡箫攀,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年肠牲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片靴跛。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缀雳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出梢睛,到底是詐尸還是另有隱情肥印,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布绝葡,位于F島的核電站深碱,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏挤牛。R本人自食惡果不足惜莹痢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望墓赴。 院中可真熱鬧竞膳,春花似錦、人聲如沸诫硕。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)章办。三九已至锉走,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間藕届,已是汗流浹背挪蹭。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留休偶,地道東北人梁厉。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像踏兜,于是被迫代替她去往敵國(guó)和親词顾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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