SpringBoot 整合ehcache 3.x
注:如果您发现任何不正确的内容,或者您想要分享有关本文主题的更多信息,请撰写评论或联系我 [email protected]。
以下环境基于 windows 10 java version “1.8.0_171” 代码编辑器为idea
文中代码可能不太严谨,仅供研究测试使用!
在编写后台项目过程中,遇到后台某个页面会检索大量数据到页面,导致页面反应速度变慢,且这些数据属于变动不频繁的(很少有删、改、增之类的操作),为了更好的提供服务,以最快的速度展示数据,所以想到了添加缓存。
项目使用的是springBoot,就就怎么方便怎么来吧。用spring官方的总比自己写的能硬、更坚挺、更持久!
Cache Abstraction,自spring3.1开始支持像开启事务那样非侵入的添加缓存。spring原话如下
从3.1版开始,Spring框架就支持向现有Spring应用程序透明地添加缓存。与事务支持类似,缓存抽象允许一致地使用各种缓存解决方案,对代码的影响最小。
从Spring 4.1开始,在JSR-107注释和更多定制选项的支持下,缓存抽象得到了显著扩展。
这块解释的内容有点照抄spring,不过都是为了尽快学习掌握。
缓冲区就像你在网上看小视频,比如pornhub之类的,视频需要加载速度,你看的是4K的。而你又很猴急,
4K,是一种高清显示技术。主要应用于电视行业、电影行业、手机行业等。作为电视行业显示技术的革命性突破,4K已经成为行业内的常青树,热度从2012年开始就一直是有增无减。画质技术作为电视的核心要素,与3D、多屏互动等技术相比,画质技术给人们带来的不是一时新鲜感,它是从本质上提升电视的表现力,让用户能够感受到最优秀的画质所带来的视觉盛宴。
这个时候,如果没有缓存的话,网站加载一帧你看一帧。这对于看小片快进的你来说很难受,但是有了缓存之后(还有各种技术),网站会直接加载一分钟(对于你来说是直接看到有十分钟,其实还是一帧一帧,攒到十帧才给你看),你就看的很爽了,直接就完事了。可能解释的不太好。。。看看spring的解释
术语“缓冲区”和“缓存”往往可以互换使用。但是请注意,它们代表不同的东西。传统上,缓冲区用作数据在快实体和慢实体之间的中间临时存储。由于一方必须等待另一方(这会影响性能),缓冲区允许整个数据块(而不是小块)同时移动,从而缓解了这种情况。数据只从缓冲区写入和读取一次。此外,至少有一方知道缓冲区是可见的。
另一方面,缓存根据定义是隐藏的,并且任何一方都不知道缓存的发生。它还提高了性能,但是通过让相同的数据以快速方式多次读取来提高性能。
抽象缓存 是基于方法的,会保存方法的入参以及其结果,如果下次有相同的请求过来直接返回已经存储的结果(在没有清空缓存的情况下)。
缓存抽象的核心是将缓存应用于Java方法,从而根据缓存中可用的信息减少执行的次数。也就是说,每次调用目标方法时,抽象都会应用缓存行为,检查方法是否已经为给定的参数执行。如果已执行,则返回缓存的结果,而不必执行实际的方法。如果方法尚未执行,则执行该方法,并缓存结果并返回给用户,以便在下次调用该方法时返回缓存的结果。这样,对于给定的一组参数,昂贵的方法(无论是CPU绑定的还是io绑定的)只能执行一次,并且结果可以重用,而不必实际再次执行该方法。缓存逻辑被透明地应用,没有任何对调用程序的干扰。
这种方法只适用于保证为给定输入(或参数)返回相同输出(结果)的方法,无论执行了多少次。
简单理解了缓存接下来进入正题。
官网
Ehcache是一种基于标准的开源缓存,可提高性能,卸载数据库并简化可伸缩性。它是使用最广泛的基于Java的缓存,因为它功能强大,经过验证,功能齐全,并与其他流行的库和框架集成。Ehcache可以从进程内缓存扩展到使用TB级缓存的混合进程内/进程外部署。
如果要你自己实现缓存你会如何选择,创建个自定义注解然后aop监听,将查询入参和结果放入ConcurrentHashMap抑或是redis,在对数据的update、insert、delete时删除缓存。你能想到的这些早有人做出来了,能用已经成熟的东西干嘛非要造轮子。
至于为啥用ehcache,我顺手行不,用redis还得搭建redis,麻烦。。。。
spring提供缓存功能,如果还没有定义类型为CacheManager的bean 或名为CacheResolver的CacheResolver的CacheResolver。Spring Boot尝试检测以下提供者(按照指定的顺序)
Generic
JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan, and others)
EhCache 2.x
Hazelcast
Infinispan
Couchbase
Redis
Caffeine
Simple
Generic
JCache
EhCache 2.x
Hazelcast
Infinispan
Couchbase
Redis
Caffeine
Simple
如果找不到其他提供程序,则配置一个使用ConcurrentHashMap作为缓存存储的简单实现。如果应用程序中没有缓存库,这是默认设置 。至于怎么默认找到这个 SimpleCacheConfiguration 想要了解原理,你首先得搞明白springBoot 是如何自动配置的,现阶段知道是默认就行。有兴趣可以看看 org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration#cacheManager
我们先使用默认的 cache。 使用idea直接创建springBoot脚手架,并引入pom依赖
org.springframework.boot
spring-boot-starter-parent
2.1.8.RELEASE
org.springframework.boot
spring-boot-starter-cache
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@EnableCaching // 开启基于注解的缓存
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Description:
* @Author: amarone
* @Created Date: 2019年09月11日
*/
@RestController // @RequestMapping 和 @ResponseBody
public class TestController {
@RequestMapping("/{demoInt}")
public String test(@PathVariable Integer demoInt) { // 接受路径的参数做乘法运算
System.out.println("我要运算了~~~");
return String.valueOf(demoInt * 666);
}
}
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Description:
* @Author: amarone
* @Created Date: 2019年09月11日
*/
@RestController
public class TestController {
@RequestMapping("/{demoInt}")
// @Cacheable注解会先查询是否已经有缓存,有会使用缓存,没有则会执行方法并缓存。
@Cacheable(value = "operation", // 缓存的名称,在 spring配置文件中定义,必须指定至少一个
key = "#demoInt") // 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合。
public String test(@PathVariable Integer demoInt) {
System.out.println("我要运算了~~~");
return String.valueOf(demoInt * 666);
}
// 此处的入参一定要实现 java.io.Serializable 。 Integer类 继承抽象类 Number。 Number实现 Serializable
我要运算了~~~
未重启,刷新页面第二次访问,控制台没有任何输出。这就说明缓存成功。
修改pom,添加ehcache坐标,这里我们使用ehcache 3.X ,不使用 ehcache 2.x
net.sf.ehcache(Ehcache 2.x)
org.ehcache(Ehcache 3.x)
我们需要spring-boot-starter-cache和cache-api依赖,以及依赖ehcache作为缓存提供者。
org.ehcache
ehcache
3.8.0
javax.cache
cache-api
修改配置文件application.yml
spring:
cache:
jcache: # 这里是 jcache 不是 ecache。我们使用的 ecache 3.x
config: classpath:ehcache.xml
新建ehcache.xml
30
1000
10
20
完成以上配置。实际上还不能够使用缓存。还需要在ehcache.xml 添加
这里的 operation 是 TestController.test() 上@Cacheable注解的value值。 这里务必要加上。到这里基本就可以满足基本的要求。
在指定需要添加缓存的方法上加上注释,此处加的注释与上文中SpringBoot使用默认的ConcurrentHashMap 相同
@RequestMapping("/{demoInt}")
// @Cacheable注解会先查询是否已经有缓存,有会使用缓存,没有则会执行方法并缓存。
@Cacheable(value = "operation",// 缓存的名称,在 spring配置文件中定义,必须指定至少一个
key = "#demoInt") // 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合。
public String test(@PathVariable Integer demoInt) {
// 此处的入参一定要实现 java.io.Serializable 。 Integer类 继承抽象类 Number。 Number实现 Serializable
System.out.println("我要运算了~~~");
return String.valueOf(demoInt * 666);
}
此处的 @Cacheable 的value值一定要在 ehcache.xml 中cache标签alias声明
使用 @CacheEvict 注解
@CachEvict
的作用 主要针对方法配置,能够根据一定的条件对缓存进行清空 。
属性 | 解释 | 示例 |
---|---|---|
allEntries | 是否清空所有缓存内容,缺省为 false,如果指定为true,则方法调用后将立即清空所有缓存 | @CachEvict(value=”testcache”,allEntries=true) |
beforeInvocation | 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存 | @CachEvict(value=”testcache”,beforeInvocation=true) |
为了方便测试,我新建了一个对象,并重新编写了测试的Controller
import java.io.Serializable;
/**
* @Description:
* @Author: amarone
* @Created Date: 2019年09月12日
*/
public class Person implements Serializable { // 一定要实现 Serializable!!!!
private static final long serialVersionUID = -680651108611576893L;
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Description:
* @Author: amarone
* @Created Date: 2019年09月11日
*/
@RestController
public class TestController {
@RequestMapping("/{demoInt}")
// @Cacheable注解会先查询是否已经有缓存,有会使用缓存,没有则会执行方法并缓存。
@Cacheable(value = "operation",// 缓存的名称,在 spring配置文件中定义,必须指定至少一个
key = "#demoInt") // 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合。
public Object test(@PathVariable Integer demoInt) {
// 此处的入参一定要实现 java.io.Serializable 。 Integer类 继承抽象类 Number。 Number实现 Serializable
Person person = new Person();
person.setAge(demoInt);
person.setName("张三" + demoInt);
System.out.println(person.toString());
return person;
}
/**
* I'm going to kill all the data
* 方法调用后清空所有缓存
*/
@RequestMapping("/all")
@CacheEvict(value = "operation", allEntries = true)
public String cleanAll() {
return "SUCCESS";
}
/**
* kill one data
*/
@RequestMapping("/kill/{demoInt}")
@CacheEvict(value = "operation", key = "#demoInt")
public String cleanOne(@PathVariable Integer demoInt) {
return "SUCCESS";
}
更多支持的注解以及用法请看 spring 官网
Annotation parameter | Description |
---|---|
value / cacheNames | 要存储方法执行结果的缓存的名称 |
key | The key for the cache entries as Spring Expression Language (SpEL). If the parameter is not specified, a key is created for all method parameters by default. |
keyGenerator | 实现密钥生成器接口的bean的名称,因此允许创建用户定义的缓存密钥。 |
condition | Condition as Spring Expression Language (SpEL) that specifies when a result is to be cached. |
unless | Condition as Spring Expression Language (SpEL) that specifies when a result should not be cached. |