一、JSR-107
Java Caching定义了5个核心接口,分别是CachingProvider, CacheManager, Cache, Entry 和 Expiry。
1、CachingProvider定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期访问多个CachingProvider。
2、CacheManager定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。
3、Cache是一个类似Map的数据结构并临时存储以Key为索引的值。一个Cache仅被一个CacheManager所拥有。
4、Entry是一个存储在Cache中的key-value对。
5、Expiry 每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。
二、Spring缓存抽象
Spring从开始定义了org.springframework.cache.Cache
和org.springframework.cache.CacheManager
接口来统一不同的缓存技术,并提供了提供了各种xxxCache的实现;如RedisCache,EhCacheCache , ConcurrentMapCache
等,支持使用JCache(JSR-107)注解简化我们开发;
1、使用@Cacheable
缓存
(1)、导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
(2)、@EnableCaching
开启注解缓存
@EnableCaching
@MapperScan("com.example.springbootcache.mapper")
@SpringBootApplication
public class SpringbootCacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootCacheApplication.class, args);
}
}
(3)、使用注解进行缓存
@Cacheable(value = "emp",key = "#id",condition = "#id>1")
public Employee getEmp(Integer id){
Employee byId = employeeMapper.getById(id);
System.out.println("查询数据库");
return byId;
}
注解作用:
value:指定缓存组件Cache的名称,并将方法执行结果存储在其中。一个Cache可以储存多个key-value缓存键值对。可以同时指定多个,让一个键值对存储在多个Cache中。
key:缓存的键,如果不指定,则默认是使用方法参数的值,指定的话可以使用SpEL表达式。例如:#id。
除获取参数值外,Cache中SpEL表达式还可以获取如下值:
#root.methodName: 当前被调用的方法名
#root.method.name: 当前被调用的方法名
#root.target:当前被调用的目标对象
#root.targetClass:当前被调用的目标对象的类
#root.args[0]:当前参数列表的第一个值
#a0:方法的参数,a为参数名臣,0为索引
#result: 方法执行的返回值
condition:指定符合条件的情况下才缓存
unless:否定缓存;即当unless指定的条件为满足是,方法的返回值就不会被缓存
cacheManager:指定缓存管理器;或者cacheResolver指定获取解析器
sync:是否使用异步模式
2、缓存原理分析
(1)、缓存自动配置类,根据注解可以看出,这个类中导入了一个CacheConfigurationImportSelector
,用于收集需要导入的配置类。
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({CacheManager.class})
@ConditionalOnBean({CacheAspectSupport.class})
@ConditionalOnMissingBean(
value = {CacheManager.class},
name = {"cacheResolver"}
)
@EnableConfigurationProperties({CacheProperties.class})
@AutoConfigureAfter({CouchbaseDataAutoConfiguration.class, HazelcastAutoConfiguration.class, HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class})
@Import({CacheAutoConfiguration.CacheConfigurationImportSelector.class, CacheAutoConfiguration.CacheManagerEntityManagerFactoryDependsOnPostProcessor.class})
public class CacheAutoConfiguration {
public CacheAutoConfiguration() {
}
(2)、这个类中调用了一个selectImports
方法,这个方法中的CacheType
为一个枚举类,定义了10种缓存类型,通过values
得到所有10种类中,遍历后取得到所有的缓存的类型,并导入。
static class CacheConfigurationImportSelector implements ImportSelector {
CacheConfigurationImportSelector() {
}
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
CacheType[] types = CacheType.values();
String[] imports = new String[types.length];
for(int i = 0; i < types.length; ++i) {
imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
}
return imports;
}
}
public enum CacheType {
GENERIC,
JCACHE,
EHCACHE,
HAZELCAST,
INFINISPAN,
COUCHBASE,
REDIS,
CAFFEINE,
SIMPLE,
NONE;
(3)、经过断电调试,最终确定SimpleCacheConfiguration
为默认生效的配置类。这个类为容器中注入了一个ConcurrentMapCacheManager,通过这个Manager可以获取到Cache组件,ConcurrentMapCache调用其put方法数据保存在ConcurrentMap中。
@Bean
ConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers) {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
List<String> cacheNames = cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return (ConcurrentMapCacheManager)cacheManagerCustomizers.customize(cacheManager);
}
protected Cache createConcurrentMapCache(String name) {
SerializationDelegate actualSerialization = this.isStoreByValue() ? this.serialization : null;
return new ConcurrentMapCache(name, new ConcurrentHashMap(256), this.isAllowNullValues(), actualSerialization);
}
(4)、方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;
* (CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建。
(5)、去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;
* key是按照某种策略生成的;默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key;
* SimpleKeyGenerator生成key的默认策略;
* 如果没有参数;key=new SimpleKey();
* 如果有一个参数:key=参数的值
* 如果有多个参数:key=new SimpleKey(params);
key和KeyGenerator只能同时使用一个。 KeyGenerator使用方法如下:
(A)定义keyGenenrator
@Configuration
public class MyKeygenerator {
@Bean
public KeyGenerator myKeyGenerator(){
return new KeyGenerator() {
@Override
public Object generate(Object o, Method method, Object... objects) {
return method.getName()+"["+ Arrays.asList(objects)+"]";
}
};
}
(B)、调用keyGenenrator
此处的其对应的值需是定义的keyGenerator的方法名
@Cacheable(value = "emp",keyGenerator = "myKeyGenerator")
public Employee getEmp(Integer id){
Employee byId = employeeMapper.getById(id);
System.out.println("查询数据库");
return byId;
}
(6)没有查到缓存就调用目标方法;
(7)、将目标方法返回的结果,放进缓存中
2、@CachePut
的使用
@CachePut
:既调用方法,又更新缓存数据;例如在修改操作时,修改了数据库的某个数据,同时更新缓存。
默认情况下:先调用目标方法,之后再将目标方法的结果缓存起来。在一下两种情况下,修改更新后,getEmp中的缓存值不会发生变化,因为updateEmp()
和getEmp()
的key值不一致,导致存储为两个键值对。若要一直,就要保证键相同,可以在updateEmp()
中使用key = "#employee.id"
或者"#result.id"
,但是@Cacheable
不可已使用"#result.id"
,因为它是在方法执行前进行缓存查询和创建Cache
,此时没有result结果。
@CachePut(/*value = "emp",*/key = "#employee")
public Employee updateEmp(Employee employee){
System.out.println("updateEmp:"+employee);
employeeMapper.update(employee);
return employee;
}
@Cacheable(value = "emp",key = "#id")
public Employee getEmp(Integer id){
Employee byId = employeeMapper.getById(id);
System.out.println("查询数据库");
return byId;
}
3、@CacheEvict
的使用
@CacheEvict
:清除缓存数据
@CacheEvict(value="emp",key = "#id",beforeInvocation = true)
public void deleteEmp(Integer id){
empMapper.deleteEmpById(id);
}
注解内容说明:
key:根据键指定清除缓存
allEntries:默认为false。若配置`allEntries = true`,则清除当前Cache中的所有缓存内容,此时可不再配置key。
beforeInvocation:默认为false,即在方法执行完毕后再清除缓存。但当方法出现异常时,则不会清除。
设置为true,则代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除
4、@Caching
定义复杂的缓存规则
@Caching(
cacheable = {@Cacheable("……"),@Cacheable("……")},
put = {@CachePut("……"),@CachePut("……")},
evict = {@CacheEvict("……"),@CacheEvict("……")}
)