当前位置: 首页 > news >正文

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

相关文章:

  • BASNet调研
  • Android Kotlin 基础知识codelab activity 和 fragment 生命周期
  • 数据结构---KMP算法
  • PHP——运算符
  • 笔试强训48天——day25
  • 有了@MapperScan就不用@Mapper了你知道嘛
  • Docker之Nacos的持久化和集群部署
  • 前端——表单相关的属性(上)
  • 【C++初阶7-stringOJ】上手用一下
  • 【Java 实战】通过ElasticSearch实现全局搜索功能
  • webgis —— 为瓦片构建缓存
  • 最惨面试季:“这么简单的9道题,我刷掉了90%的测试员。”
  • c++11 function模板:模板特化与可变参数函数模板
  • CSDN竞赛14期题解
  • Qt创建线程的几种方式_创建一个新线程的方法
  • [译]Python中的类属性与实例属性的区别
  • 【Leetcode】104. 二叉树的最大深度
  • 【每日笔记】【Go学习笔记】2019-01-10 codis proxy处理流程
  • JAVA并发编程--1.基础概念
  • Magento 1.x 中文订单打印乱码
  • oschina
  • Spring核心 Bean的高级装配
  • Sublime text 3 3103 注册码
  • Vim 折腾记
  • web标准化(下)
  • 工程优化暨babel升级小记
  • 简单数学运算程序(不定期更新)
  • 使用 @font-face
  • 使用docker-compose进行多节点部署
  • 体验javascript之美-第五课 匿名函数自执行和闭包是一回事儿吗?
  • 新版博客前端前瞻
  • 正则学习笔记
  • python最赚钱的4个方向,你最心动的是哪个?
  • ​插件化DPI在商用WIFI中的价值
  • ​软考-高级-信息系统项目管理师教程 第四版【第23章-组织通用管理-思维导图】​
  • # 计算机视觉入门
  • ###项目技术发展史
  • #git 撤消对文件的更改
  • #LLM入门|Prompt#2.3_对查询任务进行分类|意图分析_Classification
  • (003)SlickEdit Unity的补全
  • (1)(1.11) SiK Radio v2(一)
  • (PHP)设置修改 Apache 文件根目录 (Document Root)(转帖)
  • (Redis使用系列) Springboot 在redis中使用BloomFilter布隆过滤器机制 六
  • (翻译)terry crowley: 写给程序员
  • (论文阅读30/100)Convolutional Pose Machines
  • (十)【Jmeter】线程(Threads(Users))之jp@gc - Stepping Thread Group (deprecated)
  • (顺序)容器的好伴侣 --- 容器适配器
  • .dwp和.webpart的区别
  • .net on S60 ---- Net60 1.1发布 支持VS2008以及新的特性
  • .Net高阶异常处理第二篇~~ dump进阶之MiniDumpWriter
  • .net下简单快捷的数值高低位切换
  • @PreAuthorize注解
  • @Transactional 竟也能解决分布式事务?
  • @Valid和@NotNull字段校验使用
  • [20170713] 无法访问SQL Server