前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >以Spring Cache扩展为例介绍如何进行高效的源码的阅读

以Spring Cache扩展为例介绍如何进行高效的源码的阅读

作者头像
方丈的寺院
发布2019-08-05 17:37:21
6140
发布2019-08-05 17:37:21
举报
文章被收录于专栏:方丈的寺院方丈的寺院

摘要

日常开发中,需要用到各种各样的框架来实现API、系统的构建。作为程序员,除了会使用框架还必须要了解框架工作的原理。这样可以便于我们排查问题,和自定义的扩展。那么如何去学习框架呢。通常我们通过阅读文档、查看源码,然后又很快忘记。始终不能融汇贯通。本文主要基于Spring Cache扩展为例,介绍如何进行高效的源码阅读。

SpringCache的介绍

为什么以Spring Cache为例呢,原因有两个

  1. Spring框架是web开发最常用的框架,值得开发者去阅读代码,吸收思想
  2. 缓存是企业级应用开发必不可少的,而随着系统的迭代,我们可能会需要用到内存缓存、分布式缓存。那么Spring Cache作为胶水层,能够屏蔽掉我们底层的缓存实现。

一句话解释Spring Cache: 通过注解的方式,利用AOP的思想来解放缓存的管理。

step1 查看文档

首先通过查看官方文档,概括了解Spring Cache https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-caching.html

重点两点

  1. 两个接口抽象 Cache, CacheManager,具体的实现都是基于这两个抽象实现。典型的SPI机制,和eat your dog food。当需要提供接口给外部调用,首先自己内部的实现也必须基于同样一套抽象机制

The cache abstraction does not provide an actual store and relies on abstraction materialized by the org.springframework.cache.Cache and org.springframework.cache.CacheManager interfaces.

  1. Spring Cache提供了这些缓存的实现,如果没有一种 CacheManage,或者 CacheResolver,会按照指定的顺序去实现

If you have not defined a bean of type CacheManager or a CacheResolver named cacheResolver (see CachingConfigurer), Spring Boot tries to detect the following providers (in the indicated order): 1.Generic 2.JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan, and others) 3.EhCache 2.x 4.Hazelcast 5.Infinispan 6.Couchbase 7.Redis 8.Caffeine 9.Simple

step2 run demo

对Spring Cache有了一个大概的了解后,我们首先使用起来,跑个demo。

定义一个用户查询方法

代码语言:javascript
复制
@Component
public class CacheSample {
    @Cacheable(cacheNames = "users")
    public Map<Long, User> getUser(final Collection<Long> userIds) {
        System.out.println("not cache");
        final Map<Long, User> mapUser = new HashMap<>();
        userIds.forEach(userId -> {
            mapUser.put(userId, User.builder().userId(userId).name("name").build());
        });
        return mapUser;
    }

配置一个CacheManager

代码语言:javascript
复制
@Configuration
public class CacheConfig {
    @Primary
    @Bean(name = { "cacheManager" })
    public CacheManager getCache() {
      return new ConcurrentMapCacheManager("users");
    }

API调用

代码语言:javascript
复制
@RestController
@RequestMapping("/api/cache")
public class CacheController {
    @Autowired
    private CacheSample cacheSample;
    @GetMapping("/user/v1/1")
    public List<User> getUser() {
        return cacheSample.getUser(Arrays.asList(1L,2L)).values().stream().collect(Collectors.toList());
    }
    }

step3 debug 查看实现

demo跑起来后,就是debug看看代码如何实现的了。因为直接看源代码的,没有调用关系,看起来会一头雾水。通过debug能够使你更快了解一个实现。

通过debug我们会发现主要控制逻辑是在切面CacheAspectSupport 会先根据cache key找缓存数据,没有的话put进去。

代码语言:javascript
复制
// 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<>();
if (cacheHit == null) {
   collectPutRequests(contexts.get(CacheableOperation.class),
         CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
}

step4 实现扩展

知道如何使用Spring Cache后,我们需要进一步思考,就是如何扩展。那么带着问题出发。比如Spring Cache不支持批量key的缓存,像上文我们举的例子,我们希望缓存的key是userId,而不是CollectionuserIds。以userId为key,这样的缓存命中率更高,存储的成本更小。

代码语言:javascript
复制
@Cacheable(cacheNames = "users")
    public Map<Long, User> getUser(final Collection<Long> userIds) {

所以我们要实现对Spring Cache进行扩展。step3中我们已经大致了解了Spring Cache的实现。那么实现这个扩展的功能就是拆分CollectionuserIds,缓存命中的从缓存中获取,没有命中的,调用源方法。

代码语言:javascript
复制
@Aspect
@Component
public class CacheExtenionAspect {

    @Autowired
    private CacheExtensionManage cacheExtensionManage;

    /**
     * 返回的结果中缓存命中的从缓存中获取,没有命中的调用原来的方法获取
     * @param joinPoint
     * @return
     */
    @Around("@annotation(org.springframework.cache.annotation.Cacheable)")
    @SuppressWarnings("unchecked")
    public Object aroundCache(final ProceedingJoinPoint joinPoint) {

        // 修改掉Collection值,cacheResult需要重新构造一个
        args[0] = cacheResult.getMiss();
        try {
            final Map<Object, Object> notHit = CollectionUtils.isEmpty(cacheResult.getMiss()) ? null
                    : (Map<Object, Object>) (method.invoke(target, args));
            final Map<Object, Object> hits = cacheResult.getHit();
            if (Objects.isNull(notHit)) {
                return hits;
            }
            // 设置缓存
            cacheResult.getCache().putAll(notHit);
            hits.putAll(notHit);
            return hits;
    }
}

然后扩展 Cache, CacheManage重写Cache的查找缓存方法,返回新的CacheResult

代码语言:javascript
复制
public static Object lookup(final CacheExtension cache, final Object key) {
        if (key instanceof Collection) {
            final Collection<Object> originalKeys = ((Collection) key);
            if (originalKeys == null || originalKeys.isEmpty()) {
                return CacheResult.builder().cache(cache).miss(
                        Collections.emptySet())
                        .build();
            }
            final List<Object> keys = originalKeys.stream()
                    .filter(Objects::nonNull).collect(Collectors.toList());
            final Map<Object, Object> hits = cache.getAll(keys);
            final Set<Object> miss = new HashSet(keys);
            miss.removeAll(hits.keySet());
            return CacheResult.builder().cache(cache).hit(hits).miss(miss).build();
        }
        return null;
    }

CacheResult就是新的缓存结果格式

代码语言:javascript
复制
@Builder
    @Setter
    @Getter
    static class CacheResult {
        final CacheExtension cache;
        // 命中的缓存结果
        final Map<Object, Object> hit;
        // 需要重新调用源方法的keys
        private Set<Object> miss;
    }

然后扩展CacheManager,没什么重写,就是自定义一种manager类型 为缓存指定新的CacheManager

代码语言:javascript
复制
@Primary
  @Bean
  public CacheManager getExtensionCache() {
        return new CacheExtensionManage("users2");
  }

完整代码 https://github.com/FS1360472174/javaweb/tree/master/web/src/main/java/com/fs/web/cache

总结

本文主要介绍一种源码学习方法,纯属抛砖引玉,如果你有好的方法,欢迎分享。

关注公众号【方丈的寺院】,第一时间收到文章的更新,与方丈一起开始技术修行之路

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-07-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 方丈的寺院 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 摘要
  • SpringCache的介绍
    • step1 查看文档
      • step2 run demo
        • step3 debug 查看实现
          • step4 实现扩展
          • 总结
          相关产品与服务
          云数据库 Redis
          腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档