文章目录
- Ehcache 的简单使用
- 背景
- 使用
- 版本
- 配置
- 配置项
- 编程式配置
- XML 配置
- 自定义监听器
- 验证
- 示例代码
- 改进代码
- 备注
- 完整示例代码
- 官方文档
Ehcache 的简单使用
背景
当一个JavaEE-Java Enterprise Edition应用想要对热数据(经常被访问,很少被修改的数据)进行缓存时,在遥远的年代,还没有Redis,开发者们想到的是直接利用JDK中的集合进行缓存。随之而来的问题是,JVM内存毕竟有限,如果热数据太多,过期策略、驱逐策略这些都需要开发者手动编写。那么Ehcache是主要解决这类问题的。同时,如果应该目前只是单机应用,那Ehcache就更适合了,不需要引入Redis从而导致增加系统复杂度,只需要引入一个体积很小的jar包即可,而且Ehcache为了缓存数据的存储,提供了多种方案,例如:直接使用JVM内存、使用堆外内存、使用硬盘来存储。使得开发者专注于业务开发,而无需过多担心脱离业务的数据缓存问题。
Ehcache在单体应用方面表现优秀,Eachce的开发者也同样想到了集群的处理方案。本文只关注单体应用的Ehcache的使用。至于集群方面的处理,个人认为是现在的集群处理一般使用Redis,Ehcache的集群方案使用者不多
使用
示例程序使用一个非常简单的Springboot项目来说明
版本
目前Ehcache的最新版本是3。由于3和之前的版本2发生了较大的变化,所以版本3和2之间是不兼容的。在Ehcache版本2中,还提供了ehcache-web,该功能是缓存整个web页面的响应,结合Filter实现,这个功能在遥远的JavaEE年代的单机应用上,用着确实舒服。不过,版本3中去掉了这个功能,Ehcache3的开发者觉得此功能太过于细化,偏离了Ehcache的方向
配置
Ehcache3提供了XML文件配置和编程式配置方式。无论是XML配置还是编程式配置方式,配置项都是一样的,所以先了解下Ehcache中的常见的配置项
配置项
- cache alias - cache别名,一个应用可能有多个cache,每个cache需要一个名字
- cache key type - 具体一个cache的key的类型
- cache value type - cache key 对应的value 类型
- cache expiry - 过期策略, Ehcache提供三种,永不过期、timeToLive、timeToIdle
- cache resources - 资源配置,配置一个cache中最大的资源数,以及资源的位置,如上文说的堆、堆外、硬盘
- cache listeners - 监听器,主要是监听cache项的某一种事件,例如:创建cache项、删除、过期、更新等
基本配置项就这些,和我们在远古时代,自己写缓存考虑到的几个点基本一致。
编程式配置
@Slf4j
@Configuration
public class EhcacheConfiguration {public static final String CACHE_NAME = "demo";/*** 过期策略* no expiry* timeToLive* timeToIdle-this means cache mappings will expire after a fixed duration following the time they were last accessed* https://www.ehcache.org/documentation/3.9/expiry.html** 存储位置选择:* 1.堆* 2.堆外-需要自己定义资源池* 3.磁盘* 4.集群* https://www.ehcache.org/documentation/3.9/tiering.html** 驱逐策略:* 官方对ehcache3的驱逐策略给的资料较少,而且提示,驱逐时会降低效率。网上查资料有的说,在ehcache看来,所有的缓存对象都是等价的* https://www.ehcache.org/documentation/3.9/eviction-advisor.html* @return org.ehcache.CacheManager*/@Beanpublic CacheManager cacheManager(CacheEventListener<Object, Object> cacheEventListener) {return initCacheManagerFromProgrammatic(cacheEventListener);}public CacheManager initCacheManagerFromProgrammatic(CacheEventListener<Object, Object> cacheEventListener) {return CacheManagerBuilder.newCacheManagerBuilder().withCache(CACHE_NAME,CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, DataVO.class, ResourcePoolsBuilder.heap(2))// 过期策略只能选一种,存在多种,后面的覆盖前面的.withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofSeconds(30))).withExpiry(ExpiryPolicyBuilder.timeToIdleExpiration(Duration.ofMinutes(2))).withExpiry(ExpiryPolicy.NO_EXPIRY)// 配置监听器.withService(initCacheEventListenerConfigurationBuilder(cacheEventListener))).build(true);}/*** cache监听器* @param cacheEventListener* @return*/private CacheEventListenerConfigurationBuilder initCacheEventListenerConfigurationBuilder(CacheEventListener<Object, Object> cacheEventListener) {return CacheEventListenerConfigurationBuilder.newEventListenerConfiguration(cacheEventListener, EventType.CREATED, EventType.EXPIRED, EventType.UPDATED, EventType.REMOVED).unordered().asynchronous();}
}
XML 配置
<?xml version="1.0" encoding="UTF-8"?>
<ehcache:configxmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'xmlns:ehcache='http://www.ehcache.org/v3'xsi:schemaLocation="http://www.ehcache.org/v3 https://www.ehcache.org/schema/ehcache-core-3.9.xsd"><ehcache:cache alias="demo"><ehcache:key-type>java.lang.Long</ehcache:key-type><ehcache:value-type>com.example.ehcache.vo.DataVO</ehcache:value-type><ehcache:expiry><ehcache:tti unit="minutes">1</ehcache:tti></ehcache:expiry><ehcache:listeners><ehcache:listener><ehcache:class>com.example.ehcache.config.CacheEventLogListener</ehcache:class><ehcache:event-firing-mode>ASYNCHRONOUS</ehcache:event-firing-mode><ehcache:event-ordering-mode>UNORDERED</ehcache:event-ordering-mode><!--定义多个监听事件--><ehcache:events-to-fire-on>CREATED</ehcache:events-to-fire-on><ehcache:events-to-fire-on>UPDATED</ehcache:events-to-fire-on><ehcache:events-to-fire-on>REMOVED</ehcache:events-to-fire-on><ehcache:events-to-fire-on>EXPIRED</ehcache:events-to-fire-on></ehcache:listener></ehcache:listeners><ehcache:resources><ehcache:heap unit="entries">10</ehcache:heap></ehcache:resources></ehcache:cache>
</ehcache:config>
注意:这里的listeners标签一定要放在resource标签之前
自定义监听器
我们根据Ehcache的官方接口CacheEventListener,自定义一个监听器,其作用主要是通过log打印事件名字、key、value的值
@Component
@Slf4j
public class CacheEventLogListener implements CacheEventListener<Object, Object> {@Overridepublic void onEvent(CacheEvent<? extends Object, ? extends Object> cacheEvent) {log.info("cacheType is {}, key is {}, oldValue {}, newValue {}", cacheEvent.getType().toString(), cacheEvent.getKey(), cacheEvent.getOldValue(), cacheEvent.getNewValue());}
}
验证
示例代码
我们使用一个简单Controller来验证Ehcache的缓存和自定义监听器的功能
@RestController
@RequestMapping(path = "/data")
public class CommonDataController {@PostMappingpublic Long createDataVO(@RequestBody DataVO data) {Random random = new Random();Long result = random.nextLong();data.setId(result);Cache<Long, DataVO> cache = cacheManager.getCache(EhcacheConfiguration.CACHE_NAME, Long.class, DataVO.class);cache.put(result, data);return result;}@GetMapping(path = "/{id}")public DataVO getCacheData(@PathVariable Long id) {Cache<Long, DataVO> cache = cacheManager.getCache(EhcacheConfiguration.CACHE_NAME, Long.class, DataVO.class);DataVO result;result = cache.get(id);if (Objects.isNull(result)) {throw new RuntimeException("cache not exist");}return result;}
}
这里只列出部分代码,通过一个POST接口和GET接口验证下
curl -X POST -H 'Content-Type: application/json' http://localhost:18080/ehcache3/data
请求成功会返回新数据的ID - 8585661300356241871

根据ID请求
curl -X GET http://localhost:18080/ehcache3/data/8585661300356241871

等到我们配置的过期策略过期之后,再请求同一个ID,可以由上面第一张截图那样,自定义监听器把过期事件打印出来了
改进代码
我们简单的改进一下代码,使得当前应用支持XML配置和编程式配置,在开启特定属性下,使用XML配置,否则使用默认配置。
在application.yml中增加属性如下
ehcache:read-from-xml: true
EhcacheConfiguration修改后的整体配置如下
@Slf4j
@Configuration
public class EhcacheConfiguration {public static final String CACHE_NAME = "demo";@Value("${ehcache.read-from-xml}")private Boolean readFromXml;/*** 过期策略* no expiry* timeToLive* timeToIdle-this means cache mappings will expire after a fixed duration following the time they were last accessed* https://www.ehcache.org/documentation/3.9/expiry.html** 存储位置选择:* 1.堆* 2.堆外-需要自己定义资源池* 3.磁盘* 4.集群* https://www.ehcache.org/documentation/3.9/tiering.html** 驱逐策略:* 官方对ehcache3的驱逐策略给的资料较少,而且提示,驱逐时会降低效率。网上查资料有的说,在ehcache看来,所有的缓存对象都是等价的* https://www.ehcache.org/documentation/3.9/eviction-advisor.html* @return org.ehcache.CacheManager*/@Beanpublic CacheManager cacheManager(CacheEventListener<Object, Object> cacheEventListener) {CacheManager result;if (readFromXml) {result = initCacheManagerFromXml();}else {result = initCacheManagerFromProgrammatic(cacheEventListener);}return result;}private CacheManager initCacheManagerFromXml() {URL resource = getClass().getResource("/ehcache.xml");Objects.requireNonNull(resource);XmlConfiguration xmlConfiguration = new XmlConfiguration(resource);CacheManager result = CacheManagerBuilder.newCacheManager(xmlConfiguration);result.init();return result;}public CacheManager initCacheManagerFromProgrammatic(CacheEventListener<Object, Object> cacheEventListener) {return CacheManagerBuilder.newCacheManagerBuilder().withCache(CACHE_NAME,CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, DataVO.class, ResourcePoolsBuilder.heap(2))// 过期策略只能选一种,存在多种,后面的覆盖前面的.withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofSeconds(30))).withExpiry(ExpiryPolicyBuilder.timeToIdleExpiration(Duration.ofMinutes(2))).withExpiry(ExpiryPolicy.NO_EXPIRY)// 配置监听器.withService(initCacheEventListenerConfigurationBuilder(cacheEventListener))).build(true);}/*** cache监听器* @param cacheEventListener* @return*/private CacheEventListenerConfigurationBuilder initCacheEventListenerConfigurationBuilder(CacheEventListener<Object, Object> cacheEventListener) {return CacheEventListenerConfigurationBuilder.newEventListenerConfiguration(cacheEventListener, EventType.CREATED, EventType.EXPIRED, EventType.UPDATED, EventType.REMOVED).unordered().asynchronous();}
}
到此为止,Ehcache的简单使用就是这样
备注
完整示例代码
- 示例代码仓库
官方文档
- 官方文档,更多详细的配置说明都可以在官方文档中找到













