zoukankan      html  css  js  c++  java
  • 关于SpringCache的一些认识

    关于SpringCache的一些认识

    项目中用到了redis,同时在一些高频的查询方法使用redis作为缓存。但是因为没使用过SpringCache,当时看着网上的教程做了配置后,确实缓存生效了,就没有再管它。

    突然项目经理跟我说redis中的缓存数据没有设置过期时间。看了下ttl都是-1,确实有问题。

    为了更好的说清楚过程,下面就从头到位按顺序记录一下整个SpringCache的使用过程。而我之前又没有使用过SpringCache,所以会有很多低级的错误。如果你是对SpringCache比较熟悉的大佬,这文章应该不用往下看了。

    1. 网上教程

    首先记录一下我找到的一些教程。

    基本如下:

    1. 最简单的,引入spring-boot-starter-data-redis,启动类上增加@EnableCaching注解。在需要缓存的方法上使用@Cacheable注解。这里说一下,Cacheable注解的cacheNames必须配,否则报错。

    2. 复杂一些,在上面的基础上,增加对redis的配置,如CacheManager、key,value的序列化规则等等。

    示例代码:

    import org.springframework.cache.CacheManager;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    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.GenericJackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializationContext;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    import java.time.Duration;
    
    @Configuration
    public class RedisConfig {
        /**
         * 配置redis缓存管理器
         *
         * @param redisConnectionFactory Redis连接工厂
         * @return 缓存管理器
         */
        @Bean
        public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
            //通过 Config 对象即可对缓存进行自定义配置
            RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                    // 禁止缓存 null 值
                    .disableCachingNullValues()
                    // 设置 key 序列化
                    .serializeKeysWith(keyPair())
                    // 设置 value 序列化
                    .serializeValuesWith(valuePair())
                    // 设置缓存前缀
                    .prefixCacheNameWith("cache:scrd:")
                    // 设置过期时间
                    .entryTtl(Duration.ofMinutes(30L));
    
            // 返回 Redis 缓存管理器
           return RedisCacheManager.builder(redisConnectionFactory)
                   .withCacheConfiguration("redis-test", cacheConfig)
                   .build();
        }
    
    
        /**
         * 配置键序列化
         *
         * @return StringRedisSerializer
         */
        private RedisSerializationContext.SerializationPair<String> keyPair() {
            return RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer());
        }
    
        /**
         * 配置值序列化,使用 GenericJackson2JsonRedisSerializer 替换默认序列化
         *
         * @return GenericJackson2JsonRedisSerializer
         */
        private RedisSerializationContext.SerializationPair<Object> valuePair() {
            return RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer());
        }
    }
    

    2. 项目背景

    我们的项目中会调用第三方的系统查询产品数据,这些数据可以说就是不会改变的。我们当时决定将数据缓存并且过期时间设置为12个小时,其他的一般缓存默认过期时间为30分钟。

    因为找到的SpringCache教程基本就是上面的使用方法,基本没有介绍如何配置不同的ttl,所以只能自己寻找思路了。

    通过上面的教程,我猜想:CacheManager可以配置cacheName和RedisCacheConfiguration,那么我配置了2个CacheManager,分别设置不同的cacheName和RedisCacheConfiguration,是不是可以支持不同的过期时间。然后在需要的方法上指定对应配置的cacheNames。
    比如下面的代码就是使用@Cacheable(cacheNames = "product")

    按照这个思路我写了这样的代码:

    /**
        * 配置默认redis缓存管理器
        *
        * @param redisConnectionFactory Redis连接工厂
        * @return 缓存管理器
        */
    @Bean("defaultCacheManger")
    public CacheManager defaultCacheManger(RedisConnectionFactory redisConnectionFactory) {
        //通过 Config 对象即可对缓存进行自定义配置
        RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                // 禁止缓存 null 值
                .disableCachingNullValues()
                // 设置 key 序列化
                .serializeKeysWith(keyPair())
                // 设置 value 序列化
                .serializeValuesWith(valuePair())
                // 设置缓存前缀
                .prefixCacheNameWith("cache:scrd:default:")
                // 设置过期时间
                .entryTtl(Duration.ofMinutes(10L));
    
        // 返回 Redis 缓存管理器
        return RedisCacheManager.builder(redisConnectionFactory)
                .withCacheConfiguration("redis-test", cacheConfig)
                .build();
    }
    
    /**
        * 配置产品信息redis缓存管理器
        *
        * @param redisConnectionFactory Redis连接工厂
        * @return 缓存管理器
        */
    @Bean("productCacheManager")
    public CacheManager productCacheManager(RedisConnectionFactory redisConnectionFactory) {
        //通过 Config 对象即可对缓存进行自定义配置
        RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                // 禁止缓存 null 值
                .disableCachingNullValues()
                // 设置 key 序列化
                .serializeKeysWith(keyPair())
                // 设置 value 序列化
                .serializeValuesWith(valuePair())
                // 设置缓存前缀
                .prefixCacheNameWith("cache:scrd:product:")
                // 设置过期时间
                .entryTtl(Duration.ofMinutes(720L));
    
        // 返回 Redis 缓存管理器
        return RedisCacheManager.builder(redisConnectionFactory)
                .withCacheConfiguration("product", cacheConfig)
                .build();
    }
    

    很遗憾,启动报错。

    java.lang.IllegalStateException: No CacheResolver specified, and no unique bean of type CacheManager found. Mark one as primary or declare a specific CacheManager to use.
    

    大意是:找到多个CacheManager,不知道注入哪一个,需要使用@Primary指定一下默认使用哪一个。

    果断给defaultCacheManger加上@Primary注解,正常启动。测试调用后,确实可以在redis看到数据。

    3. 问题分析

    上面看到的redis中数据其实有问题的,剧透一下:redis中key按照配置应该是以“cache:scrd:product:”开头,但是实际redis中缓存相关的key都是以“product开头”。因为“缓存”功能一直正常,所以我以为这是前缀的配置失效了。直到项目经理告诉我ttl有问题,我才会想起这个不一样的地方。

    通过看源码,打断点,发现SpringCache在写数据进redis时,并没有使用我在productCacheManager中的相关配置,不仅是ttl的配置没有了,前缀的也没有了。

    scrd-0.png

    这让我不禁怀疑@Cacheable注解的cacheNames参数是不是没办法关联到productCacheManager。查看@Cacheable注解的源码,发现可以直接配置cacheManager。然后就试了下直接配置cacheManager = "productCacheManager"

    重启测试,发现这次可以正常使用我在productCacheManager中的配置了。

    scrd-1.png

    4. 优化总结

    这里重申一下:配置了cacheManager后,cacheNames还是要配置。因为我不配置就报错了。这个报错让我想到了,我一开始的思路或许是错误的。

    从上面的过程来看,每个缓存必须指定对应的cacheName,但是可以不指定cacheManager。所以cacheManager应该可以由框架默认注入,而默认的cacheManager就是加了@Primary的那个Bean。这也是为什么手动指定cacheManager后可以正常。

    再次观察CacheManager的配置代码。

    查看配置代码中RedisCacheConfiguration是在withCacheConfiguration方法中使用的,参数还有cacheName。一开始我以为cacheName是给CacheManager设置的属性。查看源码发现该方法将RedisCacheConfiguration存在一个key为cacheName的Map中,所以RedisCacheConfiguration是和cacheName对应的。一个CacheManager可以有多个cacheName和RedisCacheConfiguration的组合。也就是说withCacheConfiguration方法可以多次调用。从而实现不同缓存,不同配置。使用的地方,通过cacheName来获取对应的配置。

    withCacheConfiguration源码:

    /**
     * @param cacheName
     * @param cacheConfiguration
     * @return this {@link RedisCacheManagerBuilder}.
     * @since 2.2
     */
    public RedisCacheManagerBuilder withCacheConfiguration(String cacheName,
            RedisCacheConfiguration cacheConfiguration) {
    
        Assert.notNull(cacheName, "CacheName must not be null!");
        Assert.notNull(cacheConfiguration, "CacheConfiguration must not be null!");
    
        this.initialCaches.put(cacheName, cacheConfiguration);
        return this;
    }
    

    总结一下:SpringCache中一般只需要配置一个CacheManager,通过配置cacheName来指定特殊配置。除非是对接了多种缓存实现,比如即用redis,也用Ehcache。这个时候,就需要配置多个CacheManager,在使用的地方指定cacheManager。

    调整后的代码:

    /**
     * 配置redis缓存管理器
     *
     * @param redisConnectionFactory Redis连接工厂
     * @return 缓存管理器
     */
    @Bean
    public CacheManager cacheManger(RedisConnectionFactory redisConnectionFactory) {
        //通过 Config 对象即可对缓存进行自定义配置
        RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                // 禁止缓存 null 值
                .disableCachingNullValues()
                // 设置 key 序列化
                .serializeKeysWith(keyPair())
                // 设置 value 序列化
                .serializeValuesWith(valuePair())
                // 设置缓存前缀
                .prefixCacheNameWith("cache:scrd:")
                // 设置过期时间
                .entryTtl(Duration.ofMinutes(10L));
        RedisCacheManager.RedisCacheManagerBuilder cacheManagerBuilder = RedisCacheManager.builder(redisConnectionFactory);
        //设置默认的配置,当设置测cacheName没有配置的时候,使用默认配置
        cacheManagerBuilder.cacheDefaults(cacheConfig);
    
        //entryTtl方法不是在原有对象中修改配置
        //而是会返回一个新的RedisCacheConfiguration对象,需要用cacheConfig接收。否则设置无效。
        cacheConfig = cacheConfig.entryTtl(Duration.ofMinutes(720L));
    
        //设置cacheName对呀的配置
        cacheManagerBuilder.withCacheConfiguration("product", cacheConfig);
    
        // 返回 Redis 缓存管理器
        return cacheManagerBuilder.build();
    }
    
  • 相关阅读:
    API函数
    平台调用教程
    查看网页源文件方法
    网页端商品链接转换为手机端链接的部分网址规则
    中文分词消除歧义简单思想
    java 链接数据库时的配置代码
    手机参数更新语句根据Id 可以得到某手机的各种参数
    中文分词—基于Lucene的分词器—支持中英文混合词
    修改Imdict做自己的分词器
    制作可输入的下拉框
  • 原文地址:https://www.cnblogs.com/jimmyfan/p/14412363.html
Copyright © 2011-2022 走看看