平常開發中,須要用到各類各樣的框架來實現API、系統的構建。做爲程序員,除了會使用框架還必需要了解框架工做的原理。這樣能夠便於咱們排查問題,和自定義的擴展。那麼如何去學習框架呢。一般咱們經過閱讀文檔、查看源碼,而後又很快忘記。始終不能融匯貫通。本文主要基於Spring Cache擴展爲例,介紹如何進行高效的源碼閱讀。html
爲何以Spring Cache爲例呢,緣由有兩個java
一句話解釋Spring Cache: 經過註解的方式,利用AOP的思想來解放緩存的管理。git
首先經過查看官方文檔,歸納瞭解Spring Cache
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-caching.html程序員
重點兩點github
Cache
,CacheManager
,具體的實現都是基於這兩個抽象實現。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.web
Spring Cache提供了這些緩存的實現,若是沒有一種CacheManage
,或者CacheResolver
,會按照指定的順序去實現spring
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.Simpleapi
對Spring Cache有了一個大概的瞭解後,咱們首先使用起來,跑個demo。緩存
定義一個用戶查詢方法app
@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()); } }
demo跑起來後,就是debug看看代碼如何實現的了。
由於直接看源代碼的,沒有調用關係,看起來會一頭霧水。經過debug可以使你更快了解一個實現。
經過debug咱們會發現主要控制邏輯是在切面CacheAspectSupport
會先根據cache key找緩存數據,沒有的話put進去。
// 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); }
知道如何使用Spring Cache後,咱們須要進一步思考,就是如何擴展。那麼帶着問題出發。
好比Spring Cache不支持批量key的緩存,像上文咱們舉的例子,咱們但願緩存的key是userId,而不是Collection
@Cacheable(cacheNames = "users") public Map<Long, User> getUser(final Collection<Long> userIds) {
因此咱們要實現對Spring Cache進行擴展。step3中咱們已經大體瞭解了Spring Cache的實現。那麼實現這個擴展的功能就是拆分Collection
@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
本文主要介紹一種源碼學習方法,純屬拋磚引玉,若是你有好的方法,歡迎分享。
關注公衆號【方丈的寺院】,第一時間收到文章的更新,與方丈一塊兒開始技術修行之路