破解北京pk10冠亚和值 1比0.95刷流水教程 pk10稳赚技巧方案 北京pk10怎么研究走势 北京pk赛车一天多少期 北京pk赛车历史记录 全天北京pk10赛车计划 北京pk10冠军单双技巧 北京pk两期免费计划 北京pk10冠军公式大全 pk10高手单期人工计划 北京pk10免费人工计划 pk10一天赚300好搞吗 北京pk10杀3码公式 app软件购买北京pk10 北京pk10每天开多少期 北京pk10精准一期计划 北京pk拾全天精准计划 二分pk10怎么玩 pk10怎么引诱别人玩 北京赛車pk10网站 北京pk2期计划在线网站 pk10前三跨度怎么算 赌场最怕什么样的赌法 北京pk赛车彩票官网 怎样控制自己每天赢500 北京pk10正规彩票网站 北京pk10前五1码计划 一无所有怎么白手起家 北京pk10去一尾图解

Spring Cache框架

时间:2019-01-28   来源:尚学堂   阅读:289

本文是缓存系列第三篇,前两篇分别介绍了 Guava 和 JetCache。

前两篇我们讲了 Guava 和 JetCache,它们都是缓存的具体实现,今天给大家分析一下 Spring 框架本身对这些缓存具体实现的支持和融合。使用 Spring Cache 将大大的减少我们的Spring项目中缓存使用的复杂度,提高代码可?#21015;浴?#26412;文将从以下几个方面来认识Spring Cache框架。

背景

SpringCache 产生的背景其实与Spring产生的背景有点类似。由于 Java EE 系统框架?#20998;住?#20302;效,代码可观性低,对象创建和?#35272;?#20851;系复杂, Spring 框架出来了,目前基本上所有的Java后台项目都离不开 Spring 或 SpringBoot (对 Spring 的进一步简化)。现在项目面临高并发的问题越来越多,各类缓存的应用也增多,那么在通用的 Spring 框架上,就需要有一种更加便捷简单的方式,来完成缓存的支持,就这样 SpringCache就出现了。

?#36824;?#39318;先我们需要明白的一点是,SpringCache 并非某一种 Cache 实现的技术,SpringCache 是一种缓存实现的通用技术,基于 Spring 提供的 Cache 框架,让开发者更容易将自己的缓存实现高效便捷的?#24230;?#21040;自己的项目中。?#27604;唬琒pringCache 也提供了本身的简单实现 NoOpCacheManager、ConcurrentMapCacheManager 等。通过 SpringCache,可以快速?#24230;?#33258;己的Cache实现。

用法

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

注意点:

1.开启 EnableCaching 注解,默认没有开启 Cache。

2.配置 CacheManager。

@Bean
@Qualifier("concurrentMapCacheManager")
@Primary
ConcurrentMapCacheManager concurrentMapCacheManager() {
    return new ConcurrentMapCacheManager();
}

这里使用了 @Primary 和 @Qualifier 注解,@Qualifier 注解是给这个 Bean 加一个名?#37073;?#29992;于同一个接口 Bean?的多个实现时,指定当前 Bean?的名?#37073;?#20063;就意味着 CacheManager 可以配置多个,并?#20197;?#19981;同的方法场景下使用。@Primary 注解是当接口 Bean?有多个时,优先注入当前 Bean?。

现在拿 CategoryService 实现来分析。

public class CategoryService {
 
    @Caching(evict = {@CacheEvict(value = CategoryCacheConstants.CATEGORY_DOMAIN,
            key = "#category.getCategoryCacheKey()",
            beforeInvocation = true)})
    public int add(Category category) {
        System.out.println("模拟进行数据库?#25442;?#25805;作......");
        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("模拟进行数据库?#25442;?#25805;作......");
        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("模拟进行数据库?#25442;?#25805;作......");
        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("模拟进行数据库?#25442;?#25805;作......");
        Category result = new Category();
        result.setCateId(category.getCateId());
        result.setCateName(category.getCateId() + "CateName");
        result.setParentId(category.getCateId() - 10);
        return result;
    }
}

CategoryService 通过对 category 对象的数据库增删改查,模拟缓存失效和缓存增加的结果。使用非常简便,把注解加在方法上,则可以达到缓存的生效和失效方案。

深入源码

源码分析我们分为几个方面一步一步解释其中的实现原理和实现细节。源码基于 Spring 4.3.7.RELEASE 分析。

发现

SpringCache 在方法上使用注解发挥缓存的作用,缓存的发现是基于 AOP 的 PointCut 和 MethodMatcher 通过在注入的 class 中?#19994;?#27599;个方法?#31995;?#27880;解,并解析出来。

首先看到 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;
}

这个方法会解析 Cacheable、CacheEvict、CachePut 和 Caching 4个注解,?#19994;?#26041;法?#31995;?#36825;4个注解后,会将注解中的?#38382;?#35299;析出来,作为后续注解生效的一个依据。这里举例说一下 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 注解是用于缓存失效。这里代码会根据 CacheEvict 的配置生产一个 CacheEvictOperation 的类,注解?#31995;?name、key、cacheManager 和 beforeInvocation 等都会传递进来。

另外需要将一下 Caching 注解,这个注解通过 parseCachingAnnotation 方法解析?#38382;?#20250;拆分成 Cacheable、CacheEvict、CachePut 注解,也就对应我们缓存中的增加、失效和更新操作。

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;
}

?#32531;?#22238;到 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;
    }
}

这里会将解析出来的 CacheOperation 放在当前 Map<Object, Collection<CacheOperation>> attributeCache =?new ConcurrentHashMap<Object, Collection<CacheOperation>>(1024); 属性上,为后续拦截方法时处理缓存做好数据的准备。

注解产生作用

当访问 categoryService.get(category) 方法时,会走到 CglibAopProxy.intercept() 方法,这也说明缓存注解是基于动态代理实现,通过方法的拦截来动态设?#27809;?#22833;效缓存。方法中会通过 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); 来拿到当前调用方法的 Interceptor 链。往下走会调用 CacheInterceptor 的 invoke 方法,最终调用 execute 方法,我们重点分析这个方法的实现。

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;
}

我们的方法没有使用同步,走到 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);
        }
    }
}

注意这个方法传入的 beforeInvocation ?#38382;?#26159; true,说明是方法执行前进行的操作,这里是取出 CacheEvictOperation,operation.isBeforeInvocation(),调用下面方法:

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 中有个?#38382;?cacheWide,如果使用这个?#38382;?#24182;设置为true,则在缓存失效时,会调用 clear 方法进行全部缓存的清理,否则只对当前 key 进行 evict 操作。本文中,doEvict() 最终会调用到 ConcurrentMapCache的evict(Object key) 方法,将 key 缓存失效。

回到 execute 方法,走到 Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class)); 这一步,这里会根据当前方法是否有 CacheableOperation 注解,进行缓存的查询,如果没有命中缓存,则会调用方法拦截器 CacheInterceptor 的 proceed 方法,进?#24615;?#26041;法的调用,得到缓存 key 对应的 value,?#32531;?#36890;过 cachePutRequest.apply(cacheValue) 设置缓存。

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

doPut() 方法最终对调用到 ConcurrentMapCache 的 put 方法,完成缓存的设置工作。

最后 execute 方法还有最后一步 processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue); 处理针对执行方法后缓存失效的注解策略。

优缺点

优点

  • 方便快捷高效,可直接?#24230;?#22810;个现有的 cache 实现,简写了很多代码,可观性非常强。

缺点

1.内部调用,非 public 方法上使用注解,会导致缓存无效。由于 SpringCache 是基于 Spring AOP 的动态代理实现,由于代理本身的问题,当同一个类中调用另一个方法,会导致另一个方法的缓存不能使用,这个在编码上需要注意,避免在同一个类中这样调用。如果非要这样做,可以通过再次代理调用,如 ((Category)AopContext.currentProxy()).get(category) 这样避免缓存无效。

2.不能支持多级缓存设置,如默?#31995;?#26412;地缓存取数据,本地缓存没?#24615;?#21435;远端缓存取数据,?#32531;?#36828;程缓存取回来数据再存到本地缓存。

扩展知识点

1.动态代理:JDK、CGLIB代理。

2.SpringAOP、方法拦截器。

Demo

参考链接

相关资讯

  • ?#26412;?#26657;区
  • 山西校区
  • 郑州校区
  • 武汉校区
  • 四川校区
  • 长沙校区
  • 深圳校区
  • 上海校区
  • 广州校区
  • 保定招生办

?#26412;?#28023;淀区校区(总部):?#26412;?#24066;海淀区西三旗街道建?#26576;?#35199;?#20998;?#33150;建华商务大厦东侧二层尚学堂
?#26412;?#20140;南校区:?#26412;?#20134;庄经济开发区科创十四街6号院1号楼 赛蒂国?#20351;?#19994;园
咨询电话:400-009-1906 / 010-56233821
面授课程: JavaEE培训大数据就业班培训大数据云计算周末班培训零基础大数据连读班培训大数据云计算高手班培训人工智能周末班培训人工智能+Python全栈培训H5+PHP全栈工程师培训

山西学区地址:山西省晋中市榆次区大学城大学生活广场万科商业A1座702

郑州学区地址?#27721;幽系繾由?#21153;产业园6号楼4层407
咨询电话:0371-55177956

武汉学区地址?#27721;?#21271;省武汉?#34218;?#22799;区江夏大道26号 宏信悦谷创业园4楼
咨询电话:027-87989193

四川学区地址:成都市高新区锦晖西一街99号布鲁明顿大厦2栋1003室
咨询电话:028-65176856 / 13880900114

网址:http://www.cssxt.com/
咨询电话:0731-83072091

深圳校区地址:深圳市宝安区航城街道航城大道航城创新创业园A4栋210(固戍地铁站C出口)
咨询电话:0755-23061965 / 18898413781

上海尚学堂?#23665;?#26657;区地址:上海市?#23665;?#21306;荣乐东路2369弄45号绿地伯顿大厦2层
咨询电话:021-67690939

广州校区地址:广州市天河区元岗横路31号慧通产业广场B区B1栋6楼尚学堂(地铁3号线或6号线到“天河客运站”D出口,右拐直走约800米)
咨询电话:020-2989 6995

保定招生办公室

地址?#27721;?#21271;省保定市竞秀区朝阳南大街777号鸿悦国际1101室

电话:15132423123

Copyright 2006-2019 ?#26412;?#23578;学堂科技有限公司  京ICP备13018289号-19  京公网安备11010802015183  
媒体联系:18610174079 ?#35780;?#24072;  
pk10单双最好方法