Spring AOP 实现源码分析

1. AOP 概念

AOP(Aspect Oriented Programming),即面向切面编程。

  • 连接点(JoinPoint)
    程序执行的某个特定位置:如类开始初始化前、类初始化后、类某个方法调用前、调用后、方法抛出异常后。一个类或一段程序代码拥有一些具有边界性质的特定点,这些点中的特定点就称为“连接点”。Spring仅支持方法的连接点,即仅能在方法调用前、方法调用后、方法抛出异常时以及方法调用前后这些程序执行点织入增强。
  • 切点(Pointcut)
    每个类具有多个连接点,如果一个类拥有15个方法,那么这些方法都是连接点,连接点相当于数据库中的记录,而切点相当于查询条件。切点和连接点不是一对一的关系,一个切点可以匹配多个连接点。Spring AOP的规则解析引擎负责切点所设定的查询条件,找到对应的连接点。其实确切地说,不能称之为查询连接点,因为连接点是方法执行前、执行后等包括方位信息的具体程序执行点,而切点只定位到某个方法上,所以如果希望定位到具体连接点上,还需要提供方位信息。
  • 增强(Advice)
    增强是织入到目标类连接点上的一段程序代码,在Spring中,增强除用于描述一段程序代码外,还拥有另一个和连接点相关的信息,这便是执行点的方位。结合执行点方位信息切点信息,我们就可以找到特定的连接点。
  • 通知器(Advisor)
    当我们完成切面增强设计(Advice)和切入点的设计(Pointcut),需要一个对象把他们结合起来,Advisor 就是起到这个作用,通过Advisor ,可以确定在哪个Pointcut 使用哪个Advice。所以一个Advisor包含一个Advice 和 一个Pointcut 信息。

2. 一些疑问

  1. Spring AOP 增强的代理类 在什么时候创建 ?
  2. Spring AOP 怎样为一个类 和 方法 匹配 增强的 ?
  3. Spring AOP 是如何 协调 前置通知 后置通知 异常通知 返回通知的?
  4. 切面类 可以被 AOP增强么?

3. 注册 AnnotationAwareAspectJAutoProxyCreator

AnnotationAwareAspectJAutoProxyCreator是用来处理 当前项目当中 所有 AspectJ 注解的切面类。以及所有的 Spring Advisor ,同时 也是一个 BeanPostProcessor 。所以 想了解Spring AOP 如何实现,就要先分析 AnnotationAwareAspectJAutoProxyCreator,那么他从何而来,是如何注册到容器内的。
注册AnnotationAwareAspectJAutoProxyCreator方式:

  1. 使用 EnableAspectJAutoProxy注解,改注解 又被 @Import 注解注释,向容器中注册了AspectJAutoProxyRegistrar,最后由 AspectJAutoProxyRegistrar 向容器中注册 AnnotationAwareAspectJAutoProxyCreator。
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            //向容器注册 AnnotationAwareAspectJAutoProxyCreator
        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
        AnnotationAttributes enableAspectJAutoProxy =
                AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
        //对proxyTargetClass 属性的支持,切换JDK代理 和 CGLIB 代理
        if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
            AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
        }
        //是否对 AopContext.currentPoxy支持。
        if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
            AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
        }
    }
}
  1. 使用 @EnableAutoConfiguration注解。改注解会向容器注册Spring.factories文件中声明的类,其中AopAutoConfiguration是对Spring AOP自动配置的支持。
@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
    @Configuration
    最终还是通过 EnableAspectJAutoProxy 注册AnnotationAwareAspectJAutoProxyCreator
    @EnableAspectJAutoProxy(proxyTargetClass = false)
    判断条件 ,可以在 application.properties 中 配置。
    @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = true)
    public static class JdkDynamicAutoProxyConfiguration {
    }
    @Configuration
    @EnableAspectJAutoProxy(proxyTargetClass = true)
    @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = false)
    public static class CglibAutoProxyConfiguration {
    }
}
  1. xml 标签
    Spring 采用 自定义标签 <aop:aspectj-autoproxy /> 注册AnnotationAwareAspectJAutoProxyCreator

4. AnnotationAwareAspectJAutoProxyCreator的初始化

AnnotationAwareAspectJAutoProxyCreator初始化 主要初始一些 组件对象。

组件包括:

  • AspectJAdvisorFactory
    根据AspectJ 注释的切面类 创建 Spring AOP Advisors 的工厂接口。Advisor增强器的创建 都是由AspectJAdvisorFactory完成。
  • BeanFactoryAspectJAdvisorsBuilder
    从项目中 查找所有的切面类,然后使用 AspecJAdvisorFactory 创建 Advisors。
  • advisorRetrievalHelper
    对Xml 配置Advisor 的支持。从容器中检索 所有的 Advisor 类型。

5 . 创建代理类过程

由于 AnnotationAwareAspectJAutoProxyCreator 是一个 BeanPostProcessor 实现类,Spring 会在对一个对象的初始化前后执行 BeanPostProcssor 的接口方法。Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;就是 创建增强代理对象的方法。该方法被AnnotationAwareAspectJAutoProxyCreator的父类AbstractAutoProxyCreator实现。

  • postProcessAfterInitialization
@Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    //bean 就是需要 被增强的类。
        if (bean != null) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            //防止 bean 多次被增强。
            if (!this.earlyProxyReferences.contains(cacheKey)) {
            //如果有必要 就进行封装,有没有必要主要取决于 bean 是否需要被增强。
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
    }
  • wrapIfNecessary
    对给定bean 就行封装。
/**
     *如果有必要 封装 bean
     */
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
//如果已经处理过
        if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
            return bean;
        }
//如果不需要增强 就直接返回
        if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
            return bean;
        }
        //如果是一个 组件类,组件类包括Advice,Pointcut,Advisor,AopInfrastructureBean实现类,和 Aspect注解注释的类。所以 切面类 自己不会被AOP 增强。
        if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }
        // 为bean 获取和 匹配合适的 增强器 
        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;
    }
5.1 获取增强器
  • getAdvicesAndAdvisorsForBean(为bean获取和匹配合适的增强器)
    getAdvicesAndAdvisorsForBean ---> findEligibleAdvisors:

    为bean获取和匹配增强器分为两步:

5.1.1 获取到所有的增强器
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
    
    //获取所有的增强器
        List<Advisor> candidateAdvisors = findCandidateAdvisors();
    //匹配合适的增强器
        List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
        extendAdvisors(eligibleAdvisors);
        if (!eligibleAdvisors.isEmpty()) {
            eligibleAdvisors = sortAdvisors(eligibleAdvisors);
        }
        return eligibleAdvisors;
    }
  • findCandidateAdvisors 获取到所有的增强器
    调用AnnotationAwareAspectjAutoProxyCreator 的 findCandidateAdvisors
    @Override
    protected List<Advisor> findCandidateAdvisors() {
       // 对xml 配置 Advisor 的支持.
       List<Advisor> advisors = super.findCandidateAdvisors();
       // 搜索容器中所有 @Aspecj 注解申明的 切面类,构建增强器
       advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
       return advisors;
    }
    
  • aspectJAdvisorsBuilder.buildAspectJAdvisors()
    代码比较多,就不粘出来了,大致逻辑就是
  1. 获取容器中已经注册的BeanNames
  2. 遍历 所有已经注册的Bean,查找 @AspectJ
  3. 通过AspectJAdvisorFactory获取Advisor。怎么获取的 就不解释了。
5.1.2 匹配合适的增强器

匹配合适的增强器,Spring 使用了 AopUtils.canApply。

匹配逻辑就是:

遍历 所有增强器,采用增强器的 Poincut 对一个对象的所有方法进行匹配,一旦有方法匹配到,就返回true.

对方法的匹配需要遍历 所有方法 一一就行匹配。

5.2 创建 代理类

通过 ProxyFactory 创建动态代理类,创建之前 先配置ProxyFactory,设置目标对象,设置是否对目标对象代理(会影响采用JDK代理或者Cglib代理),设置增强器集合,等。

配置ProxyFactory完成之后就是获取代理类了,

调用ProxyFactory.getProxy:

public Object getProxy(ClassLoader classLoader) {
    //先获取 AopProxy,AopProxy有JDK实现和 Cglib实现两种。
        return createAopProxy().getProxy(classLoader);
}

判断采取JDK代理 或Cglib代理。
判断条件:

  1. Optimize: 是否采用了 激进的优化策略,该优化仅支持 Cglib代理
  2. ProxyTargetClass: 代理目标类,代理目标类 就是采用 子类继承的方式创建代理,所以也是Cglib代理,可以通过。
  3. hasNoUserSuppliedProxyInterfaces:判断是否是实现了接口,如果没有必须采用Cglib代理。
    所以如果我们想在项目中 采用Cglib代理的话 application.properties中配置:
    spring.aop.proxy-target-class=false,或者使用注解配置proxyTargetClass = true .
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    //判断采用什么代理类型。
    //Optimize 是否采用了 激进的优化策略,该优化仅支持 Cglib代理
    //ProxyTargetClass 代理目标类,代理目标类 就是采用 子类继承的方式创建代理,所以也是Cglib代理,可以通过
    // 判断是否是实现了接口,如果没有必须采用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.");
            }
            如果目标对象是接口 采用 JDK代理。
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                return new JdkDynamicAopProxy(config);
            }
           
            return new ObjenesisCglibAopProxy(config);
        }
        else {
            return new JdkDynamicAopProxy(config);
        }
    }

  • 拿JdkDynamicAopProxy来说。

获取代理类:

JDK动态代理的关键是 InvocationHandler,JdkDynamicAopProxy实现了InvocationHandler接口。

@Override
    public Object getProxy(ClassLoader classLoader) {
        if (logger.isDebugEnabled()) {
            logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
        }
        Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
        findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
        //获取代理类,其中InvocationHanler 是 this,就是JdkDynamicAopProxy。
        return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
    }

6.增强方法的执行

增强方法的执行 是AOP的核心所在,理解Spring Aop 是如何 协调 各种通知 的执行,是理解的关键。

通知 分为前置 后置 异常 返回 环绕通知,在一个方法中每种通知的执行时机不同,协调他们之间执行顺序很重要。

但是Spring AOP 采用了很聪明的方法让各种各样的通知准确有序的工作。

  • JdkDynamicAopProxy.invoker

由于invoker 方法很大,我删除了大部分的代码,保留几处关键代码:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //省略了 对 hashCode equals 等方法的处理....
    //exposeProxy属性的支持。
            if (this.advised.exposeProxy) {
                // Make invocation available if necessary.
                oldProxy = AopContext.setCurrentProxy(proxy);
                setProxyContext = true;
            }
            // 获取目标对象类
            target = targetSource.getTarget();
            if (target != null) {
                targetClass = target.getClass();
            }
      // 获取拦截器链 这里this.advised 就是AnnotationAwareAspectJAutoProxyCreator
      List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
     //如果 没有拦截器链 则直接执行。
      if (chain.isEmpty()) {
         Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
         retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
      }
      else {    
          //开始执行拦截器链
         invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
         retVal = invocation.proceed();
      }
    //返回执行结果。
      return retVal;
  
}
6.1 创建拦截器链

拦截器链InterceptorAndDynamicMethodMatcher 类 和 MethodInterceptor 类型的集合。

InterceptorAndDynamicMethodMatcher 封装了 MethodInterceptor 和 MethodMather ,作用就是在执行时进行再次匹配。

创建拦截器的过程就是 对所有 适合 目标类的 Advisor进行再一次筛选。对匹配的Advisor封装成 MethodInterceptor。通过MethodInterceptor 统一 增强方法的调用。

6.2 执行拦截器链

使用ReflectiveMethodInvocation 对连接器链进行封装。通过process 方法触发拦截器开始执行。

public Object proceed() throws Throwable {
        //  判断拦截器链 是否执行结束,如果执行结束执行目标方法。
        if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
            return invokeJoinpoint();
        }
        //获取下一个 需要执行的 拦截器
        Object interceptorOrInterceptionAdvice =
        this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
        //如果需要执行时 进行匹配
        if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
            InterceptorAndDynamicMethodMatcher dm =
                    (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
            //如果匹配,并不清楚 为什么还要在这里 进行再次匹配。
            if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) 
            //注意这里 将ReflectiveMethodInvocation 自己当参数 ,传入调用。
                return dm.interceptor.invoke(this);
            }
            else {
            //递归的调用
                return proceed();
            }
        }
        else {
            //如果 MethodInterceptor 
            return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
        }
    }

查看代码并没有发现任何的关于协调前置,后置各种通知的代码。其实所有的协调工作都是由MethodInterceptor 自己维护。

  • 前置MethodInterceptor(MethodBeforeAdviceInterceptor)的invoke
    @Override
  public Object invoke(MethodInvocation mi) throws Throwable {
        //激活 前置增强方法
      this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() );
        //继续调用下一个 拦截器。
      return mi.proceed();
  }

  • 后置MethodInterceptor(AspectJAfterAdvice)

    @Override
      public Object invoke(MethodInvocation mi) throws Throwable {
          try {
                //继续调用一下拦截器。
              return mi.proceed();
          }
          finally {
                //在finally 里面激活 后置增强方法
              invokeAdviceMethod(getJoinPointMatch(), null, null);
          }
      }
    

    通过对比前置 和 后置 拦截器,可以发现 具体协调各种通知 的 逻辑 实际是利用了 递归的思想。
    后置拦截器为例: 先执行 递归方法 proceed()。最后从递归方法返回的时候 最后调用 后置方法。

7. 模拟拦截器调用

为了更好理解 拦截器调用,自己实现了一个简单的拦截器链调用过程。

7.1 准备工作
  • 接口

    //方法调用   
    interface MethodInvocation{
             Object process() throws Throwable;
        }
    //方法拦截器
        interface MethodInterceptor{
            Object invoke(MethodInvocation mi) throws Throwable;
        }
    
  • 实现

    //后置增强方法的 拦截器
    
    class AfterMethodInterceptor implements MethodInterceptor{
            String identification;
            public AfterMethodInterceptor(String identification){
                this.identification = identification;
            }
            @Override
            public Object invoke(MethodInvocation mi) throws Throwable {
                try {
                    return mi.process();
                }finally {
                    System.out.println("执行后置通知"+identification);
                }
            }
        }
    //前置 的 方法拦截器 
        class BeforMethodInterceptor implements MethodInterceptor{
            String identification;
            public BeforMethodInterceptor(String identification){
                this.identification = identification;
            }
            @Override
            public Object invoke(MethodInvocation mi) throws Throwable {
                System.out.println("执行前置通知"+identification);
                return mi.process();
            }
        }
    // 默认的 方法调用实现
     class DefaultMethodInvacation implements MethodInvocation {
            List<MethodInterceptor> chian;
            Object target; //目标对象
            Method method; //目标方法
            Object[] args; //方法参数
            int currentChianIndex; //记录拦截器链当前执行位置
            public  DefaultMethodInvacation(List<MethodInterceptor> chian,Object target,Method method,Object[] args){
                this.chian = chian;
                this.method = method;
                this.target = target;
                this.args = args;
            }
            @Override
            public Object process() throws Throwable{
                Object value ;
                //如果 拦截器 执行完毕 执行 目标方法
                if(currentChianIndex == chian.size()){
                    value = method.invoke(target, args);
                    return value;
                }
                //获取下一个 拦截器
                MethodInterceptor methodInterceptor = chian.get(currentChianIndex++);
                return methodInterceptor.invoke(this);
            }
        }
    //目标对象
    class TargetObj{
        //目标方法
          public void targetMethod(){
               System.out.println("-----目标方法执行----");
           }
        }
    
7.2 测试代码
@Test
    public void aopchain() throws Throwable {
        List<MethodInterceptor> chain = Lists.newArrayList();
        //拦截器链,穿插这 放入 前置 和 后置 拦截器 。
        chain.add(new AfterMethodInterceptor("1"));
        chain.add(new BeforMethodInterceptor("1"));
        chain.add(new AfterMethodInterceptor("2"));
        chain.add(new BeforMethodInterceptor("2"));
        chain.add(new AfterMethodInterceptor("3"));
        chain.add(new BeforMethodInterceptor("3"));
        //目标对象
        TargetObj targetObj = new TargetObj();
        //目标方法
        Method method = TargetObj.class.getMethod("targetMethod");
        DefaultMethodInvacation defaultMethodInvacation = new DefaultMethodInvacation(chain, targetObj, method, null);
        //执行拦截器链
        defaultMethodInvacation.process();
    }
7.3 执行结果

虽然 前置 和 后置 都是 无序的 存放在 拦截器链中,但是 前置 和 后置 都在各自的位置执行。

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

推荐阅读更多精彩内容