一、SpringBoot 缓存原理探究
1、SpringCache 介绍
在SpringBoot中,数据的缓存管理存储依赖于Spring框架中cache相关的org.springframework.cache.Cache和org.springframework.cache.CacheManager缓存管理器接口。
如果程序中没有定义类型为CacheManager的Bean组件或者是名为cacheResolver的CacheResolver缓存解析器,Spring Boot将尝试选择并启用以下缓存组件(按照指定的顺序):
(1)Generic (2)JCache (JSR-107)(EhCache 3、Hazelcast、Infinispan等) (3)EhCache 2.x (4)Hazelcast (5)Infinispan (6)Couchbase (7)Redis (8)Caffeine (9)Simple (10)NoOp
上面按照Spring Boot缓存组件的加载顺序,列举了支持的10种缓存组件,在项目中添加某个缓存管理组件(例如Redis)后,Spring Boot项目会选择并启用对应的缓存管理器。
如果项目中同时添加了多个缓存组件,且没有指定缓存管理器或者缓存解析器(CacheManager或者cacheResolver),那么Spring Boot会按照上述顺序在添加的多个缓存中优先启用指定的缓存组件进行缓存管理。
Spring Boot默认缓存管理中,没有添加任何缓存管理组件能实现缓存管理。这是因为开启缓存管理后,Spring Boot会按照上述列表顺序查找有效的缓存组件进行缓存管理,如果没有任何缓存组件,会默认使用Simple缓存组件进行管理。
Simple缓存组件是Spring Boot默认的缓存管理组件,它默认使用内存中的ConcurrentMap进行缓存存储,所以在没有添加任何第三方缓存组件的情况下,可以实现内存中的缓存管理,但是我们不推荐使用这种缓存管理方式。
当在Spring Boot默认缓存管理的基础上引入Redis缓存组件,即在pom.xml文件中添加Spring Data Redis依赖启动器后,SpringBoot会使用RedisCacheConfigratioin当做生效的自动配置类进行缓存相关的自动装配,容器中使用的缓存管理器是RedisCacheManager, 这个缓存管理器创建的Cache为 RedisCache, 进而操控redis进行数据的缓存。
依赖:org.springframework.boot 和 spring-boot-starter-data-redis
2、缓存配置类:SpringBoot提供了10个配置类,对应上面介绍的那 10 个缓存组件,其中SimpleCacheConfiguration是默认配置类
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.SimpleCacheConfiguration // 默认
org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
3、SpringBoot各类缓存优先级
(1)默认使用默认缓存,当导入其他依赖则默认缓存失效
(2)导入多种依赖,则按照上面排序生效其中一个
// 源码
@ConditionalOnMissingBean({CacheManager.class}) @Conditional({CacheCondition.class}) class SimpleCacheConfiguration { SimpleCacheConfiguration() { }
源码中,使用了注解@ConditionalOnMissingBean,其作用是:判断当前需要注入Spring容器中的bean的实现类是否已经含有,有的话不注入,没有就注入(被覆盖)。当注入其他缓存后,会在容器中注入对应缓存的CacheManager,此时,由于上述注解的原因,默认缓存则不再生效。
4、缓存结构:Cache结构是一个map: ConcurrentMap<Object, Object>
5、核心思想:
当我们调用一个方法时会把该方法的参数和返回结果作为一个键值对存放在缓存中,等下次利用同样的参数来调用该方法时将不会再执行,而是直接从缓存中获取结果进行返回。
理解:springboot 的缓存机制是通过切面编程 aop 来实现的。
二、SpringCache 注解
Spring Cache 提供了 @Cacheable 、@CachePut 、@CacheEvict 、@Caching 等注解,在方法上使用。
基于注解方式SpringCache引入Redis做缓存,需要先了解@EnableCaching、@CacheConfig、@Cacheable、@CachePut、@CacheEvict、@Caching相关注解的使用。
1、@EnableCaching
开启缓存功能,一般放在启动类上或者自定义的RedisConfig配置类上。
2、@CacheConfig:在类上使用,当该类有多个方法有同样的缓存操作时使用
当我们需要缓存的地方越来越多,可以使用@CacheConfig(cacheNames = "cacheName") 注解在 class 之上来统一指定value的值,统一管理keys,这时可省略value,如果你在你的方法依旧写上了value,那么依然以方法的value值为准。
@Service @CacheConfig(cacheNames = "categories") public class CategoryServiceImpl implements CategoryService{}
3、@Cacheable:用于查询数据,添加缓存
根据方法对其返回结果进行缓存,下次请求时,如果缓存存在,则直接读取缓存数据返回;如果缓存不存在,则执行方法,并把返回的结果存入缓存中。一般用在查询方法上。
4、@CachePut:用于新增数据时, 自动更新缓存
使用该注解标志的方法,每次都会执行,并将结果存入指定的缓存中。其他方法可以直接从响应的缓存中读取缓存数据,而不需要再去查询数据库。
一般用在新增方法上。 查看源码,属性值同上@Cacheable差不多。
5、@CacheEvict:用于修改或删除数据时,清空缓存
使用该注解标志的方法,会清空指定的缓存。一般用在更新或者删除方法上。
查看源码,属性值与@Cacheable差不多,独有的两个属性如下:
allEntries是否清空所有缓存,默认为false。如果指定为true,则方法调用后将立即清空所有的缓存
beforeInvocation是否在方法执行前就清空所有缓存,默认为false。如果指定为true,则方法执行前就会清空所有的缓存
6、@Caching:该注解可以实现同一个方法上同时使用多种注解
// 可从其源码看出
public @interface Caching { Cacheable[] cacheable() default {}; CachePut[] put() default {}; CacheEvict[] evict() default {}; }
@Caching(evict = { @CacheEvict(cacheNames = "eventAskMaxId", key = "#ask.eventId", condition = "#ask.eventId != null"), @CacheEvict(cacheNames = "eventAskTotal", key = "#ask.eventId", condition = "#ask.eventId != null") }) public void insert(EventAsk ask) {}
三、SpringBoot快速集成Redis
1、导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、添加配置:连接池参数要选择lettuce的(默认),不要配成jedis的,源码中jedis很多类不存在,因此是不生效的。
3、使用:(1)缓存:直接使用SpringBoot的缓存注解即可;(2)操作:使用 RedisTemplate 或 RedisUtils 操作数据
需要注意的是:实体类要实现Serializable(序列化)才能存入redis, 不然会报错
public class Table implements Serializable {}
四、如何编写Redis配置文件
1、配置文件解决核心问题:
(1)实现序列化后,redis工具中显示乱码:重写redisTemplate方法,使用fastjson进行序列化解决
(2)设置默认过期时间
(3)Redis 挂后,报错且不走数据库:捕获Redis连接超时的异常,使Redis挂掉后,不影响正常访问
2、序列化问题源码分析:
(1)默认使用的RedisTemplate设置的JDK序列化(序列化效果不好),可以自定义redisTemplate替换默认的
(2)对象的两个泛型都是Object,需要强制转换为<String, Object>
(3)由于String 是redis最常用的类型,所以单独提供了StringRedisTemplate
// RedisAutoConfiguration源码
@Bean @ConditionalOnMissingBean( name = {"redisTemplate"} )// 可以自定义redisTemplate替换默认的
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { // 默认没有做额外的序列化(使用的RedisTemplate默认序列化),redis对象都是需要序列化的 // 两个泛型都是Object,需要强制转换为<String, Object>
RedisTemplate<Object, Object> template = new RedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean // 由于String 是redis最常用的类型,所以单独提供了StringRedisTemplate
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; }
// RedisTemplate源码 if (this.defaultSerializer == null) { // RedisTemplate默认配置了JDK序列化
this.defaultSerializer = new JdkSerializationRedisSerializer(...); }
3、自定义 Redis 的序列化及配置
(1)使用fastjson序列化redis
(2)Redis 配置文件
public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {} @Slf4j @Configuration @EnableCaching @ConditionalOnClass(RedisOperations.class) @EnableConfigurationProperties(RedisProperties.class) public class RedisConfig extends CachingConfigurerSupport { // 1、解决序列化问题
@Bean(name = "redisTemplate") @SuppressWarnings("unchecked") @ConditionalOnMissingBean(name = "redisTemplate") public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<Object, Object> template = new RedisTemplate<>(); //使用fastjson序列化
FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class); // ......
return template; } // 2、设置key默认过期时间
@Bean public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { return new RedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory), this.redisCacheConfiguration(1800), // 默认配置
this.initialCacheConfigurations()); // 指定key过期时间配置
} // 3、配置redis挂掉后,捕获异常,不影响查询数据库
public CacheErrorHandler errorHandler() { CacheErrorHandler cacheErrorHandler = new CacheErrorHandler() { ...... }; return cacheErrorHandler; } }
注意上面是伪代码,项目上的代码就不方便贴咯。