SpringCache 管理缓存分析

点击上方 IT牧场 ,选择 置顶或者星标

技术干货每日送达


缓存作为日常开发中必备的环节,主流的缓存中间件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.1
        port: 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<String, 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<Object> 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<String, Long> getExpires() {
        return expires;
    }

    public void setExpires(Map<String, Long> expires) {
        this.expires = expires;
    }
}

实现原理

@Cacheable(value = "cache",key = "#p0")
@Override
public 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<String, Cache> cacheMap = new ConcurrentHashMap<String, 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配置中进行自定义。

@Override
protected 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 conditions
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
// Collect puts from any @Cacheable miss, if no cached item is found
List<CachePutRequest> cachePutRequests = new LinkedList<CachePutRequest>();
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<Boolean>() {

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

干货分享

最近将个人学习笔记整理成册,使用PDF分享。关注我,回复如下代码,即可获得百度盘地址,无套路领取!

•001:《Java并发与高并发解决方案》学习笔记;•002:《深入JVM内核——原理、诊断与优化》学习笔记;•003:《Java面试宝典》•004:《Docker开源书》•005:《Kubernetes开源书》•006:《DDD速成(领域驱动设计速成)》•007:全部•008:加技术群讨论

近期热文

LinkedBlockingQueue vs ConcurrentLinkedQueue解读Java 8 中为并发而生的 ConcurrentHashMapRedis性能监控指标汇总最全的DevOps工具集合,再也不怕选型了!微服务架构下,解决数据库跨库查询的一些思路聊聊大厂面试官必问的 MySQL 锁机制

关注我

喜欢就点个"在看"呗^_^

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值