原创

SpringCache

温馨提示:
本文最后更新于 2022年10月27日,已超过 918 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

官网地址:https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#cache

一 理解缓存抽象

1. 官网文档

与 Spring Framework 中的其他服务一样,缓存服务是一种抽象(不是缓存实现),需要使用实际存储来存储缓存数据——也就是说,抽象使您不必编写缓存逻辑,但确实不提供实际的数据存储。这种抽象是由 org.springframework.cache.Cacheorg.springframework.cache.CacheManager接口实现的。

2. 注意

要使用缓存抽象,您需要注意两个方面:

  • 缓存声明:标识需要缓存的方法及其策略。
  • 缓存配置:存储数据和从中读取数据的后备缓存。

二 使用SpringCache

1. 加入依赖

  <!-- SpringCache依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

2. 在启动类上开启缓存

@EnableCaching

3. 在需要使用注解的方法上添加对应注解

@Cacheable: Triggers cache population.   添加缓存

@CacheEvict: Triggers cache eviction.  移除缓存

@CachePut: Updates the cache without interfering with the method execution.  在不干扰方法执行的情况下更新缓存

@Caching: Regroups multiple cache operations to be applied on a method.  重新组合多个缓存操作以应用于一个方法

@CacheConfig: Shares some common cache-related settings at class-level.  在类级别共享一些常见的缓存相关设置。

3. 自定义缓存配置

  1. 找到cache的自动化配置类 CacheAutoConfiguration

  2. 该类会自动导入配置对应相关的组件

        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;
            }
    
        }
    

    CacheConfigurations类包含的配置

    static {
            Map<CacheType, Class<?>> mappings = new EnumMap<>(CacheType.class);
            mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class);
            mappings.put(CacheType.EHCACHE, EhCacheCacheConfiguration.class);
            mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class);
            mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class);
            mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class);
            mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class);
            mappings.put(CacheType.REDIS, RedisCacheConfiguration.class);
            mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class);
            mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class);
            mappings.put(CacheType.NONE, NoOpCacheConfiguration.class);
            MAPPINGS = Collections.unmodifiableMap(mappings);
        }
    

    我们在配置文件中使用的是redis,所以此处会自动导入redis的自动配置类RedisCacheConfiguration

  3. RedisCacheConfiguration配置类中自动配置了redis的缓存管理器

        @Bean
        RedisCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers,
                ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
                ObjectProvider<RedisCacheManagerBuilderCustomizer> redisCacheManagerBuilderCustomizers,
                RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
            RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(
                    determineConfiguration(cacheProperties, redisCacheConfiguration, resourceLoader.getClassLoader()));
            List<String> cacheNames = cacheProperties.getCacheNames();
            if (!cacheNames.isEmpty()) {
                builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
            }
            redisCacheManagerBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
            return cacheManagerCustomizers.customize(builder.build());
        }
    
    1. redisCache配置都在对应的org.springframework.data.redis.cache.RedisCacheConfiguration配置类中

          //RedisCacheConfiguration 类
      
          private final Duration ttl;
          private final boolean cacheNullValues;
          private final CacheKeyPrefix keyPrefix;
          private final boolean usePrefix;
      
          private final SerializationPair<String> keySerializationPair;
          private final SerializationPair<Object> valueSerializationPair;
      
          private final ConversionService conversionService;
      
          @SuppressWarnings("unchecked")
          private RedisCacheConfiguration(Duration ttl, Boolean cacheNullValues, Boolean usePrefix, CacheKeyPrefix keyPrefix,
                  SerializationPair<String> keySerializationPair, SerializationPair<?> valueSerializationPair,
                  ConversionService conversionService) {
      
              this.ttl = ttl;
              this.cacheNullValues = cacheNullValues;
              this.usePrefix = usePrefix;
              this.keyPrefix = keyPrefix;
              this.keySerializationPair = keySerializationPair;
              this.valueSerializationPair = (SerializationPair<Object>) valueSerializationPair;
              this.conversionService = conversionService;
          }
      
      1. 配置源码分析

        /*
         * Copyright 2012-2019 the original author or authors.
         *
         * 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
         *
         *      https://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 org.springframework.boot.autoconfigure.cache;
        
        import java.util.LinkedHashSet;
        import java.util.List;
        
        import org.springframework.beans.factory.ObjectProvider;
        import org.springframework.boot.autoconfigure.AutoConfigureAfter;
        import org.springframework.boot.autoconfigure.cache.CacheProperties.Redis;
        import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
        import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
        import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
        import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
        import org.springframework.cache.CacheManager;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Conditional;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.core.io.ResourceLoader;
        import org.springframework.data.redis.cache.RedisCacheManager;
        import org.springframework.data.redis.cache.RedisCacheManager.RedisCacheManagerBuilder;
        import org.springframework.data.redis.connection.RedisConnectionFactory;
        import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
        import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;
        
        /**
         * Redis cache configuration.
         *
         * @author Stephane Nicoll
         * @author Mark Paluch
         * @author Ryon Day
         */
        @Configuration(proxyBeanMethods = false)
        @ConditionalOnClass(RedisConnectionFactory.class)
        @AutoConfigureAfter(RedisAutoConfiguration.class)
        @ConditionalOnBean(RedisConnectionFactory.class)
        @ConditionalOnMissingBean(CacheManager.class)
        @Conditional(CacheCondition.class)
        class RedisCacheConfiguration {
        
            @Bean
            RedisCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers,
                    ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
                    ObjectProvider<RedisCacheManagerBuilderCustomizer> redisCacheManagerBuilderCustomizers,
                    RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
                //创建缓存管理器
                RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(
                        //获取配置
                        determineConfiguration(cacheProperties, redisCacheConfiguration, resourceLoader.getClassLoader()));
                List<String> cacheNames = cacheProperties.getCacheNames();
                if (!cacheNames.isEmpty()) {
                    builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
                }
                redisCacheManagerBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
                return cacheManagerCustomizers.customize(builder.build());
            }
            //获取对应的配置
            private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration(
                    CacheProperties cacheProperties,
                    ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
                    ClassLoader classLoader) {
                //    1.调用createConfiguration方法,返回默认的缓存管理器
                //  2. redisCacheConfiguration.getIfAvailable...  见下
                return redisCacheConfiguration.getIfAvailable(() -> createConfiguration(cacheProperties, classLoader));
            }
        
            private org.springframework.data.redis.cache.RedisCacheConfiguration createConfiguration(
                    CacheProperties cacheProperties, ClassLoader classLoader) {
                Redis redisProperties = cacheProperties.getRedis();
                org.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration
                        .defaultCacheConfig();
                config = config.serializeValuesWith(
                        SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
                if (redisProperties.getTimeToLive() != null) {
                    config = config.entryTtl(redisProperties.getTimeToLive());
                }
                if (redisProperties.getKeyPrefix() != null) {
                    config = config.prefixKeysWith(redisProperties.getKeyPrefix());
                }
                if (!redisProperties.isCacheNullValues()) {
                    config = config.disableCachingNullValues();
                }
                if (!redisProperties.isUseKeyPrefix()) {
                    config = config.disableKeyPrefix();
                }
                return config;
            }
        
        }
        

        5.1 redisCacheConfiguration.getIfAvailable方法实现类ClientFactoryObjectProvider

            @Override
            public T getIfAvailable(Supplier<T> defaultSupplier) throws BeansException {
                return delegate().getIfAvailable(defaultSupplier);
            }
        
                private ObjectProvider<T> delegate() {
                if (this.provider == null) {
                    this.provider = this.clientFactory.getProvider(this.name, this.type);
                }
                return this.provider;
            }
        

        delegate().getIfAvailable(defaultSupplier) 在容器中通过对象查询bean。如果有,则返回容器中的对象,如果没有就返回当前对象。(通过生成的默认缓存配置去容器中查找是否有相同的Bean(是否已经存在),如果有则返回对应的Bean,如果没有则返回当前配置类)

        1. 对此,我们只需要在配置文件中新增对应的Bean RedisCacheConfiguration即可

          ```java
          import org.springframework.boot.autoconfigure.cache.CacheProperties;
          import org.springframework.boot.context.properties.EnableConfigurationProperties;
          import org.springframework.cache.annotation.EnableCaching;
          import org.springframework.context.annotation.Bean;
          import org.springframework.context.annotation.Configuration;
          import org.springframework.data.redis.cache.RedisCacheConfiguration;
          import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
          import org.springframework.data.redis.serializer.RedisSerializationContext;
          import org.springframework.data.redis.serializer.StringRedisSerializer;

        @Configuration
        @EnableCaching
        @EnableConfigurationProperties(CacheProperties.class)//使CacheProperties配置文件生效,可以在容器中直接使用
        public class MyRedisConfigure  {

            //1. CacheProperties可以使用自动注解使用
        //    @Autowired
        //    CacheProperties cacheProperties;

            /**
             * remark:@ConfigurationProperties(prefix = "spring.cache")
             * public class CacheProperties
             *  配置文件中的cache配置在这个类生效,使用自定义配置后会失效
                2.CacheProperties直接在方法上使用
             */
            @Bean
            RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties){

                //配置key和val的序列化方式
                RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
                config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class)));
                config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));

                //获取cache的配置文件配置,使其生效
                CacheProperties.Redis redisProperties = cacheProperties.getRedis();
                if (redisProperties.getTimeToLive() != null) {
                    config = config.entryTtl(redisProperties.getTimeToLive());
                }
                if (redisProperties.getKeyPrefix() != null) {
                    config = config.prefixKeysWith(redisProperties.getKeyPrefix());
                }
                if (!redisProperties.isCacheNullValues()) {
                    config = config.disableCachingNullValues();
                }
                if (!redisProperties.isUseKeyPrefix()) {
                    config = config.disableKeyPrefix();
                }
                return config;
            }


        }
        ```

4. 缓存注解使用方式

https://docs.spring.io/spring-framework/docs/5.2.19.RELEASE/spring-framework-reference/integration.html#cache

     执行时清除category组下 key为getOneLevelCategory的缓存
     @CacheEvict(value = {"category"},key = "'getOneLevelCategory'") 
     执行时会清除所有category组下的缓存
     @CacheEvict(value = {"category"},allEntries = true) 
     先清除category组下 key为getOneLevelCategory的缓存 再向category组下缓存key为temp的值
     @Caching(cacheable = {@Cacheable(value = {"category"},key = "'temp'")},
        evict = { @CacheEvict(value = {"category"},key = "'getOneLevelCategory'")}
        )  
    先清除category组下 key为getOneLevelCategory的缓存。再缓存
    @CachePut(value = {"category"},key = "'getOneLevelCategory'")
正文到此结束