我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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

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

具体整个流程是这样的

在这里插入图片描述

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");
 
    
    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;
    }
 
    
    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> { 
    
    String getName();
 
    
    V put(K key, V value);
 
    
    V put(K key, V value, long expire, TimeUnit timeUnit);
 
    
    V get(K key);
 
    
    void remove(K key);
 
    
    Set<K> keys();
 
    
    Set<K> keys(K pattern);
 
    
    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 { 
    
    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



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;
    }
 
    
    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使用方法一致。

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

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Spring Boot怎么利用拦截器加缓存完成接口防刷

小编给大家分享一下Spring Boot怎么利用拦截器加缓存完成接口防刷,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!为什么需要接口防刷为了减缓服务器压力,将服务
2023-06-29

Asp.net Core 3.1基于AspectCore实现AOP实现事务、缓存拦截器功能

最近想给我的框架加一种功能,就是比如给一个方法加一个事务的特性Attribute,那这个方法就会启用事务处理。给一个方法加一个缓存特性,那这个方法就会进行缓存。 这个也是网上说的面向切面编程AOP。 AOP的概念也很好理解,跟中间件差不多,
2022-06-07

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录