一、概述
SpringCache
本身是一个缓存体系的抽象实现,并没有具体的缓存能力,要使用SpringCache
还需要配合具体的缓存实现来完成。
虽然如此,但是SpringCache是所有Spring支持的缓存结构的基础
,而且所有的缓存的使用最后都要归结于SpringCache。
它可以将方法的运行结果
进行缓存;以后再要相同的数据,直接从缓存中获取,不用调用方法;
二、缓存注解
SpringCache缓存功能的实现是依靠下面的这几个注解完成的。
- @EnableCaching:开启缓存功能
- @Cacheable:定义缓存,用于触发缓存
- @CachePut:定义更新缓存,触发缓存更新
- @CacheEvict:定义清除缓存,触发缓存清除
- @Caching:组合定义多种缓存功能
- @CacheConfig:定义公共设置,位于
class
之上
1、@CacheConfig
该注解标注于类
之上,用于配置该类中会用到的一些公共的缓存
相关配置。
@CacheConfig(cacheNames = “pers.liuchengyin.service.blogServiceImpl”)
cachNames
的值pers.liuchengyin.service.blogServiceImpl
就是这个类公共的缓存对象
,就相当于一个名为pers.liuchengyin.service.blogServiceImpl
的Map
对象,下文简称对象
2、@Cacheable
用在查询的方法
上,方法的返回值将被加入缓存
。该注解标注的方法每次被调用前都会触发缓存校验
,校验指定参数的缓存
是否已存在(已发生过相同参数的调用),若存在,直接返回缓存结果,否则执行方法内容,最后将方法执行结果保存到缓存中。
该注解有这些参数:value
、cacheNames
、key
、condition
、unless
、keyGenerator
、cacheManager
、cacheResolver
①value
、cacheNames
是两个同等的参数。cacheNames是Spring4新增的,作为value
的别名,用于指定缓存对象
,不是必须指定的。如果不指定则使用类
上面配置的CacheConfig
配置的cacheNames
。
②key
就是缓存对象存储在Map
集合中的key
值,不是必需,缺省
按照方法的所有参数
组合作 为key
值, 可以自己配置,但需要SpEL
表达式。比如
@Cacheable(key = "#p0") // 表示第一个参数作为缓存的key值
getBlog(Integer id, String name){}
@Cacheable(key = "#id") // 也可以用参数名作为缓存的key值,这里的参数是id
getBlog(Integer id, String name){}
@Cacheable(key = "#user.id") // 甚至可以用对象里的某个属性作为key值
getBlog(Integer id, User user){}
③condition
缓存对象的条件,非必需,也需使用SpEL
表达式,只有满足表达式条件的内容才会被缓存。比如
@Cacheable(key = "#p0",condition = "#username.lenth < 3")
getBlog(Integer id, String username){} // 只有第二个参数长度小于3的时候才缓存进去
④unless
与condition
相似,但unless
是方法被调用之后才做判断的,所以可以对结果result
进行判断是否去缓存。
⑤keyGenerator
用于指定key 生成器
,非必需。
若需要指定一个自定义的key生成器,我们需要去实现 org.springframework.cache.interceptor.KeyGenerator
接口,并使用该参数来指定
⑥cacheManager
用于指定使用哪个缓存管理器
,非必需。只有当有多个缓存管理器时才需要使用。
⑦cacheResolver
用于指定使用那个缓存解析器
,非必需。需通过 org.springframework.cache.interceptor.CacheResolver
接口来实现自己的缓存解析器,并用该参数指定。
3、@CachePut
用于更新缓存
,无论结果
是否已经缓存,都会在方法执行结束插入缓存,相当于更新缓存,一般用于更新方法
和新增方法
之上。其参数与@Cacheable
类似。
4、@CacheEvict
配置于方法上,通常用在删除
方法上,用来从缓存中移除相应数据。 除了同@Cacheable
一样的参数之外,它还有下面两个参数:
①allEntries
,非必需,默认为false
。当为true
时,会移除对应缓存对象的所有缓存数据,也就是移除cacheName
或value
指定的对象的缓存数据。
②beforeInvocation
,非必需, 默认为false
,会在调用方法之后
移除数据。 当为 true
时,会在调用方法之前
移除数据。
5、@Caching
配置于方法上,用于定义复杂的缓存规则,比如
@Caching(
cacheable={
@Cacheable(key = "#name")
},
put={
@CachePut("#result.id"),
@CachePut("#result.email")
}
)
Blog getByBlogNmae(String name){}
6、小结
一般来说,我们都在Service
实现层的类上面使用@CacheConfig
来指定公共缓存配置,在查询
的方法上使用@Cacheable
,在新增/修改
的方法上使用@CachePut
,在删除
的方法上使用@CacheEvict
。
三、原理
SpringBoot中的自动配置类CacheAutoConfiguration
中的缓存配置类有如下几种:
org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration
org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration
org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
其中,SimpleCacheConfiguration
是默认生效的。它的作用就是给容器中注册一个CacheManager
:ConcurrentMapCacheManager。这个Manager可以获取和创建ConcurrentMapCache
类型的组件,可以将数据保存到ConcurrenMap
中。
CacheManager
缓存管理器,它是来管理多个Cache
组件的,对于缓存的CRUD操作实际上是在Cache
组件中完成的。前面所说的cacheName/value
就是每一个Cache
组件的名字。key
也就是缓存的数据,也是就是说Cache
组件里是Key-value键值对。这些键值对是以数组的形式存储在它们所对应的Cache
组件里的。这个Key
和前面所说的keyGenerator
只需选择一个就行,一个是我们指定key
,一个是根据生成器自动生成。
四、运行流程
①@Cacheable
1、方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取。
CacheManager先获取相应的缓存,第一次获取缓存如果没有Cache组件会自动创建。
2、去Cache中查找缓存的内容
3、没有查到缓存就调用目标方法
4、将目标方法返回的结果返回并放进缓存中
5、下一次来调用,因为有这个缓存值,就不会再调用目标方法,直接返回缓存中的值
②@CachePut
1、先调用目标方法 - 这里面就可能涉及数据库的数据修改
2、将目标方法的结果缓存起来(也就是覆盖老的缓存)
该注解所指定的Cache
组件需要与对应@Cacheable
的一致,不然就起不到修改的作用。
五、使用示例
1、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
2、配置ehcache.xml
ehcache.xml是缓存的配置文件,默认在resources下,可以通过yml配置文件修改其读取位置。
spring:
cache:
ehcache:
config:claspath:config/ehcache_config.xml # config文件夹下的echache_config.xml
这里我们就使用默认的
<ehcache>
<diskStore path="java.io.tmpdir/cache"/>
<!-- 默认缓存策略 -->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>
<!--
自定义缓存策略
name:缓存名称
maxElementsInMemory:缓存最大个数,0表示无穷大
eternal:缓存对象是否永久有效,如果为true,忽略timeToIdleSeconds、timeToLiveSeconds
timeToIdleSeconds:允许对象处于空闲状态的最长时间,以秒为单位
timeToLiveSeconds:对象允许存在于缓存中的最长时间,以秒为单位
overflowToDisk:true表示当内存缓存的对象数目达到了maxElementsInMemory界限后,会把溢出的对象写到硬盘缓存中,对象需实现序列化接口
diskPersistent:是否缓存虚拟机重启期数据,是否持久化磁盘缓存
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,以秒为单位
-->
<cache name="student_cache"
maxElementsInMemory="10000"
eternal="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
diskPersistent="true"
diskExpiryThreadIntervalSeconds="600"
/>
</ehcache>
3、创建POJO类
@Data
public class Student implements Serializable {
/** ID */
private Long id;
/** 姓名 */
private String name;
/** 性别 */
private String sex;
/** 班级 */
protected String classGrade;
/** 入学日期 */
private Date admissionDate;
}
4、创建Service类
@Service
@CacheConfig(cacheNames = "student_cache") // 缓存名
public class StudentServiceImpl implements StudentService {
@Override
@Cacheable // 默认以参数组合为Key
public Student getStudentById(Long id) {
System.out.println("直接获取学生信息:");
Student student = new Student();
student.setId(1L);
student.setName("柳成荫");
student.setSex("男");
student.setClassGrade("21班");
student.setAdmissionDate(new Date());
return student;
}
@Override
@CachePut(key = "#student.id") // 指定对象的Id为Key
public Student updateStudent(Student student) {
System.out.println("修改学生信息:");
student.setName("九月清晨");
return student;
}
@Override
@CacheEvict(key = "#id") // 删除Id为Key的缓存
public void deleteStudentById(Long id) {
System.out.println("删除学生信息:");
}
}
5、测试
为了方便测试,直接使用SpringBootTest来进行测试,直接调用Service。
@RunWith(SpringRunner.class)
@SpringBootTest
public class DoTest {
@Autowired
private StudentService studentService;
@Test
public void test1(){
// 调用查询信息
Student student = studentService.getStudentById(1L);
System.out.println(student);
System.out.println("--------------------分割线-----------------------");
// 调用查询信息
Student student1 = studentService.getStudentById(1L);
System.out.println(student1);
System.out.println("--------------------分割线-----------------------");
// 修改学生信息
Student student2 = studentService.updateStudent(student1);
System.out.println(student2);
System.out.println("--------------------分割线-----------------------");
// 调用查询信息
Student student3 = studentService.getStudentById(1L);
System.out.println(student3);
System.out.println("--------------------分割线-----------------------");
// 删除学生信息
studentService.deleteStudentById(1L);
System.out.println("--------------------分割线-----------------------");
// 调用查询信息
Student student4 = studentService.getStudentById(1L);
System.out.println(student4);
}
}
6、运行结果分析
从运行截图和Service里的方法来看:
1、第一次查询时,发现是调用了方法的
2、第二次查询时,没有出现直接获取学生信息
这一句,说明是从缓存中取的结果
3、接着,调用修改学生信息的方法,name
被修改成九月清晨
4、第三次查询时,发现name
变成了九月清晨
,依旧是从缓存中获取的,说明修改了缓存
5、接着,调用删除学生信息的方法
6、第四次查询时,出现直接获取学生信息
这一句,并且name
变为柳成荫
,说明删除缓存了,直接调用方法获取的结果
六、自定义缓存Key
Spring提供了一个root对象生成Key,如下图:
除此之外,我们也可也自定义缓存Key的生成器KeyGenerator
,如下
@Component
public class MyKeyGenerator implements KeyGenerator {
/**
* 自定义生成Key
* @param target 当前对象
* @param method 当前请求的方法
* @param params 方法的参数
* @return
*/
@Override
public Object generate(Object target, Method method, Object... params) {
String className = target.getClass().getSimpleName();
String methodName = method.getName();
String args = "";
for (Object param : params) {
if (param instanceof Long){
args += param;
}else if(param instanceof Student){
Student student = (Student) param;
args += student.getName();
}
}
// 这只是个示例,根据自己需求进行返回Key
return className + methodName + args;
}
}
在使用的时候,我们只需要指定生成策略即可:
@Autowired
private MyKeyGenerator myKeyGenerator;
@Cacheable(keyGenerator = "myKeyGenerator")
public void getStudent(Long id, Student student){
System.out.println("....");
}