關於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"); /** * 緩存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使用方法一致。
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- spring cache註解@Cacheable緩存穿透詳解
- SpringBoot @Cacheable自定義KeyGenerator方式
- Spring @Cacheable指定失效時間實例
- 使用@CacheEvict 多參數如何匹配刪除
- 如何使用Redis實現電商系統的庫存扣減