当前位置: 首页 > 科技观察

如何高效阅读源码:以SpringCache扩展为例,教你如何理解

时间:2023-03-14 22:13:49 科技观察

摘要在日常开发中,需要各种框架来实现API和系统构建。作为程序员,除了会使用框架,还必须了解框架是如何工作的。这使我们更容易解决问题和自定义扩展。那么如何学习框架。通常我们看文档,看源码,然后很快就忘记了。它永远无法整合。本文主要以SpringCache扩展为例,介绍如何高效阅读源码。SpringCache的介绍以SpringCache为例。原因有二:Spring框架是Web开发常用的框架,值得开发者阅读代码吸取思想;缓存对于企业级应用程序开发至关重要。对于系统迭代,我们可能需要使用内存缓存和分布式缓存。那么SpringCache作为胶水层,可以屏蔽我们底层的缓存实现。一句话解释SpringCache:通过注解,用AOP的思想解放缓存管理。step1查看文档首先,通过查看官方文档,了解一下SpringCache的概况。https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-caching.html重点关注两点:1.抽象Cache和CacheManager这两个接口,具体实现根据关于这两个抽象实现。典型的SPI机制,吃你的狗粮。当需要提供接口供外部调用时,首先其自身的内部实现也必须基于同一套抽象机制。缓存抽象不提供由org.springframework.cache.Cacheandorg.springframework.cache.CacheManagerinterfaces.2实现的实际存储和浮雕抽象。SpringCache提供了这些缓存的实现。如果没有CacheManage,或者CacheResolver,就会按照指定的顺序执行。如果您尚未定义类型为CacheManager或CacheResolver的名为cacheResolver的bean(请参阅CachingConfigurer),SpringBootries将检测以下提供程序(按指示的顺序):1.Generic2.JCache(JSR-107)(EhCache3、Hazelcast、Infinispan等)3.EhCache2.x4.Hazelcast5.Infinispan6.CouchbaseSimpfine运行7.Redis对Cache有了大致的了解之后,我们先来使用它,跑个demo。定义用户查询方法:@ComponentpublicclassCacheSample{@Cacheable(cacheNames="users")publicMapgetUser(finalCollectionuserIds){System.out.println("notcache");finalMapmapUser=newHashMap<>();userIds.forEach(userId->{mapUser.put(userId,User.builder().userId(userId).name("name").build());});returnmapUser;}配置缓存管理器:@ConfigurationpublicclassCacheConfig{@Primary@Bean(name={"cacheManager"})publicCacheManagergetCache(){returnnewConcurrentMapCacheManager("users");}API调用:@RestController@RequestMapping("/api/cache")publicclassCacheController{@AutowiredprivateCacheSample缓存示例;@GetMapping("/user/v1/1")publicListgetUser(){returncacheSample.getUser(Arrays.asList(1L,2L)).values().stream().collect(Collectors.toList());}}step3debug检查实现演示运行后,是时候调试看看代码是如何实现的了。因为如果直接看源码,没有任何调用关系,好像是一头雾水。调试可以让您更快地理解实现。通过调试,我们会发现主要的控制逻辑是在切面CacheAspectSupport中会先根据缓存key找到缓存的数据,没有则放入。step4实现扩展在知道了如何使用SpringCache之后,我们还需要进一步思考,也就是如何进行扩展。然后从问题开始。例如,SpringCache不支持批量键缓存。像我们上面给出的例子,我们希望缓存的key是userId,而不是CollectionuserIds。以userId为key,缓存命中率更高,存储成本更低。@Cacheable(cacheNames="users")publicMapgetUser(finalCollectionuserIds){}所以我们需要扩展SpringCache。在step3中,我们对SpringCache的实现有了一个大概的了解。那么实现这个扩展的功能就是拆分CollectionuserIds,从缓存中获取缓存命中,没有命中则调用source方法。@Aspect@ComponentpublicclassCacheExtenionAspect{@AutowiredprivateCacheExtensionManagecacheExtensionManage;/***从缓存中获取返回结果中的缓存命中,没有命中则调用原方法*@paramjoinPoint*@return*/@Around("@annotation(org.springframework.cache.annotation.Cacheable)")@SuppressWarnings("unchecked")publicObjectaroundCache(finalProceedingJoinPointjoinPoint){//修改Collection值,cacheResult需要重构一个args[0]=cacheResult.getMiss();try{finalMapnotHit=CollectionUtils.isEmpty(cacheResult.getMiss())?null:(Map)(method.invoke(target,args));finalMaphits=cacheResult.getHit();if(Objects.isNull(notHit)){returnhits;}//设置缓存cacheResult.getCache().putAll(notHit);hits.putAll(notHit);returnhits;}}然后展开Cache,CacheManage重写了Cache的查找缓存方法,返回一个新的CacheResultpublicstaticObjectlookup(finalCacheExtensioncache,finalObjectkey){if(keyinstanceofCollection){finalCollectionoriginalKeys=((Collection)key);if(originalKeys==null||ooriginalKeys.isEmpty()){returnCacheResult.builder().cache(cache).miss(Collections.emptySet()).build();}finalListkeys=originalKeys.stream().filter(Objects::nonNull).collect(Collectors.toList());finalMaphits=cache.getAll(keys);finalSetmiss=newHashSet(keys);miss.removeAll(hits.keySet());returnCacheResult。builder().cache(cache).hit(hits).miss(miss).build();}returnnull;}CacheResult是新的缓存结果格式@Builder@Setter@GetterstaticclassCacheResult{finalCacheExtensioncache;//命中缓存结果finalMaphit;//keysprivateSetmiss;这需要再次调用源方法;然后扩展CacheManager,无需重写,只需自定义一个管理器类型,为缓存指定一个新的CacheManager:@Primary@BeanpublicCacheManagergetExtensionCache(){returnnewCacheExtensionManage("users2");}完整代码:https://github.com/FS1360472174/javaweb/tree/master/web/src/main/java/com/fs/web/cache介绍一个源码学习方法,纯属介绍。如果你有好的方法,欢迎分享。