欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring @Cacheable指定失效时间实例

程序员文章站 2022-03-06 13:55:33
目录spring @cacheable指定失效时间新版本配置老版本配置@cacheable缓存失效时间策略默认实现及扩展背景spring cache redis实现spring cache 失效时间自...

spring @cacheable指定失效时间

新版本配置

@configuration
@enablecaching
public class rediscacheconfig {
    @bean
    public rediscachemanagerbuildercustomizer rediscachemanagerbuildercustomizer() {
        return (builder) -> {
            for (map.entry<string, duration> entry : rediscachename.getcachemap().entryset()) {
                builder.withcacheconfiguration(entry.getkey(),
                        rediscacheconfiguration.defaultcacheconfig().entryttl(entry.getvalue()));
            }
        };
    }
 
    public static class rediscachename { 
        public static final string cache_10min = "cache_10min"; 
        @getter
        private static final map<string, duration> cachemap; 
        static {
            cachemap = immutablemap.<string, duration>builder().put(cache_10min, duration.ofseconds(10l)).build();
        } 
    } 
 
}

老版本配置

interface cachenames{
    string cache_15mins = "sssss:cache:15m";
        /** 30分钟缓存组 */
    string cache_30mins = "sssss:cache:30m";
        /** 60分钟缓存组 */
    string cache_60mins = "sssss:cache:60m";
        /** 180分钟缓存组 */
    string cache_180mins = "sssss:cache:180m";
}
 
@component
 public class rediscachecustomizer
            implements cachemanagercustomizer<rediscachemanager> {
        /** cachemanager缓存自定义初始化比较早,尽量不要@autowired 其他spring 组件 */
        @override
        public void customize(rediscachemanager cachemanager) {
            // 自定义缓存名对应的过期时间
            map<string, long> expires = immutablemap.<string, long>builder()
                    .put(cachenames.cache_15mins, timeunit.minutes.toseconds(15))
                    .put(cachenames.cache_30mins, timeunit.minutes.toseconds(30))
                    .put(cachenames.cache_60mins, timeunit.minutes.toseconds(60))
                    .put(cachenames.cache_180mins, timeunit.minutes.toseconds(180)).build();
            // spring cache是根据cache name查找缓存过期时长的,如果找不到,则使用默认值
            cachemanager.setdefaultexpiration(timeunit.minutes.toseconds(30));
            cachemanager.setexpires(expires);
        }
    } 
 
  @cacheable(key = "key", cachenames = cachenames.cache_15mins)
    public string demo2(string key) {
        return "abc" + key;
  }

@cacheable缓存失效时间策略默认实现及扩展

之前对spring缓存的理解是每次设置缓存之后,重复请求会刷新缓存时间,但是问题排查阅读源码发现,跟自己的理解大相径庭。所有的你以为都仅仅是你以为!!!!

背景

目前项目使用的spring缓存,主要是cachemanager、cache以及@cacheable注解,spring现有的缓存注解无法单独设置每一个注解的失效时间,spring官方给的解释:spring cache是一个抽象而不是一个缓存实现方案。

因此对于缓存失效时间(ttl)的策略依赖于底层缓存中间件,官方给举例:concurrentmap是不支持失效时间的,而redis是支持失效时间的。

Spring @Cacheable指定失效时间实例

spring cache redis实现

spring缓存注解@cacheable底层的cachemanager与cache如果使用redis方案的话,首次设置缓存数据之后,每次重复请求相同方法读取缓存并不会刷新失效时间,这是spring的默认行为(受一些缓存影响,一直以为每次读缓存也会刷新缓存失效时间)。

可以参见源码:

org.springframework.cache.interceptor.cacheaspectsupport#execute(org.springframework.cache.interceptor.cacheoperationinvoker, java.lang.reflect.method, org.springframework.cache.interceptor.cacheaspectsupport.cacheoperationcontexts)

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, () -> 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<>();
		if (cachehit == null) {
			collectputrequests(contexts.get(cacheableoperation.class),
					cacheoperationexpressionevaluator.no_result, cacheputrequests);
		} 
		object cachevalue;
		object returnvalue;
 
		if (cachehit != null && !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;
	}

因此如果我们需要自行控制缓存失效策略,就可能需要一些开发工作,具体如下。

spring cache 失效时间自行刷新

1:基于spring的cache组件进行定制,对get方法进行重写,刷新过期时间。相对简单,不难;此处不贴代码了。

2:可以使用后台线程进行定时的缓存刷新,以达到刷新时间的作用。

3:使用spring data redis模块,该模块提供对了ttl更新策略的,可以参见:org.springframework.data.redis.core.partialupdate

注意:

spring对于@cacheable注解是由spring-context提供的,spring-context提供的缓存的抽象,是一套标准而不是实现。

而partialupdate是由于spring-data-redis提供的,spring-data-redis是一套spring关于redis的实现方案。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。