springboot 事務(wù)失效以及排查過程

排查了好久,終于解決诱担,希望這次的排查過程對(duì)大家也有幫助辐马,廢話少說,上源碼

開發(fā)環(huán)境

springboot 2.3.11
jdk8
gradle6.4
HikariDataSource
ps: 本環(huán)節(jié)使用雙數(shù)據(jù)源舞虱,在service層做切面攔截,切換具體的數(shù)據(jù)源

問題

在定義了具體的事務(wù)管理以后母市,想著不要手動(dòng)切庫矾兜,因?yàn)槭聞?wù)管理那里已經(jīng)顯示的注入了具體的數(shù)據(jù)源,然后結(jié)果導(dǎo)致的是事務(wù)失效患久,發(fā)生異常不回滾椅寺。代碼如下

    @Override
    @Transactional(rollbackFor = Exception.class, transactionManager = DsConst.AGLOUD_TRANSACTION_MANAGER)
    public void ex() {

        jdbcTemplate.execute("insert into table_name1(name) values (1)");

        if (1 == 1) throw new RuntimeException("出異常了啊=А7蹬痢!高镐!");
        jdbcTemplate.execute("insert into table_name2(name) values (1)");

    }
排查過程

第一步溉旋,先排查源碼,spring攔截事務(wù)的攔截器是TransactionInterceptor類的invokeWithinTransaction方法嫉髓,源碼如下

@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, TransactionAspectSupport.InvocationCallback invocation) throws Throwable {
    TransactionAttributeSource tas = this.getTransactionAttributeSource();
    //1:獲取事務(wù)屬性配置信息:通過 TransactionAttributeSource.getTransactionAttribute解析@Trasaction注解得到事務(wù)屬性配置信息
    TransactionAttribute txAttr = tas != null ? tas.getTransactionAttribute(method, targetClass) : null;
    //2:獲取事務(wù)管理器
    TransactionManager tm = this.determineTransactionManager(txAttr);


    //將事務(wù)管理器tx轉(zhuǎn)換為 PlatformTransactionManager
    PlatformTransactionManager ptm = this.asPlatformTransactionManager(tm);
    String joinpointIdentification = this.methodIdentification(method, targetClass, txAttr);

    //createTransactionIfNecessary內(nèi)部观腊,這里就不說了邑闲,內(nèi)部主要就是使用spring事務(wù)硬
    //編碼的方式開啟事務(wù),最終會(huì)返回一個(gè)TransactionInfo對(duì)象
    TransactionAspectSupport.TransactionInfo txInfo = this.createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
    //業(yè)務(wù)方法返回值
    Object retVal;
    try {
        //調(diào)用aop中的下一個(gè)攔截器梧油,最終會(huì)調(diào)用到業(yè)務(wù)目標(biāo)方法苫耸,獲取到目標(biāo)方法的返回值
        retVal = invocation.proceedWithInvocation();
    } catch (Throwable var18) {
        //3:異常情況下,如何走儡陨?可能只需提交褪子,也可能只需回滾,這個(gè)取決于事務(wù)的配置
        this.completeTransactionAfterThrowing(txInfo, var18);
        throw var18;
    } finally {
        //清理事務(wù)信息
        this.cleanupTransactionInfo(txInfo);
    }

    if (retVal != null && vavrPresent && TransactionAspectSupport.VavrDelegate.isVavrTry(retVal)) {
        TransactionStatus status = txInfo.getTransactionStatus();
        if (status != null && txAttr != null) {
            retVal = TransactionAspectSupport.VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
        }
    }
    //4:業(yè)務(wù)方法返回之后骗村,只需事務(wù)提交操作
    this.commitTransactionAfterReturning(txInfo);
    return retVal;

}

當(dāng)我跟進(jìn)去時(shí)候嫌褪,發(fā)現(xiàn)事務(wù)并不是一個(gè)新的事務(wù)了,spring判斷回滾的代碼如下completeTransactionAfterThrowing,進(jìn)去,最后執(zhí)行到這里

private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
        try {
            boolean unexpectedRollback = unexpected;

            try {
                triggerBeforeCompletion(status);

                if (status.hasSavepoint()) {
                
                    status.rollbackToHeldSavepoint();
                }
                  //判斷是否是一個(gè)新的事務(wù)胚股,如果是就回滾
                else if (status.isNewTransaction()) {
                    
                    doRollback(status);
                }

我很疑惑笼痛,為什么不是一個(gè)新的事務(wù),而且我也沒有整嵌套事務(wù)什么花里胡哨的東西琅拌,怎么就不是新的事務(wù)了缨伊。于是我另外起了一個(gè)項(xiàng)目對(duì)比debugger,好久才發(fā),具體如下


77ca8e6de60a157292f1b0faf302cf3.png

乍一看进宝,我是不是眼花了?谭弧!党晋!為什么目標(biāo)對(duì)象和代理對(duì)象都是代理對(duì)象呢谭胚。我xx,Cglib創(chuàng)建代理的時(shí)候隶校,是可以代理類也再創(chuàng)建代理的嗎漏益?本著求真務(wù)實(shí)的精神,用cglib的原生Api做了測(cè)試深胳,發(fā)生會(huì)報(bào)錯(cuò),不能這樣子铜犬。代碼如下

package com.shiguiwu.springmybatis.spring.aop.cglib;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;

/**
 * @description: callbak
 * 1. Cglib根據(jù)父類,Callback, Filter 及一些相關(guān)信息生成key
 * 2. 然后根據(jù)key 生成對(duì)應(yīng)的子類的二進(jìn)制表現(xiàn)形式
 * 3. 使用ClassLoader裝載對(duì)應(yīng)的二進(jìn)制,生成Class對(duì)象,并緩存
 * 4. 最后實(shí)例化Class對(duì)象,并緩存
 * @author: stone
 * @date: Created by 2021/5/18 19:54
 * @version: 1.0.0
 * @pakeage: com.shiguiwu.springmybatis.spring.aop.cglib
 */
public class CglibCallbackObjTest {

    interface Service1 {
        void m1();
    }


    interface Service2 {
        void m2();
    }

    public static class Service implements Service1, Service2 {

        @Override
        public void m1() {
            System.out.println("m1");

        }

        @Override
        public void m2() {
            System.out.println("m2");
        }
    }

    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();

        //設(shè)置父類
        enhancer.setSuperclass(Service.class);

        //設(shè)置代理對(duì)象需要實(shí)現(xiàn)的接口
        enhancer.setInterfaces(new Class[]{Service1.class, Service2.class});

        //通過Callback來對(duì)被代理方法進(jìn)行增強(qiáng)
        enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
            long l = System.nanoTime();
            Object result = methodProxy.invokeSuper(o, objects);
            System.out.println(method.getName() + "耗時(shí)為:" + (System.nanoTime() - l));
            return result;
        });


        Object proxy = enhancer.create();

        //if (proxy instanceof Service) {
        //    ((Service) proxy).m1();
        //    ((Service) proxy).m2();
        //}

        System.out.println("父類" + proxy.getClass().getSuperclass());

        System.out.println(proxy.getClass());
        System.out.println("創(chuàng)建代理類實(shí)現(xiàn)的接口如下:");
        for (Class<?> cs : proxy.getClass().getInterfaces()) {
            System.out.println(cs);
        }


        Enhancer enhancer1 = new Enhancer();

        enhancer1.setSuperclass(proxy.getClass());

        enhancer1.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
            long l = System.nanoTime();
            Object result = methodProxy.invokeSuper(o, objects);
            System.out.println(method.getName() + "耗時(shí)11為:" + (System.nanoTime() - l));
            return result;
        });

        Object proxy1 = enhancer1.create();

        System.out.println("父類" + proxy1.getClass().getSuperclass());

        System.out.println(proxy1.getClass());
        //if (proxy1 instanceof Service) {
        //    ((Service) proxy1).m1();
        //    ((Service) proxy1).m2();
        //}
    }


}

運(yùn)行這段代碼會(huì)報(bào)錯(cuò)

Exception in thread "main" org.springframework.cglib.core.CodeGenerationException: java.lang.ClassFormatError-->Duplicate method name&signature in class file com/shiguiwu/springmybatis/spring/aop/cglib/CglibCallbackObjTest$Service$$EnhancerByCGLIB$$c12e4df0$$EnhancerByCGLIB$$eed200b8
    at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:538)
    at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363)
    at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:585)
    at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.ap

但是舞终,要知道,spring鬼的很癣猾,說不定他就是可以的敛劝,結(jié)果測(cè)試代碼,發(fā)現(xiàn)纷宇,spring基于cglib創(chuàng)建的代理是可以夸盟,他會(huì)判斷當(dāng)前的目標(biāo)對(duì)象是不是代理對(duì)象,如果是,則以目標(biāo)對(duì)象的父類來創(chuàng)建代理對(duì)象像捶,但是目標(biāo)對(duì)象還是代理對(duì)象上陕。代碼org.springframework.aop.framework.CglibAopProxy#getProxy(@Nullable ClassLoader classLoader) 如下

    try {
            Class<?> rootClass = this.advised.getTargetClass();
            Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

            Class<?> proxySuperClass = rootClass;
          //判斷目標(biāo)對(duì)象是不是代理對(duì)象
            if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
                proxySuperClass = rootClass.getSuperclass();
                Class<?>[] additionalInterfaces = rootClass.getInterfaces();
                for (Class<?> additionalInterface : additionalInterfaces) {
                    this.advised.addInterface(additionalInterface);
                }
            }

這個(gè)時(shí)候桩砰,我們需要了解springbean的生命周期了,spring是什么時(shí)候释簿,偷梁換柱的亚隅,把目標(biāo)對(duì)象變成代理對(duì)象。現(xiàn)有我知道的就是引入@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)注解庶溶,然后spring向容器中添加了一個(gè)AnnotationAwareAspectJAutoProxyCreator煮纵,這個(gè)類很重要,它是一個(gè)SmartInstantiationAwareBeanPostProcessor的子類偏螺,他就是springbean在初始化后調(diào)用的方法行疏,然后判斷bean 需不需要生成代理對(duì)象。主要邏輯在抽象類AbstractAutoProxyCreator#postProcessAfterInitialization

      @Override
    public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
        if (bean != null) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
    }

然后我把斷點(diǎn)打到這個(gè)位置套像,因?yàn)轭惐容^多酿联,為了不干擾我們調(diào)試,使用條件debugger凉夯,具體如下


image.png

最終發(fā)現(xiàn)货葬,有在創(chuàng)建代理對(duì)象的時(shí)候,上面的方法進(jìn)了兩次劲够。然后發(fā)現(xiàn)他們是不同的類進(jìn)來的震桶,原來我項(xiàng)目代碼中,創(chuàng)建代理用到了兩個(gè)類分別是AnnotationAwareAspectJAutoProxyCreator和DefaultAdvisorAutoProxyCreator征绎。這個(gè)兩個(gè)類型具有相同的父類蹲姐,也是BeanPostProcessor,也會(huì)攔截bean的創(chuàng)建過程人柿。

但是我本地的項(xiàng)目主要AnnotationAwareAspectJAutoProxyCreator柴墩,于是為了模擬現(xiàn)場(chǎng)的環(huán)境,我手動(dòng)注入了DefaultAdvisorAutoProxyCreator凫岖,最后發(fā)現(xiàn)本地是跟上次的情況一樣江咳,創(chuàng)建了嵌套代理。


    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);

        return defaultAdvisorAutoProxyCreator;
    }

那是不是因?yàn)榍短状碜屖聞?wù)失效呢哥放,最后的答案是:不是歼指。本地照樣能回滾,原來我第一個(gè)打點(diǎn)斷的時(shí)候甥雕,只關(guān)注了是不是新事務(wù)踩身,但是spring aop是一個(gè)方法調(diào)用鏈,所以當(dāng)舊的事務(wù)通過調(diào)用棧出來的時(shí)候,這個(gè)事務(wù)就是一個(gè)新事務(wù)社露,也執(zhí)行了回滾操作.

心累挟阻。。。
然后我在想是不是postgres沒有開啟事務(wù)支持呢附鸽,或者是數(shù)據(jù)庫需要手動(dòng)設(shè)置事務(wù)支持脱拼。通過實(shí)驗(yàn)我又排除了這個(gè)可能。代碼如下

   @Autowired
    private DataSource dataSource;


    @Resource
    private DataSource masterDataSource;
    @Test
    public void tx() {

        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        //1.定義一個(gè)事務(wù)管理器
        PlatformTransactionManager transactionManager = new DataSourceTransactionManager(masterDataSource);

        boolean a = true;
        //清空數(shù)據(jù)
        jdbcTemplate.update("truncate table table_name1");

        System.out.println("PROPAGATION_REQUIRED start ==========================================================");

        //2.定義一個(gè)事務(wù)屬性
        TransactionDefinition transactionDefinition = new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRED);

        //3.取一個(gè)事務(wù)狀態(tài),開啟事務(wù)了
        TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);

        //設(shè)置擴(kuò)展點(diǎn)
        //addSynchronization("ts-1", 2);
        //        //addSynchronization("ts-2", 1);
        try {
            //4.執(zhí)行業(yè)務(wù)

            jdbcTemplate.execute("insert into table_name1(name) values (1)");

            if (a) {
                throw new RuntimeException("===");
            }
            //jdbcTemplate.update("insert into book(book_name) values (?)", "成是非");
            jdbcTemplate.execute("insert into table_name1(name) values (100)");

            //此時(shí)拒炎,在執(zhí)行一個(gè)方法事務(wù)
            //other(jdbcTemplate, transactionManager);

            //5.提交事務(wù)
            System.out.println("PROPAGATION_REQUIRED 準(zhǔn)備commit");
            transactionManager.commit(transactionStatus);
            System.out.println("PROPAGATION_REQUIRED commit完畢");
        } catch (Exception e) {
            e.printStackTrace();
            //6.回滾事務(wù):platformTransactionManager.rollback
            transactionManager.rollback(transactionStatus);

        }

        System.out.println("after==========================>:" + jdbcTemplate.queryForList("SELECT * from table_name1"));
    }

經(jīng)過上面代碼挪拟,最終發(fā)現(xiàn),JdbcTemplate 的數(shù)據(jù)源和PlatformTransactionManager 管理器的數(shù)據(jù)源不是同一個(gè)击你,為什么不是同一個(gè)就失效呢玉组?這里需要了解spring內(nèi)部是怎么控制事務(wù)的。
這里簡(jiǎn)單說一下丁侄,源碼不是我們的重點(diǎn):
spring在開始事務(wù)之前惯雳,會(huì)在事務(wù)管理器中拿數(shù)據(jù)源,通過該數(shù)據(jù)源獲取一個(gè)數(shù)據(jù)庫連接鸿摇,同時(shí)將數(shù)據(jù)庫連接設(shè)置為手動(dòng)提交石景,然后通過ThreadLocal綁定到當(dāng)前線程中,綁定的格式map類型datasource->ConnectionHolder拙吉。再來就是執(zhí)行業(yè)務(wù)了潮孽,jdbcTemplate中有數(shù)據(jù)源,這個(gè)時(shí)候筷黔,重點(diǎn)來了啊往史。根據(jù)jdbcTemplate的數(shù)據(jù)源,從本地線程中拿連接佛舱,此時(shí)由于數(shù)據(jù)源不一樣椎例,根本拿不到連接,spring幫我們本次數(shù)據(jù)源中再創(chuàng)建一個(gè)連接请祖,用此時(shí)的連接執(zhí)行sql订歪,然后自動(dòng)提交了。而我們保存線程本地的連接一直沒干活肆捕,所以事務(wù)失效刷晋!

再次排查

于是呢,我在事務(wù)方法哪里又手動(dòng)進(jìn)行切庫慎陵,這回總該數(shù)據(jù)源一致了吧掏秩。雖然說是動(dòng)態(tài)數(shù)據(jù)源,但是具體返回的數(shù)據(jù)源應(yīng)該由我來設(shè)置的荆姆。

image.png

但是,事與愿違映凳,還是不行胆筒,于是我在切庫的切面那里打了個(gè)斷點(diǎn),代碼如下


然后發(fā)現(xiàn),代碼居然是先執(zhí)行了事務(wù)攔截器仆救,在執(zhí)行的切面抒和,炸了啊。怎么可能呢彤蔽,查資料也是事務(wù)攔截器優(yōu)先級(jí)很低的摧莽,但是不管你怎么設(shè)置的切面順序,始終但是先執(zhí)行的事務(wù)攔截器顿痪。也就是說镊辕,事務(wù)管理里的數(shù)據(jù)源,不管你怎么切換蚁袭,始終都是默認(rèn)的數(shù)據(jù)源征懈,這樣就實(shí)現(xiàn)不了其他庫的事務(wù)控制。最后回到最開始的問題揩悄,有一個(gè)事務(wù)攔截器是代理的代理卖哎,切面是目標(biāo)對(duì)象的代理,所以不管你怎么設(shè)置删性,代理的代理方法始終是最先執(zhí)行的亏娜。

最后整個(gè)人都麻了,然后只能從為什么有兩個(gè)代理生成器的人手蹬挺,最后通過尋找维贺,在jar里面,有人顯示注入了DefaultAdvisorAutoProxyCreator汗侵,這個(gè)是由于項(xiàng)目遺留的bug幸缕,將那個(gè)源碼改掉就解決了。

回顧

本問題涉及的知識(shí)點(diǎn)比較多晰韵,事務(wù)发乔,代理,切面雪猪,spring bean的創(chuàng)建過程栏尚。本人水平有限,如有錯(cuò)誤只恨,請(qǐng)批評(píng)指正译仗,多謝!9倜佟纵菌!88888

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市休涤,隨后出現(xiàn)的幾起案子咱圆,更是在濱河造成了極大的恐慌笛辟,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件序苏,死亡現(xiàn)場(chǎng)離奇詭異手幢,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)忱详,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門围来,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人匈睁,你說我怎么就攤上這事监透。” “怎么了软舌?”我有些...
    開封第一講書人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵才漆,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我佛点,道長(zhǎng)醇滥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任超营,我火速辦了婚禮鸳玩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘演闭。我一直安慰自己不跟,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開白布米碰。 她就那樣靜靜地躺著窝革,像睡著了一般。 火紅的嫁衣襯著肌膚如雪吕座。 梳的紋絲不亂的頭發(fā)上虐译,一...
    開封第一講書人閱讀 51,604評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音吴趴,去河邊找鬼漆诽。 笑死,一個(gè)胖子當(dāng)著我的面吹牛锣枝,可吹牛的內(nèi)容都是我干的厢拭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼撇叁,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼供鸠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起陨闹,我...
    開封第一講書人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤回季,失蹤者是張志新(化名)和其女友劉穎家制,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泡一,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年觅廓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鼻忠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡杈绸,死狀恐怖帖蔓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情瞳脓,我是刑警寧澤塑娇,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站劫侧,受9級(jí)特大地震影響埋酬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜烧栋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一写妥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧审姓,春花似錦珍特、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至酬姆,卻和暖如春嗜桌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背轴踱。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工症脂, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人淫僻。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓诱篷,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親雳灵。 傳聞我的和親對(duì)象是個(gè)殘疾皇子棕所,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

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