摘要

日常开发中,需要用到各种各样的框架来实现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。

定义一个用户查询方法

@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

@Configuration
public class CacheConfig {
    @Primary
    @Bean(name = { "cacheManager" })
    public CacheManager getCache() {
      return new ConcurrentMapCacheManager("users");
    }

API调用

@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进去。

step4 实现扩展

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

  @Cacheable(cacheNames = "users")
    public Map<Long, User> getUser(final Collection<Long> userIds) {}

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

@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

  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就是新的缓存结果格式

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

然后扩展CacheManager,没什么重写,就是自定义一种manager类型

为缓存指定新的CacheManager

@Primary @Bean public CacheManager getExtensionCache() { return new CacheExtensionManage("users2"); }

完整代码

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

总结

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

如何进行高效的源码阅读:以Spring Cache扩展为例带你搞清楚的更多相关文章

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

    摘要 日常开发中,需要用到各种各样的框架来实现API.系统的构建.作为程序员,除了会使用框架还必须要了解框架工作的原理.这样可以便于我们排查问题,和自定义的扩展.那么如何去学习框架呢.通常我们通过阅读 ...

  2. (转) Spring源码阅读 之 Spring整体架构

    标签(空格分隔): Spring 声明:本文系转载,原地地址:spring framework 4 源码阅读 Spring骨架 Spring的骨架,也是Spring的核心包.主要包含三个内容 cont ...

  3. Three.js源码阅读笔记-5

    Core::Ray 该类用来表示空间中的“射线”,主要用来进行碰撞检测. THREE.Ray = function ( origin, direction ) { this.origin = ( or ...

  4. JDK 1.8源码阅读 TreeMap

    一,前言 TreeMap:基于红黑树实现的,TreeMap是有序的. 二,TreeMap结构 2.1 红黑树结构 红黑树又称红-黑二叉树,它首先是一颗二叉树,它具体二叉树所有的特性.同时红黑树更是一颗 ...

  5. 【源码阅读】Java集合之三 - ArrayDeque源码深度解读

    Java 源码阅读的第一步是Collection框架源码,这也是面试基础中的基础: 针对Collection的源码阅读写一个系列的文章,本文是第三篇ArrayDeque. ---@pdai JDK版本 ...

  6. java源码阅读Hashtable

    1类签名与注释 public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, C ...

  7. 源码阅读之HashMap(JDK8)

    概述 HashMap根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的. HashMap最多只允许一条记录的键为null,允许多条记录 ...

  8. 源码阅读之LinkedList(JDK8)

    inkedList概述 LinkedList是List和Deque接口的双向链表的实现.实现了所有可选列表操作,并允许包括null值. LinkedList既然是通过双向链表去实现的,那么它可以被当作 ...

  9. JDK部分源码阅读与理解

    本文为博主原创,允许转载,但请声明原文地址:http://www.coselding.cn/article/2016/05/31/JDK部分源码阅读与理解/ 不喜欢重复造轮子,不喜欢贴各种东西.JDK ...

随机推荐

  1. WPF显示html的几种方法

    原文:WPF显示html的几种方法 客户希望系统在一些特定的条件下,界面上能显示用户自定义的格式和内容,格式和内容通过html指定. 基本上在wpf中显示html有以下四种方法. 1.       W ...

  2. 通过NLayer和NAudio转换MP3成WAV

    NuGet安装: Install-Package NLayer.NAudioSupport 示例代码: using Microsoft.Win32; using NAudio.Wave; using ...

  3. C#有哪几种定时器

    1.定义在System.Windows.Forms里2.定义在System.Threading.Timer类里3.定义在System.Timers.Timer类里 System.Windows.For ...

  4. C#高性能大容量SOCKET并发(零):代码结构说明

    原文:C#高性能大容量SOCKET并发(零):代码结构说明 C#版完成端口具有以下特点: 连接在线管理(提供在线连接维护,连接会话管理,数据接收,连接断开等相关事件跟踪): 发送数据智能合并(组件会根 ...

  5. RedHat 7.3 修改ASM磁盘绑定路径

    RedHat 7中,很多命令发生了改变,绑定磁盘不再是start_udev,而是udevadm,具体绑定方式,请看另一篇博文: http://www.cnblogs.com/zx3212/p/6757 ...

  6. CSS3 Generator提供了13个CSS3较为常用的属性代码生成工具,而且可以通过这款工具除了在线生成效果代码之外,还可以实时看到你修改的效果,以及浏览器的兼容性。

    CSS3 Generator提供了13个CSS3较为常用的属性代码生成工具,而且可以通过这款工具除了在线生成效果代码之外,还可以实时看到你修改的效果,以及浏览器的兼容性. CSS3 Generator ...

  7. 设置windows2008系统缓存大小限制,解决服务器运行久了因物理内存耗尽出僵死(提升权限后,使用SetSystemFileCacheSize API函数,并将此做成了一个Service)

    声明: 找到服务器僵死的原因了,原因是虚拟内存设置小于物理内存. 只要虚拟内存设置为系统默认大小就不会出生僵死的现象了. 当时因为服务器内存48G,系统默认虚拟内存大小也是48G, 觉得太占硬盘空间, ...

  8. ThoughtWorks 面试备忘录

    ThoughtWorks 面试备忘录 前言 前段时间 ThoughtWorks 在网上和拉勾网合作搞了一次网络招聘,名为抛弃简历!让代码说话!,可谓赚足了眼球,很多程序猿纷纷摩拳擦掌.踊跃提交代码,在 ...

  9. 30+ 强大的Buddypress主题–开始您的社区站点吧

    BuddyPress起源于2008年,当时设计者设想添加社交网络功能到WordPress多用户版本中.第一个正式稳定版本的发布是在2009年的5月.自从那时起.BuddyPress开始快速的成长和演变 ...

  10. SYN2101型 NTP网络时间服务器

    SYN2101型  NTP网络时间服务器   时钟校准服务器时间 ntp服务器ntp时间校准服务器使用说明视频链接: http://www.syn029.com/h-pd-56-0_310_1_-1. ...