缓存作为日常开发中必备的环节,主流的缓存中间件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本地缓存超时 spring cache本地缓存_缓存

此外,spring-context作为缓存的超类, 基于spi的思想:只提供功能接口,实现由接入源(redis、guava等)实现Cache/CacheManager处理自己的源逻辑。

Spring本地缓存超时 spring cache本地缓存_Spring本地缓存超时_02

源码分析

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 通过配置--注解的形式解藕业务逻辑代码,方便管理和操作。可以结合特定的缓存策略提高资源利用率和缓存命中率,此外还包括双写一致性问题都可以适当的处理这类问题。