RuoYi-Vue-Plus (SpringCache、CacheManager、@Cacheable)

慈云数据 2024-05-09 技术支持 41 0

一、概述

        1、SpringCache是Spring提供的一个缓存框架,在Spring3.1版本开始支持将缓存添加到现有的spring应用程序中,在4.1开始,缓存已支持JSR-107注释和更多自定义的选项。

RuoYi-Vue-Plus (SpringCache、CacheManager、@Cacheable)
(图片来源网络,侵删)

        2、SpringCache利用了AOP,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能了,做到了对代码侵入性做小。

        3、SpringCache框架还提供了CacheManager接口,可以实现降低对各种缓存框架的耦合。它不是具体的缓存实现,它只提供一整套的接口和代码规范、配置、注解等,用于整合各种缓存方案,比如Caffeine、Guava Cache、Ehcache。

RuoYi-Vue-Plus (SpringCache、CacheManager、@Cacheable)
(图片来源网络,侵删)

二、SpringCache概念

接口:

1、Cache接口:缓存接口,定义缓存操作。实现有 如RedisCache、EhCacheCache、ConcurrentMapCache等

2、cacheResolver:指定获取解析器

3、CacheManager:缓存管理器,管理各种缓存(Cache)组件;如:RedisCacheManager,使用redis作为缓存。指定缓存管理器

注解:

1- @Cacheable:在方法执行前查看是否有缓存对应的数据,如果有直接返回数据,如果没有调用方法获取数据返回,并缓存起来。

2- @CacheEvict:将一条或多条数据从缓存中删除。

3- @CachePut:将方法的返回值放到缓存中

4- @EnableCaching:开启缓存注解功能

5- @Caching:组合多个缓存注解;

6- @CacheConfig:统一配置@Cacheable中的value值

三、spring缓存整合redis 

RedisConfig 
类路径: com.ruoyi.framework.config.RedisConfig
1- spring 自动管理缓存机制

@EnableCaching //开启spring缓存,提升性能

@Slf4j
@Configuration
@EnableCaching //1- spring 自动管理缓存机制 ,,提升性能
@EnableConfigurationProperties(RedissonProperties.class)
public class RedisConfig {
 2- 整合自定义缓存管理器
 /**
     * 2-自定义缓存管理器 整合spring-cache
     */
    @Bean
    public CacheManager cacheManager() {
        return new PlusSpringCacheManager();
    }

自定义 管理器:PlusSpringCacheManager,实现CacheManager 接口,基于redssion操作缓存

/**
 * Copyright (c) 2013-2021 Nikita Koksharov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.ruoyi.framework.manager;
import com.ruoyi.common.utils.redis.RedisUtils;
import org.redisson.api.RMap;
import org.redisson.api.RMapCache;
import org.redisson.spring.cache.CacheConfig;
import org.redisson.spring.cache.RedissonCache;
import org.springframework.boot.convert.DurationStyle;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.transaction.TransactionAwareCacheDecorator;
import org.springframework.util.StringUtils;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
 * A {@link org.springframework.cache.CacheManager} implementation
 * backed by Redisson instance.
 * 

* 修改 RedissonSpringCacheManager 源码 * 重写 cacheName 处理方法 支持多参数 * * @author Nikita Koksharov * */ @SuppressWarnings("unchecked") public class PlusSpringCacheManager implements CacheManager { //是否自动配置name private boolean dynamic = true; //是否允许null private boolean allowNullValues = true; //事务提交之后执行 private boolean transactionAware = true; // 常用缓存配置 ttl; maxIdleTime; maxSize; 等 Map configMap = new ConcurrentHashMap(); // 缓存实例 ConcurrentMap instanceMap = new ConcurrentHashMap(); /** * Creates CacheManager supplied by Redisson instance */ public PlusSpringCacheManager() { } /** * Defines possibility of storing {@code null} values. *

* Default is true * * @param allowNullValues stores if true */ public void setAllowNullValues(boolean allowNullValues) { this.allowNullValues = allowNullValues; } /** * Defines if cache aware of Spring-managed transactions. * If {@code true} put/evict operations are executed only for successful transaction in after-commit phase. *

* Default is false * * @param transactionAware cache is transaction aware if true */ public void setTransactionAware(boolean transactionAware) { this.transactionAware = transactionAware; } /** * Defines 'fixed' cache names. * A new cache instance will not be created in dynamic for non-defined names. *

* `null` parameter setups dynamic mode * * @param names of caches */ public void setCacheNames(Collection names) { if (names != null) { for (String name : names) { getCache(name); } dynamic = false; } else { dynamic = true; } } /** * Set cache config mapped by cache name * * @param config object */ public void setConfig(Map config) { this.configMap = (Map) config; } protected CacheConfig createDefaultConfig() { return new CacheConfig(); } @Override public Cache getCache(String name) { // 重写 cacheName 支持多参数 /** * 演示案例 : String DEMO_CACHE = "demo:cache#60s#10m#20"; */ String[] array = StringUtils.delimitedListToStringArray(name, "#"); name = array[0]; Cache cache = instanceMap.get(name); if (cache != null) { return cache; } //2- dynamic=false 不会动态生成 if (!dynamic) { //return cache; return null; } CacheConfig config = configMap.get(name); if (config == null) { config = createDefaultConfig(); configMap.put(name, config); } //setTTL if (array.length > 1) { config.setTTL(DurationStyle.detectAndParse(array[1]).toMillis()); } //setMaxIdleTime if (array.length > 2) { config.setMaxIdleTime(DurationStyle.detectAndParse(array[2]).toMillis()); } //setMaxSize if (array.length > 3) { config.setMaxSize(Integer.parseInt(array[3])); } if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) { return createMap(name, config); } return createMapCache(name, config); } private Cache createMap(String name, CacheConfig config) { //1-获取缓存 RMap map = RedisUtils.getClient().getMap(name); //2-没有过期时间传2个参数 Cache cache = new RedissonCache(map, allowNullValues); // 3-事务提交 之后执行 if (transactionAware) { cache = new TransactionAwareCacheDecorator(cache); } //4-不存在就添加 Cache oldCache = instanceMap.putIfAbsent(name, cache); if (oldCache != null) { cache = oldCache; } return cache; } private Cache createMapCache(String name, CacheConfig config) { //1-获取缓存 RMapCache map = RedisUtils.getClient().getMapCache(name); //2-有过期时间传3个参数 ,config 里面有 ttl、maxIdleTime、maxSize Cache cache = new RedissonCache(map, config, allowNullValues); // 3-事务提交 之后执行 if (transactionAware) { cache = new TransactionAwareCacheDecorator(cache); } //4-不存在就添加 Cache oldCache = instanceMap.putIfAbsent(name, cache); if (oldCache != null) { cache = oldCache; } else { map.setMaxSize(config.getMaxSize()); } return cache; } //返回不可修改的集合 @Override public Collection getCacheNames() { return Collections.unmodifiableSet(configMap.keySet()); } }

3-@Cacheable

以下Cacheable几个属性分别演示了如何使用:(支持SPEL表达式)

  • cacheNames 
  • key 
  • sync
  • condition 
  • sync 
    /**
         * cacheNames: 指定名称 可以是数组
         *        key: 支持spel表达式,可以获取参数
         * @author syf
         * @date 2024/5/7 11:03
         * @param id
         * @param pageQuery
         * @return java.lang.String
         */
        @Cacheable(cacheNames = "cache1", key = "#id + '_cache' + #pageQuery.pageNum")
        @GetMapping("test1")
        public  String test1(String id, PageQuery pageQuery){
            return "ok";
        }
        /**
         *  condition :符合条件进行缓存
         *        #id != null :表示传入 id不为空才会缓存进入redis,id为空则不缓存
         * @author syf
         * @date 2024/5/7 11:03
         * @param id
         * @return java.lang.String
         */
        @Cacheable(cacheNames = "cache2", key = "#id + '_cache'" , condition = "#id != null")
        @GetMapping("test2")
        public  String test2(String id){
            return "ok";
        }
        /**
         *  unless 符合条件不缓存
         *        #result == null :接口返回结果为空则不进行缓存
         * @author syf
         * @date 2024/5/7 11:03
         * @param id
         * @return java.lang.String
         */
        @Cacheable(cacheNames = "cache3", key = "#id + '_cache'" , unless = "#result == null")
        @GetMapping("test3")
        public  String test3(String id){
            return null;
        }
        /**
         *  sync = true
         *       同步阻塞:同时进来多个请求, 等待前面调用返回并缓存,才能回进入下个请求
         *       作用:防止缓存积存
         * @author syf
         * @date 2024/5/7 11:03
         * @param id
         * @return java.lang.String
         */
        @Cacheable(cacheNames = "cache4", key = "#id + '_cache'", sync = true)
        @GetMapping("test4")
        public  String test4(String id){
            return null;
        }
        /**
         *  获取类中参数
         *         比较繁琐,一般是在实现类中传递登录参数,用spel获取
         * @author syf
         * @date 2024/5/7 11:03
         * @return java.lang.String
         */
        @Cacheable(cacheNames = "cache5", key = "T(com.ruoyi.common.helper.LoginHelper).getLoginUser().getLoginId()")
        @GetMapping("test5")
        public  String test5(){
            LoginUser loginUser = LoginHelper.getLoginUser();
            return "ok";
        }
    4- @CachePut

    缓存更新

    执行该方法,并将执行结果以键值对的形式存入指定的缓存中。

      /**
         *  结果不为空进行更新
         * @author syf
         * @date 2024/5/7 11:03
         * @param id
         * @return java.lang.String
         */
        @CachePut(cacheNames = "cache2", key = "#id + '_cache'" , condition = "#result != null")
        @GetMapping("test2")
        public  String test2(String id){
            boolean flag = doUpdate();
            return flag  ? "ok" : null;
        }
    5- @CacheEvict

    缓存删除

    执行该方法,并将缓存中结果删除。

    allEntries  删除所有cacheNames = "cache4",下面缓存
    beforeInvocation  默认false,方法执行之后有异常不执行。true:方法执行之后有异常,也执行
      /**
         *  删除缓存
         * @author syf
         * @date 2024/5/7 11:03
         * @param id
         * @return java.lang.String
         */
        @CacheEvict(cacheNames = "cache4", key = "#id + '_cache'")
        @GetMapping("test7")
        public  String test7(String id){
            boolean flag = doDelete();
            return flag  ? "ok" : null;
        }
      /**
         *  删除所有缓存
         * @author syf
         * @date 2024/5/7 11:03
         * @param id
         * @return java.lang.String
         */
        @CacheEvict(cacheNames = "cache4", allEntries = true)
        @GetMapping("test8")
        public  String test8(String id){
            return null;
        }
       /**
         *  beforeInvocation 无论是否有异常都执行操作
         * @author syf
         * @date 2024/5/7 11:03
         * @param id
         * @return java.lang.String
         */
        @CacheEvict(cacheNames = "cache4", beforeInvocation = true)
        @GetMapping("test9")
        public  String test9(String id){
            return null;
        }
     6-@Caching:

    指定多个Spring Cache相关的注解

    三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict。

        @Caching(
                cacheable = {@Cacheable(value = "uer1",key = "#userName")},
                put = {@CachePut(value = "uer1", key = "#result.id"),
                        @CachePut(value = "uer1", key = "#result.age")
                }
        )
        public User getStuByStr(String userName) {
          
            List users= listMapper.selectByList(studentExample);
            return Optional.ofNullable(users).orElse(null).get(0);
        }

    四、若依框架中缓存使用(自定义SpringCache 源码解读)

    CacheNames 常量设置规则:
    key 格式为: cacheNames#ttl#maxIdleTime#maxSize
    /**
     * 缓存组名称常量
     * 

    * key 格式为 cacheNames#ttl#maxIdleTime#maxSize *

    * ttl 过期时间 如果设置为0则不过期 默认为0 * maxIdleTime 最大空闲时间 根据LRU算法清理空闲数据 如果设置为0则不检测 默认为0 (超过maxIdleTime LRU算法自动清理) * maxSize 组最大长度 根据LRU算法清理溢出数据 如果设置为0则无限长 默认为0 *

    * 例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500 * * @author Lion Li */

    PlusSpringCacheManager 实现 CacheManager 接口,重写 getCache 方法,

    就是配置了  :ttl、maxIdleTime、maxSize  三个参数吗,如下:

    @Override
        public Cache getCache(String name) {
            // 重写 cacheName 支持多参数
            /**
             * 1-演示案例   :  String DEMO_CACHE = "demo:cache#60s#10m#20";
             */
            String[] array = StringUtils.delimitedListToStringArray(name, "#");
            name = array[0];
            Cache cache = instanceMap.get(name);
            if (cache != null) {
                return cache;
            }
            //2- dynamic=false 不会动态生成
            if (!dynamic) {
                //return cache;
                return null;
            }
            CacheConfig config = configMap.get(name);
            if (config == null) {
                config = createDefaultConfig();
                configMap.put(name, config);
            }
            
            //setTTL
            if (array.length > 1) {
                config.setTTL(DurationStyle.detectAndParse(array[1]).toMillis());
            }
            //setMaxIdleTime
            if (array.length > 2) {
                config.setMaxIdleTime(DurationStyle.detectAndParse(array[2]).toMillis());
            }
            //setMaxSize
            if (array.length > 3) {
                config.setMaxSize(Integer.parseInt(array[3]));
            }
            if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) {
                return createMap(name, config);
            }
            return createMapCache(name, config);
        }

     重点:下面就是PlusSpringCacheManager ,操作缓存的地方

    上面调用了:createMap、createMapCache 2个方法对比:

    1-逻辑:

    createMapCache 多了个 setMaxSize判断,其他都一样

    else {
                map.setMaxSize(config.getMaxSize());
            }

    2- 返回类型 

    createMap 返回  RMap

    createMapCache 返回 RMapCache

    对比: RMapCache 继承了 RMap 多了对于ttl、maxIdleTime、maxSize 的配置

    相同 :都是基于redisson,缓存到redis

    private Cache createMap(String name, CacheConfig config) {
            //1-获取缓存
            RMap map = RedisUtils.getClient().getMap(name);
            //2-没有过期时间传2个参数
            Cache cache = new RedissonCache(map, allowNullValues);
            // 3-事务提交 之后执行
            if (transactionAware) {
                cache = new TransactionAwareCacheDecorator(cache);
            }
            //4-不存在就添加
            Cache oldCache = instanceMap.putIfAbsent(name, cache);
            if (oldCache != null) {
                cache = oldCache;
            }
            return cache;
        }
        private Cache createMapCache(String name, CacheConfig config) {
            //1-获取缓存
            RMapCache map = RedisUtils.getClient().getMapCache(name);
            //2-有过期时间传3个参数 ,config 里面有  ttl、maxIdleTime、maxSize
            Cache cache = new RedissonCache(map, config, allowNullValues);
            // 3-事务提交 之后执行
            if (transactionAware) {
                cache = new TransactionAwareCacheDecorator(cache);
            }
            //4-不存在就添加
            Cache oldCache = instanceMap.putIfAbsent(name, cache);
            if (oldCache != null) {
                cache = oldCache;
            } else {
                map.setMaxSize(config.getMaxSize());
            }
            return cache;
        }

     上面  TransactionAwareCacheDecorator:

    所执行的put操作,是在事务提交之后执行

     public void put(final Object key, @Nullable final Object value) {
            if (TransactionSynchronizationManager.isSynchronizationActive()) {
                TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                    public void afterCommit() {
                        TransactionAwareCacheDecorator.this.targetCache.put(key, value);
                    }
                });
            } else {
                this.targetCache.put(key, value);
            }
        }

     五、缓存工具类

    private static final CacheManager CACHE_MANAGER = SpringUtils.getBean(CacheManager.class);
    主要是获取 CacheManager 接口,提供对缓存CRUD操作 :
    
    public interface CacheManager {
        @Nullable
        Cache getCache(String name);
        Collection getCacheNames();
    }
     
     
    
    package com.ruoyi.common.utils.redis;
    import com.ruoyi.common.utils.spring.SpringUtils;
    import lombok.AccessLevel;
    import lombok.NoArgsConstructor;
    import org.redisson.api.RMap;
    import org.springframework.cache.Cache;
    import org.springframework.cache.CacheManager;
    import java.util.Set;
    /**
     * 缓存操作工具类 {@link }
     *
     * @author Michelle.Chung
     * @date 2022/8/13
     */
    @NoArgsConstructor(access = AccessLevel.PRIVATE)
    @SuppressWarnings(value = {"unchecked"})
    public class CacheUtils {
        private static final CacheManager CACHE_MANAGER = SpringUtils.getBean(CacheManager.class);
        /**
         * 获取缓存组内所有的KEY
         *
         * @param cacheNames 缓存组名称
         */
        public static Set keys(String cacheNames) {
            RMap rmap = (RMap) CACHE_MANAGER.getCache(cacheNames).getNativeCache();
            return rmap.keySet();
        }
        /**
         * 获取缓存值
         *
         * @param cacheNames 缓存组名称
         * @param key        缓存key
         */
        public static  T get(String cacheNames, Object key) {
            Cache.ValueWrapper wrapper = CACHE_MANAGER.getCache(cacheNames).get(key);
            return wrapper != null ? (T) wrapper.get() : null;
        }
        /**
         * 保存缓存值
         *
         * @param cacheNames 缓存组名称
         * @param key        缓存key
         * @param value      缓存值
         */
        public static void put(String cacheNames, Object key, Object value) {
            CACHE_MANAGER.getCache(cacheNames).put(key, value);
        }
        /**
         * 删除缓存值
         *
         * @param cacheNames 缓存组名称
         * @param key        缓存key
         */
        public static void evict(String cacheNames, Object key) {
            CACHE_MANAGER.getCache(cacheNames).evict(key);
        }
        /**
         * 清空缓存值
         *
         * @param cacheNames 缓存组名称
         */
        public static void clear(String cacheNames) {
            CACHE_MANAGER.getCache(cacheNames).clear();
        }
    }
    
微信扫一扫加客服

微信扫一扫加客服

点击启动AI问答
Draggable Icon