1、Spring缓存
1.1、缓存依赖
org.springframework.boot
spring-boot-starter-cache
1.2、缓存注解
@EnableCaching:在主入口类上添加该注解,用于开启缓存;
@CacheConfig:标注在类上,表示当前类使用的缓存组件中的key为该注解指定的cacheNames/value,当该注解指定了cacheNames/value之后,@Cacheable上就无需再使用cacheNames/value了;
-
@Cacheable:将方法的结果进行缓存;
cacheNames/value:缓存组件的名字;
key:缓存数据使用的key,默认是使用方法参数的值,可以使用SpEL表达式,比如#id == #a0 == #p0 == #root.args[0]都表示使用第一个参数的值作为缓存的key;
keyGenerator:key的生成器,可以自己指定key的生成器的组件id;
cacheManager:指定缓存管理器;
cacheResolver:指定获取解析器;
-
condition:指定符合条件的情况下才缓存,支持SpEL表达式;
- condition=“#id>1”:表示id的值大于1时才缓存。
-
unless:否定缓存,当unless指定的条件为true,方法的返回值就不会被缓存,该属性可以获取到结果进行判断,unless与condition刚好相反;
- unless=“#a0==2”:表示第一个参数的值为2时,查询结果不缓存。
-
sync:是否使用异步模式,使用sync后unless就不支持了;
其中keyGenerator与key二者只能选一,cacheResolver与cacheManager也二者选一。
-
@CachePut:用于更新缓存,它先调用方法,然后将目标方法的结果进行缓存。
能够更新缓存的前提是,@CachePut更新使用的key与@Cacheable使用的key要保持一致。
@CachePut注解属性同@Cacheable的属性类似,少了sync属性。
-
@CacheEvict:缓存清除
同样如果要清除缓存,则使用的key值要与@Cacheable使用的key一致,否则达不到清除缓存的功能。
@CacheEvict注解属性比@Cacheable注解少了sync和unless,但是多了两个属性:allEntries和beforeInvocation。
allEntries:表示是否删除所有缓存。
beforeInvocation:表示删除缓存的操作是否在目标方法执行之前。
1.3、原理
CacheAutoConfiguration是缓存的自动配置类。
@Configuration
@ConditionalOnClass(CacheManager.class)
@ConditionalOnBean(CacheAspectSupport.class)
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
@EnableConfigurationProperties(CacheProperties.class)
@AutoConfigureBefore(HibernateJpaAutoConfiguration.class)
@AutoConfigureAfter({ CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class,
RedisAutoConfiguration.class })
@Import(CacheConfigurationImportSelector.class)
public class CacheAutoConfiguration {
//other code...
static class CacheConfigurationImportSelector implements ImportSelector {
@Override
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;
}
}
}
缓存配置类(有顺序):
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
开启debug=true,查看控制台,可以发现:默认cache配置类生效的只有SimpleCacheConfiguration
@Configuration
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class SimpleCacheConfiguration {
private final CacheProperties cacheProperties;
private final CacheManagerCustomizers customizerInvoker;
SimpleCacheConfiguration(CacheProperties cacheProperties,
CacheManagerCustomizers customizerInvoker) {
this.cacheProperties = cacheProperties;
this.customizerInvoker = customizerInvoker;
}
@Bean
public ConcurrentMapCacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
List cacheNames = this.cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return this.customizerInvoker.customize(cacheManager);
}
}
该类给容器中注册了一个ConcurrentMapCacheManager类型的cacheManager组件。
public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {
private final ConcurrentMap cacheMap = new ConcurrentHashMap(16);
//other code...
//从缓存ConcurrentMap对象中获取cacheNames/value指定的Cache对象。
@Override
public Cache getCache(String name) {
Cache cache = this.cacheMap.get(name);
if (cache == null && this.dynamic) {
synchronized (this.cacheMap) {
cache = this.cacheMap.get(name);
if (cache == null) {
cache = createConcurrentMapCache(name);
this.cacheMap.put(name, cache);
}
}
}
return cache;
}
}
该组件可以获取和创建ConcurrentMapCache类型的缓存组件,作用是将数据保存在ConcurrentMap对象中。
运行流程:
1)、方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字去缓存中获取(CacheManager先获取相应的缓存),第一次获取缓存,如果没有Cache组件,则会自动创建。
2)、去Cache中查找缓存的内容,使用一个key(默认是方法的参数),其中key是按照某种策略生成的,默认是使用SimpleKeyGenerator生成的。
SimpleKeyGenerator生成key的策略:
如果没有参数,key=new SimpleKey();
如果有一个参数,key=参数值
如果有多个参数,key=new SimpleKey(params);
3)、没有查到缓存,就调用目标方法;如果查到缓存,则直接返回结果,不调用目标方法。
4)、没有查到缓存后去调用目标方法,然后将目标方法返回的结果放进缓存。
1.4、注解解释
@Cacheable:
自定义key:
methodName:当前被调用的方法名,例如#root.methodName
method:当前被调用的方法,例如#root.method.name
target:当前被调用的目标对象,例如#root.target
targetClass:当前被调用的目标对象类,例如#root.targetClass
args:当前被调用的方法的参数列表,例如#root.args[0]
caches:当前方法调用使用的缓存列表,
argument name:方法参数的名字,可以直接#参数名,也可以使用#a0或者#p0的形式,0表示参数索引,例如#id,#a0,#p0等。
例如:
@Cacheable(cacheNames = "emp", key = "#root.methodName+'[' + #id + ']'")
当然也可以自定义KeyGenerator:
@Bean("myKeyGenerator")
public KeyGenerator keyGenerator(){
return (target, method, params) -> method.getName() + "[" + Arrays.asList(params).toString() + "]";
}
则在@Cacheable上使用该自定义的KeyGenerator:
@Cacheable(cacheNames = "emp", keyGenerator = "myKeyGenerator")
@CachePut:同步更新缓存
它主要用于更新缓存,它先调用方法,然后将目标方法的结果进行缓存。但是能够更新缓存的前提是,@CachePut更新使用的key与@Cacheable使用的key要保持一致。
@Cacheable(cacheNames = "emp", key = "#a0")
public Employee getById(Integer id){}
@CachePut(cacheNames = "emp", key = "#result.id")
public Employee update(Employee employee){}
其中#result表示目标方法的返回值。因为@CachePut更新缓存比目标方法晚,所有@CachePut能获取到返回值。而@Cacheable比目标方法早,所以无法使用#result。
@CacheEvict:缓存清除
同样如果要清除缓存,则使用的key值要与@Cacheable使用的key一致,否则达不到清除缓存的功能。
属性allEntries,默认为false,表示不把cacheNames里所有的缓存都删除掉。当该值为true的时候,会把缓存中对应的cacheNames里的所有缓存都删除。
属性beforeInvocation,默认为false,表示删除缓存的操作在目标方法执行之后。当该值为true的时候,则删除缓存的操作在目标方法执行之前。区别:如果设为true,当目标方法执行出现异常后,对应的缓存已经被删除了。如果设为false(默认),当目标方法执行出现异常后,就不会把缓存删除掉。
2、Redis缓存
2.1、redis依赖
org.springframework.boot
spring-boot-starter-data-redis
2.2、redis自动配置类
@Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
@Configuration
@ConditionalOnClass(GenericObjectPool.class)
protected static class RedisConnectionConfiguration {
//other code...
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
public JedisConnectionFactory redisConnectionFactory()
throws UnknownHostException {
return applyProperties(createJedisConnectionFactory());
}
//other code...
}
@Configuration
protected static class RedisConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate
在RedisAutoConfiguration自动配置类中,注册了几个重要的Bean,分别是JedisConnectionFactory,RedisTemplate和StringRedisTemplate。可以在类中直接注入RedisTemplate和StringRedisTemplate。
2.3、redis常用数据类型
String——字符串
List——列表
Set——集合
Hash——散列
ZSet——有序集合
具体命令可参考Redis命令
2.4、使用redis序列化对象
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate employeeRedisTemplate(@Autowired RedisConnectionFactory redisConnectionFactory){
RedisTemplate template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<>(Employee.class);
template.setDefaultSerializer(serializer);
return template;
}
}
使用该redisTemplate:
@Autowired
private RedisTemplate employeeRedisTemplate;
@Test
public void test02(){
Employee e = employeeService.getById(1);
//默认保存对象,使用jdk序列化机制,序列化后的数据保存到redis中
employeeRedisTemplate.opsForValue().set("emp-01", e);
Employee employee = employeeRedisTemplate.opsForValue().get("emp-01");
System.out.println(employee);
}
2.5、redis缓存
一旦引入了redis的starter之后,cache的配置类就变成了RedisCacheConfiguration。
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisTemplate.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {
private final CacheProperties cacheProperties;
private final CacheManagerCustomizers customizerInvoker;
RedisCacheConfiguration(CacheProperties cacheProperties,
CacheManagerCustomizers customizerInvoker) {
this.cacheProperties = cacheProperties;
this.customizerInvoker = customizerInvoker;
}
@Bean
public RedisCacheManager cacheManager(RedisTemplate redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.setUsePrefix(true);
List cacheNames = this.cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return this.customizerInvoker.customize(cacheManager);
}
}
而CacheManager就变成了RedisCacheManager:
//加载缓存
@Override
protected Collection extends Cache> loadCaches() {
Assert.notNull(this.redisOperations, "A redis template is required in order to interact with data store");
Set caches = new LinkedHashSet(
loadRemoteCachesOnStartup ? loadAndInitRemoteCaches() : new ArrayList());
Set cachesToLoad = new LinkedHashSet(this.configuredCacheNames);
cachesToLoad.addAll(this.getCacheNames());
if (!CollectionUtils.isEmpty(cachesToLoad)) {
for (String cacheName : cachesToLoad) {
caches.add(createCache(cacheName));
}
}
return caches;
}
protected RedisCache createCache(String cacheName) {
long expiration = computeExpiration(cacheName);
return new RedisCache(cacheName, (usePrefix ? cachePrefix.prefix(cacheName) : null), redisOperations, expiration,
cacheNullValues);
}
RedisCacheManager通过创建RedisCache来作为缓存组件,然后通过RedisCache来进行缓存的操作。
其中在创建RedisCacheManager组件的时候传入的是RedisTemplate
public RedisTemplate() {}
public void afterPropertiesSet() {
super.afterPropertiesSet();
boolean defaultUsed = false;
if (defaultSerializer == null) {
defaultSerializer = new JdkSerializationRedisSerializer(
classLoader != null ? classLoader : this.getClass().getClassLoader());
}
if (enableDefaultSerializer) {
if (keySerializer == null) {
keySerializer = defaultSerializer;
defaultUsed = true;
}
if (valueSerializer == null) {
valueSerializer = defaultSerializer;
defaultUsed = true;
}
if (hashKeySerializer == null) {
hashKeySerializer = defaultSerializer;
defaultUsed = true;
}
if (hashValueSerializer == null) {
hashValueSerializer = defaultSerializer;
defaultUsed = true;
}
}
if (enableDefaultSerializer && defaultUsed) {
Assert.notNull(defaultSerializer, "default serializer null and not all serializers initialized");
}
if (scriptExecutor == null) {
this.scriptExecutor = new DefaultScriptExecutor(this);
}
initialized = true;
}
问题:如何将RedisCacheManager使用json序列化机制呢?
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate employeeRedisTemplate(@Autowired RedisConnectionFactory redisConnectionFactory){
RedisTemplate template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<>(Employee.class);
template.setDefaultSerializer(serializer);
return template;
}
//自定义缓存管理器CacheManager
@Bean
@Primary
public RedisCacheManager employeeRedisCacheManager(@Autowired RedisTemplate employeeRedisTemplate){
RedisCacheManager manager = new RedisCacheManager(employeeRedisTemplate);
//使用前缀,会使key多一个前缀,默认会使用cacheNames作为key的前缀
manager.setUsePrefix(true);
return manager;
}
}
但是该RedisCacheManager只能操作Employee,如果使用该RedisCacheManager来操作其他Object,就会报错,所以通常情况下,不应该修改默认的RedisCacheManager。
如果有多个CacheManager缓存管理器,则可以在@CacheConfig或者@Cacheable中的属性cacheManager上指定所使用的CacheManager。