《心做し》真是太好听啦 ❤️❤️❤️
最近重构项目,再次使用了一下spring缓存,在这里记录一下。在spring 3.1的时候,引入了对Cache的支持,和事务一样,其实就是做了一个方法aop。spring cache默认将传入参数作为key,将返回值作为value进行保存,如果调用方法有相应的key则不执行方法之间使用aop中的逻辑,即返回缓存结果。
spring cache支持xml和注解的方式,这里我们只谈注解方式
注解 | 说明 |
---|---|
@EnableCaching | 开启缓存功能,放在相应配置类或启动类上 |
@CacheConfig | 缓存配置,设置缓存名称 |
@Cacheable | 执行方法前先查询缓存,有则直接返回缓存数据,否则查询数据再将数据放入缓存;也可以放类上,表示该类所有方法都支持缓存 |
@CachePut | 执行新增或更新方法后,将数据放入缓存中(每次都会执行,保持缓存和数据始终一致) |
@CacheEvict | 清除缓存 |
@Caching | 组合多个缓存操作 |
参数 | 类型 | 说明 |
---|---|---|
proxyTargetClass | boolean | 是否要基于cglib生成代理去实现缓存 |
mode | AdviceMode | 选择缓存模式、默认是AdviceMode.PROXY 可以切换为 AdviceMode#ASPECTJ |
order | int | 设置缓存管理器执行的顺序 |
参数 | 类型 | 说明 |
---|---|---|
cacheNames | String[] | 缓存的名称,别名value |
keyGenerator | String | 缓存key的生成器 |
cacheManager | String | 配置使用那个缓存管理器、和cacheResolver排斥 |
cacheResolver | String | 定义使用那个拦截器、和cacheManager互斥 |
参数 | 类型 | 说明 |
---|---|---|
value | String[] | 缓存的名称,别名cacheNames |
cacheNames | String[] | 缓存的名称,别名value |
key | String | 缓存key的值、默认是以所有的参数作为key、也可以直接配置keyGenerator或者spel表达式 |
keyGenerator | String | 缓存key的生成器 |
cacheManager | String | 配置使用那个缓存管理器、和cacheResolver排斥 |
cacheResolver | String | 定义使用那个拦截器、和cacheManager互斥 |
condition | String | 根据spel表达式来可以配置什么条件下进行缓存 默认全部缓存 |
unless | String | 和condition相反 |
sync | boolean | 是否开启同步功能、默认不开启 |
参数 | 类型 | 说明 |
---|---|---|
value | String[] | 缓存的名称,别名cacheNames |
cacheNames | String[] | 缓存的名称,别名value |
key | String | 缓存key的值、默认是以所有的参数作为key、也可以直接配置keyGenerator或者spel表达式 |
keyGenerator | String | 缓存key的生成器 |
cacheManager | String | 配置使用那个缓存管理器、和cacheResolver排斥 |
cacheResolver | String | 定义使用那个拦截器、和cacheManager互斥 |
condition | String | 根据spel表达式来可以配置什么条件下进行缓存 默认全部缓存 |
unless | String | 和condition相反 |
参数 | 类型 | 说明 |
---|---|---|
value | String[] | 缓存的名称,别名cacheNames |
cacheNames | String[] | 缓存的名称,别名value |
key | String | 缓存key的值、默认是以所有的参数作为key、也可以直接配置keyGenerator或者spel表达式 |
keyGenerator | String | 缓存key的生成器 |
cacheManager | String | 配置使用那个缓存管理器、和cacheResolver排斥 |
cacheResolver | String | 定义使用那个拦截器、和cacheManager互斥 |
condition | String | 根据spel表达式来可以配置什么条件下进行缓存 默认全部缓存 |
allEntries | boolean | 是否删除所有键的缓存 默认不删除 |
beforeInvocation | boolean | 是否在调用此方法前 删除缓存(不管方法执行成功与否都删除) |
参数 | 类型 | 说明 |
---|---|---|
cacheable | Cacheable[] | 多个@Cacheable |
put | CachePut[] | 多个@CachePut |
evict | CacheEvict[] | 多个@CacheEvict |
然后,我们这里介绍一下CacheManger,针对不同的缓存技术,需要实现不同的cacheManager,Spring定义了如下的cacheManger实现。
CacheManger | 描述 |
---|---|
SimpleCacheManager | 使用简单的Collection来存储缓存,主要用于测试 |
ConcurrentMapCacheManager | 使用ConcurrentMap作为缓存技术(默认) |
NoOpCacheManager | 测试用 |
EhCacheCacheManager | 使用EhCache作为缓存技术,以前在hibernate的时候经常用 |
GuavaCacheManager | 使用google guava的GuavaCache作为缓存技术 |
HazelcastCacheManager | 使用Hazelcast作为缓存技术 |
JCacheCacheManager | 使用JCache标准的实现作为缓存技术,如Apache Commons JCS |
RedisCacheManager | 使用Redis作为缓存技术 |
你可以创建不同的CacheManger来配置多个缓存,然后通过CacheManger的bean名称来指定使用哪个CacheManger,类似:
@Bean("redisCacheManager")
@Primary
public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
...
}
@Bean("guavaCacheManager")
public GuavaCacheManager guavaCacheManager(RedisTemplate redisTemplate) {
...
}
调用:
@Cacheable(value = "redisCache",key="#key",cacheManager="redisCacheManager")
public String cacheRedisTest(String key) {
...
return key;
}
@Cacheable(value = "guavaCache",key="#key",cacheManager="guavaCacheManager")
public String cacheGuavaTest(String key) {
...
return key;
}
加上
org.springframework.boot
spring-boot-starter-cache
加在启动类或者配置类上都可以
@SpringBootApplication
@EnableCaching
public class CacheDemoApplication {
public static void main(String[] args) {
SpringApplication.run(CacheDemoApplication.class, args);
}
}
因为懒,我这里就不配置真实数据库了,用日志来判断方法是否执行,正常使用再看数据库数据即可
@Repository
@CacheConfig(cacheNames = "testCache1,testCache2")
public class CacheDao {
@Cacheable(value = "testCache1", key = "#key")
public String cacheableTest(int key) {
System.out.println("执行了cacheableTest方法");
return "返回结果" + key + "啦";
}
@CachePut(value = "testCache2", key = "#key")
public String cachePutTest(int key) {
System.out.println("执行了cachePutTest方法");
return "返回结果" + key + "啦";
}
@CacheEvict(value = "testCache1", allEntries = true, beforeInvocation = true)
public String cacheEvictTest(int key) {
System.out.println("执行了cacheEvictTest方法");
return "返回结果" + key + "啦";
}
/**
* 先清除,再加
*
* @param key
* @return
*/
@Caching(cacheable = @Cacheable(value = "testCache2", key = "#key"),
evict = @CacheEvict(value = "testCache1", key = "#key"))
public String cachingTest(int key) {
System.out.println("执行了cachingTest方法");
return "返回结果" + key + "啦";
}
}
本地缓存的问题当然就是不支持分布式调用啦,下面我们看整合redis来进行分布式缓存。
将本地缓存配置换成相应的redis整合配置,并引入jedis
org.springframework.boot
spring-boot-starter-data-redis
redis.clients
jedis
2.10.2
可以将@EnableCaching移到配置类上,*处填上相应的RedisCluster的host和password
@Configuration
@EnableCaching
public class RedisCacheConfig {
@Bean
public RedisClusterConfiguration getRedisCluster() {
RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration();
Set jedisClusterNodes = new HashSet<>();
String[] add = "***************".split(",");
for (String temp : add) {
String[] hostAndPort = StringUtils.split(temp, ":");
jedisClusterNodes.add(new RedisNode(hostAndPort[0], Integer.parseInt(hostAndPort[1])));
}
redisClusterConfiguration.setClusterNodes(jedisClusterNodes);
redisClusterConfiguration.setPassword("*******");
return redisClusterConfiguration;
}
@Bean
public JedisPoolConfig getJedisPoolConfig() {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(8);
jedisPoolConfig.setMaxIdle(8);
jedisPoolConfig.setMaxWaitMillis(3000);
return jedisPoolConfig;
}
@Bean
public JedisConnectionFactory redisConnectionFactory(RedisClusterConfiguration redisClusterConfiguration, JedisPoolConfig jedisPoolConfig) {
return new JedisConnectionFactory(redisClusterConfiguration, jedisPoolConfig);
}
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory cf) {
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(cf);
//序列化配置
// Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
// ObjectMapper om = new ObjectMapper();
// om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
// jackson2JsonRedisSerializer.setObjectMapper(om);
// redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
// redisTemplate.afterPropertiesSet();
return redisTemplate;
}
//定制缓存管理器的属性,自行做一些扩展和定制
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
//设置缓存过期时间1天
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofDays(1));
return RedisCacheManager
.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
.cacheDefaults(redisCacheConfiguration).build();
}
}
好了,大功告成,是不是非常简单,我们看看效果:
1、缓存配置在dao层(我一般这样)
2、将自身注入
@Bean
public CacheManager cacheManager(RedisTemplate redisTemplate) {
RedisCacheManager cacheManager= new RedisCacheManager(redisTemplate);
Map expiresMap=new HashMap<>();
expiresMap.put("Product",5L);
cacheManager.setExpires(expiresMap);
return cacheManager;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
//设置缓存过期时间1天
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofDays(1));
return RedisCacheManager
.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
.cacheDefaults(redisCacheConfiguration).build();
}
或者不用builder模式,用构造函数也行:
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
//设置缓存过期时间
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(30));
RedisCacheManager cacheManager = new RedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory), redisCacheConfiguration);
return cacheManager;
}
总的来说,spring给我提供了这个机制还是很方便的,其实我们还可以继续拓展,
*
等表达式。这个我们可以自己单独写个工具类或者写个aop切面,再或者使用自定义注解实现清除,因为本质上就是清理redis当中的key而已,而redis是支持*
操作的,这个以后看有没有机会在单独写一篇文章来介绍吧,这里就不赘述了