中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

Spring Cache框架

2018-11-22    來(lái)源:importnew

容器云強(qiáng)勢(shì)上線!快速搭建集群,上萬(wàn)Linux鏡像隨意使用

本文是緩存系列第三篇,前兩篇分別介紹了 Guava 和 JetCache。

前兩篇我們講了 Guava 和 JetCache,它們都是緩存的具體實(shí)現(xiàn),今天給大家分析一下 Spring 框架本身對(duì)這些緩存具體實(shí)現(xiàn)的支持和融合。使用 Spring Cache 將大大的減少我們的Spring項(xiàng)目中緩存使用的復(fù)雜度,提高代碼可讀性。本文將從以下幾個(gè)方面來(lái)認(rèn)識(shí)Spring Cache框架。

背景

SpringCache 產(chǎn)生的背景其實(shí)與Spring產(chǎn)生的背景有點(diǎn)類似。由于 Java EE 系統(tǒng)框架臃腫、低效,代碼可觀性低,對(duì)象創(chuàng)建和依賴關(guān)系復(fù)雜, Spring 框架出來(lái)了,目前基本上所有的Java后臺(tái)項(xiàng)目都離不開(kāi) Spring 或 SpringBoot (對(duì) Spring 的進(jìn)一步簡(jiǎn)化)。現(xiàn)在項(xiàng)目面臨高并發(fā)的問(wèn)題越來(lái)越多,各類緩存的應(yīng)用也增多,那么在通用的 Spring 框架上,就需要有一種更加便捷簡(jiǎn)單的方式,來(lái)完成緩存的支持,就這樣 SpringCache就出現(xiàn)了。

不過(guò)首先我們需要明白的一點(diǎn)是,SpringCache 并非某一種 Cache 實(shí)現(xiàn)的技術(shù),SpringCache 是一種緩存實(shí)現(xiàn)的通用技術(shù),基于 Spring 提供的 Cache 框架,讓開(kāi)發(fā)者更容易將自己的緩存實(shí)現(xiàn)高效便捷的嵌入到自己的項(xiàng)目中。當(dāng)然,SpringCache 也提供了本身的簡(jiǎn)單實(shí)現(xiàn) NoOpCacheManager、ConcurrentMapCacheManager 等。通過(guò) SpringCache,可以快速嵌入自己的Cache實(shí)現(xiàn)。

用法

源碼已分享至Github:https://github.com/zhuzhenke/common-caches

注意點(diǎn):

  1. 開(kāi)啟 EnableCaching 注解,默認(rèn)沒(méi)有開(kāi)啟 Cache。
  2. 配置 CacheManager。
@Bean
@Qualifier("concurrentMapCacheManager")
@Primary
ConcurrentMapCacheManager concurrentMapCacheManager() {
    return new ConcurrentMapCacheManager();
}

這里使用了 @Primary 和 @Qualifier 注解,@Qualifier 注解是給這個(gè) Bean 加一個(gè)名字,用于同一個(gè)接口 Bean?的多個(gè)實(shí)現(xiàn)時(shí),指定當(dāng)前 Bean?的名字,也就意味著 CacheManager 可以配置多個(gè),并且在不同的方法場(chǎng)景下使用。@Primary 注解是當(dāng)接口 Bean?有多個(gè)時(shí),優(yōu)先注入當(dāng)前 Bean?。

現(xiàn)在拿 CategoryService 實(shí)現(xiàn)來(lái)分析。

public class CategoryService {

    @Caching(evict = {@CacheEvict(value = CategoryCacheConstants.CATEGORY_DOMAIN,
            key = "#category.getCategoryCacheKey()",
            beforeInvocation = true)})
    public int add(Category category) {
        System.out.println("模擬進(jìn)行數(shù)據(jù)庫(kù)交互操作......");
        System.out.println("Cache became invalid,value:" + CategoryCacheConstants.CATEGORY_DOMAIN
                + ",key:" + category.getCategoryCacheKey());
        return 1;
    }

    @Caching(evict = {@CacheEvict(value = CategoryCacheConstants.CATEGORY_DOMAIN,
            key = "#category.getCategoryCacheKey()",
            beforeInvocation = true)})
    public int delete(Category category) {
        System.out.println("模擬進(jìn)行數(shù)據(jù)庫(kù)交互操作......");
        System.out.println("Cache became invalid,value:" + CategoryCacheConstants.CATEGORY_DOMAIN
                + ",key:" + category.getCategoryCacheKey());
        return 0;
    }

    @Caching(evict = {@CacheEvict(value = CategoryCacheConstants.CATEGORY_DOMAIN,
            key = "#category.getCategoryCacheKey()")})
    public int update(Category category) {
        System.out.println("模擬進(jìn)行數(shù)據(jù)庫(kù)交互操作......");
        System.out.println("Cache updated,value:" + CategoryCacheConstants.CATEGORY_DOMAIN
                + ",key:" + category.getCategoryCacheKey()
                + ",category:" + category);
        return 1;
    }

    @Cacheable(value = CategoryCacheConstants.CATEGORY_DOMAIN,
            key = "#category.getCategoryCacheKey()")
    public Category get(Category category) {
        System.out.println("模擬進(jìn)行數(shù)據(jù)庫(kù)交互操作......");
        Category result = new Category();
        result.setCateId(category.getCateId());
        result.setCateName(category.getCateId() + "CateName");
        result.setParentId(category.getCateId() - 10);
        return result;
    }
}

CategoryService 通過(guò)對(duì) category 對(duì)象的數(shù)據(jù)庫(kù)增刪改查,模擬緩存失效和緩存增加的結(jié)果。使用非常簡(jiǎn)便,把注解加在方法上,則可以達(dá)到緩存的生效和失效方案。

深入源碼

源碼分析我們分為幾個(gè)方面一步一步解釋其中的實(shí)現(xiàn)原理和實(shí)現(xiàn)細(xì)節(jié)。源碼基于 Spring 4.3.7.RELEASE 分析。

發(fā)現(xiàn)

SpringCache 在方法上使用注解發(fā)揮緩存的作用,緩存的發(fā)現(xiàn)是基于 AOP 的 PointCut 和 MethodMatcher 通過(guò)在注入的 class 中找到每個(gè)方法上的注解,并解析出來(lái)。

首先看到 org.springframework.cache.annotation.SpringCacheAnnotationParser 類:

protected Collection<CacheOperation> parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae) {
	Collection<CacheOperation> ops = null;

	Collection<Cacheable> cacheables = AnnotatedElementUtils.getAllMergedAnnotations(ae, Cacheable.class);
	if (!cacheables.isEmpty()) {
		ops = lazyInit(ops);
		for (Cacheable cacheable : cacheables) {
			ops.add(parseCacheableAnnotation(ae, cachingConfig, cacheable));
		}
	}
	Collection<CacheEvict> evicts = AnnotatedElementUtils.getAllMergedAnnotations(ae, CacheEvict.class);
	if (!evicts.isEmpty()) {
		ops = lazyInit(ops);
		for (CacheEvict evict : evicts) {
			ops.add(parseEvictAnnotation(ae, cachingConfig, evict));
		}
	}
	Collection<CachePut> puts = AnnotatedElementUtils.getAllMergedAnnotations(ae, CachePut.class);
	if (!puts.isEmpty()) {
		ops = lazyInit(ops);
		for (CachePut put : puts) {
			ops.add(parsePutAnnotation(ae, cachingConfig, put));
		}
	}
	Collection<Caching> cachings = AnnotatedElementUtils.getAllMergedAnnotations(ae, Caching.class);
	if (!cachings.isEmpty()) {
		ops = lazyInit(ops);
		for (Caching caching : cachings) {
			Collection<CacheOperation> cachingOps = parseCachingAnnotation(ae, cachingConfig, caching);
			if (cachingOps != null) {
				ops.addAll(cachingOps);
			}
		}
	}

	return ops;
}

這個(gè)方法會(huì)解析 Cacheable、CacheEvict、CachePut 和 Caching 4個(gè)注解,找到方法上的這4個(gè)注解后,會(huì)將注解中的參數(shù)解析出來(lái),作為后續(xù)注解生效的一個(gè)依據(jù)。這里舉例說(shuō)一下 CacheEvict 注解。

CacheEvictOperation parseEvictAnnotation(AnnotatedElement ae, DefaultCacheConfig defaultConfig, CacheEvict cacheEvict) {
	CacheEvictOperation.Builder builder = new CacheEvictOperation.Builder();

	builder.setName(ae.toString());
	builder.setCacheNames(cacheEvict.cacheNames());
	builder.setCondition(cacheEvict.condition());
	builder.setKey(cacheEvict.key());
	builder.setKeyGenerator(cacheEvict.keyGenerator());
	builder.setCacheManager(cacheEvict.cacheManager());
	builder.setCacheResolver(cacheEvict.cacheResolver());
	builder.setCacheWide(cacheEvict.allEntries());
	builder.setBeforeInvocation(cacheEvict.beforeInvocation());

	defaultConfig.applyDefault(builder);
	CacheEvictOperation op = builder.build();
	validateCacheOperation(ae, op);

	return op;
}

CacheEvict 注解是用于緩存失效。這里代碼會(huì)根據(jù) CacheEvict 的配置生產(chǎn)一個(gè) CacheEvictOperation 的類,注解上的 name、key、cacheManager 和 beforeInvocation 等都會(huì)傳遞進(jìn)來(lái)。

另外需要將一下 Caching 注解,這個(gè)注解通過(guò) parseCachingAnnotation 方法解析參數(shù),會(huì)拆分成 Cacheable、CacheEvict、CachePut 注解,也就對(duì)應(yīng)我們緩存中的增加、失效和更新操作。

Collection<CacheOperation> parseCachingAnnotation(AnnotatedElement ae, DefaultCacheConfig defaultConfig, Caching caching) {
	Collection<CacheOperation> ops = null;

	Cacheable[] cacheables = caching.cacheable();
	if (!ObjectUtils.isEmpty(cacheables)) {
		ops = lazyInit(ops);
		for (Cacheable cacheable : cacheables) {
			ops.add(parseCacheableAnnotation(ae, defaultConfig, cacheable));
		}
	}
	CacheEvict[] cacheEvicts = caching.evict();
	if (!ObjectUtils.isEmpty(cacheEvicts)) {
		ops = lazyInit(ops);
		for (CacheEvict cacheEvict : cacheEvicts) {
			ops.add(parseEvictAnnotation(ae, defaultConfig, cacheEvict));
		}
	}
	CachePut[] cachePuts = caching.put();
	if (!ObjectUtils.isEmpty(cachePuts)) {
		ops = lazyInit(ops);
		for (CachePut cachePut : cachePuts) {
			ops.add(parsePutAnnotation(ae, defaultConfig, cachePut));
		}
	}

	return ops;
}

然后回到 AbstractFallbackCacheOperationSource 類:

public Collection<CacheOperation> getCacheOperations(Method method, Class<?> targetClass) {
	if (method.getDeclaringClass() == Object.class) {
		return null;
	}

	Object cacheKey = getCacheKey(method, targetClass);
	Collection<CacheOperation> cached = this.attributeCache.get(cacheKey);

	if (cached != null) {
		return (cached != NULL_CACHING_ATTRIBUTE ? cached : null);
	}
	else {
		Collection<CacheOperation> cacheOps = computeCacheOperations(method, targetClass);
		if (cacheOps != null) {
			if (logger.isDebugEnabled()) {
				logger.debug("Adding cacheable method '" + method.getName() + "' with attribute: " + cacheOps);
			}
			this.attributeCache.put(cacheKey, cacheOps);
		}
		else {
			this.attributeCache.put(cacheKey, NULL_CACHING_ATTRIBUTE);
		}
		return cacheOps;
	}
}

這里會(huì)將解析出來(lái)的 CacheOperation 放在當(dāng)前 Map<Object, Collection<CacheOperation>> attributeCache =?new ConcurrentHashMap<Object, Collection<CacheOperation>>(1024); 屬性上,為后續(xù)攔截方法時(shí)處理緩存做好數(shù)據(jù)的準(zhǔn)備。

注解產(chǎn)生作用

當(dāng)訪問(wèn) categoryService.get(category) 方法時(shí),會(huì)走到 CglibAopProxy.intercept() 方法,這也說(shuō)明緩存注解是基于動(dòng)態(tài)代理實(shí)現(xiàn),通過(guò)方法的攔截來(lái)動(dòng)態(tài)設(shè)置或失效緩存。方法中會(huì)通過(guò) List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); 來(lái)拿到當(dāng)前調(diào)用方法的 Interceptor 鏈。往下走會(huì)調(diào)用 CacheInterceptor 的 invoke 方法,最終調(diào)用 execute 方法,我們重點(diǎn)分析這個(gè)方法的實(shí)現(xiàn)。

private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
	// Special handling of synchronized invocation
	if (contexts.isSynchronized()) {
		CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
		if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
			Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
			Cache cache = context.getCaches().iterator().next();
			try {
				return wrapCacheValue(method, cache.get(key, new Callable<Object>() {
					@Override
					public Object call() throws Exception {
						return unwrapReturnValue(invokeOperation(invoker));
					}
				}));
			}
			catch (Cache.ValueRetrievalException ex) {
				// The invoker wraps any Throwable in a ThrowableWrapper instance so we
				// can just make sure that one bubbles up the stack.
				throw (CacheOperationInvoker.ThrowableWrapper) ex.getCause();
			}
		}
		else {
			// No caching required, only call the underlying method
			return invokeOperation(invoker);
		}
	}

	// Process any early evictions
	processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
			CacheOperationExpressionEvaluator.NO_RESULT);

	// 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<CachePutRequest>();
	if (cacheHit == null) {
		collectPutRequests(contexts.get(CacheableOperation.class),
				CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
	}

	Object cacheValue;
	Object returnValue;

	if (cacheHit != null && cachePutRequests.isEmpty() && !hasCachePut(contexts)) {
		// If there are no put requests, just use the cache hit
		cacheValue = cacheHit.get();
		returnValue = wrapCacheValue(method, cacheValue);
	}
	else {
		// Invoke the method if we don't have a cache hit
		returnValue = invokeOperation(invoker);
		cacheValue = unwrapReturnValue(returnValue);
	}

	// Collect any explicit @CachePuts
	collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);

	// Process any collected put requests, either from @CachePut or a @Cacheable miss
	for (CachePutRequest cachePutRequest : cachePutRequests) {
		cachePutRequest.apply(cacheValue);
	}

	// Process any late evictions
	processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);

	return returnValue;
}

我們的方法沒(méi)有使用同步,走到 processCacheEvicts 方法。

private void processCacheEvicts(Collection<CacheOperationContext> contexts, boolean beforeInvocation, Object result) {
	for (CacheOperationContext context : contexts) {
		CacheEvictOperation operation = (CacheEvictOperation) context.metadata.operation;
		if (beforeInvocation == operation.isBeforeInvocation() && isConditionPassing(context, result)) {
			performCacheEvict(context, operation, result);
		}
	}
}

注意這個(gè)方法傳入的 beforeInvocation 參數(shù)是 true,說(shuō)明是方法執(zhí)行前進(jìn)行的操作,這里是取出 CacheEvictOperation,operation.isBeforeInvocation(),調(diào)用下面方法:

private void performCacheEvict(CacheOperationContext context, CacheEvictOperation operation, Object result) {
	Object key = null;
	for (Cache cache : context.getCaches()) {
		if (operation.isCacheWide()) {
			logInvalidating(context, operation, null);
			doClear(cache);
		}
		else {
			if (key == null) {
				key = context.generateKey(result);
			}
			logInvalidating(context, operation, key);
			doEvict(cache, key);
		}
	}
}

這里需要注意了,operation 中有個(gè)參數(shù) cacheWide,如果使用這個(gè)參數(shù)并設(shè)置為true,則在緩存失效時(shí),會(huì)調(diào)用 clear 方法進(jìn)行全部緩存的清理,否則只對(duì)當(dāng)前 key 進(jìn)行 evict 操作。本文中,doEvict() 最終會(huì)調(diào)用到 ConcurrentMapCache的evict(Object key) 方法,將 key 緩存失效。

回到 execute 方法,走到 Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class)); 這一步,這里會(huì)根據(jù)當(dāng)前方法是否有 CacheableOperation 注解,進(jìn)行緩存的查詢,如果沒(méi)有命中緩存,則會(huì)調(diào)用方法攔截器 CacheInterceptor 的 proceed 方法,進(jìn)行原方法的調(diào)用,得到緩存 key 對(duì)應(yīng)的 value,然后通過(guò) cachePutRequest.apply(cacheValue) 設(shè)置緩存。

public void apply(Object result) {
	if (this.context.canPutToCache(result)) {
		for (Cache cache : this.context.getCaches()) {
			doPut(cache, this.key, result);
		}
	}
}

doPut() 方法最終對(duì)調(diào)用到 ConcurrentMapCache 的 put 方法,完成緩存的設(shè)置工作。

最后 execute 方法還有最后一步 processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue); 處理針對(duì)執(zhí)行方法后緩存失效的注解策略。

優(yōu)缺點(diǎn)

優(yōu)點(diǎn)

  • 方便快捷高效,可直接嵌入多個(gè)現(xiàn)有的 cache 實(shí)現(xiàn),簡(jiǎn)寫了很多代碼,可觀性非常強(qiáng)。

缺點(diǎn)

  1. 內(nèi)部調(diào)用,非 public 方法上使用注解,會(huì)導(dǎo)致緩存無(wú)效。由于 SpringCache 是基于 Spring AOP 的動(dòng)態(tài)代理實(shí)現(xiàn),由于代理本身的問(wèn)題,當(dāng)同一個(gè)類中調(diào)用另一個(gè)方法,會(huì)導(dǎo)致另一個(gè)方法的緩存不能使用,這個(gè)在編碼上需要注意,避免在同一個(gè)類中這樣調(diào)用。如果非要這樣做,可以通過(guò)再次代理調(diào)用,如 ((Category)AopContext.currentProxy()).get(category) 這樣避免緩存無(wú)效。
  2. 不能支持多級(jí)緩存設(shè)置,如默認(rèn)到本地緩存取數(shù)據(jù),本地緩存沒(méi)有則去遠(yuǎn)端緩存取數(shù)據(jù),然后遠(yuǎn)程緩存取回來(lái)數(shù)據(jù)再存到本地緩存。

擴(kuò)展知識(shí)點(diǎn)

  1. 動(dòng)態(tài)代理:JDK、CGLIB代理。
  2. SpringAOP、方法攔截器。

Demo

  • https://github.com/zhuzhenke/common-caches

參考鏈接

  • Spring Cache:https://www.ibm.com/developerworks/cn/opensource/os-cn-spring-cache/index.html

標(biāo)簽: 代碼 開(kāi)發(fā)者 數(shù)據(jù)庫(kù)

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點(diǎn)!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請(qǐng)與原作者聯(lián)系。

上一篇:讓SpringBoot啟動(dòng)更快一點(diǎn)

下一篇:SpringBoot系列三:SpringBoot自定義Starter