九、SpringBoot集成緩存

1肖粮、Spring緩存

1.1孤页、緩存依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

1.2、緩存注解

  • @EnableCaching:在主入口類上添加該注解涩馆,用于開啟緩存行施;

  • @CacheConfig:標(biāo)注在類上,表示當(dāng)前類使用的緩存組件中的key為該注解指定的cacheNames/value魂那,當(dāng)該注解指定了cacheNames/value之后蛾号,@Cacheable上就無需再使用cacheNames/value了;

  • @Cacheable:將方法的結(jié)果進(jìn)行緩存涯雅;

    • cacheNames/value:緩存組件的名字须教;

    • key:緩存數(shù)據(jù)使用的key,默認(rèn)是使用方法參數(shù)的值,可以使用SpEL表達(dá)式轻腺,比如#id == #a0 == #p0 == #root.args[0]都表示使用第一個(gè)參數(shù)的值作為緩存的key乐疆;

    • keyGenerator:key的生成器,可以自己指定key的生成器的組件id贬养;

    • cacheManager:指定緩存管理器挤土;

    • cacheResolver:指定獲取解析器;

    • condition:指定符合條件的情況下才緩存误算,支持SpEL表達(dá)式仰美;

      • condition=“#id>1”:表示id的值大于1時(shí)才緩存。
    • unless:否定緩存儿礼,當(dāng)unless指定的條件為true咖杂,方法的返回值就不會(huì)被緩存,該屬性可以獲取到結(jié)果進(jìn)行判斷蚊夫,unless與condition剛好相反诉字;

      • unless=“#a0==2”:表示第一個(gè)參數(shù)的值為2時(shí),查詢結(jié)果不緩存知纷。
    • sync:是否使用異步模式壤圃,使用sync后unless就不支持了;

      其中keyGeneratorkey二者只能選一琅轧,cacheResolvercacheManager也二者選一伍绳。

  • @CachePut:用于更新緩存,它先調(diào)用方法乍桂,然后將目標(biāo)方法的結(jié)果進(jìn)行緩存冲杀。

    ? 能夠更新緩存的前提是,@CachePut更新使用的key與@Cacheable使用的key要保持一致睹酌。

    @CachePut注解屬性同@Cacheable的屬性類似权谁,少了sync屬性。

  • @CacheEvict:緩存清除

    ? 同樣如果要清除緩存忍疾,則使用的key值要與@Cacheable使用的key一致闯传,否則達(dá)不到清除緩存的功能谨朝。

    @CacheEvict注解屬性比@Cacheable注解少了sync和unless卤妒,但是多了兩個(gè)屬性:allEntriesbeforeInvocation

    ? allEntries:表示是否刪除所有緩存字币。

    ? beforeInvocation:表示刪除緩存的操作是否在目標(biāo)方法執(zhí)行之前则披。

1.3、原理

CacheAutoConfiguration是緩存的自動(dòng)配置類洗出。

@Configuration
@ConditionalOnClass(CacheManager.class)
@ConditionalOnBean(CacheAspectSupport.class)
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
@EnableConfigurationProperties(CacheProperties.class)
@AutoConfigureBefore(HibernateJpaAutoConfiguration.class)
@AutoConfigureAfter({ CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class,
        RedisAutoConfiguration.class })
@Import(CacheConfigurationImportSelector.class)
public class CacheAutoConfiguration {
    
    //other code...
    
    static class CacheConfigurationImportSelector implements ImportSelector {

        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            CacheType[] types = CacheType.values();
            String[] imports = new String[types.length];
            for (int i = 0; i < types.length; i++) {
                imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
            }
            return imports;
        }
    }
}

緩存配置類(有順序):

org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration

org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration

org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration

org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration

org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration

org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration

org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration

org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration

org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration

org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration

org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration

開啟debug=true士复,查看控制臺(tái),可以發(fā)現(xiàn):默認(rèn)cache配置類生效的只有SimpleCacheConfiguration

@Configuration
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class SimpleCacheConfiguration {

    private final CacheProperties cacheProperties;

    private final CacheManagerCustomizers customizerInvoker;

    SimpleCacheConfiguration(CacheProperties cacheProperties,
            CacheManagerCustomizers customizerInvoker) {
        this.cacheProperties = cacheProperties;
        this.customizerInvoker = customizerInvoker;
    }

    @Bean
    public ConcurrentMapCacheManager cacheManager() {
        ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
        List<String> cacheNames = this.cacheProperties.getCacheNames();
        if (!cacheNames.isEmpty()) {
            cacheManager.setCacheNames(cacheNames);
        }
        return this.customizerInvoker.customize(cacheManager);
    }

}

該類給容器中注冊(cè)了一個(gè)ConcurrentMapCacheManager類型的cacheManager組件。

public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {

    private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<String, Cache>(16);

    //other code...
    
    //從緩存ConcurrentMap對(duì)象中獲取cacheNames/value指定的Cache對(duì)象阱洪。
    @Override
    public Cache getCache(String name) {
        Cache cache = this.cacheMap.get(name);
        if (cache == null && this.dynamic) {
            synchronized (this.cacheMap) {
                cache = this.cacheMap.get(name);
                if (cache == null) {
                    cache = createConcurrentMapCache(name);
                    this.cacheMap.put(name, cache);
                }
            }
        }
        return cache;
    }
}

該組件可以獲取和創(chuàng)建ConcurrentMapCache類型的緩存組件便贵,作用是將數(shù)據(jù)保存在ConcurrentMap對(duì)象中。

運(yùn)行流程:

? 1)冗荸、方法運(yùn)行之前承璃,先去查詢Cache(緩存組件),按照cacheNames指定的名字去緩存中獲劝霰尽(CacheManager先獲取相應(yīng)的緩存)盔粹,第一次獲取緩存,如果沒有Cache組件程癌,則會(huì)自動(dòng)創(chuàng)建舷嗡。

? 2)、去Cache中查找緩存的內(nèi)容嵌莉,使用一個(gè)key(默認(rèn)是方法的參數(shù))进萄,其中key是按照某種策略生成的,默認(rèn)是使用SimpleKeyGenerator生成的烦秩。

? SimpleKeyGenerator生成key的策略:

? 如果沒有參數(shù)垮斯,key=new SimpleKey();

? 如果有一個(gè)參數(shù),key=參數(shù)值

? 如果有多個(gè)參數(shù)只祠,key=new SimpleKey(params);

? 3)兜蠕、沒有查到緩存,就調(diào)用目標(biāo)方法抛寝;如果查到緩存熊杨,則直接返回結(jié)果,不調(diào)用目標(biāo)方法盗舰。

? 4)晶府、沒有查到緩存后去調(diào)用目標(biāo)方法,然后將目標(biāo)方法返回的結(jié)果放進(jìn)緩存钻趋。

1.4川陆、注解解釋

@Cacheable

自定義key:

? methodName:當(dāng)前被調(diào)用的方法名,例如#root.methodName

? method:當(dāng)前被調(diào)用的方法蛮位,例如#root.method.name

? target:當(dāng)前被調(diào)用的目標(biāo)對(duì)象较沪,例如#root.target

? targetClass:當(dāng)前被調(diào)用的目標(biāo)對(duì)象類,例如#root.targetClass

? args:當(dāng)前被調(diào)用的方法的參數(shù)列表失仁,例如#root.args[0]

? caches:當(dāng)前方法調(diào)用使用的緩存列表尸曼,

? argument name:方法參數(shù)的名字,可以直接#參數(shù)名萄焦,也可以使用#a0或者#p0的形式控轿,0表示參數(shù)索引,例如#id,#a0茬射,#p0等鹦蠕。

例如:

@Cacheable(cacheNames = "emp", key = "#root.methodName+'[' + #id + ']'")

當(dāng)然也可以自定義KeyGenerator:

@Bean("myKeyGenerator")
public KeyGenerator keyGenerator(){
    return (target, method, params) -> method.getName() + "[" + Arrays.asList(params).toString() + "]";
}

則在@Cacheable上使用該自定義的KeyGenerator:

@Cacheable(cacheNames = "emp", keyGenerator = "myKeyGenerator")

@CachePut:同步更新緩存

它主要用于更新緩存,它先調(diào)用方法在抛,然后將目標(biāo)方法的結(jié)果進(jìn)行緩存片部。但是能夠更新緩存的前提是,@CachePut更新使用的key與@Cacheable使用的key要保持一致霜定。

@Cacheable(cacheNames = "emp", key = "#a0")
public Employee getById(Integer id){}

@CachePut(cacheNames = "emp", key = "#result.id")
public Employee update(Employee employee){}

其中#result表示目標(biāo)方法的返回值档悠。因?yàn)锧CachePut更新緩存比目標(biāo)方法晚,所有@CachePut能獲取到返回值望浩。而@Cacheable比目標(biāo)方法早辖所,所以無法使用#result。

@CacheEvict:緩存清除

同樣如果要清除緩存磨德,則使用的key值要與@Cacheable使用的key一致缘回,否則達(dá)不到清除緩存的功能。

? 屬性allEntries典挑,默認(rèn)為false酥宴,表示不把cacheNames里所有的緩存都刪除掉。當(dāng)該值為true的時(shí)候您觉,會(huì)把緩存中對(duì)應(yīng)的cacheNames里的所有緩存都刪除拙寡。

? 屬性beforeInvocation,默認(rèn)為false琳水,表示刪除緩存的操作在目標(biāo)方法執(zhí)行之后肆糕。當(dāng)該值為true的時(shí)候,則刪除緩存的操作在目標(biāo)方法執(zhí)行之前在孝。區(qū)別:如果設(shè)為true诚啃,當(dāng)目標(biāo)方法執(zhí)行出現(xiàn)異常后,對(duì)應(yīng)的緩存已經(jīng)被刪除了私沮。如果設(shè)為false(默認(rèn))始赎,當(dāng)目標(biāo)方法執(zhí)行出現(xiàn)異常后,就不會(huì)把緩存刪除掉仔燕。

2造垛、Redis緩存

2.1、redis依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2.2涨享、redis自動(dòng)配置類

@Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
    
    @Configuration
    @ConditionalOnClass(GenericObjectPool.class)
    protected static class RedisConnectionConfiguration {
        
        //other code...
        
        @Bean
        @ConditionalOnMissingBean(RedisConnectionFactory.class)
        public JedisConnectionFactory redisConnectionFactory()
                throws UnknownHostException {
            return applyProperties(createJedisConnectionFactory());
        }
        
        //other code...
        
    }
    
    @Configuration
    protected static class RedisConfiguration {

        @Bean
        @ConditionalOnMissingBean(name = "redisTemplate")
        public RedisTemplate<Object, Object> redisTemplate(
                RedisConnectionFactory redisConnectionFactory)
                throws UnknownHostException {
            RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }

        @Bean
        @ConditionalOnMissingBean(StringRedisTemplate.class)
        public StringRedisTemplate stringRedisTemplate(
                RedisConnectionFactory redisConnectionFactory)
                throws UnknownHostException {
            StringRedisTemplate template = new StringRedisTemplate();
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }

    }
}

在RedisAutoConfiguration自動(dòng)配置類中筋搏,注冊(cè)了幾個(gè)重要的Bean仆百,分別是JedisConnectionFactory厕隧,RedisTemplateStringRedisTemplate。可以在類中直接注入RedisTemplateStringRedisTemplate吁讨。

2.3髓迎、redis常用數(shù)據(jù)類型

String——字符串

List——列表

Set——集合

Hash——散列

ZSet——有序集合

具體命令可參考Redis命令

2.4、使用redis序列化對(duì)象

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<Object, Employee> employeeRedisTemplate(@Autowired RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<Object, Employee> template = new RedisTemplate<>();

        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer<Employee> serializer = new Jackson2JsonRedisSerializer<>(Employee.class);
        template.setDefaultSerializer(serializer);
        return template;
    }
}

使用該redisTemplate:

@Autowired
private RedisTemplate<Object, Employee> employeeRedisTemplate;

@Test
public void test02(){
    Employee e = employeeService.getById(1);
    //默認(rèn)保存對(duì)象建丧,使用jdk序列化機(jī)制排龄,序列化后的數(shù)據(jù)保存到redis中
    employeeRedisTemplate.opsForValue().set("emp-01", e);

    Employee employee = employeeRedisTemplate.opsForValue().get("emp-01");
    System.out.println(employee);
}

2.5、redis緩存

一旦引入了redis的starter之后翎朱,cache的配置類就變成了RedisCacheConfiguration橄维。

@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisTemplate.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {

    private final CacheProperties cacheProperties;

    private final CacheManagerCustomizers customizerInvoker;

    RedisCacheConfiguration(CacheProperties cacheProperties,
            CacheManagerCustomizers customizerInvoker) {
        this.cacheProperties = cacheProperties;
        this.customizerInvoker = customizerInvoker;
    }

    @Bean
    public RedisCacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        cacheManager.setUsePrefix(true);
        List<String> cacheNames = this.cacheProperties.getCacheNames();
        if (!cacheNames.isEmpty()) {
            cacheManager.setCacheNames(cacheNames);
        }
        return this.customizerInvoker.customize(cacheManager);
    }

}

而CacheManager就變成了RedisCacheManager

//加載緩存
@Override
protected Collection<? extends Cache> loadCaches() {

    Assert.notNull(this.redisOperations, "A redis template is required in order to interact with data store");

    Set<Cache> caches = new LinkedHashSet<Cache>(
        loadRemoteCachesOnStartup ? loadAndInitRemoteCaches() : new ArrayList<Cache>());

    Set<String> cachesToLoad = new LinkedHashSet<String>(this.configuredCacheNames);
    cachesToLoad.addAll(this.getCacheNames());

    if (!CollectionUtils.isEmpty(cachesToLoad)) {

        for (String cacheName : cachesToLoad) {
            caches.add(createCache(cacheName));
        }
    }

    return caches;
}

protected RedisCache createCache(String cacheName) {
    long expiration = computeExpiration(cacheName);
    return new RedisCache(cacheName, (usePrefix ? cachePrefix.prefix(cacheName) : null), redisOperations, expiration,
                          cacheNullValues);
}

RedisCacheManager通過創(chuàng)建RedisCache來作為緩存組件,然后通過RedisCache來進(jìn)行緩存的操作拴曲。

其中在創(chuàng)建RedisCacheManager組件的時(shí)候傳入的是RedisTemplate<Object, Object>争舞,而它默認(rèn)使用的是jdk的序列化機(jī)制。

public RedisTemplate() {}

public void afterPropertiesSet() {

    super.afterPropertiesSet();

    boolean defaultUsed = false;

    if (defaultSerializer == null) {

        defaultSerializer = new JdkSerializationRedisSerializer(
            classLoader != null ? classLoader : this.getClass().getClassLoader());
    }

    if (enableDefaultSerializer) {

        if (keySerializer == null) {
            keySerializer = defaultSerializer;
            defaultUsed = true;
        }
        if (valueSerializer == null) {
            valueSerializer = defaultSerializer;
            defaultUsed = true;
        }
        if (hashKeySerializer == null) {
            hashKeySerializer = defaultSerializer;
            defaultUsed = true;
        }
        if (hashValueSerializer == null) {
            hashValueSerializer = defaultSerializer;
            defaultUsed = true;
        }
    }

    if (enableDefaultSerializer && defaultUsed) {
        Assert.notNull(defaultSerializer, "default serializer null and not all serializers initialized");
    }

    if (scriptExecutor == null) {
        this.scriptExecutor = new DefaultScriptExecutor<K>(this);
    }

    initialized = true;
}

問題:如何將RedisCacheManager使用json序列化機(jī)制呢澈灼?

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<Object, Employee> employeeRedisTemplate(@Autowired RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<Object, Employee> template = new RedisTemplate<>();

        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer<Employee> serializer = new Jackson2JsonRedisSerializer<>(Employee.class);
        template.setDefaultSerializer(serializer);
        return template;
    }

    //自定義緩存管理器CacheManager
    @Bean
    @Primary
    public RedisCacheManager employeeRedisCacheManager(@Autowired RedisTemplate<Object, Employee> employeeRedisTemplate){
        RedisCacheManager manager = new RedisCacheManager(employeeRedisTemplate);
        //使用前綴竞川,會(huì)使key多一個(gè)前綴,默認(rèn)會(huì)使用cacheNames作為key的前綴
        manager.setUsePrefix(true);
        return manager;
    }

}

但是該RedisCacheManager只能操作Employee叁熔,如果使用該RedisCacheManager來操作其他Object委乌,就會(huì)報(bào)錯(cuò),所以通常情況下荣回,不應(yīng)該修改默認(rèn)的RedisCacheManager遭贸。

如果有多個(gè)CacheManager緩存管理器,則可以在@CacheConfig或者@Cacheable中的屬性cacheManager上指定所使用的CacheManager心软。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末革砸,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子糯累,更是在濱河造成了極大的恐慌算利,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件泳姐,死亡現(xiàn)場(chǎng)離奇詭異效拭,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)胖秒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門缎患,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人阎肝,你說我怎么就攤上這事挤渔。” “怎么了风题?”我有些...
    開封第一講書人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵判导,是天一觀的道長(zhǎng)嫉父。 經(jīng)常有香客問我,道長(zhǎng)眼刃,這世上最難降的妖魔是什么绕辖? 我笑而不...
    開封第一講書人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮擂红,結(jié)果婚禮上仪际,老公的妹妹穿的比我還像新娘。我一直安慰自己昵骤,他們只是感情好树碱,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著变秦,像睡著了一般赴恨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上伴栓,一...
    開封第一講書人閱讀 51,698評(píng)論 1 305
  • 那天伦连,我揣著相機(jī)與錄音,去河邊找鬼钳垮。 笑死惑淳,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的饺窿。 我是一名探鬼主播歧焦,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼肚医!你這毒婦竟也來了绢馍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤肠套,失蹤者是張志新(化名)和其女友劉穎舰涌,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體你稚,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瓷耙,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了刁赖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搁痛。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖宇弛,靈堂內(nèi)的尸體忽然破棺而出鸡典,到底是詐尸還是另有隱情,我是刑警寧澤枪芒,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布彻况,位于F島的核電站谁尸,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏疗垛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一硫朦、第九天 我趴在偏房一處隱蔽的房頂上張望贷腕。 院中可真熱鬧,春花似錦咬展、人聲如沸泽裳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)涮总。三九已至,卻和暖如春祷舀,著一層夾襖步出監(jiān)牢的瞬間瀑梗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工裳扯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留抛丽,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓饰豺,卻偏偏與公主長(zhǎng)得像亿鲜,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子冤吨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

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