【3】Spring源码-AOP

4. AOP

使用面向对象编程(OOP)有一些弊端,当需要为多个不具有继承关系的对象引入同一个公共行为时,例如日志、安全检测等,我们只有在每个对象里引用公共行为,这样程序中就产生了大量的重复代码,程序就不便于维护了,所以就有了一个对面向对象编程的补充,即面向方面编程(AOP),AOP所关注的方向是横向的,不同于OOP的纵向。

AOP注入到Bean中的入口:

image.png

BeanFactory的getBean()方法的一大堆逻辑中会调用postProcessAfterInitialization方法,postProcessAfterInitialization方法会返回被代理的bean对象。也就是说getBean()方法会拿出被代理的对象。

AnnotationAwareAspectJAutoProxyCreator实现了BeanPostProcessor接口,而实现BeanPostProcessor后,在getBean时,spring会遍历所有已经注册的BeanProcessor,调用其postProcessAfterInitialization方法,AOP的主要逻辑在此postProcessAfterInitialization方法中。AnnotationAwareAspectJAutoProxyCreator继承自AbstractAutoProxyCreator,所以后续需要看下实现类AbstractAutoProxyCreator#postProcessAfterInitialization()方法

image.png

4.1 动态代理

// AbstractAutoProxyCreator#postProcessAfterInitialization()
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException {
   if (bean != null) {
      Object cacheKey = getCacheKey(bean.getClass(), beanName);
      if (this.earlyProxyReferences.remove(cacheKey) != 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;
   }
   // 在缓存中寻找Bean,如果发现该Bean无需代理(没匹配到Advisors)则直接返回
   if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
      return bean;
   }
   if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
      this.advisedBeans.put(cacheKey, Boolean.FALSE);
      return bean;
   }

   // 获取增强方法或者增强器
   Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
   // protected static final Object[] DO_NOT_PROXY = null;将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;
}

后面将主要看一下getAdvicesAndAdvisorsForBean和createProxy方法。

4.1.1 获取增强方法或增强器

protected Object[] getAdvicesAndAdvisorsForBean(
      Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
   // 主要实现逻辑在这里
   List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
   if (advisors.isEmpty()) {
      return DO_NOT_PROXY;
   }
   return advisors.toArray();
}
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
   // 获取所有定义的所有增强器
   List<Advisor> candidateAdvisors = findCandidateAdvisors();
   // 获取可以被apply的增强器
   List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
   extendAdvisors(eligibleAdvisors);
   if (!eligibleAdvisors.isEmpty()) {
      // 根据@Priority注解中的value排序
      eligibleAdvisors = sortAdvisors(eligibleAdvisors);
   }
   return eligibleAdvisors;
}

findCandidateAdvisors

  1. 获取所有beanName,这一步骤中所有在beanFactory中注册的bean都会被提取出来。
  2. 遍历所有beanName,并找出声明AspectJ注解的类(反射获取注解@Aspect),进行进一步的处理。
    a. 获取PointCut注解,并获取其中的表达式@PointCut(“execution(* .test*()..)”)。
    b. 获取@Before,@Around,@After等注解,并交给对应的增强器。
  3. 对标记为AspectJ注解的类进行增强器的提取。
  4. 将提取结果加入缓存。

其中2.a 步,在经历比较深的调用层次后,我们可以找到关键代码,如下:

public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut,
      MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {

   Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
   validate(candidateAspectClass);

   AspectJAnnotation<?> aspectJAnnotation =
         AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
   if (aspectJAnnotation == null) {
      return null;
   }

   // If we get here, we know we have an AspectJ method.
   // Check that it's an AspectJ-annotated class
   if (!isAspect(candidateAspectClass)) {
      throw new AopConfigException("Advice must be declared inside an aspect type: " +
            "Offending method '" + candidateAdviceMethod + "' in class [" +
            candidateAspectClass.getName() + "]");
   }

   if (logger.isDebugEnabled()) {
      logger.debug("Found AspectJ method: " + candidateAdviceMethod);
   }

   AbstractAspectJAdvice springAdvice;
   
   // 根据不同的注解类型封装不同的增强器
   switch (aspectJAnnotation.getAnnotationType()) {
      case AtPointcut:
         if (logger.isDebugEnabled()) {
            logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");
         }
         return null;
      case AtAround:
         springAdvice = new AspectJAroundAdvice(
               candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
         break;
      case AtBefore:
         springAdvice = new AspectJMethodBeforeAdvice(
               candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
         break;
      case AtAfter:
         springAdvice = new AspectJAfterAdvice(
               candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
         break;
      case AtAfterReturning:
         springAdvice = new AspectJAfterReturningAdvice(
               candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
         AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
         if (StringUtils.hasText(afterReturningAnnotation.returning())) {
            springAdvice.setReturningName(afterReturningAnnotation.returning());
         }
         break;
      case AtAfterThrowing:
         springAdvice = new AspectJAfterThrowingAdvice(
               candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
         AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
         if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
            springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
         }
         break;
      default:
         throw new UnsupportedOperationException(
               "Unsupported advice type on method: " + candidateAdviceMethod);
   }

   // Now to configure the advice...
   springAdvice.setAspectName(aspectName);
   springAdvice.setDeclarationOrder(declarationOrder);
   String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
   if (argNames != null) {
      springAdvice.setArgumentNamesFromStringArray(argNames);
   }
   springAdvice.calculateArgumentBindings();

   return springAdvice;
}

以前置增强和后置增强为例

前置增强:

在拦截器链中放置MethodBeforeAdviceInterceptor,而在MethodBeforeAdviceInterceptor中又放置了AspectJMethodBeforeAdvice,并在调用invoke时首先串联调用。

public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {

   private final MethodBeforeAdvice advice;

   /**
    * Create a new MethodBeforeAdviceInterceptor for the given advice.
    * @param advice the MethodBeforeAdvice to wrap
    */
   public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
      Assert.notNull(advice, "Advice must not be null");
      this.advice = advice;
   }


   @Override
   public Object invoke(MethodInvocation mi) throws Throwable {
      // 先调用AspectJMethodBeforeAdvice#before(),再执行原有方法
      this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
      return mi.proceed();
   }
}
public class AspectJMethodBeforeAdvice extends AbstractAspectJAdvice implements MethodBeforeAdvice, Serializable {

   public AspectJMethodBeforeAdvice(
         Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {

      super(aspectJBeforeAdviceMethod, pointcut, aif);
   }

   @Override
   public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
      // 反射调用增强方法
      invokeAdviceMethod(getJoinPointMatch(), null, null);
   }

   @Override
   public boolean isBeforeAdvice() {
      return true;
   }

   @Override
   public boolean isAfterAdvice() {
      return false;
   }

后置增强:

与前置增强不同,没有提供中间的类,而是在拦截器链中直接使用AspectJAfterAdvice

// 实现了MethodInterceptor接口
public class AspectJAfterAdvice extends AbstractAspectJAdvice
      implements MethodInterceptor, AfterAdvice, Serializable {

   public AspectJAfterAdvice(
         Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {

      super(aspectJBeforeAdviceMethod, pointcut, aif);
   }


   @Override
   public Object invoke(MethodInvocation mi) throws Throwable {
      try {
         // 调用原方法
         return mi.proceed();
      }
      finally {
         // 反射调用增强方法
         invokeAdviceMethod(getJoinPointMatch(), null, null);
      }
   }

   @Override
   public boolean isBeforeAdvice() {
      return false;
   }

   @Override
   public boolean isAfterAdvice() {
      return true;
   }
}

findCandidateAdvisors方法中返回了所有的增强器,下面看一下如何寻找匹配的增强器。
findAdvisorsThatCanApply
这个方法中主要调用了AopUtils#canApply()方法,用于判断pointCut中表达式与该bean是否匹配。注意哦,findAdvisorsThatCanApply是从getBean方法一路调用过来的。

public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
   /**
    * 处理引介增强,引介增强主要是可以给被代理类动态增加一个父类或实现一个接口(提供默认实现方法)或添加一个字段
    * 用的不太多,没细看
    */
   if (advisor instanceof IntroductionAdvisor) {
      return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
   }
   else if (advisor instanceof PointcutAdvisor) {
      PointcutAdvisor pca = (PointcutAdvisor) advisor;
      /**
       * 判断该pointCut能否被应用在该类上
       * 找到该类的所有接口,遍历它们的所有方法,看是否与pointCut定义的表达式匹配
       */
      return canApply(pca.getPointcut(), targetClass, hasIntroductions);
   }
   else {
      // IntroductionAdvisor或PointcutAdvisor以外的其他Advisor直接返回true
      return true;
   }
}

通过getAdvicesAndAdvisorsForBean方法获取了所有可以被应用在该Bean上的增强器,供下一步创建动态代理使用。

4.1.2 根据获取的增强进行代理

根据配置创建不同类型的动态代理

  1. 通过getProxy()将代理类返回
public Object getProxy(@Nullable ClassLoader classLoader) {
   return createAopProxy().getProxy(classLoader);
}
  1. Spring的代理有JDKProxy和CglibProxy的实现
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
   // 设置<aop:aspectj-autoproxy proxy-target-class="true"/>则config.isProxyTargetClass()为true,强制使用cglib
   if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
      Class<?> targetClass = config.getTargetClass();
      if (targetClass == null) {
         throw new AopConfigException("TargetSource cannot determine target class: " +
               "Either an interface or a target is required for proxy creation.");
      }
      if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
         return new JdkDynamicAopProxy(config);
      }
      // 返回cglib动态代理
      return new ObjenesisCglibAopProxy(config);
   }
   else {
      // 返回jdk动态代理
      return new JdkDynamicAopProxy(config);
   }
}

    a. 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。(JDK动态代理只能对实现了接口的类生成代理)
    b. 如果目标对象实现了接口,可以强制使用CGLIB实现AOP。
    c. 如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换。
    d. JDK动态代理的实现方式
        i. 实现InvocationHandler函数
        ii. 实现invoke方法,在此对原方法进行增强
        iii. getProxy方法,返回代理对象

  1. Spring AOP中的JdkDynamicAopProxy也与此相似,JdkDynamicAopProxy实现了InvocationHandler接口,AOP的核心逻辑就写在JdkDynamicAopProxy的invoke方法中。返回了一个代理对象,当真正执行Bean中的方法时,会执行代理对象的invoke方法
    a. 获取当前方法的拦截器链(包含增强方法等)
    b. 如果拦截器为空,直接调用切点方法
    c. 如果有拦截器,执行拦截器链
        i. 如果拦截器链中的拦截器的method与当前传入的method匹配则执行该拦截器
        ii. 如果method不匹配,则继续调用拦截器链,直到最后一个,如果也不匹配则执行被代理的方法
        iii. 拦截器链将代理的工作委托给了各个增强器,如MethodBeforeAdviceInterceptor
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   Object oldProxy = null;
   boolean setProxyContext = false;

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

   try {
      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) {
         // 解决内部调用aop(如事务)不生效的问题,将proxy暴露出来。将proxy注入AopContext。
         // 可以使用((UserService)AopContext.currentProxy()).testRollback2()调用,AopContext中存了一个threadlocal的proxy
         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);

      // 获取之前得到的拦截器链(Advisors)
      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()) {
         // 如果没有拦截器,则直接调用原方法
         Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
         retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
      }
      else {
         // 将拦截器封装在ReflectiveMethodInvocation
         MethodInvocation invocation =
               new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, 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);
      }
   }
}
/**
 * 递归方法,循环执行拦截器链中的所有拦截器
 */
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) {
      // 动态匹配当前被调用的方法与getBean()中注入的拦截器链是否匹配
      InterceptorAndDynamicMethodMatcher dm =
            (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
      if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
         // 如果匹配则执行
         return dm.interceptor.invoke(this);
      }
      else {
         // 没匹配到,则继续遍历拦截器链
         return proceed();
      }
   }
   else {
      // 普通拦截器,直接调用拦截器,如AspectJAfterAdvice#invoke(this),传入this,可以继续执行proceed方法,继续遍历全部拦截器链
      return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
   }
}
  1. 使用Cglib进行动态代理,对应逻辑在CglibAopProxy类中,具体实现AOP的逻辑与JdkDynamicAopProxy类似。

4.1.3 AspectJ

AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。

  1. 通知:
  • 定义:切面也需要完成工作。在 AOP 术语中,切面的工作被称为通知。
  • 工作内容:通知定义了切面是什么以及何时使用。除了描述切面要完成的工作,通知还解决何时执行这个工作。
  • Spring 切面可应用的 5 种通知类型:
            Before——在方法调用之前调用通知
            After——在方法完成之后调用通知,无论方法执行成功与否
            After-returning——在方法执行成功之后调用通知
            After-throwing——在方法抛出异常后进行通知
            Around——通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
  1. 连接点:
  • 定义:连接点是一个应用执行过程中能够插入一个切面的点。
  • 连接点可以是调用方法时、抛出异常时、甚至修改字段时、
  • 切面代码可以利用这些点插入到应用的正规流程中
  • 程序执行过程中能够应用通知的所有点。
  1. 切点:
  • 定义:如果通知定义了“什么”和“何时”。那么切点就定义了“何处”。切点会匹配通知所要织入的一个或者多个连接点。
  • 通常使用明确的类或者方法来指定这些切点。
  • 作用:定义通知被应用的位置(在哪些连接点)
  • 要对哪些方法进行增强,比如对test()方法进行before和after的增强,test()方法为切点。
  1. 切面:
  • 定义:切面是通知和切点的集合,通知和切点共同定义了切面的全部功能——它是什么,在何时何处完成其功能。
  1. 引入:
  • 引入允许我们向现有的类中添加方法或属性
  1. 织入:
  • 织入是将切面应用到目标对象来创建的代理对象过程。
  • 切面在指定的连接点被织入到目标对象中,在目标对象的生命周期中有多个点可以织入

4.2 静态代理

AOP的静态代理主要是在虚拟机启动时通过改变目标对象字节码的方式来完成对目标对象的增强,它与动态代理相比具有更高的效率,因为在动态代理调用的过程中,还需要一个动态创建代理类并代理目标对象的步骤,而静态代理则是在启动时便完成了字节码增强,当系统再次调用目标类时与调用正常的类并无差别,所以在效率上会相对高些。

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