Spring核心流程分析

Spring使用BeanFactory来产生和管理Bean,它是工厂模式的实现。BeanFactory使用控制反转模式(IOC)将应用的配置和依赖性规范与实际的应用程序代码分开。BeanFactory使用依赖注入(DI)的方式给组件提供依赖 。

一、Bean的注册

image.png

Spring对于Bean的配置有两种方式:

  1. 基于资源文件解析的方式,其中最常用的是XML配置优点:可以在后期维护的时候适当地调整Bean管理模式,并且只要遵循一定的命名规范,可以让程序员不必关心Bean之间的依赖关系 。缺点:系统越庞大,XML配置文件就越大,关系错综复杂,容易导致错误 。
  2. 基于 JavaConfig 的注解配置方式优点:配置比较方便,程序员只要在service层代码设置即可实现,不需要知道系统需要多少个Bean,交给容器来注入就好了。 缺点:当你要修改或删除一个Bean的时候,你无法确定到底有多少个其他的Bean依赖于这个Bean。(解决方法 : 需要有严格的开发文档,在修改实现时尽可能继续遵守相应的接口规则,避免使其他依赖于此的Bean不可用)

其中整个Bean加载的核心部分是:DefaultListableBeanFactory,它还是Spring注册及加载Bean的默认实现。
它继承了:AbstractAutowireCapableBeanFactory
并实现了:ConfigurableListableBeanFactory以及BeanDefinitionRegistry接口 。

image.png

可以看出来,层次还是相当清晰的,我们先粗略的看看这些类都具备哪些功能。

  • AliasRegistry: 定义对alias的简单增删改等操作。
  • SimpleAliasRegistry: 主要使用map作为alias的缓存,并对接口AliasRegistry 进行实现。
  • SingletonBeanRegistry:定义对单例的注册及获取 。
  • BeanFactory:定义获取Bean及Bean的各种属性 。
  • DefauItSingletonBeanRegistry:对接口SingletonBeanRegistry各函数的实现。
  • HierarchicalBeanFactory:继承 BeanFactory,也就是在 BeanFactory 定义的功能的基础上增加了对 parentFactory的支持 。
  • BeanDefinitionRegistry: 定义对 BeanDefinition 的各种增删改操作 。
  • FactoryBeanRegistrySupport:在DefaultSingletonBeanRegistry 基础上增加了对FactoryBean的特殊处理功能 。
  • ConfigurableBeanFactory:提供配置Factory 的各种方法 。
  • ListableBeanFactory:根据各种条件获取Bean的配置清单 。
  • AbstractBeanFactory:综合FactoryBeanRegistrySupport和ConfigurableBeanFactory 的功能。
  • AutowireCapableBeanFactory:提供创建 Bean、自动注入、初始化以及应用Bean的后处理器 。
  • AbstractAutowireCapableBeanFactory:综合 AbstractBeanFactory并对接口
  • AutowireCapableBeanFactory进行实现。
  • ConfigurableListableBeanFactory: Beanfactory配置清单,指定忽略类型及接口等。
  • DefaultListableBeanFactory: 综合上面所有功能,主要是对Bean注册后的处理 。

无论是使用配置文件的方式还是使用注解的方式,我们其实只是将未来需要使用的对象的类路径,名称,以及创建的方式等等信息告诉了Spring容器,经过加载,解析之后封装成了BeanDefinition对象并缓存到BeanFactory中。当我们真正需要使用的时候Spring会根据缓存中的BeanDefinition对象,为我们创建出我们实际需要的对象。

二、Bean的实例化

1.触发实例化的时机

我们知道Spring是通过BeanFactory创建Bean的,当我们需要使用对象时,直接通过BeanFactory.getBean()方法即可获取对象。我们发现Spring将我们实例化后的对象(单例)缓存了起来。

image.png
  1. Spring容器在初始化BeanFactory的过程中会主动实例化单例非懒加载的Bean
  2. 在容器的启动过程中如果需要使用其它容器中的Bean会提前出发实例化(主要是执行的方法是:invokeBeanFactoryPostProcessors以及在其它对象实例化过程中执行属性填充populateBean方法需要注入对象的时候)。

2.实例化过程

Spring整个加载过程都在AbstractApplicationContext.refresh()中去完成。


public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
  // Prepare this context for refreshing. 准备刷新
    prepareRefresh();

    // Tell the subclass to refresh the internal bean factory.
    /*
     * 刷新内部BeanFactory
     * ClassPathXmlApplicationContext:1.新建BeanFactory,2.解析xml,3.封装成BeanDefintion对象
     * AnnotationConfigApplicationContext: 获取GenericApplicationContext中的beanFactory
     */
    ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

    // Prepare the bean factory for use in this context.
    // 为BeanFactory进行必要的准备工作
    prepareBeanFactory(beanFactory);

    try {
        // Allows post-processing of the bean factory in context subclasses.
        // 进行额外的后置处理
        postProcessBeanFactory(beanFactory);

        // Invoke factory processors registered as beans in the context.
        // 执行1.BeanDefinitionResgistryPostProcessor、2.BeanFactoryPostProcessor的回调
        invokeBeanFactoryPostProcessors(beanFactory);

        // Register bean processors that intercept bean creation.
        // 实例化所有实现了BeanPostProcessor接口的类并注册到容器中去
        registerBeanPostProcessors(beanFactory);

        // Initialize message source for this context. 国际化
        initMessageSource();

        // Initialize event multicaster for this context. 初始化事件类
        initApplicationEventMulticaster();

        // Initialize other special beans in specific context subclasses. 子容器自定义实现
        onRefresh();

        // Check for listener beans and register them. 注册事件
        registerListeners();

        // Instantiate all remaining (non-lazy-init) singletons.
        //1.bean实例化,2.ioc 3.注解支持 4.BeanPostProssor执行 5.AOP入口
        finishBeanFactoryInitialization(beanFactory);

        // Last step: publish corresponding event.
        finishRefresh();
    }  catch (BeansException ex) {
        if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                    "cancelling refresh attempt: " + ex);
        }

        // Destroy already created singletons to avoid dangling resources.
        destroyBeans();

        // Reset 'active' flag.
        cancelRefresh(ex);

        // Propagate exception to caller.
        throw ex;
    }

    finally {
        // Reset common introspection caches in Spring's core, since we
        // might not ever need metadata for singleton beans anymore...
        resetCommonCaches();
    }
}

我们着重关注一下这个方法:finishBeanFactoryInitialization它是Spring实例化的入口方法。

  • 获取BeanFactory中所有的beanDefinition名称
  • 合并RootBeanDefinition
  • 非抽象的,单例的,非懒加载的就实例化
  • 是否实现了FactoryBean接口,如果是加一个&前缀调用内部的getObject,否则直接获取
  • 首先尝试从缓存中获取getSingleton(beanName),(首次获取必然获取不到)接着进入创建方法 单例创建之前的操作:加入到正在创建的一个set集合中singletonsCurrentlyInCreation
  • 调到外部的匿名类中的实例化方法,如果有值已经创建成功singletonFactory.getObject();
  • 调到doCreateBean创建实例BeanWrapper
  • 允许早期引用加入单例工厂直接返回这个bean的引用。addSingletonFactory(beanName,()->getEarlyBeanReference(beanName, mbd, bean));
  • 填充属性的值populateBean
  • initializeBean

3.单例的循环依赖

什么是循环依赖?
循环依赖就是循环引用,就是两个或多个 bean 相互之间的持有对方,比如 CircleA 引用 C ircleB , CircleB 引用 CircleC, CircleC 引用 CircleA,则它们最终反映为一个环。

@Component
public class CircleClassA {
    public CircleClassA() {
        System.out.println("====CircleClassA====");
    }

    @Autowired
    private CircleClassB circleClassB;
}

@Component
public class CircleClassB {
    public CircleClassB() {
        System.out.println("====CircleClassB====");
    }

    @Autowired
    private CircleClassA circleClassA;
}

首先我们需要明确的一点是:Spring只会处理上述类型的循环依赖(单例,非构造函数注入)其它情况直接报错。
Spring在处理Bean实例化的过程中是如何解决循环依赖的呢?我们需要着重关注如下3个Map。

  • singletonObjects
  • earlySingletonObjects
  • singletonFactories

具体步骤如下:

  1. CircleClassA 在实例化的时候 首先从缓存中获取不到,然后进入创建方法,接着将CircleClassA加入到singletonsCurrentlyInCreation中,并在singletonFactories加入一个getEarlyBeanReference 表示当前CircleClassA正在创建中。
  2. 当CircleClassA填充属性的值populateBean时,发现依赖了CircleClassB,触发CircleClassB的实例化。
  3. 实例化CircleClassB,首先从缓存中获取不到,然后进入创建方法,接着将CircleClassB加入到singletonsCurrentlyInCreation中,并在singletonFactories加入一个getEarlyBeanReference 表示当前CircleClassB正在创建中。
  4. 当CircleClassB填充属性的值populateBean时,发现依赖了CircleClassA,触发CircleClassA的实例化。
  5. 再次进入CircleClassA 的实例化方法,此时虽然singletonObjects中获取不到CircleClassA,但是检测到CircleClassA存在早期暴露的实例因此尝试从earlySingletonObjects中获取,首次调用获取不到从singletonFactories中获取,取到之后将CircleClassA放入earlySingletonObjects,并提供给CircleClassB填充属性的值populateBean时使用。(此时的CircleClassA只是个引用的地址,实际上并不是一个完整的CircleClassA)。
  6. 此时CircleClassB已经完成了(内部依赖的CircleClassA是个不完整的实例)并提供给CircleClassA填充属性的值populateBean时使用。CircleClassA完成了CircleClassB的注入,它变成了一个完整的实例。
  7. 又由于CircleClassB中引用了CircleClassA的一个地址。所以它也同时变成了一个完整的。
  8. 实例化完成之后删除早期引用map,并放入单例map中缓存singletonObjects。

4.AOP的实现原理

4.1 使用

我们在日常开发的过程中常常会需要监控业务方法的耗时或者打印业务执行前后的日志以方便在出现业务故障的时候快速定位问题。但是我们又不希望在已经封包的业务代码中进行额外的编程,针对此种场景Spring为我们提供了切面编程。如下代码案例就是一个我们在没有改变原来方法的前提下实现了打印方法耗时的功能。

@Component
@Aspect
@Slf4j
public class MyAspect {
    @Pointcut("execution( * org.example.springaop.*.*(..))")
    public void pointCut(){};

    @Around("pointCut()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long start=System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long end = System.currentTimeMillis();
        log.info("{} invoke before at {} , after at {} cost: {}ms",joinPoint.getSignature().getName(),start,end,(end-start));
        return result;
    }
}

4.2 三要素

  • 切面类 @Aspect
  • 切点 @Pointcut
  • 通知@Around@Before@After@AfterReturning@AfterThrowing

4.3 实现

那么Spring是如何实现这么好用的功能的呢?
我们着重看一下这个注解类:@EnableAspectJAutoProxy

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

   boolean proxyTargetClass() default false;

   boolean exposeProxy() default false;

}

@Import(AspectJAutoProxyRegistrar.class)

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //注册AnnotationAwareAspectJAutoProxyCreator,AOP注解解析类
        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

        AnnotationAttributes enableAspectJAutoProxy =
                AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
        if (enableAspectJAutoProxy != null) {
            /*
             * false:1。实现了接口的使用JDK的Proxy实现代理。2.没有接口的使用cglib实现代理
             * true:全部使用cglib实现代理
             */
            if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }
            // 是否暴露代理对象
            if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
                AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
            }
        }
    }
}

可以看到加了此注解之后注册的类是:AspectJAutoProxyRegistrar,
该类实现了的接口是:ImportBeanDefinitionRegistrar

registerBeanDefinitions:该方法中向Spring中注册了:
AnnotationAwareAspectJAutoProxyCreator

image.png

通过类关系图可以发现它实现了BeanPostProcessor接口势必在每个Bean实例化的时候会调用到postProcessAfterInitialization方法。

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
  if (bean != null) {
      Object cacheKey = getCacheKey(bean.getClass(), beanName);
      if (this.earlyProxyReferences.remove(cacheKey) != bean) {
        //如果它适合被代理,则需要封装指定 bean
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    //如果已经处理过,直接返回
  if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
      return bean;
  }
  //如果无须代理,直接返回
  if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
      return bean;
  }
  //给定的 bean 类是否代表一个基础设施类 ,基础设施类不应代理 ,或者配置了指定 bean 不需要自动代理
  if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
      this.advisedBeans.put(cacheKey, Boolean.FALSE);
      return bean;
  }
  
  //如果当前的Bean有advice则创建当前类的代理类
  // Create proxy if we have advice.
  Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
  if (specificInterceptors != DO_NOT_PROXY) {
    this.advisedBeans.put(cacheKey, Boolean.TRUE);
    //创建代理
        Object proxy = createProxy(
                bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    }

    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
}

它主要实现的功能就是在Spring实例化Bean的时候,调用了BeanPostProcessor的postProcessAfterInitialization方法,判断当前实例化的Bean是否需要被代理,如果发现需要被代理,那么会创建一个代理对象并返回保存到Spring的Bean对象缓存里面去。
而使用代理调用方法时,就可以实现不更改原对象中的方法的同时,又实现了我们期望的一些功能。Spring中创建代理有两种方式:

  1. JdkDynamicAopProxy

  2. ObjenesisCglibAopProxy

JdkDynamicAopProxy:代理对象必然会实现InvocationHandler接口并实现invoke方法。因此我们将目光转移到此处:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    MethodInvocation invocation;
    Object oldProxy = null;
    boolean setProxyContext = false;

    TargetSource targetSource = this.advised.targetSource;
    Object target = null;

    try {
        //忽略equals、hashcode
        if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
            // The target does not implement the equals(Object) method itself.
            return equals(args[0]);
        }
        else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
            // The target does not implement the hashCode() method itself.
            return hashCode();
        }
        else if (method.getDeclaringClass() == DecoratingProxy.class) {
            // There is only getDecoratedClass() declared -> dispatch to proxy config.
            return AopProxyUtils.ultimateTargetClass(this.advised);
        }
        else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
                method.getDeclaringClass().isAssignableFrom(Advised.class)) {
            // Service invocations on ProxyConfig with the proxy config...
            return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
        }

        Object retVal;
        //暴露代理类
        if (this.advised.exposeProxy) {
            // Make invocation available if necessary.
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }

        // Get as late as possible to minimize the time we "own" the target,
        // in case it comes from a pool.获取被代理类
        target = targetSource.getTarget();
        Class<?> targetClass = (target != null ? target.getClass() : null);

        // Get the interception chain for this method.从代理工厂中拿到拦截器链
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

        // Check whether we have any advice. If we don't, we can fallback on direct
        // reflective invocation of the target, and avoid creating a MethodInvocation.
        //如果该方法没有拦截器,直接反射调用
        if (chain.isEmpty()) {
            // We can skip creating a MethodInvocation: just invoke the target directly
            // Note that the final invoker must be an InvokerInterceptor so we know it does
            // nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
            retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
        }
        else {
            // We need to create a method invocation...
            //如果该方法有拦截器,则调用拦截器的proceed
            invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
            // Proceed to the joinpoint through the interceptor chain.
            retVal = invocation.proceed();
        }

        // Massage return value if necessary.
        Class<?> returnType = method.getReturnType();
        if (retVal != null && retVal == target &&
                returnType != Object.class && returnType.isInstance(proxy) &&
                !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
            // Special case: it returned "this" and the return type of the method
            // is type-compatible. Note that we can't help if the target sets
            // a reference to itself in another returned object.
            retVal = proxy;
        }
        else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
            throw new AopInvocationException(
                    "Null return value from advice does not match primitive return type for: " + method);
        }
        return retVal;
    }
    finally {
        if (target != null && !targetSource.isStatic()) {
            // Must have come from TargetSource.
            targetSource.releaseTarget(target);
        }
        if (setProxyContext) {
            // Restore old proxy.
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

需要注意的是一旦代理方法是需要被增强的。
Spring就会构建一个切面:Advisor,它必须包含一个切点:Pointcut,还有一个增强:Advice。

  • Advisor:标注了@Aspect的一个切面;
  • Pointcut:我们可以将它理解为就是一个匹配的条件,一般是一个表达式或者某个注解 ;
  • Advice:常用的有@Around@Before@After@AfterReturning@AfterThrowing分别代表我们需要在目标方法执行前后可以附加的额外功能。

Spring会将一个方法所有的切面组成一个链路,并依次调用,从而实现功能的增强。

public Object proceed() throws Throwable {
    //  We start with an index of -1 and increment early.
    //如果执行链全部执行完直接调用目标类的目标方法
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        return invokeJoinpoint();
    }
    //链式调用
    Object interceptorOrInterceptionAdvice =
            this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
    if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
        // Evaluate dynamic method matcher here: static part will already have
        // been evaluated and found to match.
        InterceptorAndDynamicMethodMatcher dm =
                (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
        Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
        if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
            return dm.interceptor.invoke(this);
        }
        else {
            // Dynamic matching failed.
            // Skip this interceptor and invoke the next in the chain.
            return proceed();
        }
    }
    else {
        // It's an interceptor, so we just invoke it: The pointcut will have
        // been evaluated statically before this object was constructed.
        return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
    }
}


程序员的核心竞争力其实还是技术,因此对技术还是要不断的学习,关注 “IT 巅峰技术” 公众号 ,该公众号内容定位:中高级开发、架构师、中层管理人员等中高端岗位服务的,除了技术交流外还有很多架构思想和实战案例。

作者是 《 消息中间件 RocketMQ 技术内幕》 一书作者,同时也是 “RocketMQ 上海社区”联合创始人,曾就职于拼多多、德邦等公司,现任上市快递公司架构负责人,主要负责开发框架的搭建、中间件相关技术的二次开发和运维管理、混合云及基础服务平台的建设。

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

推荐阅读更多精彩内容