欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot使用Cache集成Redis做緩存的保姆級(jí)教程

 更新時(shí)間:2025年01月13日 09:36:27   作者:笑小楓  
Spring Cache是Spring框架提供的一個(gè)緩存抽象層,它簡化了緩存的使用和管理,Spring Cache默認(rèn)使用服務(wù)器內(nèi)存,并無法控制緩存時(shí)長,查找緩存中的數(shù)據(jù)比較麻煩,本文已常用的Redis作為緩存中間件作為示例,詳細(xì)講解項(xiàng)目中如何使用Cache提高系統(tǒng)性能,需要的朋友可以參考下

1. 項(xiàng)目背景

Spring Cache是Spring框架提供的一個(gè)緩存抽象層,它簡化了緩存的使用和管理。Spring Cache默認(rèn)使用服務(wù)器內(nèi)存,并無法控制緩存時(shí)長,查找緩存中的數(shù)據(jù)比較麻煩。

因此Spring Cache支持將緩存數(shù)據(jù)集成到各種緩存中間件中。本文已常用的Redis作為緩存中間件作為示例,詳細(xì)講解項(xiàng)目中如何使用Cache提高系統(tǒng)性能。

2. Spring Cache介紹

Spring Cache是Spring框架提供的一種緩存解決方案,基于AOP原理,實(shí)現(xiàn)了基于注解的緩存功能,只需要簡單地加一個(gè)注解就能實(shí)現(xiàn)緩存功能,對(duì)業(yè)務(wù)代碼的侵入性很小。

使用Spring Cache的方法很簡單,只需要在方法上添加注解即可實(shí)現(xiàn)將方法返回?cái)?shù)據(jù)存入緩存,以及清理緩存等注解的使用。

2.1 主要特點(diǎn)

  1. 統(tǒng)一的緩存抽象:Spring Cache為應(yīng)用提供了一種統(tǒng)一的緩存抽象,可以輕松集成各種緩存提供者(如Ehcache、Redis、Caffeine等),使用統(tǒng)一的API。
  2. 注解驅(qū)動(dòng):Spring Cache通過簡單的注解配置,如@Cacheable、@CachePut@CacheEvict等,可以快速實(shí)現(xiàn)緩存功能,而無需處理底層緩存邏輯。
  3. 靈活性和擴(kuò)展性:Spring Cache允許根據(jù)業(yè)務(wù)需求自定義緩存策略,如緩存的失效時(shí)間、緩存的淘汰策略等。同時(shí),它也提供了CacheManager接口和Cache接口,可以實(shí)現(xiàn)降低對(duì)各種緩存框架的耦合。

2.2 常用注解

@EnableCaching

  • 作用:開啟Spring的緩存注解支持。
  • 使用場景:在配置類上添加此注解,以啟用Spring Cache的注解處理功能。
  • 注意:此注解本身并不提供緩存實(shí)現(xiàn),而是允許你使用@Cacheable、@CachePut、@CacheEvict等注解來定義緩存行為。

@Cacheable

  • 作用:在方法執(zhí)行前檢查緩存,如果緩存中存在數(shù)據(jù)則直接返回,否則執(zhí)行方法并將結(jié)果緩存。
  • value:指定緩存的名稱(或名稱數(shù)組)。緩存名稱與CacheManager中配置的緩存對(duì)應(yīng)。
  • key:用于生成緩存鍵的表達(dá)式(可選)。如果不指定,則默認(rèn)使用方法的參數(shù)值作為鍵。
  • condition:條件表達(dá)式(可選),用于決定是否執(zhí)行緩存操作。
  • unless:否定條件表達(dá)式(可選),用于在方法執(zhí)行后決定是否緩存返回值。

@Cacheable注解配置參數(shù)說明

  1. value/cacheNames

    • 用于指定緩存的名稱(或名稱數(shù)組),緩存名稱作為緩存key的前綴。這是緩存的標(biāo)識(shí)符,用于在CacheManager中查找對(duì)應(yīng)的緩存。
    • valuecacheNames是互斥的,即只能指定其中一個(gè)。
  2. key

    • 用于生成緩存鍵的表達(dá)式。這個(gè)鍵用于在緩存中唯一標(biāo)識(shí)存儲(chǔ)的值。
    • 如果不指定key,則默認(rèn)使用方法的參數(shù)值(經(jīng)過某種轉(zhuǎn)換)作為鍵。
    • 可以使用Spring Expression Language(SpEL)來編寫key表達(dá)式,以實(shí)現(xiàn)動(dòng)態(tài)鍵的生成。
  3. keyGenerator

    • 指定一個(gè)自定義的鍵生成器(實(shí)現(xiàn) org.springframework.cache.interceptor.KeyGenerator 接口的類),用于生成緩存的鍵。與 key 屬性互斥,二者只能選其一。
    • 如果同時(shí)指定了keykeyGenerator,則會(huì)引發(fā)異常,因?yàn)樗鼈兪腔コ獾摹?/li>
    • 開發(fā)者可以編寫自己的KeyGenerator實(shí)現(xiàn),并將其注冊(cè)到Spring容器中,然后在@Cacheable注解中引用。
  4. cacheManager

    • CacheManager表示緩存管理器,通過緩存管理器可以設(shè)置緩存過期時(shí)間。
    • 用于指定要使用的CacheManager。這是一個(gè)可選參數(shù),通常不需要顯式指定,因?yàn)镾pring會(huì)默認(rèn)使用配置的CacheManager。
    • 如果系統(tǒng)中配置了多個(gè)CacheManager,則需要通過此參數(shù)指定使用哪一個(gè)。
  5. cacheResolver

    • 緩存解析器,用于解析緩存名稱并返回相應(yīng)的Cache對(duì)象。這也是一個(gè)可選參數(shù)。
    • 類似于cacheManager,如果系統(tǒng)中配置了多個(gè)緩存解析邏輯,可以通過此參數(shù)指定使用哪一個(gè)。
  6. condition

    • 條件表達(dá)式,用于決定是否執(zhí)行緩存操作。這是一個(gè)可選參數(shù)。
    • 條件表達(dá)式使用SpEL編寫,如果表達(dá)式返回true,則執(zhí)行緩存操作;否則不執(zhí)行。
  7. unless

    • 否定條件表達(dá)式,用于在方法執(zhí)行后決定是否緩存返回值。這也是一個(gè)可選參數(shù)。
    • condition類似,unless也使用SpEL編寫,但它是在方法執(zhí)行后才進(jìn)行評(píng)估的。
    • 如果unless表達(dá)式返回true,則不緩存返回值;否則緩存。
  8. sync

    • 是否使用異步模式進(jìn)行緩存操作。這是一個(gè)可選參數(shù),通常不需要顯式指定。
    • 在多線程環(huán)境中,如果多個(gè)線程同時(shí)請(qǐng)求相同的數(shù)據(jù)并觸發(fā)緩存操作,使用異步模式可以避免線程阻塞和重復(fù)計(jì)算。

@Cacheable注解的這些參數(shù)是互斥或相互關(guān)聯(lián)的,例如valuecacheNames不能同時(shí)指定,keykeyGenerator也不能同時(shí)指定。此外,cacheManagercacheResolver也是互斥的,因?yàn)樗鼈兌加糜谥付ň彺娴慕馕龊凸芾矸绞健?/p>

對(duì)于前兩個(gè)注解的應(yīng)用:

    @Cacheable(cacheNames = "cache:cacheByKey", key = "#id")
    public Integer cacheByKey(@PathVariable("id") Integer id) throws InterruptedException {
        Thread.sleep(5000);
        log.info("執(zhí)行了cacheByKey方法" + id);
        return id;
    }

看注釋掉的那行,取緩存名稱為cache:cacheByKey,參數(shù)id的值作為key,最終緩存key為:緩存名稱+“::”+key,例如:上述代碼id為123,最終的key為:cache:cacheByKey::123

SpEL(Spring Expression Language)是一種在 Spring 框架中用于處理字符串表達(dá)式的強(qiáng)大工具,它可以實(shí)現(xiàn)獲取對(duì)象的屬性,調(diào)用對(duì)象的方法操作。

  • 單個(gè)緩存名稱@Cacheable(value = "myCache") 表示使用名為myCache的緩存。
  • 多個(gè)緩存名稱@Cacheable(value = {"cache1", "cache2"}) 表示方法的結(jié)果將同時(shí)緩存到cache1cache2中。
  • @CacheConfig結(jié)合使用:如果類上使用了@CacheConfig注解,并且指定了cacheNames屬性,那么類中的方法在使用@Cacheable時(shí)可以省略value屬性,直接使用類級(jí)別的緩存配置。

@CacheEvict

  • 作用:從緩存中刪除數(shù)據(jù)。
  • value:指定要?jiǎng)h除的緩存的名稱(或名稱數(shù)組)。
  • key:用于指定要?jiǎng)h除的緩存鍵(可選)。如果不指定,則默認(rèn)使用方法的參數(shù)值作為鍵。
  • allEntries:布爾值,指定是否刪除緩存中的所有條目(而不是僅刪除與指定鍵匹配的條目)。
  • beforeInvocation:布爾值,指定是否在方法執(zhí)行之前刪除緩存(默認(rèn)為false,即在方法執(zhí)行之后刪除)。

@CachePut

  • 作用:更新緩存中的數(shù)據(jù),無論方法是否成功執(zhí)行,都會(huì)將結(jié)果放入緩存。
  • value、key、condition、unless:與@Cacheable中的這些屬性相同。

@Caching

  • 作用:允許在同一個(gè)方法上組合使用多個(gè)緩存注解(如@Cacheable、@CachePut、@CacheEvict)。
  • 屬性:包含一個(gè)或多個(gè)緩存注解。

@CacheConfig

  • 作用:為類級(jí)別提供緩存相關(guān)的默認(rèn)配置。
  • cacheNames:指定該類中所有方法使用的默認(rèn)緩存名稱(或名稱數(shù)組)。
  • keyGenerator:指定自定義的鍵生成器(可選)。
  • cacheManager:指定要使用的CacheManager(可選)。

3. 示例代碼

項(xiàng)目依賴于Redis配置,這里就不多贅述了。

緩存管理器配置:

定義了兩個(gè)緩存管理器,默認(rèn)cacheManager(使用@Primary標(biāo)注),一個(gè)緩存返回值為null的管理器cacheNullManager,詳情看下面代碼。

package com.maple.redis.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.lang.reflect.Method;
import java.time.Duration;

/**
 * @author 笑小楓
 * @date 2025/1/7
 */
@Slf4j
@Configuration
@EnableCaching
public class CacheConfig extends CachingConfigurerSupport {

    /**
     * 默認(rèn)緩存管理器
     * 只有CacheManger才能掃描到cacheable注解
     * spring提供了緩存支持Cache接口,實(shí)現(xiàn)了很多個(gè)緩存類,其中包括RedisCache。但是我們需要對(duì)其進(jìn)行配置,這里就是配置RedisCache
     */
    @Bean
    @Primary
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        return RedisCacheManager.RedisCacheManagerBuilder
                //Redis鏈接工廠
                .fromConnectionFactory(redisConnectionFactory)
                //緩存配置 通用配置  默認(rèn)存儲(chǔ)一小時(shí)
                .cacheDefaults(getCacheConfigurationWithTtl(Duration.ofHours(1)))
                //配置同步修改或刪除  put/evict
                .transactionAware()
                //對(duì)于不同的cacheName我們可以設(shè)置不同的過期時(shí)間
                .withCacheConfiguration("cache2:cacheByUser", getCacheConfigurationWithTtl(Duration.ofHours(2)))
                .build();
    }

    /**
     * 創(chuàng)建并返回一個(gè)CacheManager Bean,用于管理Redis緩存。
     * 主要返回結(jié)果為null時(shí)使用,會(huì)緩存null值,緩存時(shí)間為10分鐘,防止緩存穿透。
     * 使用時(shí)通過 cacheManager = "cacheNullManager" 指定使用該緩存管理器。
     */
    @Bean
    public CacheManager cacheNullManager(RedisConnectionFactory redisConnectionFactory) {
        return RedisCacheManager.RedisCacheManagerBuilder
                //Redis鏈接工廠
                .fromConnectionFactory(redisConnectionFactory)
                //緩存配置 通用配置  默認(rèn)存儲(chǔ)一小時(shí)
                .cacheDefaults(RedisCacheConfiguration
                        .defaultCacheConfig()
                        // 設(shè)置key為String
                        .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                        // 設(shè)置value 為自動(dòng)轉(zhuǎn)Json的Object
                        .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()))
                        .entryTtl(Duration.ofMinutes(10)))
                //配置同步修改或刪除  put/evict
                .transactionAware()
                .build();
    }

    /**
     * 緩存的基本配置對(duì)象
     */
    private RedisCacheConfiguration getCacheConfigurationWithTtl(Duration duration) {
        return RedisCacheConfiguration
                .defaultCacheConfig()
                //設(shè)置key value的序列化方式
                // 設(shè)置key為String
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                // 設(shè)置value 為自動(dòng)轉(zhuǎn)Json的Object
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()))
                // 不緩存null
                .disableCachingNullValues()
                // 設(shè)置緩存的過期時(shí)間
                .entryTtl(duration);
    }

    /**
     * 緩存的異常處理
     */
    @Bean
    @Override
    public CacheErrorHandler errorHandler() {
        // 異常處理,當(dāng)Redis發(fā)生異常時(shí),打印日志,但是程序正常走
        log.info("初始化 -> [{}]", "Redis CacheErrorHandler");
        return new CacheErrorHandler() {
            @Override
            public void handleCacheGetError(RuntimeException e, Cache cache, Object key) {
                log.error("Redis occur handleCacheGetError:key -> [{}]", key, e);
            }

            @Override
            public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {
                log.error("Redis occur handleCachePutError:key -> [{}];value -> [{}]", key, value, e);
            }

            @Override
            public void handleCacheEvictError(RuntimeException e, Cache cache, Object key) {
                log.error("Redis occur handleCacheEvictError:key -> [{}]", key, e);
            }

            @Override
            public void handleCacheClearError(RuntimeException e, Cache cache) {
                log.error("Redis occur handleCacheClearError:", e);
            }
        };
    }

    @Override
    @Bean("myKeyGenerator")
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuffer sb = new StringBuffer();
                sb.append(target.getClass().getName());
                sb.append(method.getName());
                for (Object obj : params) {
                    sb.append(obj.toString());
                }
                return sb.toString();
            }
        };
    }
}

使用案例:

User對(duì)象就id、name兩個(gè)字段,大家隨意~

package com.maple.redis.controller;

import com.maple.redis.bean.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.web.bind.annotation.*;

/**
 * @author 笑小楓
 * @date 2025/1/7
 */
@Slf4j
@RestController
@RequestMapping("/cache")
public class TestCacheController {

    /**
     * 獲取簡單緩存數(shù)據(jù)。
     *
     * <p>通過@Cacheable注解,該方法的結(jié)果會(huì)被緩存到名為"cache:simpleCache"的緩存中。
     * 如果在緩存中找到相同請(qǐng)求的結(jié)果,將直接返回緩存的值,避免重復(fù)執(zhí)行方法體中的邏輯。
     *
     * <p>方法內(nèi)部,使用Thread.sleep(5000)模擬了一個(gè)耗時(shí)操作,
     */
    @GetMapping("/simpleCache")
    @Cacheable(cacheNames = "cache:simpleCache")
    public String simpleCache() throws InterruptedException {
        Thread.sleep(5000);
        log.info("執(zhí)行了simpleCache方法");
        return "test";
    }

    /**
     * 如果緩存中存在對(duì)應(yīng)的ID,則直接從緩存中獲取結(jié)果,避免重復(fù)執(zhí)行耗時(shí)操作。
     * 如果緩存中不存在,則執(zhí)行方法體中的邏輯,將結(jié)果存入緩存并返回。
     * 方法執(zhí)行過程中,通過Thread.sleep模擬了一個(gè)耗時(shí)操作。
     */
    @GetMapping("/{id}")
    @Cacheable(cacheNames = "cache:cacheByKey", key = "#id")
    public Integer cacheByKey(@PathVariable("id") Integer id) throws InterruptedException {
        Thread.sleep(5000);
        log.info("執(zhí)行了cacheByKey方法" + id);
        return id;
    }

    /**
     * <p>該方法使用@Caching注解集成了多個(gè)緩存策略:</p>
     * <ul>
     *     <li>
     *         當(dāng)方法返回值為null時(shí)(即緩存穿透情況),使用名為"cacheNullManager"的CacheManager進(jìn)行緩存處理,
     *         緩存名稱為"cache2:cacheByKey",緩存鍵為傳入的用戶ID,并設(shè)置緩存過期時(shí)間為10分鐘。
     *         這通過@Cacheable注解的cacheManager屬性指定緩存管理器,unless屬性設(shè)置緩存條件(當(dāng)結(jié)果為null時(shí)緩存)。
     *     </li>
     *     <li>
     *         當(dāng)方法返回值不為null時(shí),使用默認(rèn)的CacheManager進(jìn)行緩存處理,
     *         緩存名稱和鍵的設(shè)置與上述相同,但此時(shí)緩存管理器為默認(rèn)配置。
     *         這通過另一個(gè)@Cacheable注解實(shí)現(xiàn),其unless屬性設(shè)置為當(dāng)結(jié)果為null時(shí)不緩存。
     *     </li>
     * </ul>
     *
     * <p>在方法執(zhí)行過程中,通過Thread.sleep模擬了一個(gè)耗時(shí)操作。</p>
     */
    @Caching(
            cacheable = {
                    //result為null時(shí),屬于緩存穿透情況,使用cacheNullManager緩存管理器進(jìn)行緩存,并且設(shè)置過期時(shí)間為10分鐘。
                    @Cacheable(cacheNames = "cache2:cacheByKey", key = "#id", unless = "#result != null", cacheManager = "cacheNullManager"),
                    //result不為null時(shí),使用默認(rèn)緩存管理器進(jìn)行緩存。
                    @Cacheable(cacheNames = "cache2:cacheByKey", key = "#id", unless = "#result == null")
            }
    )
    @GetMapping("/cacheMore/{id}")
    public User cacheMore(@PathVariable("id") Integer id) throws InterruptedException {
        Thread.sleep(5000);
        if (id > 100) {
            return null;
        } else {
            return new User(id, "zhangsan");
        }
    }

    @PostMapping("/cacheByUser")
    @Cacheable(cacheNames = "cache2:cacheByUser", key = "#user.id")
    public User cacheByUser(@RequestBody User user) throws InterruptedException {
        Thread.sleep(5000);
        log.info("執(zhí)行了cacheByUser方法" + user.getId());
        return user;
    }

    @PostMapping("/cacheByIdAndName")
    @Cacheable(cacheNames = "cache2:cacheByUser", key = "#user.id")
    public User cacheByIdAndName(@RequestBody User user) throws InterruptedException {
        Thread.sleep(5000);
        log.info("執(zhí)行了cacheByUser方法" + user.getId());
        return user;
    }

    /**
     * 根據(jù)用戶ID大于100的條件進(jìn)行緩存處理。
     *
     * @param user 用戶對(duì)象,包含用戶ID等信息。
     * @return 返回傳入的用戶對(duì)象。
     * @throws InterruptedException 如果線程被中斷,則拋出此異常。
     *
     *                              通過@Cacheable注解實(shí)現(xiàn)了緩存功能,當(dāng)請(qǐng)求的用戶ID大于100時(shí),會(huì)觸發(fā)緩存機(jī)制。
     *                              緩存的名稱設(shè)置為"cache2:cacheByUser",緩存的鍵為傳入的用戶對(duì)象的ID。
     *                              如果緩存中已存在對(duì)應(yīng)的用戶數(shù)據(jù),則直接從緩存中獲取并返回,避免重復(fù)執(zhí)行耗時(shí)操作。
     *                              如果緩存中不存在,則執(zhí)行方法體中的邏輯,將結(jié)果存入緩存并返回。
     *                              在方法執(zhí)行過程中,通過Thread.sleep模擬了一個(gè)耗時(shí)操作。
     */
    @PostMapping("/cacheByUserIdGt100")
    @Cacheable(cacheNames = "cache2:cacheByUser", key = "#user.id", condition = "#user.id > 100")
    public User cacheByUserIdGt100(@RequestBody User user) throws InterruptedException {
        Thread.sleep(5000);
        log.info("執(zhí)行了cacheByUser方法" + user.getId());
        return user;
    }

    /**
     * 更新用戶信息。
     * <p>
     * 使用@CachePut注解將更新后的用戶信息存入緩存中。
     * 緩存的名稱設(shè)置為"cache2:cacheByUser",緩存的鍵為傳入的User對(duì)象的ID。
     * 如果緩存中已存在對(duì)應(yīng)的用戶數(shù)據(jù),則更新緩存中的值;如果不存在,則創(chuàng)建新的緩存條目。
     * 在方法執(zhí)行過程中,通過Thread.sleep模擬了一個(gè)耗時(shí)操作。
     */
    @PostMapping("/updateUser")
    @CachePut(cacheNames = "cache2:cacheByUser", key = "#user.id")
    public User updateUser(@RequestBody User user) throws InterruptedException {
        Thread.sleep(5000);
        log.info("執(zhí)行了saveUser方法" + user.getId());
        return user;
    }

    /**
     * 刪除指定ID的用戶,并從緩存中移除對(duì)應(yīng)的數(shù)據(jù)。
     * <p>
     * 使用@CacheEvict注解用于從緩存中移除指定ID的用戶數(shù)據(jù)。
     * 緩存的名稱設(shè)置為"cache2:cacheByUser",緩存的鍵為傳入的用戶ID。
     * 在執(zhí)行刪除操作前,方法通過Thread.sleep模擬了一個(gè)耗時(shí)操作。
     */
    @DeleteMapping("/{id}")
    @CacheEvict(cacheNames = "cache2:cacheByUser", key = "#id")
    public void deleteUser(@PathVariable("id") Integer id) throws InterruptedException {
        Thread.sleep(10000);
        log.info("執(zhí)行了deleteUser方法" + id);
    }
}

模擬了多種緩存使用的方式

  • updateUser使用@CachePut對(duì)數(shù)據(jù)進(jìn)行緩存或更新。
  • deleteUser使用@CacheEvict刪除緩存。
  • cacheMore根據(jù)條件選擇不同的緩存管理器進(jìn)行緩存數(shù)據(jù)。

簡單附幾張測試截圖吧

第一次查詢,沒有緩存截圖:

后續(xù)查詢走緩存的截圖

redis緩存數(shù)據(jù)格式:

redis緩存數(shù)據(jù)詳情:

4. SpEL在Spring Cache中的應(yīng)用

4.1 SpEL概述

SpEL是Spring框架提供的一種功能強(qiáng)大的表達(dá)式語言,它能夠在運(yùn)行時(shí)查詢和操作對(duì)象圖。SpEL的語法簡潔,支持方法調(diào)用、字符串模板、集合操作、邏輯運(yùn)算等復(fù)雜功能,使得在Spring配置和代碼中能夠更輕松地處理復(fù)雜的邏輯和數(shù)據(jù)結(jié)構(gòu)。

4.2 SpEL應(yīng)用

  1. 動(dòng)態(tài)生成緩存鍵

    • 在Spring Cache中,緩存鍵(Key)用于在緩存中唯一標(biāo)識(shí)數(shù)據(jù)。通過使用SpEL表達(dá)式,可以根據(jù)方法參數(shù)、返回值等動(dòng)態(tài)生成緩存鍵。
    • 例如,在@Cacheable注解中,可以使用key屬性配合SpEL表達(dá)式來指定緩存鍵的生成規(guī)則。
  2. 條件緩存

    • Spring Cache允許通過condition屬性來指定緩存的條件。當(dāng)條件滿足時(shí),才會(huì)執(zhí)行緩存操作(如緩存數(shù)據(jù)或移除緩存)。
  3. 除非條件

    • unless屬性與condition屬性類似,但它用于指定不執(zhí)行緩存操作的條件。
    • 當(dāng)unless條件滿足時(shí),即使方法被調(diào)用,其結(jié)果也不會(huì)被緩存。
    • unless屬性同樣支持SpEL表達(dá)式。

4.3 SpEL表達(dá)式在Spring Cache中的常用變量

  1. #參數(shù)名

    • 表示方法參數(shù)??梢酝ㄟ^參數(shù)名來引用方法參數(shù)的值。
    • 例如,#param1表示第一個(gè)參數(shù)的值。
  2. #result

    • 表示方法的返回值。在@CachePut和@CacheEvict注解中,可以使用#result來引用方法的返回值。
  3. #root

    • 表示緩存表達(dá)式根對(duì)象(CacheExpressionRootObject)。它提供了對(duì)緩存操作上下文的訪問。
    • 通過#root,可以獲取到緩存的詳細(xì)信息,如緩存名稱、方法參數(shù)等。

注意:

condition屬性在Spring Cache中用于在方法執(zhí)行前判斷是否執(zhí)行緩存操作,并且不能引用方法的返回值;而unless屬性則用于在方法執(zhí)行后根據(jù)返回值或其他條件來決定是否緩存數(shù)據(jù)。

5. 工作原理

Spring Cache是基于AOP原理,對(duì)添加注解@Cacheable的類生成代理對(duì)象,在方法執(zhí)行前查看是否有緩存對(duì)應(yīng)的數(shù)據(jù),如果有直接返回?cái)?shù)據(jù),如果沒有調(diào)用源方法獲取數(shù)據(jù)返回,并緩存起來,下邊跟蹤Spring Cache的切面類CacheAspectSupport.java中的private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts)方法。

@Nullable
    private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
        if (contexts.isSynchronized()) {
            CacheOperationContext context = (CacheOperationContext)contexts.get(CacheableOperation.class).iterator().next();
            if (!this.isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
                return this.invokeOperation(invoker);
            }

            Object key = this.generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
            Cache cache = (Cache)context.getCaches().iterator().next();

            try {
                return this.wrapCacheValue(method, this.handleSynchronizedGet(invoker, key, cache));
            } catch (Cache.ValueRetrievalException var10) {
                Cache.ValueRetrievalException ex = var10;
                ReflectionUtils.rethrowRuntimeException(ex.getCause());
            }
        }

        this.processCacheEvicts(contexts.get(CacheEvictOperation.class), true, CacheOperationExpressionEvaluator.NO_RESULT);
        Cache.ValueWrapper cacheHit = this.findCachedItem(contexts.get(CacheableOperation.class));
        List<CachePutRequest> cachePutRequests = new ArrayList();
        if (cacheHit == null) {
            this.collectPutRequests(contexts.get(CacheableOperation.class), CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
        }

        Object cacheValue;
        Object returnValue;
        if (cacheHit != null && !this.hasCachePut(contexts)) {//如果緩存有,則從緩存取
            cacheValue = cacheHit.get();
            returnValue = this.wrapCacheValue(method, cacheValue);
        } else {//緩存沒有,執(zhí)行原始方法
            returnValue = this.invokeOperation(invoker);
            cacheValue = this.unwrapReturnValue(returnValue);//再存緩存
        }

        this.collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
        Iterator var8 = cachePutRequests.iterator();

        while(var8.hasNext()) {
            CachePutRequest cachePutRequest = (CachePutRequest)var8.next();
            cachePutRequest.apply(cacheValue);
        }

        this.processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
        return returnValue;
    }

6.總結(jié)

以上就是SpringBoot使用Cache集成Redis做緩存的保姆級(jí)教程的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot Cache集成Redis做緩存的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java之Maven工程打包jar

    Java之Maven工程打包jar

    Maven打包一般可以生成兩種包一種是可以直接運(yùn)行的包,一種是依賴包(只是編譯包)。Maven默認(rèn)打包時(shí)jar,如果需要修改其他類型,可以修改pom.xml。感興趣的同學(xué)可以參考閱讀
    2023-04-04
  • Java運(yùn)算符從見過到掌握上

    Java運(yùn)算符從見過到掌握上

    計(jì)算機(jī)的最基本用途之一就是執(zhí)行數(shù)學(xué)運(yùn)算,作為一門計(jì)算機(jī)語言,Java也提供了一套豐富的運(yùn)算符來操縱變量,本篇對(duì)大家的學(xué)習(xí)或工作具有一定的價(jià)值,需要的朋友可以參考下
    2021-09-09
  • java繼承學(xué)習(xí)之super的用法解析

    java繼承學(xué)習(xí)之super的用法解析

    本文介紹java繼承super的用法,Java繼承是會(huì)用已存在的類的定義作為基礎(chǔ)建立新類的技術(shù)新類的定義可以增加新的數(shù)據(jù)或者新的功能,也可以使用父類的功能,但不能選擇性的繼承父類 這種繼承使得復(fù)用以前的代碼非常容易,能夠大大的縮短開發(fā)的周期,需要的朋友可以參考下
    2022-02-02
  • 詳解IDEA啟動(dòng)多個(gè)微服務(wù)的配置方法

    詳解IDEA啟動(dòng)多個(gè)微服務(wù)的配置方法

    這篇文章主要介紹了詳解IDEA啟動(dòng)多個(gè)微服務(wù)的配置方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-01-01
  • springboot jta atomikos實(shí)現(xiàn)分布式事物管理

    springboot jta atomikos實(shí)現(xiàn)分布式事物管理

    這篇文章主要介紹了springboot jta atomikos實(shí)現(xiàn)分布式事物管理,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-12-12
  • 一文帶你深入了解Java?String的不可變性

    一文帶你深入了解Java?String的不可變性

    這篇文章主要來和大家一起深入探討一下Java?String中的不可變性,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-06-06
  • 淺談SpringBoot實(shí)現(xiàn)自動(dòng)裝配的方法原理

    淺談SpringBoot實(shí)現(xiàn)自動(dòng)裝配的方法原理

    SpringBoot的自動(dòng)裝配是它的一大特點(diǎn),可以大大提高開發(fā)效率,減少重復(fù)性代碼的編寫。本文將詳細(xì)講解SpringBoot如何實(shí)現(xiàn)自動(dòng)裝配,需要的朋友可以參考下
    2023-05-05
  • SpringBoot中Bean拷貝及工具類封裝的實(shí)現(xiàn)

    SpringBoot中Bean拷貝及工具類封裝的實(shí)現(xiàn)

    本文主要介紹了SpringBoot中Bean拷貝及工具類封裝的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-05-05
  • 多種情況下jar包獲取文件的路徑,讀取文件方式

    多種情況下jar包獲取文件的路徑,讀取文件方式

    文章介紹了在不同情況下(IDEA運(yùn)行和JAR包運(yùn)行)獲取文件路徑的方法,并總結(jié)了每種方式的適用場景
    2024-11-11
  • selenium4.0版本在springboot中的使用問題的坑

    selenium4.0版本在springboot中的使用問題的坑

    本文主要介紹了selenium4.0版本在springboot中的使用問題的坑,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07

最新評(píng)論