日常开发中,需要用到各种各样的框架来实现API、系统的构建。作为程序员,除了会使用框架还必须要了解框架工作的原理。
摘要
日常开发中,需要用到各种各样的框架来实现API、系统的构建。作为程序员,除了会使用框架还必须要了解框架工作的原理。这样可以便于我们排查问题,和自定义的扩展。那么如何去学习框架呢。通常我们通过阅读文档、查看源码,然后又很快忘记。始终不能融汇贯通。本文主要基于Spring Cache扩展为例,介绍如何进行高效的源码阅读。
Spring Cache的介绍
为什么以Spring Cache为例呢,原因有两个:
- Spring框架是Web开发常用的框架,值得开发者去阅读代码,吸收思想;
- 缓存是企业级应用开发必不可少的,而随着系统的迭代,我们可能会需要用到内存缓存、分布式缓存。那么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。当需要提供接口给外部调用,首先自己内部的实现也必须基于同样一套抽象机制。
- Thecacheabstractiondoesnotprovideanactualstoreandreliesonabstractionmaterializedbytheorg.springframework.cache.Cacheandorg.springframework.cache.CacheManagerinterfaces.
2. Spring Cache提供了这些缓存的实现,如果没有一种CacheManage,或者CacheResolver,会按照指定的顺序去实现。
- IfyouhavenotdefinedabeanoftypeCacheManageroraCacheResolvernamedcacheResolver(seeCachingConfigurer),SpringBoottriestodetectthefollowingproviders(intheindicatedorder):
- 1.Generic
- 2.JCache(JSR-107)(EhCache3,Hazelcast,Infinispan,andothers)
- 3.EhCache2.x
- 4.Hazelcast
- 5.Infinispan
- 6.Couchbase
- 7.Redis
- 8.Caffeine
- 9.Simple
step2 run demo
对Spring Cache有了一个大概的了解后,我们首先使用起来,跑个demo。
定义一个用户查询方法:
- @Component
- publicclassCacheSample{
- @Cacheable(cacheNames="users")
- publicMap<Long,User>getUser(finalCollection<Long>userIds){
- System.out.println("notcache");
- finalMap<Long,User>mapUser=newHashMap<>();
- userIds.forEach(userId->{
- mapUser.put(userId,User.builder().userId(userId).name("name").build());
- });
- returnmapUser;
- }
配置一个CacheManager:
- @Configuration
- publicclassCacheConfig{
- @Primary
- @Bean(name={"cacheManager"})
- publicCacheManagergetCache(){
- returnnewConcurrentMapCacheManager("users");
- }
API调用:
- @RestController
- @RequestMapping("/api/cache")
- publicclassCacheController{
- @Autowired
- privateCacheSamplecacheSample;
- @GetMapping("/user/v1/1")
- publicList<User>getUser(){
- returncacheSample.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,而不是CollectionuserIds。以userId为key,这样的缓存命中率更高,存储的成本更小。
- @Cacheable(cacheNames="users")
- publicMap<Long,User>getUser(finalCollection<Long>userIds){}
所以我们要实现对Spring Cache进行扩展。step3中我们已经大致了解了Spring Cache的实现。那么实现这个扩展的功能就是拆分CollectionuserIds,缓存命中的从缓存中获取,没有命中的,调用源方法。
- @Aspect
- @Component
- publicclassCacheExtenionAspect{
- @Autowired
- privateCacheExtensionManagecacheExtensionManage;
- /**
- *返回的结果中缓存命中的从缓存中获取,没有命中的调用原来的方法获取
- *@paramjoinPoint
- *@return
- */
- @Around("@annotation(org.springframework.cache.annotation.Cacheable)")
- @SuppressWarnings("unchecked")
- publicObjectaroundCache(finalProceedingJoinPointjoinPoint){
- //修改掉Collection值,cacheResult需要重新构造一个
- args[0]=cacheResult.getMiss();
- try{
- finalMap<Object,Object>notHit=CollectionUtils.isEmpty(cacheResult.getMiss())?null
- :(Map<Object,Object>)(method.invoke(target,args));
- finalMap<Object,Object>hits=cacheResult.getHit();
- if(Objects.isNull(notHit)){
- returnhits;
- }
- //设置缓存
- cacheResult.getCache().putAll(notHit);
- hits.putAll(notHit);
- returnhits;
- }
- }
- 然后扩展Cache,CacheManage
- 重写Cache的查找缓存方法,返回新的CacheResult
- publicstaticObjectlookup(finalCacheExtensioncache,finalObjectkey){
- if(keyinstanceofCollection){
- finalCollection<Object>originalKeys=((Collection)key);
- if(originalKeys==null||originalKeys.isEmpty()){
- returnCacheResult.builder().cache(cache).miss(
- Collections.emptySet())
- .build();
- }
- finalList<Object>keys=originalKeys.stream()
- .filter(Objects::nonNull).collect(Collectors.toList());
- finalMap<Object,Object>hits=cache.getAll(keys);
- finalSet<Object>miss=newHashSet(keys);
- miss.removeAll(hits.keySet());
- returnCacheResult.builder().cache(cache).hit(hits).miss(miss).build();
- }
- returnnull;
- }
- CacheResult就是新的缓存结果格式
- @Builder
- @Setter
- @Getter
- staticclassCacheResult{
- finalCacheExtensioncache;
- //命中的缓存结果
- finalMap<Object,Object>hit;
- //需要重新调用源方法的keys
- privateSet<Object>miss;
- }
然后扩展CacheManager,没什么重写,就是自定义一种manager类型。
为缓存指定新的CacheManager:
- @Primary@BeanpublicCacheManagergetExtensionCache(){returnnewCacheExtensionManage("users2");}
完整代码:
https://github.com/FS1360472174/javaweb/tree/master/web/src/main/java/com/fs/web/cache
总结
本文主要介绍一种源码学习方法,纯属抛砖引玉,如果你有好的方法,欢迎分享。
©本文为清一色官方代发,观点仅代表作者本人,与清一色无关。清一色对文中陈述、观点判断保持中立,不对所包含内容的准确性、可靠性或完整性提供任何明示或暗示的保证。本文不作为投资理财建议,请读者仅作参考,并请自行承担全部责任。文中部分文字/图片/视频/音频等来源于网络,如侵犯到著作权人的权利,请与我们联系(微信/QQ:1074760229)。转载请注明出处:清一色财经