前言
以前一直用Redis做缓存,但后来慢慢了解到了Ehcache,貌似Redis的名声早就超越了Ehcache,就很少看到有人提起Ehcache以及文章,但是,还是有必要了解一下的,说不定以后会用的上。更何况Ehcache也是Java领域中比较优秀的缓存,不知道小伙伴发现没,Ehcache正着念反着念,都是 Ehcache。
Ehcache简介
Ehcache是一个用Java实现的使用简单、高速、线程安全的缓存管理类库,提供了用内存、磁盘文件存储,以及分布式存储等管理方案。同时Ehcache作为开放源代码项目,采用限制比较宽松的Apache License V2.0作为授权方式,被广泛地用于Hibernate、Spring 等其他开源系统。
Ehcache的主要特点如下:
-
快速、简单。在过去众多的测试中已经表明Ehcache是最快的Java缓存之一,Ehcache的线程机制是为大型高并发系统设计的,Ehcache的API也易于使用,很容易部署上线和运行。
-
多种缓存策略。提供LRU、LFU和FIFO缓存策略。
-
缓存数据有两级。内存和磁盘,因此无须担心容量问题。缓存在内存和硬盘存储可以伸缩到GB
-
缓存数据会在虚拟机重启的过程中写入磁盘。Ehcache 是第一个引入缓存数据持久化存储的开源Java缓存框架,缓存的数据可以在机器重启后从磁盘上重新获得,可以通过cache.flush方法将缓存写入到磁盘上面。
-
可以通过RMI、 可插入API等方式进行分布式缓存。
-
具有缓存和缓存管理器的监听接口(CacheManagerEventListener、CacheEventListener)。
普通Java项目使用
到目前为止,ehcache最新版本是3.8.1,但是由于大部分的博客都低于这个版本,API可能有差异了,无法参考,所以还是到官网http://www.ehcache.org/documentation/3.8/getting-started.html下。
<!-- https://mvnrepository.com/artifact/org.ehcache/ehcache -->
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.8.1</version>
</dependency>
另外ehcache使用SLF4J进行记录,如果是普通Java项目,还需要加入以下依赖。
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.2</version>
</dependency>
下面是代码示例。
public class Main {
public static void main(String[] args) {
/**
* 构建CacheManager和一个Cache,别名为cache-1,键值对为Long和String
*/
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.withCache("cache-1",
CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, ResourcePoolsBuilder.heap(10)))
.build();
cacheManager.init();
/**
* 获取别名为cache-1的Cache
*/
Cache<Long, String> cache1 =
cacheManager.getCache("cache-1", Long.class, String.class);
/**
* 获取值,如果没有,则返回null
*/
System.out.println(cache1.get(1L));
/**
* put一个值
*/
cache1.put(1L,"缓存值");
System.out.println(cache1.get(1L));
/**
* 通过createCache创建一个Cache
*/
cacheManager.createCache("cache-2",
CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, User.class, ResourcePoolsBuilder.heap(10)));
Cache<Long, User> cache2 =
cacheManager.getCache("cache-2", Long.class, User.class);
System.out.println(cache2.get(1L));
cache2.put(1L,new User("张三"));
System.out.println(cache2.get(1L).getName());
}
}
我们也可以通过XML来配置。关于节点意思可以查看文档http://www.ehcache.org/documentation/3.8/xml.html。
<config
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns='http://www.ehcache.org/v3'
xsi:schemaLocation="http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core.xsd">
<cache alias="cache-1">
<key-type>java.lang.String</key-type>
<value-type>java.lang.String</value-type>
<resources>
<heap unit="entries">20</heap>
<offheap unit="MB">10</offheap>
</resources>
</cache>
</config>
只是构建CacheManager方式不同,其他API都一样。
public class XmlMain {
public static void main(String[] args) {
URL resource = XmlMain.class.getResource("/ehcache.xml");
System.out.println(resource);
Configuration xmlConfig = new XmlConfiguration(resource);
CacheManager myCacheManager = CacheManagerBuilder.newCacheManager(xmlConfig);
myCacheManager.init();
Cache<String, String> cache2 =
myCacheManager.getCache("cache-1", String.class, String.class);
System.out.println(cache2);
cache2.put("1","1");
}
}
Spring Boot整合Ehcache
首先要明确我们要做的事:实现计算某个数平方根的接口,如果此数小于10,则不进行缓存,否则将其缓存,并在5秒后过期。
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.8.1</version>
</dependency>
1.控制器
@RestController
public class TestController {
private Logger log = LoggerFactory.getLogger(TestController.class);
@Autowired
private NumberService numberService;
@GetMapping(path = "/square/{number}")
public String getSquare(@PathVariable Long number) {
log.info("计算{}的平方", number);
return String.format("{\"square\": %s}", numberService.square(number));
}
}
2.Service
我们用@Cacheable注释该方法,让Spring处理缓存。Spring会创建NumberService的代理,拦截对square方法的调用并调用Ehcache。我们还需要提供要使用的缓存的名称以及key。condition是条件,此处条件意思是缓存number大于10的数。
@Service
public class NumberService {
private Logger log = LoggerFactory.getLogger(NumberService.class);
@Cacheable(
value = "squareCache",
key = "#number",
condition = "#number>10")
public BigDecimal square(Long number) {
BigDecimal square = BigDecimal.valueOf(number)
.multiply(BigDecimal.valueOf(number));
log.info("{}的平方是{}", number, square);
return square;
}
}
3.开启缓存
我们需要开启@EnableCaching。
@Configuration
@EnableCaching
public class EhcacheConfig {
}
也可以写到启动类上。
4.XML配置
我们还需要通过xml方式配置缓存。
在resources下创建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"
xsi:schemaLocation="
http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.0.xsd
http://www.ehcache.org/v3/jsr107 http://www.ehcache.org/schema/ehcache-107-ext-3.0.xsd">
<cache alias="squareCache">
<key-type>java.lang.Long</key-type>
<value-type>java.math.BigDecimal</value-type>
<expiry>
<ttl unit="seconds">5</ttl>
</expiry>
<listeners>
<listener>
<class>com.hxl.springbootdemo.list.CacheEventLogger</class>
<event-firing-mode>ASYNCHRONOUS</event-firing-mode>
<event-ordering-mode>UNORDERED</event-ordering-mode>
<events-to-fire-on>CREATED</events-to-fire-on>
<events-to-fire-on>EXPIRED</events-to-fire-on>
</listener>
</listeners>
<resources>
<heap unit="entries">2</heap>
<offheap unit="MB">10</offheap>
</resources>
</cache>
</config>
并在applicaion.properties下加入以下配置
spring.cache.jcache.config=classpath:ehcache.xml
5.监听器
另外还有一个监听器。
public class CacheEventLogger implements CacheEventListener<Object, Object> {
private Logger log = LoggerFactory.getLogger(CacheEventLogger.class);
@Override
public void onEvent(CacheEvent<? extends Object, ? extends Object> cacheEvent) {
log.info("旧值={},key={},新值={},type={}",cacheEvent.getOldValue(),cacheEvent.getKey(),cacheEvent.getNewValue(),cacheEvent.getType().name());
}
}
运行之后在浏览器测试,当我们输入大于10的数字后,会将其缓存下来,过5秒后会过期,如果同样的数字在5秒内继续访问,则不会再次计算。
当输入小于10时,都不会缓存。