使用 Spring Cache 能够极大的简化咱们对数据的缓存,而且它封装了多种缓存,本文基于 redis 来讲明。html
一、所需依赖java
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency>
二、配置文件redis
spring: # redis链接信息 redis: host: 192.168.56.10 port: 6379 cache: # 指定使用的缓存类型 type: redis # 过时时间 redis: time-to-live: 3600000 # 是否开启前缀,默认为true use-key-prefix: true # 键的前缀,若是不配置,默认就是缓存名cacheNames key-prefix: CACHE_ # 是否缓存空置,防止缓存穿透,默认为true cache-null-values: true
三、Spring Cache 提供的注解以下,使用方法参见:官方文档,经过这些注解,咱们能够方便的操做缓存数据。spring
@Cacheable
:触发缓存写入的操做@CacheEvict
:触发缓存删除的操做@CachePut
:更新缓存,而不会影响方法的执行@Caching
:从新组合要应用于一个方法的多个缓存操做,即对一个方法添加多个缓存操做@CacheConfig
:在类级别共享一些与缓存有关的常见设置例如,若是须要对返回结果进行缓存,直接在方法上标注 @Cacheable
注解缓存
@Cacheable(cacheNames = "userList") //指定缓存的名字,便于区分不一样缓存 public List<User> getUserList() { ... }
四、redis 默认使用 jdk 序列化,须要咱们配置序列化机制,自定义一个配置类,不然存入的数据显示乱码app
@EnableCaching //开启缓存 @Configuration public class MyCacheConfig { @Bean public RedisCacheConfiguration redisCacheConfiguration(){ RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig(); //指定键和值的序列化机制 config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())); config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); return config; } }
五、使用以上配置后,虽然乱码的问题解决了,但配置文件又不生效了,好比过时时间等,这是由于在初始化时会判断用户是否自定义了配置文件,若是自定义了,原来的就不会生效,源码以下:ide
private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration(ClassLoader classLoader) { //若是配置了,就返回自定义的配置 if (this.redisCacheConfiguration != null) { return this.redisCacheConfiguration; } //没配置使用默认的配置 Redis redisProperties = this.cacheProperties.getRedis(); org.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration .defaultCacheConfig(); config = config.serializeValuesWith( SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader))); if (redisProperties.getTimeToLive() != null) { config = config.entryTtl(redisProperties.getTimeToLive()); } if (redisProperties.getKeyPrefix() != null) { config = config.prefixKeysWith(redisProperties.getKeyPrefix()); } if (!redisProperties.isCacheNullValues()) { config = config.disableCachingNullValues(); } if (!redisProperties.isUseKeyPrefix()) { config = config.disableKeyPrefix(); } return config; }
六、因此,咱们也须要手动获取 ttl、prefix 等属性,直接仿照源码就行,将配置类修改成以下:spring-boot
@EnableCaching //开启缓存 @Configuration @EnableConfigurationProperties(CacheProperties.class) //缓存的全部配置属性都在这个类里 public class MyCacheConfig { @Bean public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) { //获取默认配置 RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig(); //指定键和值的序列化机制 config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())); config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); //获取配置文件的配置 CacheProperties.Redis redisProperties = cacheProperties.getRedis(); if (redisProperties.getTimeToLive() != null) { config = config.entryTtl(redisProperties.getTimeToLive()); } if (redisProperties.getKeyPrefix() != null) { config = config.prefixKeysWith(redisProperties.getKeyPrefix()); } if (!redisProperties.isCacheNullValues()) { config = config.disableCachingNullValues(); } if (!redisProperties.isUseKeyPrefix()) { config = config.disableKeyPrefix(); } return config; } }
在 Spring 中 CacheManager 负责建立管理 Cache,Cache 负责缓存的读写,所以使用 redis 做为缓存对应的就有 RedisCacheManager 和 RedisCache。this
打开 RedisCache 源码,咱们须要注意这两个方法:code
一、读取数据,未加锁
@Override protected Object lookup(Object key) { byte[] value = cacheWriter.get(name, createAndConvertCacheKey(key)); if (value == null) { return null; } return deserializeCacheValue(value); }
二、读取数据,加锁,这是 RedisCache 中惟一一个同步方法
@Override public synchronized <T> T get(Object key, Callable<T> valueLoader) { ValueWrapper result = get(key); if (result != null) { return (T) result.get(); } T value = valueFromLoader(key, valueLoader); put(key, value); return value; }
经过打断点的方式能够知道 RedisCache 默认调用的是 lookup(),所以不能应对缓存穿透,若是有相关需求,能够这样配置:@Cacheable(sync = true)
,开启同步模式,此配置只在 @Cacheable
中才有。
Spring Cache 对于读模式下缓存失效的解决方案:
cache-null-values: true
,容许写入空值@Cacheable(sync = true)
,加锁time-to-live:xxx
,设置不一样的过时时间而对于写模式,Spring Cache 并无相应处理,咱们须要使用其它方式处理。
总的来讲:
一、对于常规数据(读多写少,及时性、一致性要求不高的数据)彻底能够使用 Spring Cache
二、对于特殊数据(好比要求高一致性)则须要特殊处理