开发者

SpringBoot Cache缓存概念讲解

开发者 https://www.devze.com 2022-12-31 10:21 出处:网络 作者: wenxueliu@HW
目录Cache自动配置ImportSelectorGenericCacheConfigurationCacheManagerValidatorRedis CacheRedisCacheConfigurationCacheConditionRedisCacheManagerRedisCacheRedisCacheWriterSpring 3.1中引入了基于注解的Cach
目录
  • Cache自动配置
  • ImportSelector
  • GenericCacheConfiguration
  • CacheManagerValidator
  • Redis Cache
    • RedisCacheConfiguration
    • CacheCondition
    • RedisCacheManager
    • RedisCache
  • RedisCacheWriter

    Spring 3.1中引入了基于注解的Cache的支持,在spring-context包中定义了org.springframework.cache.CacheManager和org.springframework.cache.Cache接口,用来统一不同的缓存的技术。

    CacheManager是Spring提供的各种缓存技术管理的抽象接口,而Cache接口包含缓存的增加、删除、读取等常用操作。针对CacheManager,Spring又提供了多种实现,比如基于Collection来实现的SimpleCacheManager、基于ConcurrentHashMap实现的ConcurrentMapCacheManager、基于EhCache实现的EhCacheCacheManager和基于JCache标准实现的JCacheCacheManager等。

    Spring Cache提供了@CacheConfig、@Cacheable、@CachePut、@CacheEvict等注解来完成缓存的透明化操作,相关功能如下。

    • @CacheConfig:用于类上,缓存一些公共设置。
    • @Cacheable:用于方法上,根据方法的请求参数对结果进行缓存,下次读取时直接读取缓存内容。
    • @CachePut:用于方法上,能够根据方法的请求参数对其结果进行缓存,和@Cacheable不同的是,它每次都会触发真实方法的调用
    • @CacheEvict:用于方法上,清除该方法的缓存,用在类上清除整个类的方法的缓存。在了解了Spring Cache的基本作用的和定义之后,下面来看在Spring Boot中是如何对Cache进行自动配置的

    Cache自动配置

    在Spring Boot中,关于Cache的默认自动配置类只有CacheAutoConfiguration,主要用于缓存抽象的自动配置,当通过@EnableCaching启用缓存机制时,根据情况可创建CacheManager。对于缓存存储可以通过配置自动检测或明确指定。CacheAutoConfiguration同样在META-INF/spring.factories文件中配置注册。

    # Auto Configure

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

    org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\

    下面先来看CacheAutoConfiguration类的注解部分代码实现。

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(CacheManager.class)
    @ConditionalOnBean(CacheASPectSupport.class)
    @ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
    @EnableConfigurationProperties(CacheProperties.class)
    @AutoConfigureAfter({ CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class,
        HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class })
    @Import(CacheConfigurationImportSelector.class)
    public class CacheAutoConfiguration { 
    ...
    }
    

    1、@ConditionalOnClass 指定需要在classpath下存在CacheManager类。

    2、@ConditionalOnBean 指定需要存在CacheAspectSupport的Bean时才生效,换句话说,就是需要在使用了@EnableCaching时才有效。这是因为该注解隐式的导致了CacheInterceptor对应的Bean的初始化,而CacheInterceptor为CacheAspectSupport的子类。

    3、@ConditionalOnMissingBean指定名称为cacheResolver的CacheManager对象不存在时生效。@EnableConfigurationProperties加载缓存的CacheProperties配置项,配置前缀为spring.cache。

    4、@EnableConfigurationProperties加载缓存的CacheProperties配置项,配置前缀为spring.cache。@AutoConfigureAfter指定该自动配置必须在缓存数据基础组件自动配置之后进行,这里包括Couchbase、Hazelcast、HibernateJpa和Redis的自动配置。

    5、@Import导入CacheConfigurationImportSelector,其实是导入符合条件的Spring Cache使用的各类基础缓存框架(或组件)的配置。

    ImportSelector

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

    导入类的获取是通过实现ImportSelector接口来完成的,具体获取步骤位于selectImports方法中。该方法中,首先获取枚举类CacheType中定义的缓存类型数据,CacheType中定义支持的缓存类型如下。

    // 支持的缓存类型(按照优先级定义)
    public enum CacheTyppythone {
        // 使用上下文中的Cache Bean进行通用缓存
        GENERIC,
        // JCache(jsR-107)支持的缓存
        JCACHE,
        // EhCache支持的缓存
        EHCACHE,
        // Hazelcast支持的缓存
        HAZELCAST,
        // Infinispan支持的缓存
        INFINISPAN,
        // Couchbase支持的缓存
        COUCHBASE,
        // Redis支持的缓存
        REDIS,
        // Caffeine支持的缓存
        CAFFEINE,
        // 内存基本的简单缓存
        SIMPLE,
        // 不支持缓存
        NONE
    }
    

    枚举类CacheType中定义了以上支持的缓存类型,而且上面的缓存类型默认是按照优先级从前到后的顺序排列的。selectImports方法中,当获取CacheType中定义的缓存类型数组之后,遍历该数组并通过CacheConfigurations的getConfigurationClass方法获得每种类型缓存对应的自动配置类(注解@Configuration的类)。CacheConfigurations相关代码如下。

    final class CacheConfigurations {
        private static final Map<CacheType, Class<?>> MAPPINGS;
        // 定义CacheType与@Configuration之间的对应关系
        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);
        }
       www.devze.com // 根据CacheType类型获得对应的@Configuration类
        static String getConfigurationClass(CacheType cacheType) {
            Class<?> configurationClass = MAPPINGS.get(cacheType);
            Assert.state(configurationClass != null, () -> "Unknown cache type " + cacheType);
            return configurationClass.getName();
        }
        ...
    }

    我们会发现通过@Import注解,CacheAutoConfiguration导入了CacheType中定义的所有类型的自动配置,也就是Spring Boot目前支持的缓存类型。而具体会自动配置哪种类型的缓存,还需要看导入的自动配置类里面的生效条件。

    GenericCacheConfiguration

    // 实例化CacheManagerCustomizers
    @Bean
    @ConditionalOnMissingBean
    public CacheManagerCustomizers cacheManagerCustomizers(
            ObjectProvider<CacheManagerCustomizer<?>> customizers) {
        return new CacheManagerCustomizers(
                customizers.orderedStream().collect(Collectors.toList()));
    }
    

    cacheManagerCusto开发者_Go开发mizers方法初始化了CacheManagerCustomizers对象的Bean,主要是将容器中存在的一个或多个CacheManagerCustomizer的Bean组件包装为CacheManager-Customizers,并将Bean注入容器。

    CacheManagerValidator

    cacheAutoConfigurationValidator方法初始化了CacheManagerValidator的Bean,该Bean用于确保容器中存在一个CacheManager对象,以达到缓存机制可以继续被配置和使用的目的,同时该Bean也用来提供有意义的异常声明。

    // 实例化CacheManagerValidator
    @Bean
    public CacheManagerValidator cacheAutoConfigurationValidator(CacheProperties-
    cacheProperties,
            ObjectProvider<CacheManager> cacheManager) {
        return new CacheManagerValidator(cacheProperties, cacheManager);
    }
    // CacheManagerValidator的具体定义,用于检查并抛出有意义的异常
    static class CacheManagerValidator implements InitializingBean {
        private final CacheProperties cacheProperties;
        private final ObjectProvider<CacheManager> cacheManager;
        CacheManagerValidator(CacheProperties cacheProperties, ObjectProvider<Cache-
    Manager> cacheManager) {
            this.cacheProperties = cacheProperties;
            this.cacheManager = cacheManager;
        }
        @Override
        public void afterPropertiesSet() {
            Assert.notNull(this.cacheManager.getIfAvailable(),
                    () -> "No cache manager could be auto-configured, check your configuration (caching " + "type is '"  + this.cacheProperties.getType() + "')");
        }
    }
    

    Redis Cache

    RedisCacheConfiguration

    我们以RedisCacheConfiguration为例进行分析。源码如下

    @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) {
    		return redisCacheConfiguration.getIfAvailable(() -> createConfiguration(cacheProperties, classLoader));
    	}
    	private org.springframework.data.redis.cache.RedisCacheConfiguration createConfiguration(
    			CacheProperties cacheProperties, ClassLoader classLoader) {
    		Redis redisProperties = cacheProperties.getRedis();
    		org.springframeworkandroid.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.prefixCacheNameWith(redisProperties.getKeyPrefix());
    		}
    		if (!redisProperties.isCacheNullValues()) {
    			config = config.disableCachingNullValues();
    		}
    		if (!redisProperties.isUseKeyPrefix()) {
    			config = config.disableKeyPrefix();
    		}
    		return config;
    	}
    }

    @ConditionalOnClass:当 RedisConnectionFactory 在存在时

    @AutoConfigureAfter:在 RedisAutoConfiguration 之后加载

    @ConditionalOnBean:RedisConnectionFactory 实例化之后

    @ConditionalOnMissingBean:当 CacheManager 的 Bean 不存在时进行实例化操作

    @Conditional:满足 CacheCondition 条件时,进行实例化操作

    CacheCondition

    class CacheCondition extends SpringBootCondition {
        @Override
        public ConditionOutcome getMatchOutcome(ConditionContext context, Annotated-
    TypeMetadata metadata) {
            String sourceClass = "";
            if (metadata instanceof ClassMetadata) {
                sourceClass = ((ClassMetadata) metadata).getClassName();
            }
            ConditionMessage.Builder message = ConditionMessage.forCondition("Cache", 
    sourceClass);
            Environment environment = context.getEnvironment();
            try {
                // 创建指定环境的Binder,然后绑定属性到对象上
                BindResult<CacheType> specified = Binder.get(environment).bind("spri编程ng.
    cache.type", CacheType.class);
                // 如果未绑定,则返回匹配
                if (!specified.isBound()) {
                    return ConditionOutcome.match(message.because("automatic cache type"));
                }
                // 获取所需的缓存类型
                CacheType required = CacheConfigurations.getType(((AnnotationMetadata) 
    metadata).getClassName());
                // 如果已绑定,并且绑定的类型与所需的缓存类型相同,则返回匹配
                if (specified.get() == required) {
                    return ConditionOutcome.match(message.because(specified.get() + " cache type"));
                }
            } catch (BindException ex) {
            }
            // 其他情况则返回不匹配
            return ConditionOutcome.noMatch(message.because("unknown cache type"));
        }
    }

    CacheCondition的核心逻辑就是首先通过Binder进行指定属性和类的绑定,然后通过绑定结果(BindResult)进行判断:如果判断结果是未绑定,则直接返回条件匹配;否则,判断绑定的缓存类型与所需的缓存类型是否相等,如果相等则返回条件匹配;其他情况则返回条件不匹配。

    RedisCacheManager

    1、管理多个 RedisCache

    2、每个 RedisCache 包含一个 name

    3、每个 RedisCache 可以包含一个 RedisCacheConfiguration(可以有默认配置)

    4、支持配置是否动态增加 Cache

    public abstract class AbstractCacheManager implements CacheManager, InitializingBean {
    	private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
    	private volatile Set<String> cacheNames = Collections.emptySet();
    }
    public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {
        private final RedisCacheWriter cacheWriter;
        private final RedisCacheConfiguration defaultCacheConfig;
        private final Map<String, RedisCacheConfiguration> initialCacheConfiguration;
        private final boolean allowInFlightcacheCreation;
        protected Collection<RedisCache> loadCaches() {
            List<RedisCache> caches = new LinkedList();
            Iterator var2 = this.initialCacheConfiguration.entrySet().iterator();
            while(var2.hasNext()) {
                Entry<String, RedisCacheConfiguration> entry = (Entry)var2.next();
                caches.add(this.createRedisCache((String)entry.getKey(), (RedisCacheConfiguration)entry.getValue()));
            }
            return caches;
        }
        protected RedisCache getMissingCache(String name) {
            return this.allowInFlightCacheCreation ? this.createRedisCache(name, this.defaultCacheConfig) : null;
        }
        public Map<String, RedisCacheConfiguration> getCacheConfigurations() {
            Map<String, RedisCacheConfiguration> configurationMap = new HashMap(this.getCacheNames().size());
            this.getCacheNames().forEach((it) -> {
                RedisCache cache = (RedisCache)RedisCache.class.cast(this.lookupCache(it));
                configurationMap.put(itvHrKLxSFx, cache != null ? cache.getCacheConfiguration() : null);
            });
            return Collections.unmodifiableMap(configurationMap);
        }
        protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
            return new RedisCache(name, this.cacheWriter, cacheConfig != null ? cacheConfig : this.defaultCacheConfig);
        }
    }

    RedisCache

    public class RedisCache extends AbstractValueAdaptingCache {
        private static final byte[] BINARY_NULL_VALUE;
        private final String name;
        private final RedisCacheWriter cacheWriter;
        private final RedisCacheConfiguration cacheConfig;
        private final ConversionService conversionService;
    }
    

    RedisCache 的特点

    1、key 支持非 String 类型,比如 Map 和 Collection

    2、RedisCacheWriter 是更底层的实现,RedisCache 对 RedisCacheWriter 进行封装。

    3、通过 ConversionService 对 key 进行转换

    RedisCacheWriter

    通过 RedisConnectionFactory 取一个 RedisConnection,然后执行命令。putIfAbsent 支持分布式锁。

    到此这篇关于SpringBoot Cache缓存概念讲解的文章就介绍到这了,更多相关SpringBoot Cache内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

    0

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    关注公众号