關(guān)于Spring?Cache?緩存攔截器(?CacheInterceptor)
Spring Cache 緩存攔截器( CacheInterceptor)
打開(kāi)Spring Cache的核心緩存攔截器CacheInterceptor,可以看到具體實(shí)現(xiàn):
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默認(rèn)實(shí)現(xiàn)了Spring aop的MethodInterceptor接口,MethodInterceptor的功能是做方法攔截。攔截的方法都會(huì)調(diào)用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);
//獲取執(zhí)行方法上所有的緩存操作集合。如果有緩存操作則執(zhí)行到execute(...),如果沒(méi)有就執(zhí)行invoker.invoke()直接調(diào)用執(zhí)行方法了
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:執(zhí)行方法后,將方法返回結(jié)果存放到緩存中。不管有沒(méi)有緩存過(guò),執(zhí)行方法都會(huì)執(zhí)行,并緩存返回結(jié)果(unless可以否決進(jìn)行緩存)。(當(dāng)然,這里說(shuō)的緩存都要滿足condition條件)@Cacheable:如果沒(méi)有緩存過(guò),獲取執(zhí)行方法的返回結(jié)果;如果緩存過(guò),則直接從緩存中獲取,不再執(zhí)行方法。@CacheEvict:如果設(shè)置了beforeIntercepte則在方法執(zhí)行前進(jìn)行緩存刪除操作,如果沒(méi)有,則在執(zhí)行方法調(diào)用完后進(jìn)行緩存刪除操作。
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的緩存數(shù)據(jù)
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
// 如果@Cacheable沒(méi)有被緩存,那么就需要將數(shù)據(jù)緩存起來(lái),這里將@Cacheable操作收集成CachePutRequest集合,以便后續(xù)做@CachePut緩存數(shù)據(jù)存放。
List<CachePutRequest> cachePutRequests = new LinkedList<CachePutRequest>();
if (cacheHit == null) {
collectPutRequests(contexts.get(CacheableOperation.class),
CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
}
Object cacheValue;
Object returnValue;
//如果沒(méi)有@CachePut操作,就使用@Cacheable獲取的結(jié)果(可能也沒(méi)有@Cableable,所以result可能為空)。
if (cacheHit != null && cachePutRequests.isEmpty() && !hasCachePut(contexts)) {
//如果沒(méi)有@CachePut操作,并且cacheHit不為空,說(shuō)明命中緩存了,直接返回緩存結(jié)果
cacheValue = cacheHit.get();
returnValue = wrapCacheValue(method, cacheValue);
}
else {
// 否則執(zhí)行具體方法內(nèi)容,返回緩存的結(jié)果
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;
}
//根據(jù)key從緩存中查找,返回的結(jié)果是ValueWrapper,它是返回結(jié)果的包裝器
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;
}
具體整個(gè)流程是這樣的

CacheInterceptor.java
項(xiàng)目中基本上都需要使用到Cache的功能, 但是Spring提供的Cacheable并不能很好的滿足我們的需求, 所以這里自己借助Spring思想完成自己的業(yè)務(wù)邏輯.
定義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;
/**
* 過(guò)期時(shí)間
*/
private long timeout;
/**
* 過(guò)期時(shí)間單位
*/
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();
/**
* 添加一個(gè)緩存實(shí)例
*
* @param key
* @param value
*/
V put(K key, V value);
/**
* 添加一個(gè)可過(guò)期的緩存實(shí)例
* @param key
* @param value
* @param expire
* @param timeUnit
* @return
*/
V put(K key, V value, long expire, TimeUnit timeUnit);
/**
* 返回緩存數(shù)據(jù)
*
* @param key
* @return
*/
V get(K key);
/**
* 刪除一個(gè)緩存實(shí)例, 并返回緩存數(shù)據(jù)
*
* @param key
* @return
*/
void remove(K key);
/**
* 獲取所有的緩存key
* @return
*/
Set<K> keys();
/**
* 獲取所有的緩存key
* @return
*/
Set<K> keys(K pattern);
/**
* 獲取所有的緩存數(shù)據(jù)
* @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;
}
}
實(shí)現(xiàn)CacheInterceptor.java
/**
* 緩存數(shù)據(jù)過(guò)濾器, 緩存到redis數(shù)據(jù)中的數(shù)據(jù)是ServiceResult.getDateMap()數(shù)據(jù)
* 使用: 在service方法上添加com.chinaredstar.urms.annotations.Cacheable注解, 并指定RedisKeyEunm和cache key, cache key支持Spel表達(dá)式
* 以下情況不緩存數(shù)據(jù):
* 1: 返回狀態(tài)為fasle時(shí), 不緩存數(shù)據(jù)
* 2: 返回dataMap為空時(shí), 不緩存數(shù)據(jù)
* 3: 返回?cái)?shù)據(jù)結(jié)構(gòu)不是ServiceReslut實(shí)例時(shí), 不緩存數(shù)據(jù)
*
* 當(dāng)緩存問(wèn)題時(shí), 不影響正常業(yè)務(wù), 但所有的請(qǐng)求都會(huì)打到DB上, 對(duì)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(">>>>>>>> -- 從緩存中獲取數(shù)據(jù)異常 : {}", ExceptionUtil.exceptionStackTrace(e));
}
if (value != null) {
logger.info(">>>>>>>> -- 從緩存中獲取數(shù)據(jù) : {}", JsonUtil.toJson(value));
return ServiceResult.newInstance(true, value);
}
value = methodInvocation.proceed();
logger.info(">>>>>>>> -- 從接口中獲取數(shù)據(jù) : {}", 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(">>>>>>>> -- 將數(shù)據(jù)放入緩存異常 : {}", 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>
測(cè)試使用
@Cacheable(value = RedisKey.TEST_CACHE, key = "#code + ':' + #user.id")
public ServiceResult<String> test(String code, User user){
return new ServiceResult("success");
}
說(shuō)明
Cacheable其中的參數(shù)key拼接的規(guī)則支持Spring SpeL表達(dá)式。其規(guī)則和Spring Cacheable使用方法一致。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java實(shí)現(xiàn)Excel批量導(dǎo)入數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)Excel批量導(dǎo)入數(shù)據(jù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-08-08
Java連接Oracle數(shù)據(jù)庫(kù)并查詢
這篇文章主要介紹了Java連接Oracle數(shù)據(jù)庫(kù)并查詢的相關(guān)資料,需要的朋友可以參考下2017-04-04
spring帶bean和config如何通過(guò)main啟動(dòng)測(cè)試
這篇文章主要介紹了spring帶bean和config,通過(guò)main啟動(dòng)測(cè)試,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07
Java Socket實(shí)現(xiàn)的傳輸對(duì)象功能示例
這篇文章主要介紹了Java Socket實(shí)現(xiàn)的傳輸對(duì)象功能,結(jié)合具體實(shí)例形式分析了java socket傳輸對(duì)象的原理及接口、客戶端、服務(wù)器端相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-06-06
Java實(shí)戰(zhàn)項(xiàng)目 圖書(shū)管理系統(tǒng)
這篇文章主要介紹了使用java SSM jsp mysql maven設(shè)計(jì)實(shí)現(xiàn)的精品圖書(shū)管理系統(tǒng),是一個(gè)很好的實(shí)例,對(duì)大家的學(xué)習(xí)和工作具有借鑒意義,建議收藏一下2021-09-09
spring boot + jpa + kotlin入門(mén)實(shí)例詳解
這篇文章主要介紹了spring boot + jpa + kotlin入門(mén)實(shí)例詳解 ,需要的朋友可以參考下2017-07-07
SpringBoot使用Mybatis注解實(shí)現(xiàn)分頁(yè)動(dòng)態(tài)sql開(kāi)發(fā)教程
這篇文章主要為大家介紹了SpringBoot使用Mybatis注解實(shí)現(xiàn)分頁(yè)及動(dòng)態(tài)sql開(kāi)發(fā)教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03

