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

关于Spring Cache 缓存拦截器( CacheInterceptor)

程序员文章站 2022-06-16 13:07:06
目录spring cache 缓存拦截器( cacheinterceptor)spring cache常用的三种缓存操作具体整个流程是这样的cacheinterceptor.java定义cacheab...

spring cache 缓存拦截器( cacheinterceptor)

打开spring cache的核心缓存拦截器cacheinterceptor,可以看到具体实现:

public class cacheinterceptor extends cacheaspectsupport implements methodinterceptor, serializable {
	@override
	public object invoke(final methodinvocation invocation) throws throwable {
		method method = invocation.getmethod();
		cacheoperationinvoker aopallianceinvoker = new cacheoperationinvoker() {
			@override
			public object invoke() {
				try {
					return invocation.proceed();
				}
				catch (throwable ex) {
					throw new throwablewrapper(ex);
				}
			}
		};
		try {
			return execute(aopallianceinvoker, invocation.getthis(), method, invocation.getarguments());
		}
		catch (cacheoperationinvoker.throwablewrapper th) {
			throw th.getoriginal();
		}
	}
}

cacheinterceptor默认实现了spring aop的methodinterceptor接口,methodinterceptor的功能是做方法拦截。拦截的方法都会调用invoke方法,在invoke方法里面主要缓存逻辑是在execute方法里面,该方法是继承了父类cacheaspectsupport。

protected object execute(cacheoperationinvoker invoker, object target, method method, object[] args) {
		// check whether aspect is enabled (to cope with cases where the aj is pulled in automatically)
		if (this.initialized) {
			class<?> targetclass = gettargetclass(target);
			//获取执行方法上所有的缓存操作集合。如果有缓存操作则执行到execute(...),如果没有就执行invoker.invoke()直接调用执行方法了
			collection<cacheoperation> operations = getcacheoperationsource().getcacheoperations(method, targetclass);
			if (!collectionutils.isempty(operations)) {
				return execute(invoker, method, new cacheoperationcontexts(operations, method, args, target, targetclass));
			}
		}
		return invoker.invoke();
	}

集合collection operations中存放了所有的缓存操作cacheputoperation、cacheableoperation、cacheevictoperation

spring cache常用的三种缓存操作

  • @cacheput:执行方法后,将方法返回结果存放到缓存中。不管有没有缓存过,执行方法都会执行,并缓存返回结果(unless可以否决进行缓存)。(当然,这里说的缓存都要满足condition条件)
  • @cacheable:如果没有缓存过,获取执行方法的返回结果;如果缓存过,则直接从缓存中获取,不再执行方法。
  • @cacheevict:如果设置了beforeintercepte则在方法执行前进行缓存删除操作,如果没有,则在执行方法调用完后进行缓存删除操作。
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);
			}
		}
		// 处理beforeintercepte=true的缓存删除操作
		processcacheevicts(contexts.get(cacheevictoperation.class), true,
				cacheoperationexpressionevaluator.no_result);
		// 从缓存中查找,是否有匹配@cacheable的缓存数据
		cache.valuewrapper cachehit = findcacheditem(contexts.get(cacheableoperation.class));
		// 如果@cacheable没有被缓存,那么就需要将数据缓存起来,这里将@cacheable操作收集成cacheputrequest集合,以便后续做@cacheput缓存数据存放。
		list<cacheputrequest> cacheputrequests = new linkedlist<cacheputrequest>();
		if (cachehit == null) {
			collectputrequests(contexts.get(cacheableoperation.class),
					cacheoperationexpressionevaluator.no_result, cacheputrequests);
		}
		object cachevalue;
		object returnvalue;
		//如果没有@cacheput操作,就使用@cacheable获取的结果(可能也没有@cableable,所以result可能为空)。
		if (cachehit != null && cacheputrequests.isempty() && !hascacheput(contexts)) {
			//如果没有@cacheput操作,并且cachehit不为空,说明命中缓存了,直接返回缓存结果
			cachevalue = cachehit.get();
			returnvalue = wrapcachevalue(method, cachevalue);
		}
		else {
			// 否则执行具体方法内容,返回缓存的结果
			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;
	}
	//根据key从缓存中查找,返回的结果是valuewrapper,它是返回结果的包装器
	private cache.valuewrapper findcacheditem(collection<cacheoperationcontext> contexts) {
		object result = cacheoperationexpressionevaluator.no_result;
		for (cacheoperationcontext context : contexts) {
			if (isconditionpassing(context, result)) {
				object key = generatekey(context, result);
				cache.valuewrapper cached = findincaches(context, key);
				if (cached != null) {
					return cached;
				}
				else {
					if (logger.istraceenabled()) {
						logger.trace("no cache entry for key '" + key + "' in cache(s) " + context.getcachenames());
					}
				}
			}
		}
		return null;
	}
	private cache.valuewrapper findincaches(cacheoperationcontext context, object key) {
		for (cache cache : context.getcaches()) {
			cache.valuewrapper wrapper = doget(cache, key);
			if (wrapper != null) {
				if (logger.istraceenabled()) {
					logger.trace("cache entry for key '" + key + "' found in cache '" + cache.getname() + "'");
				}
				return wrapper;
			}
		}
		return null;
	}

具体整个流程是这样的

关于Spring Cache 缓存拦截器( CacheInterceptor)

cacheinterceptor.java

项目中基本上都需要使用到cache的功能, 但是spring提供的cacheable并不能很好的满足我们的需求, 所以这里自己借助spring思想完成自己的业务逻辑.

定义cacheable注解

@target({elementtype.method, elementtype.type})
@retention(retentionpolicy.runtime)
@inherited
@documented
public @interface cacheable { 
    rediskey value(); 
    string key();
}

定义rediskey.java

public enum rediskeyenum { 
    test_cache("test:", 24, timeunit.hours, "test");
 
    /**
     * 缓存key的前缀
     */
    private string keyprefix;
 
    /**
     * 过期时间
     */
    private long timeout;
 
    /**
     * 过期时间单位
     */
    private timeunit timeunit;
 
    /**
     * 描述
     */
    private string desc; 
    private static final string redis_key_defualt_separator = ":"; 
    rediskey(string keyprefix, long timeout, timeunit timeunit, string desc){
        this.keyprefix = keyprefix;
        this.timeout = timeout;
        this.timeunit = timeunit;
        this.desc = desc;
    }
 
    public long gettimeout() {
        return timeout;
    }
 
    public timeunit gettimeunit() {
        return timeunit;
    }
 
    public string getdesc() {
        return desc;
    }
 
    /**
     * 获取完整的缓存key
     * @param keys
     * @return
     */
    public string getkey(string... keys) {
        if(keys == null || keys.length <= 0){
            return this.keyprefix;
        }
        string rediskey = keyprefix;
        for (int i = 0, length = keys.length; i < length; i++) {
            string key = keys[i];
            rediskey += key;
            if (i < length - 1) {
                rediskey += redis_key_defualt_separator;
            }
        }
        return rediskey;
    }
}

cache.java

public interface cache<k, v> { 
    /**
     * 返回缓存名称
     * @return
     */
    string getname();
 
    /**
     * 添加一个缓存实例
     *
     * @param key
     * @param value
     */
    v put(k key, v value);
 
    /**
     * 添加一个可过期的缓存实例
     * @param key
     * @param value
     * @param expire
     * @param timeunit
     * @return
     */
    v put(k key, v value, long expire, timeunit timeunit);
 
    /**
     * 返回缓存数据
     *
     * @param key
     * @return
     */
    v get(k key);
 
    /**
     * 删除一个缓存实例, 并返回缓存数据
     *
     * @param key
     * @return
     */
    void remove(k key);
 
    /**
     * 获取所有的缓存key
     * @return
     */
    set<k> keys();
 
    /**
     * 获取所有的缓存key
     * @return
     */
    set<k> keys(k pattern);
 
    /**
     * 获取所有的缓存数据
     * @return
     */
    collection<v> values();
 
    /**
     * 清空所有缓存
     */
    void clear();
}

rediscache.java

public class rediscache<k, v> implements cache<k, v> { 
    public static final string default_cache_name =  rediscache.class.getname() + "_cache_name"; 
    private redistemplate<k, v> redistemplate; 
    private valueoperations<k, v> valueoperations; 
    public rediscache(redistemplate redistemplate) {
        this.redistemplate = redistemplate;
        this.valueoperations = redistemplate.opsforvalue();
        datatype datatype = redistemplate.type("a");
    }
 
    @override
    public string getname() {
        return default_cache_name;
    }
 
    @override
    public v put(k key, v value) {
        valueoperations.set(key, value);
        return value;
    }
 
    @override
    public v put(k key, v value, long expire, timeunit timeunit) {
        valueoperations.set(key, value, expire, timeunit);
        return value;
    }
 
    @override
    public v get(k key) {
        return valueoperations.get(key);
    }
 
    @override
    public void remove(k key) {
//        v value = valueoperations.get(key);
        redistemplate.delete(key);
    }
 
    @override
    public set<k> keys() {
        return null;
    }
 
    @override
    public set<k> keys(k pattern) {
        return redistemplate.keys(pattern);
    }
 
    @override
    public collection<v> values() {
        return null;
    }
 
    @override
    public void clear() { 
    }
}

cachemanager.java

public interface cachemanager { 
    /**
     * 获取缓存
     * @return
     */
    cache getcache(string name);
 
    /**
     * 获取所有的缓存名称
     */
    collection<string> getcachenames(); 
}

abstractcachemanager.java

public abstract class abstractcachemanager implements cachemanager, initializingbean, disposablebean { 
    private static final logger logger = loggerfactory.getlogger(abstractcachemanager.class); 
    private final map<string, cache> cachemap = new concurrenthashmap<>(16); 
    private volatile set<string> cachenames = collections.emptyset(); 
    private static final string default_cache_name_suffix = "_cache_name";
 
    @override
    public void afterpropertiesset() throws exception {
        initlalizingcache();
    }
 
    private void initlalizingcache(){
        collection<? extends cache> caches = loadcaches();
        synchronized (this.cachemap) {
            this.cachenames = collections.emptyset();
            this.cachemap.clear();
            set<string> cachenames = new linkedhashset<string>(caches.size());
            for (cache cache : caches) {
                string name = cache.getname();
                if(stringutils.isempty(name)){
                    name = cache.getclass().getname() + default_cache_name_suffix;
                }
                this.cachemap.put(name, cache);
                cachenames.add(name);
            }
            this.cachenames = collections.unmodifiableset(cachenames);
        }
    }
 
    @override
    public cache getcache(string name) {
        cache cache = cachemap.get(name);
        if(cache != null){
            return cache;
        }
        return null;
    }
 
    protected abstract collection<? extends cache> loadcaches(); 
    @override
    public collection<string> getcachenames() {
        return this.cachenames;
    }
 
    @override
    public void destroy() throws exception {
        cachemap.clear();
    }
}

rediscachemanager.java

public class rediscachemanager extends abstractcachemanager { 
    private redistemplate redistemplate; 
    public rediscachemanager(redistemplate redistemplate) {
        this.redistemplate = redistemplate;
    } 
 
    @override
    protected collection<? extends cache> loadcaches() {
        collection<cache<string, object>> caches = new arraylist<>();
        rediscache<string, object> rediscache = new rediscache<>(redistemplate);
        caches.add(rediscache);
        return caches;
    }
}

实现cacheinterceptor.java

/**
 * 缓存数据过滤器, 缓存到redis数据中的数据是serviceresult.getdatemap()数据
 * 使用: 在service方法上添加com.chinaredstar.urms.annotations.cacheable注解, 并指定rediskeyeunm和cache key, cache key支持spel表达式
 * 以下情况不缓存数据:
 *  1: 返回状态为fasle时, 不缓存数据
 *  2: 返回datamap为空时, 不缓存数据
 *  3: 返回数据结构不是servicereslut实例时, 不缓存数据
 *
 * 当缓存问题时, 不影响正常业务, 但所有的请求都会打到db上, 对db有很大的冲击
 */
public class cacheinterceptor implements methodinterceptor { 
    private static final logger logger = loggerfactory.getlogger(cacheinterceptor.class); 
    private static final parameternamediscoverer parameternamediscoverer = new defaultparameternamediscoverer(); 
    private cachemanager cachemanager; 
    public void setcachemanager(cachemanager cachemanager) {
        this.cachemanager = cachemanager;
    }
 
    @override
    public object invoke(methodinvocation methodinvocation) throws throwable {
        method method = methodinvocation.getmethod();
        object[] args = methodinvocation.getarguments();
        cacheable cacheable = method.getannotation(cacheable.class);
        if (cacheable == null) {
            return methodinvocation.proceed();
        }
        string key = parsecachekey(method, args, cacheable.key());
        logger.info(">>>>>>>> -- 获取缓存key : {}", key);
        if(stringutils.isempty(key)){
            return methodinvocation.proceed();
        }
        rediskey rediskey = cacheable.value();
        cache cache = cachemanager.getcache(rediscache.default_cache_name);
        object value = null;
        try{
            value = cache.get(rediskey.getkey(key));
        } catch (exception e){
            logger.info(">>>>>>>> -- 从缓存中获取数据异常 : {}", exceptionutil.exceptionstacktrace(e));
        }
        if (value != null) {
            logger.info(">>>>>>>> -- 从缓存中获取数据 : {}", jsonutil.tojson(value));
            return serviceresult.newinstance(true, value);
        }
        value = methodinvocation.proceed();
        logger.info(">>>>>>>> -- 从接口中获取数据 : {}", jsonutil.tojson(value));
        if ( value != null && value instanceof serviceresult ) {
            serviceresult result = (serviceresult) value;
            if(!result.issuccess() || result.getdatamap() == null){
                return value;
            }
            try{
                cache.put(rediskey.getkey(key), result.getdatamap(), rediskey.gettimeout(), rediskey.gettimeunit());
            } catch (exception e){
                logger.info(">>>>>>>> -- 将数据放入缓存异常 : {}", exceptionutil.exceptionstacktrace(e));
            }
        }
        return value;
    }
 
    /**
     * 使用spel解析缓存key
     * @param method
     * @param args
     * @param expressionstring
     * @return
     */
    private string parsecachekey(method method, object[] args, string expressionstring) {
        string[] parameternames = parameternamediscoverer.getparameternames(method);
        evaluationcontext context = new standardevaluationcontext();
        if (parameternames != null && parameternames.length > 0
                && args != null && args.length > 0
                && args.length == parameternames.length ) {
            for (int i = 0, length = parameternames.length; i < length; i++) {
                context.setvariable(parameternames[i], args[i]);
            }
        }
        expressionparser parser = new spelexpressionparser();
        expression expression = parser.parseexpression(expressionstring);
        return (string) expression.getvalue(context);
    }
}

配置spring.xml

    <bean id="rediscachemanager" class="com.package.cache.rediscachemanager">
        <constructor-arg ref="cacheredistemplate" />
    </bean>  
    <bean id="cacheinterceptor" class="com.package.interceptor.cacheinterceptor" p:cachemanager-ref="rediscachemanager"/>
 
    <!-- 方法拦截器 methodinterceptor -->
    <aop:config proxy-target-class="true">
        <aop:pointcut id="cacheinterceptorpointcut" expression="execution(* com.package..*(..))
                                                                and @annotation(com.package.annotations.cacheable)"/>
        <aop:advisor advice-ref="cacheinterceptor" pointcut-ref="cacheinterceptorpointcut" order="2" />
    </aop:config>

测试使用

@cacheable(value = rediskey.test_cache, key = "#code + ':' + #user.id")
public serviceresult<string> test(string code, user user){ 
    return new serviceresult("success");
}

说明

cacheable其中的参数key拼接的规则支持spring spel表达式。其规则和spring cacheable使用方法一致。

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