AutowireCapableBeanFactory探密(3)——依赖解析

前情回顾

在前两篇文章中,多次提及AutowireCapableBeanFactory#resolveDependency方法,原因是该方法很重要,在Spring很多场合都涉及该方法的调用,包括但不限于以下场景:

  1. 解析@Resouce注解的元素(CommonAnnotationBeanPostProcessor#autowireResource)
  2. 解析@Autowired、@Value注解的元素(AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject、AutowiredAnnotationBeanPostProcessor.AutowiredMethodElement#inject)
  3. autowire = byType(AbstractAutowireCapableBeanFactory#autowireByType)
  4. 构造器装配(ConstructorResolver#resolveAutowiredArgument)

了解此方法的底层工作原理,有助于提升对Spring Bean解析的认知能力

案例

本文以一种经典的注入案例进行探讨:注入集合对象

// 接口
public interface BarService {}

// 实现类1
@Service
public class BarServiceImplOne implements BarService {}

// 实现类2
@Service
public class BarServiceImplTwo implements BarService {}


@Service
public class FooService {
    // barServices集合有两个元素,分别是BarServiceImplOne、BarServiceImplTwo
    @Resource
    private List<BarService> barServices;
}

注入集合的姿势Spring官网就有介绍,开发中也比较常用,现在借助该方式来探寻其中的一些细节问题,并介绍resolveDependency在其中起了怎样的作用

源码分析

源码基于Spring 5.1.11.RELEASE

resolveDependency翻译成中文即是解析依赖,其方法签名如下:

Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
            @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException;
  • descriptor: 依赖描述符,它描述了一个待注入的依赖信息:要么是构造器参数,要么是方法参数,要么是字段,并且提供了非常友好的、一种统一的方式去访问
  • requestingBeanName: 声明依赖的Bean。举例,如果A依赖B,则requestingBeanName表示的是A
  • autowiredBeanNames: 待装配的Bean名称列表,即解析出来的bean names。使用上,一般是由外部传进来一个空的集合,在方法内部进行Bean的解析,如果符合条件,就将该bean name添加到集合内。潜台词是,可能会有多个符合条件的Bean,其实也很好理解,如果被依赖的类(接口)有多个实现类,且都被Spring管理,就存在多个符合条件的Bean的可能性
  • typeConverter: 类型转化器,用于类型转换

由于使用的是@Resouce注解,故直接定位到CommonAnnotationBeanPostProcessor#autowireResourceAutowireCapableBeanFactory探密(2)——传统装配模式与现代注解驱动注入方式一文中也有简单的介绍

protected Object autowireResource(BeanFactory factory, LookupElement element, @Nullable String requestingBeanName)
        throws NoSuchBeanDefinitionException {
    // ...(省略)

    if (factory instanceof AutowireCapableBeanFactory) {
        AutowireCapableBeanFactory beanFactory = (AutowireCapableBeanFactory) factory;
        // 依赖描述符
        DependencyDescriptor descriptor = element.getDependencyDescriptor();
        if (this.fallbackToDefaultTypeMatch && element.isDefaultName && !factory.containsBean(name)) {
            // 满足条件,进入该分支
            // 空集合
            autowiredBeanNames = new LinkedHashSet<>();
            // 进行Bean解析, requestingBeanName: fooService
            resource = beanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null);
            if (resource == null) {
                throw new NoSuchBeanDefinitionException(element.getLookupType(), "No resolvable resource object");
            }
        }
    // ...(省略)
}

接着调用beanFactory.resolveDependency

// DefaultListableBeanFactory#resolveDependency

public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
        @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
    // 初始化参数解析器
    descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
    // 处理依赖类型为Option类的case,很显然,需要JDK1.8以上才支持,一般不会进入此处
    if (Optional.class == descriptor.getDependencyType()) {
        return createOptionalDependency(descriptor, requestingBeanName);
    }
    // 处理类型为ObjectFactory、ObjectProvider,略过
    else if (ObjectFactory.class == descriptor.getDependencyType() ||
            ObjectProvider.class == descriptor.getDependencyType()) {
        return new DependencyObjectProvider(descriptor, requestingBeanName);
    }
    // 处理类型为JSR-330的javax.inject.Provider,略过
    else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
        return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);
    }
    // 99%的情况进入else的分支
    else {
        // 处理@Lazy 注解的情况,一般特殊需要才会在字段或方法上标注@Lazy,不是本文重点,略过
        Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
                descriptor, requestingBeanName);
        if (result == null) {
            // 大部分情况会走下面的case,进行真正的解析
            result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
        }
        return result;
    }
}

虽然有很多条件分支存在,但大部分场景都不会用到,因此只需要关注result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);这一行核心代码即可

有意思的是,一些人认为Spring考虑周全,兼容各种case,360度无死角,正是Java web领域它独领风骚的魅力之所在;与此同时,另一些人认为正是因为考虑太周全,Spring变的越来越臃肿不堪,代码阅读越发困难,需要瘦瘦身,甚至需要一个更轻量级的框架来替代。天下大势,分久必合,合久必分,苍天饶过谁?

整体上看一下doResolveDependency方法核心逻辑

public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
        @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

    InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
    try {
        // 只有ShortcutDependencyDescriptor实现了resolveShortcut方法,返回了非空值。目前版本代码只在AutowiredFieldElement、AutowiredMethodElement类中使用到,也即是说,只有解析@Autowired、@Value注解的元素才会用到,目的是为了将解析结果缓存起来,避免重复解析
        Object shortcut = descriptor.resolveShortcut(this);
        if (shortcut != null) {
            return shortcut;
        }
        // 依赖的类型type: java.util.List
        Class<?> type = descriptor.getDependencyType();
        
        // 处理@Value注解,取值注解中的value属性中的值(原样,未经过解析的),如果descriptor未被@Value标注,则返回null
        // 注:从此处可知,@Value注解的优先级较高,只要找到了就处理,不再往下走
        Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
        if (value != null) {
            if (value instanceof String) {
                // 处理占位符如${},做占位符的替换(不解析SP EL表达式)
                String strVal = resolveEmbeddedValue((String) value);
                BeanDefinition bd = (beanName != null && containsBean(beanName) ?
                        getMergedBeanDefinition(beanName) : null);
                // 解析SP EL(如#{})
                value = evaluateBeanDefinitionString(strVal, bd);
            }
            TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
            try {
                // 类型转换,把解析出来的结果转成type类型
                return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());
            }
            catch (UnsupportedOperationException ex) {
                // A custom TypeConverter which does not support TypeDescriptor resolution...
                return (descriptor.getField() != null ?
                        converter.convertIfNecessary(value, type, descriptor.getField()) :
                        converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
            }
        }
        // 本文重点,解析"集合类"Bean,如果依赖的类型不是"集合类",则返回null
        // 注:"集合类"是口语描述,目的是方便记忆,实际上,还支持数组类型和Map类型
        
        /**
        * 1. array
        * 2. Collection及其子类
        * 3. Map
        */
        Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
        if (multipleBeans != null) {
            return multipleBeans;
        }

        // 代码走到此处,说明依赖的是非"集合类",
        // 查找所有类型为type的实例,存放在matchingBeans <beanName, bean>
        Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
        if (matchingBeans.isEmpty()) {
            if (isRequired(descriptor)) {
                // 如果IoC容器中找不到符合条件的Bean,且依赖项标识为required,则抛出NoSuchBeanDefinitionException异常
                raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
            }
            return null;
        }

        String autowiredBeanName;
        Object instanceCandidate;
        // 如果找到多个元素,Spring要按一定的机制进行挑选,如果不满足规则可能需要抛出异常
        if (matchingBeans.size() > 1) {
            // 按以下顺序,找到符合条件的就直接返回
            // 1. 挑选出被标识为primary的bean
            // 2. 挑选标识了@Priority,且先级级最高的bean。可以不标识,一旦标识,不允许同一优先级的存在
            // 3. fallback,依赖的名称与matchingBeans中任意一Key匹配
            autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
            if (autowiredBeanName == null) {
                if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
                    // 非集合类,找到了多个符合条件的Bean,抛出异常
                    return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
                }
                else {
                    // In case of an optional Collection/Map, silently ignore a non-unique case:
                    // possibly it was meant to be an empty collection of multiple regular beans
                    // (before 4.3 in particular when we didn't even look for collection beans).
                    return null;
                }
            }
            instanceCandidate = matchingBeans.get(autowiredBeanName);
        }
        else {
            // We have exactly one match.
            // 找到匹配的唯一元素,直接取出来
            Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
            autowiredBeanName = entry.getKey();
            instanceCandidate = entry.getValue();
        }

        if (autowiredBeanNames != null) {
            // 将待装配的Bean名称放入autowiredBeanNames集合里
            autowiredBeanNames.add(autowiredBeanName);
        }
        if (instanceCandidate instanceof Class) {
            instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
        }
        Object result = instanceCandidate;
        if (result instanceof NullBean) {
            if (isRequired(descriptor)) {
                raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
            }
            result = null;
        }
        // 类型校验,确保类型与解析出来的Bean实例能够匹配
        if (!ClassUtils.isAssignableValue(type, result)) {
            throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
        }
        return result;
    }
    finally {
        ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
    }
}

本文案例是注入集合类对象,因此把关注点放到Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);

private Object resolveMultipleBeans(DependencyDescriptor descriptor, @Nullable String beanName,
        @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) {

    // 依赖的类型
    final Class<?> type = descriptor.getDependencyType();

    if (descriptor instanceof StreamDependencyDescriptor) {
        // ...(特殊的用法,省略)
    }
    // 如果是数组
    else if (type.isArray()) {
        // 与下边的分支逻辑类似
        Class<?> componentType = type.getComponentType();
        ResolvableType resolvableType = descriptor.getResolvableType();
        Class<?> resolvedArrayType = resolvableType.resolve(type);
        if (resolvedArrayType != type) {
            componentType = resolvableType.getComponentType().resolve();
        }
        if (componentType == null) {
            return null;
        }
        Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType,
                new MultiElementDescriptor(descriptor));
        if (matchingBeans.isEmpty()) {
            return null;
        }
        if (autowiredBeanNames != null) {
            autowiredBeanNames.addAll(matchingBeans.keySet());
        }
        TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
        Object result = converter.convertIfNecessary(matchingBeans.values(), resolvedArrayType);
        if (result instanceof Object[]) {
            Comparator<Object> comparator = adaptDependencyComparator(matchingBeans);
            if (comparator != null) {
                Arrays.sort((Object[]) result, comparator);
            }
        }
        return result;
    }
    // 如果依赖的类型是Collection及其子接口(不能是具体实现类)
    else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {
        // 获取集合元素的泛型信息,即集合元素类型。如果没有泛型信息,即获取不了元素类型,则返回null
        Class<?> elementType = descriptor.getResolvableType().asCollection().resolveGeneric();
        if (elementType == null) {
            return null;
        }
        // 查找所有类型为type的实例,存放在matchingBeans <beanName, bean>
        Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType,
                new MultiElementDescriptor(descriptor));
        if (matchingBeans.isEmpty()) {
            return null;
        }
        // 将待装配的Bean名称放入autowiredBeanNames集合里
        if (autowiredBeanNames != null) {
            autowiredBeanNames.addAll(matchingBeans.keySet());
        }
        TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
        // 将解析出来的结果转换成目标类型type的元素(List)
        Object result = converter.convertIfNecessary(matchingBeans.values(), type);
        if (result instanceof List) {
            // 如果待注入对象为List实例,就再按AnnotationAwareOrderComparator排个序
            // 可用PriorityOrdered、Ordered、@Order、@Priority定义顺序
            Comparator<Object> comparator = adaptDependencyComparator(matchingBeans);
            if (comparator != null) {
                ((List<?>) result).sort(comparator);
            }
        }
        return result;
    }
    // 如果依赖的是Map类型(如: Map<String, BarService>)
    else if (Map.class == type) {
        ResolvableType mapType = descriptor.getResolvableType().asMap();
        Class<?> keyType = mapType.resolveGeneric(0);
        if (String.class != keyType) {
            // Map的Key必须为String类型,表示Bean的名称
            return null;
        }
        Class<?> valueType = mapType.resolveGeneric(1);
        // 同样的,value类型的泛型信息必须指定,否则为null
        if (valueType == null) {
            return null;
        }
        Map<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType,
                new MultiElementDescriptor(descriptor));
        if (matchingBeans.isEmpty()) {
            return null;
        }
        if (autowiredBeanNames != null) {
            autowiredBeanNames.addAll(matchingBeans.keySet());
        }
        // map无序,直接返回
        return matchingBeans;
    }
    else {
        return null;
    }
}

Array与Collection及其子接口的处理逻辑相似,都是找到matchingBeans,并通过TypeConverter转换成目标类型,再经过AnnotationAwareOrderComparator排序,如此,返回的结集便是带有顺序的Array或Collection

Map的处理是找到matchingBeans,但不排序,此处需要注意的是,Key必须为String类型,表示Bean的名称。在本案例中,可写成依赖的属性是: Map<String, BarService>

doResolveDependencyresolveMultipleBeans方法中多次出现findAutowireCandidates的调用,它的作用是根据requiredType在IoC中找到匹配的Bean实例,并组装成Map<BeanName, BeanInstance>返回,源码如下:

protected Map<String, Object> findAutowireCandidates(
            @Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {
    // 从IoC中拿到所有类型为requiredType的bean name,本质上调用的是ListableBeanFactory#getBeanNamesForType方法进行获取
    String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
            this, requiredType, true, descriptor.isEager());
    Map<String, Object> result = new LinkedHashMap<>(candidateNames.length);
    
    // 处理特殊的依赖,如BeanFactory、ApplicationContext等,这些类的实例并不在狭义的IoC容器中,而是保存在resolvableDependencies
    // 只能通过遍历resolvableDependencies与requiredType进行比较,满足条件返返回
    for (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) {
        Class<?> autowiringType = classObjectEntry.getKey();
        if (autowiringType.isAssignableFrom(requiredType)) {
            Object autowiringValue = classObjectEntry.getValue();
            autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
            if (requiredType.isInstance(autowiringValue)) {
                result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);
                break;
            }
        }
    }
    // 本案例中candidateNames有两个元素[barServiceImplOne、barServiceImplTwo]
    for (String candidate : candidateNames) {
        // 非自引用 且 candidate对应的BeanDefinition是autowireCandidate,则表明符合条件,添加到CandidateEntry中
        // 注:autowireCandidate是AbstractBeanDefinition的一个属性,默认值为true,即所有的Bean默认都支持autowire
        if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) {
            addCandidateEntry(result, candidate, descriptor, requiredType);
        }
    }
    if (result.isEmpty()) {
        boolean multiple = indicatesMultipleBeans(requiredType);
        // Consider fallback matches if the first pass failed to find anything...
        DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();
        for (String candidate : candidateNames) {
            if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor) &&
                    (!multiple || getAutowireCandidateResolver().hasQualifier(descriptor))) {
                addCandidateEntry(result, candidate, descriptor, requiredType);
            }
        }
        if (result.isEmpty() && !multiple) {
            // Consider self references as a final pass...
            // but in the case of a dependency collection, not the very same bean itself.
            for (String candidate : candidateNames) {
                if (isSelfReference(beanName, candidate) &&
                        (!(descriptor instanceof MultiElementDescriptor) || !beanName.equals(candidate)) &&
                        isAutowireCandidate(candidate, fallbackDescriptor)) {
                    addCandidateEntry(result, candidate, descriptor, requiredType);
                }
            }
        }
    }
    return result;
}



private void addCandidateEntry(Map<String, Object> candidates, String candidateName,
        DependencyDescriptor descriptor, Class<?> requiredType) {

    if (descriptor instanceof MultiElementDescriptor) {
        // 下面的一行代码本质上是: beanFactory.getBean(beanName), 即根据beanName上IoC容器中查找
        Object beanInstance = descriptor.resolveCandidate(candidateName, requiredType, this);
        if (!(beanInstance instanceof NullBean)) {
            // 找到,添加进CandidateEntry中
            candidates.put(candidateName, beanInstance);
        }
    }
    else if (containsSingleton(candidateName) || (descriptor instanceof StreamDependencyDescriptor &&
            ((StreamDependencyDescriptor) descriptor).isOrdered())) {
        // 同上
        Object beanInstance = descriptor.resolveCandidate(candidateName, requiredType, this);
        candidates.put(candidateName, (beanInstance instanceof NullBean ? null : beanInstance));
    }
    else {
        candidates.put(candidateName, getType(candidateName));
    }
}

需要说明一下的是,findAutowireCandidates方法中出现了resolvableDependencies(Map<Class<?>, Object>)属性,它定义在DefaultListableBeanFactory,其作用是存放Spring内部一些特殊的Bean,比如BeanFactory、ResourceLoader、ApplicationContext、ServletRequest等;而一般普通的Bean存放在DefaultSingletonBeanRegistry.singletonObjects(Map<String, Object>)属性中,该属性就是狭义上的IoC容器

总结

AutowireCapableBeanFactory#resolveDependency,本质上是根据descriptor(依赖描述符)到Spring中找到符合描述的Bean(们)并返回。既可以解析由@Resouce标注的依赖信息,也可以解析由@Autowired、@Value标注的依赖信息;既可以解析单一依赖元素,也可以解析多个依赖("集合类")元素,当是集合类元素时,如果是Array或者是Collection,还可以根据PriorityOrdered、Ordered、@Order、@Priority定义注入的元素顺序;既在狭义的IoC容器(singletonObjects)中寻找,也在特殊的容器(resolvableDependencies)中寻找。

总之,该方法的能力非常强大,涉及的面也非常地广泛,因此,本文仅分享了其中一些与注入集合对象案例相关的细节。受限于作者的表达功力,本文并不足以描述它的全貌,还有诸多细节未能展开进行讲解,例如:

  1. @Lazy是如何解析的?
  2. @Value的占位符如何解析?SP EL表达式又如何解析?
  3. determineAutowireCandidate的细节是如何展开的?
  4. 类型转换又是如何进行的?
  5. 该方法还支持哪些骚操作?

导读:

AutowireCapableBeanFactory探密(1)——为第三方框架赋能

AutowireCapableBeanFactory探密(2)——传统装配模式与现代注解驱动注入方式

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

推荐阅读更多精彩内容