首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

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

摘要

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

Spring Cache的介绍

为什么以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。当需要提供接口给外部调用,首先自己内部的实现也必须基于同样一套抽象机制。

代码语言:javascript
复制
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.?

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

代码语言:javascript
复制
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进去。

step4 实现扩展

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

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

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

代码语言: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????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:

代码语言: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

总结

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

  • 发表于:
  • 原文链接http://news.51cto.com/art/201907/600321.htm
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券
http://www.vxiaotou.com