亲宝软件园·资讯

展开

SpringBoot Cache缓存概念讲解

wenxueliu@HW 人气:0

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等注解来完成缓存的透明化操作,相关功能如下。

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 CacheType {
    // 使用上下文中的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);
    }
    // 根据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()));
}

cacheManagerCustomizers方法初始化了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.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.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("spring.
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(it, 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 支持分布式锁。

加载全部内容

相关教程
猜你喜欢
用户评论