缓存作为日常开发中必备的环节,主流的缓存中间件Redis、Guava、Mongo等等可以很好的缓解服务器压力,提高系统响应。
为什么要引入SpringCache管理缓存
现在技术栈以Spring为核心, SpringCache的作为缓存的治理,可以很好的引入到项目当中,不会对现有的架构体系造成冲突,方便维护和管理。
- 业务逻辑:Spring-Cache的引入,将缓存逻辑和策略 和业务代码进行解藕,做到分而治之的效果。
- 缓存管理:Spring-Cache 内库中包含操作Redis、缓存管理器、承接Spring上下文、动态代理等模块
- 缓存策略:在缓存策略当中,不仅包含cache-aside、cache-read/write-though 等等策略模型,在Spring-cache中同样包含
- 操作便捷:通过配置redisteplate、rediscachemanger结合注解的形式,去除多余的模版/工具操作
自定义配置
- application.yml
spring:redis:host: 127.0.0.1port: 6379 database: 0 pool:max-active: 20 max-wait: 5000 max-idle: 10 min-idle: 2 timeout: 6000 expires:cache: 10000
- Bean 配置:
@Configuration@EnableCaching@ConfigurationProperties(prefix ="spring.redis")public class redisConfig extends CachingConfigurerSupport {@Value("${spring.application.name}")private String prefixName; private Map, Long> expires; @Bean @SuppressWarnings("rawtypes")public CacheManager cacheManager(RedisTemplate redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate); //设置redis的名称 //cacheManager.setCacheNames(Arrays.asList("redis_first_")); //设置开启前缀 cacheManager.setUsePrefix(true); //设置默认的前缀 //cacheManager.setCachePrefix(new DefaultRedisCachePrefix(prefixName)); cacheManager.setDefaultExpiration(3000); //自定义过期时间 cacheManager.setExpires(expires); return cacheManager; }/** * 配置redis */ @Bean public RedisTemplate redisTemplate(RedisConnectionFactory factory){
StringRedisTemplate template = new StringRedisTemplate(factory); Jackson2JsonRedisSerializer serializer =new Jackson2JsonRedisSerializer<>(Object.class); ObjectMapper mapper = new ObjectMapper(); mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); serializer.setObjectMapper(mapper); template.setValueSerializer(serializer); template.afterPropertiesSet(); return template; }/*@Bean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) { StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(); stringRedisTemplate.setConnectionFactory(factory); return stringRedisTemplate; }*/ public Map, Long> getExpires() {return expires; }public void setExpires(Map, Long> expires) {this.expires = expires; }
}
实现原理
@Cacheable(value = "cache",key = "#p0")@Overridepublic Student queryList(String name){
System.out.println(name); Student s = new Student(); s.setId(1); s.setFieldName("first-bllod"); s.setName(name); return s;}
基于动态代理模式对当前方法进行增强,之后通过CacheInterceptor拦截器 执行缓存管理,CacheAspectSupport做通用的缓存逻辑,包括组装目标类、处理注解(@Cacheable、@Cacheconfig)、解析SpeL表达式、维护CacheManager和RedisCache关系等等;
此外,spring-context作为缓存的超类, 基于spi的思想:只提供功能接口,实现由接入源(redis、guava等)实现Cache/CacheManager处理自己的源逻辑。
源码分析
org.springframework.cache.support.AbstractCacheManager
private final ConcurrentMap, Cache> cacheMap = new ConcurrentHashMap, Cache>(16);
@Override
public Cache getCache(String name) {
Cache cache = this.cacheMap.get(name); if (cache != null) {return cache; }else {// Fully synchronize now for missing cache creation... synchronized (this.cacheMap) {
cache = this.cacheMap.get(name); if (cache == null) {
cache = getMissingCache(name); if (cache != null) {
cache = decorateCache(cache); this.cacheMap.put(name, cache); updateCacheNames(name); }
}return cache; }
}
}
AbstractCacheMamager 中维护一份RedisCache的本地缓存, 未找到key->cacheName,调用getMissingCache()方法创建当前cachename 的cache对象, 并设置缓存时间。
ps:缓存时间可以通过expires,Bean配置中进行自定义。
@Overrideprotected Cache getMissingCache(String name) {return this.dynamic ? createCache(name) : null;}@SuppressWarnings("unchecked")protected RedisCache createCache(String cacheName) {long expiration = computeExpiration(cacheName); return new RedisCache(cacheName, (usePrefix ? cachePrefix.prefix(cacheName) : null), redisOperations, expiration, cacheNullValues);}protected long computeExpiration(String name) {
Long expiration = null; if (expires != null) {
expiration = expires.get(name); }return (expiration != null ? expiration.longValue() : defaultExpiration);}
下一步通过org.springframework.cache.interceptor.CacheAspectSupport#execute处理目标方法上的注解,然后对目标方法进行缓存操作。
// Check if we have a cached item matching the conditionsCache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));// Collect puts from any @Cacheable miss, if no cached item is foundList cachePutRequests = new LinkedList();if (cacheHit == null) {
collectPutRequests(contexts.get(CacheableOperation.class), CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);}
org.springframework.data.redis.cache.RedisCache#get(),cachekey不存在返回null,存在进行反序列化获得value。
public RedisCacheElement get(final RedisCacheKey cacheKey) {
Assert.notNull(cacheKey, "CacheKey must not be null!"); Boolean exists = (Boolean) redisOperations.execute(new RedisCallback() {@Override public Boolean doInRedis(RedisConnection connection) throws DataAccessException {return connection.exists(cacheKey.getKeyBytes()); }
}); if (!exists.booleanValue()) {return null; }return new RedisCacheElement(cacheKey, fromStoreValue(lookup(cacheKey)));}
写在最后:
Springcache 通过配置--注解的形式解藕业务逻辑代码,方便管理和操作。可以结合特定的缓存策略提高资源利用率和缓存命中率,此外还包括双写一致性问题都可以适当的处理这类问题。