ehcache使用及缓存不生效处理方法
Posted 我爱看明朝
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ehcache使用及缓存不生效处理方法相关的知识,希望对你有一定的参考价值。
ehcache使用及缓存不生效处理方法
ehcache是什么
ehcache是一个纯java的进程内的缓存框架, 具有快速,精干等特点。java中最应用广泛的一款缓存框架(java’s most widely-used cache)
ehcache是一个基于标准、开源的高性能缓存,扩展简单。因为它健壮、经过验证、功能齐全、方便的和第三方框架与库集成。Ehcache 从进程内缓存一直扩展到具有 TB 大小缓存的进程内/进程外混合部署。
(ehcache is an open source, standards-based cache that boosts performance, offloads your databases, and simplifies scalability, it’s the most
widely-used java-based cache because it’s robust, proven, full-featured, and integrates with other popular libraries and frameworks, ehcache
from in-prrocess caching, all the way to mixed in process/out-of process deployments with terabyte-sized caches.)
优缺点
优点:
1.纯java开发,便于学习,集成进主流的spring boot
2.不依赖中间件
3.快速,简单
4.缓存数据由两级: 内存和磁盘,无需担心容量问题
缺点:
1.多节点不能同步,缓存共享麻烦
2.一般在架构中应用于二级缓存(一级缓存redis ---->二级缓存ehcache -> database)
怎么用
这里我们使用ehcache3
引入依赖包
<!-- ehcache 缓存 -->
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
</dependency>
ehcache.xml配置
<config
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns='http://www.ehcache.org/v3'
xmlns:jsr107='http://www.ehcache.org/v3/jsr107'>
<cache-template name="heap-cache">
<listeners>
</listeners>
<resources>
<heap unit="entries">2000</heap>
<offheap unit="MB">100</offheap>
</resources>
</cache-template>
<cache-template name="defaultCache">
<heap unit="entries">100</heap>
</cache-template>
<cache alias="allViewScenesCount" uses-template="defaultCache">
<expiry>
<ttl unit="hours">4</ttl>
</expiry>
</cache>
</config>
集成到springboot
ehcache集成到springboot。 springboot启动类增加注解@EnableCaching
@Configuration
public class CacheConfig
@Bean
public CacheManager cacheManager() throws FileNotFoundException, URISyntaxException
return new JCacheCacheManager(jCacheManager());
@Bean
public javax.cache.CacheManager jCacheManager() throws FileNotFoundException, URISyntaxException
EhcacheCachingProvider ehcacheCachingProvider = new EhcacheCachingProvider();
URI uri = ResourceUtils.getURL("classpath:ehcache.xml").toURI();
javax.cache.CacheManager cacheManager = ehcacheCachingProvider.getCacheManager(uri, this.getClass().getClassLoader());
return cacheManager;
缓存结果
@Slf4j
@RestController
@RequestMapping("/api/statistics")
public class StatisticsController
@RequestMapping("/allViewScenesCount")
public RestResult<Object> allViewScenesCount(@RequestBody StatisticsDTO statisticsDTO)
StatisticsViewAllVO viewAllVO = statisticsService.allViewScenesCount(statisticsDTO);
return RestResult.ok(viewAllVO);
@Service
@Slf4j
public class StatisticsService
// 这里会使用allViewScenesCount作为key, statisticsDTO的所有属性作为field, StatisticsViewAllVO的所有属性作为value
// 这里注意StatisticsViewAllVO 需要实现 Serializable 接口,否则无法序列化到磁盘
@Cacheable(value = "allViewScenesCount")
public StatisticsViewAllVO allViewScenesCount(StatisticsDTO statisticsDTO)
//do something
原理
请求流程
用户发起请求---> StatisticsController.allViewScenesCount() ---> CglibAopProxy 动态代理 ---> cacheInterceptor 缓存查询与存储 --->
StatisticsService.allViewScenesCount() --> cacheInterceptor 缓存存储 --> CglibAopProxy 代理返回结果
CglibAopProxy(spring)
-------cacheInterceptor(ehcache)
-----------StatisticsService(我们的代码)
-------cacheInterceptor(ehcache)
CglibAopProxy(spring)
ehcache数据结构
ehcache的数据结构: key field value (类似redis的hash)
key: 为注解 @Cacheable(value = "diffMoneyTrend")
filed: 为@Cacheable(value = "diffMoneyTrend", key = '')的key, 如果没有则是方法的参数
spring cglib代理
CglibAopProxy
retVal = (new CglibAopProxy.CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy)).proceed();
proxy: CglibAopProxy
target: statistiocsService
chain: cacheInterceptor
@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable
Object oldProxy = null;
boolean setProxyContext = false;
Object target = null;
TargetSource targetSource = this.advised.getTargetSource();
Object var16;
try
if (this.advised.exposeProxy)
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
// 代理目标bean,我们的statistiocsService
target = targetSource.getTarget();
Class<?> targetClass = target != null ? target.getClass() : null;
// 缓存切面
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal;
// 判断是否有其他调用链,没有,直接代理我们bean
if (chain.isEmpty() && CglibAopProxy.CglibMethodInvocation.isMethodProxyCompatible(method))
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = CglibAopProxy.invokeMethod(target, method, argsToUse, methodProxy);
else
//有其他调用链
retVal = (new CglibAopProxy.CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy)).proceed();
// 返回结果
retVal = CglibAopProxy.processReturnType(proxy, target, method, retVal);
var16 = retVal;
finally
if (target != null && !targetSource.isStatic())
targetSource.releaseTarget(target);
if (setProxyContext)
AopContext.setCurrentProxy(oldProxy);
return var16;
CacheAspectSupport缓存切面
@Nullable
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, handleSynchronizedGet(invoker, key, cache));
catch (Cache.ValueRetrievalException ex)
// Directly propagate ThrowableWrapper from the invoker,
// or potentially also an IllegalArgumentException etc.
ReflectionUtils.rethrowRuntimeException(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 ArrayList<>();
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;
//CacheAspectSupport
private Object execute()
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);
@cacheable不生效,处理
注意:因为控制器调用service通过了cglib代理,只对调用生成代理方法: 因此ehcahce只对一级方法生效:如seviceA,有A,B方法, A内部调用B方法,B加@cacheable缓存不生效。
例如:
B方法注解不生效
@Slf4j
@RestController
@RequestMapping("/api/statistics")
public class StatisticsController
@RequestMapping("/allViewScenesCount")
public RestResult<Object> allViewScenesCount(@RequestBody StatisticsDTO statisticsDTO)
StatisticsViewAllVO viewAllVO = statisticsService.A(statisticsDTO);
return RestResult.ok(viewAllVO);
@Service
@Slf4j
public class StatisticsService
public StatisticsViewAllVO A(StatisticsDTO statisticsDTO)
// 这里调用不会有cglib动态代理,因此也就没有缓存切面了
return b();
@Cacheable(value = "B")
public StatisticsViewAllVO b(StatisticsDTO statisticsDTO)
//do something
修改使B方法缓存生效
@Slf4j
@RestController
@RequestMapping("/api/statistics")
public class StatisticsController
@RequestMapping("/allViewScenesCount")
public RestResult<Object> allViewScenesCount(@RequestBody StatisticsDTO statisticsDTO)
StatisticsViewAllVO viewAllVO = statisticsService.A(statisticsDTO);
return RestResult.ok(viewAllVO);
@Service
@Slf4j
public class StatisticsService
public StatisticsViewAllVO A(StatisticsDTO statisticsDTO)
// 我们强制这里用动态代理, 这里执行就会走切面了
StatisticsService statisticsService = SpringUtil.getBean(StatisticsService.class);
return statisticsService.b();
@Cacheable(value = "B")
public StatisticsViewAllVO b(StatisticsDTO statisticsDTO)
//do something
参考
https://www.jianshu.com/p/154c82073b07
https://www.ehcache.org/
https://www.ehcache.org/documentation/3.10/xml.html
以上是关于ehcache使用及缓存不生效处理方法的主要内容,如果未能解决你的问题,请参考以下文章