目录
由于Cache是通过CacheManager进行管理,在springboot的autoConfig包中,来看看cache的自动配置
自定义(解决默认序列化)JdkSerializationRedisSerializer
缓存
- 内存的速度远远大于硬盘的速度
- 缓存主要是在获取资源方便性能优化的关键方面
- Redis 是缓存数据库
- 缓存未命中解决与防止缓存击穿
基于spring的Cache进行缓存(基于注解)
Spring缓存支持
Spring定义了org.springframework.cache.CacheManager
和 org.springframework.cache.Cache
接口来统一不同缓存技术。
其中CacheManager是Spring提供的各种缓存技术抽象接口,内部使用Cache接口进行缓存的增删改查操作,我们一般不会直接和Cache打交道。
针对不同的缓存技术,Spring有不同的CacheManager
实现类,定义如下表:
CacheManager | 描述 |
---|---|
SimpleCacheManager | 使用简单的Collection存储缓存数据,用来做测试用 |
ConcurrentMapCacheManager | 使用ConcurrentMap存储缓存数据 |
EhCacheCacheManager | 使用EhCache作为缓存技术 |
GuavaCacheManager | 使用Google Guava的GuavaCache作为缓存技术 |
JCacheCacheManager | 使用JCache(JSR-107)标准的实现作为缓存技术,比如Apache Commons JCS |
RedisCacheManager | 使用Redis作为缓存技术【本次使用的】 |
声明式缓存注解
Spring提供4个注解来声明缓存规则,如下表所示:
注解 | 说明 |
---|---|
@Cacheable | 方法执行前先看缓存中是否有数据,如果有直接返回。如果没有就调用方法,并将方法返回值放入缓存 |
@CachePut | 无论怎样都会执行方法,并将方法返回值放入缓存 |
@CacheEvict | 将数据从缓存中删除 |
@Caching | 可通过此注解组合多个注解策略在一个方法上面 |
@Cacheable 、@CachePut 、@CacheEvict都有value属性,指定要使用的缓存名称,而key属性指定缓存中存储的键。
@EnableCaching 开启缓存。
@Cacheable
这个注解含义是方法结果会被放入缓存,并且一旦缓存后,下一次调用此方法,会通过key去查找缓存是否存在,如果存在就直接取缓存值,不再执行方法。
这个注解有几个参数值,定义如下
参数 | 解释 |
---|---|
cacheNames | 缓存名称 |
value | 缓存名称的别名 |
condition | Spring SpEL 表达式,用来确定是否缓存 |
key | SpEL 表达式,用来动态计算key |
keyGenerator | Bean 名字,用来自定义key生成算法,跟key不能同时用 |
unless | SpEL 表达式,用来否决缓存,作用跟condition相反 |
sync | 多线程同时访问时候进行同步 |
在计算key、condition或者unless的值得时候,可以使用到以下的特有的SpEL表达式
表达式 | 解释 |
---|---|
#result | 表示方法的返回结果 |
#root.method | 当前方法 |
#root.target | 目标对象 |
#root.caches | 被影响到的缓存列表 |
#root.methodName | 方法名称简称 |
#root.targetClass | 目标类 |
#root.args[x] | 方法的第x个参数 |
@CachePut
该注解在执行完方法后会触发一次缓存put操作,参数跟@Cacheable一致
@CacheEvict
该注解在执行完方法后会触发一次缓存evict操作,参数除了@Cacheable里的外,还有个特殊的allEntries
, 表示将清空缓存中所有的值。
在SpringBoot中配置多个cache,实现多个cacheManager灵活切换
先看看如何使用
1、要想使用Cache缓存,那首先必须的开启吧
@EnableCaching
2、由于Cache是通过CacheManager进行管理,在springboot的autoConfig包中,来看看cache的自动配置
CacheManager需要一个cacheManager对象(bean)【看下面源码分析,有详细说明】
3、在需要加入缓存的地方加上相关注解即可
@Cacheable(value = "a:obj", key = "#cacheName")
@RequestMapping("/k")
public Object cacheobjct(String cacheName) {
redisTemplate.opsForValue().set("a:wolf:sty01", "wolf-spring-boot");
return new User("郑先生", "正显示测试缓存", 18);
}
//我这里就是在controller的方法上加的,最终执行后,在redis就给我创建好了这个缓存数据
4、查看结果
(1)下次在启动,只要redis里面有这个key的值,直接通过redis加装,如果没得话,就通过执行具体逻辑加载,然后在放到redis中
在看看别人的例子
这个就数据连接池、缓存配置类、cacheMangaer全部都自己配,不用springboot底层的默认配置
反过来想,这其实没必要的。能够使用底层配置好了的就最好使用配置好了的,除非你不懂。拷贝人家代码,结果代码可以正常运行就不管了。俗话说:知其然,必要知其所以然;虽然我不是大牛,但是这就是我学习的方式和初衷;让学习代码变得快乐,不枯燥。【2021年2月25日记】--多年后,自己再看看,看看还有啥子心得。
源码分析
@EnableCaching
通过开启Cache,然后程序加载扫描所有的类或者方法上加了缓存注解的,然后给他通过AOP切面的方式进行动态代理,让具体执行的方法具有Cache的功能
由于Cache是通过CacheManager进行管理,在springboot的autoConfig包中,来看看cache的自动配置
先看看传统基于xml配置的cacheManager
既然用了springboot那他底层是如何生成cacheManager【重点在这,我要一个bean】的
cacheManager咋个来的
【源码源码】
//他是一共配置类
@Configuration(
proxyBeanMethods = false
)
//当前系统环境存在这个class的时候才会启用这个配置类【这些都是springboot用的最多,最基础的东西,必须要掌握,如果不知道,先学习基础。不然下面这些注解,会看不懂
@ConditionalOnClass({CacheManager.class})
//当前系统环境存在CacheAspectSupport这个class的时候,配置类启用做
@ConditionalOnBean({CacheAspectSupport.class})
//如果容器中没得CacheManager.class类型的bean才进行加载或者beanName=cacheResolver的
@ConditionalOnMissingBean(
value = {CacheManager.class},
name = {"cacheResolver"}
)
//开启自动加载配置文件:把当前系统环境配置文件的信息,按CacheProperties配置类定义的规则加载到CacheProperties中
//CacheProperties @ConfigurationProperties(prefix = "spring.cache")加载已spring.cache开头的配置信息,然后封装到CacheProperties对象的属性中【这个用的也多,很重要】
@EnableConfigurationProperties({CacheProperties.class})
//由于CacheManager需要数据连接,所以这个配置必须在下面这些数据连接配置好了以后在加载
@AutoConfigureAfter({CouchbaseDataAutoConfiguration.class, HazelcastAutoConfiguration.class, HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class})
//给容器导入下面这个2个bean
@Import({CacheAutoConfiguration.CacheConfigurationImportSelector.class, CacheAutoConfiguration.CacheManagerEntityManagerFactoryDependsOnPostProcessor.class})
public class CacheAutoConfiguration {
public CacheAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean
public CacheManagerCustomizers cacheManagerCustomizers(ObjectProvider<CacheManagerCustomizer<?>> customizers) {
return new CacheManagerCustomizers((List)customizers.orderedStream().collect(Collectors.toList()));
}
@Bean
public CacheAutoConfiguration.CacheManagerValidator cacheAutoConfigurationValidator(CacheProperties cacheProperties, ObjectProvider<CacheManager> cacheManager) {
return new CacheAutoConfiguration.CacheManagerValidator(cacheProperties, cacheManager);
}
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]);
}
/** 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 【redis环境引入--它变成了默认了】
org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration 【默认】---通过ImportSelector给容器导入SimpleCacheConfiguration这个bean【但是---但是】
org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
---由于我当前项目环境引入了redis相关信息及默认有redicConnectionFactory的存在,也就是bean按顺序创建的时候,RedisCacheConfiguration这个配置先于
SimpleCacheConfiguration加载,那恰好RedisCacheConfiguration满足条件【看下面源码剖析】
*/
return imports;
}
}
static class CacheManagerValidator implements InitializingBean {
private final CacheProperties cacheProperties;
private final ObjectProvider<CacheManager> cacheManager;
CacheManagerValidator(CacheProperties cacheProperties, ObjectProvider<CacheManager> cacheManager) {
this.cacheProperties = cacheProperties;
this.cacheManager = cacheManager;
}
public void afterPropertiesSet() {
Assert.notNull(this.cacheManager.getIfAvailable(), () -> {
return "No cache manager could be auto-configured, check your configuration (caching type is \'" + this.cacheProperties.getType() + "\')";
});
}
}
@ConditionalOnClass({LocalContainerEntityManagerFactoryBean.class})
@ConditionalOnBean({AbstractEntityManagerFactoryBean.class})
static class CacheManagerEntityManagerFactoryDependsOnPostProcessor extends EntityManagerFactoryDependsOnPostProcessor {
CacheManagerEntityManagerFactoryDependsOnPostProcessor() {
super(new String[]{"cacheManager"});
}
}
}
//在看看SimpleCacheConfiguration的定义嘛
//那想为什么SimpleCacheConfiguration是默认是呢?
mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class.getName());
mappings.put(CacheType.EHCACHE, EhCacheCacheConfiguration.class.getName());
mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class.getName());
mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class.getName());
mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class.getName());
mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class.getName());
mappings.put(CacheType.REDIS, RedisCacheConfiguration.class.getName());
mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class.getName());
mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class.getName());
mappings.put(CacheType.NONE, NoOpCacheConfiguration.class.getName());
【请分别看这些配置类上面的条件:恰恰只有SimpleCacheConfiguration在程序启动的时候满足条件】
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnMissingBean({CacheManager.class})
@Conditional({CacheCondition.class})
class SimpleCacheConfiguration {
SimpleCacheConfiguration() {
}
//给容器创建一个ConcurrentMapCacheManager--CacheManager【基于内存,线程安全的缓存管理器】
@Bean
ConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers) {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
List cacheNames = cacheProperties.getCacheNames();
if(!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return (ConcurrentMapCacheManager)cacheManagerCustomizers.customize(cacheManager);
}
}
//【】
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({RedisConnectionFactory.class})//加入redis以后,springboot底层会通过RedisAutoConfiguration自动配置的@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
//导入了JedisConnectionFactory redisConnectionFactory的连接工厂
@AutoConfigureAfter({RedisAutoConfiguration.class})//然后恰好,这个配置类由在RedisAutoConfiguration配置类完成之后,在进行加载的
@ConditionalOnBean({RedisConnectionFactory.class})//这个时候,在redis自动配置类中,已经完成了RedisConnectionFactory的创建
@ConditionalOnMissingBean({CacheManager.class})//容器中呢,由不存在bean类型为CacheManager的bean
@Conditional({CacheCondition.class})//这个是一个条件,也就是满足CacheCondition里面自定义的条件后,这个配置类才会加载
class RedisCacheConfiguration {
RedisCacheConfiguration() {
}
//重点在这里,这里创建了基于redis的RedisCacheManager
//【重点分析】
//1、cacheProperties把基于spring.cache的配置文件类加载进来,这里初始化的时候需要用到里面的参数
//2、
@Bean
RedisCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers, ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration, ObjectProvider<RedisCacheManagerBuilderCustomizer> redisCacheManagerBuilderCustomizers, RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(this.determineConfiguration(cacheProperties, redisCacheConfiguration, resourceLoader.getClassLoader()));
List cacheNames = cacheProperties.getCacheNames();
if(!cacheNames.isEmpty()) {
builder.initialCacheNames(new LinkedHashSet(cacheNames));
}
if(cacheProperties.getRedis().isEnableStatistics()) {
builder.enableStatistics();
}
redisCacheManagerBuilderCustomizers.orderedStream().forEach((customizer) -> {
customizer.customize(builder);
});
//这里最后就返回了基于RedisCacheManager
return (RedisCacheManager)cacheManagerCustomizers.customize(builder.build());
}
//基于配置信息生成了RedisCacheConfiguration配置
private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration(CacheProperties cacheProperties, ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration, ClassLoader classLoader) {
return (org.springframework.data.redis.cache.RedisCacheConfiguration)redisCacheConfiguration.getIfAvailable(() -> {
return this.createConfiguration(cacheProperties, classLoader);
});
}
//创建配置信息:主要是cacheManagerCustomizers.customize(builder.build())这个里面会使用配置信息里面设置的属性
private org.springframework.data.redis.cache.RedisCacheConfiguration createConfiguration(CacheProperties cacheProperties, ClassLoader classLoader) {
//拿到redis的配置信息:从配置类里面拿(配置类里面包含redis配置信息)
Redis redisProperties = cacheProperties.getRedis();
//这里创建了一个默认的RedisCacheConfiguration
org.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration.defaultCacheConfig();
//设置序列化保存值的时候,自用的哪种序列化方式【重点来了--------假如说:我们要自定义保存值或者设置key使用的序列化策略的时候,可以通过修改勒个进行调整嘛】
//【再说一次,这里是重点:后面可能由于这种默认序列化策略不好,保存到redis的值,看起不舒服,想通过自定义的策略方式来搞一搞】
config = config.serializeValuesWith(SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
if(redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if(redisProperties.getKeyPrefix() != null) {
config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());
}
if(!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if(!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}
【源码结束】
自定义(解决默认序列化)JdkSerializationRedisSerializer
JdkSerializationRedisSerializer
来嘛,加入我现在要分析问题;进入redis数据库查看这个key的值,哪看得懂。所以要改一改默认基于JdkSerializationRedisSerializer的序列化方式
如何自定义虚拟化
思路和方法
1、想办法给替换RedisCacheConfiguration
config.serializeValuesWith(SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader)))//把JdkSerializationRedisSerializer替换成自己想实现的虚拟化方式
通过上面源码里面有,但是反过来想。这个层级太复杂了,而且不好弄。那我直接重新自定义CacheManager,给cacheManager里面保存值的序列化方式进行设置就行了嘛
2、上代码
//必须要定义个配置类【这个也可以加一些配置类加载策略-根据情况需要】
@Configuration
public class CacheManagerConfig {
//自定义RedisCacheManager
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
//序列化方式基于StringRedisSerializer
RedisSerializer<String> strSerializer = new StringRedisSerializer();
//序列化方式基于Jackson2JsonRedisSerializer
Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSeial.setObjectMapper(om);
//下面是核心
//创建一个默认的defaultCacheConfig【这个开始分析springboot默认源码的时候看到过,直接用。不要去创造】
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
//设置过期时间(redis-Key)--也可以根据需要在注入CacheProperties cacheProperties【配置类信息,通过配置类信息获取--这样可以动态替换】
//我研究的时候直接研究底层,具体的方式,生产级别使用。晓得以后咋个搞就行了。
.entryTtl(Duration.ofDays(1))
//【重点】这个就设置自己的序列化方式
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(strSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(jacksonSeial))
.disableCachingNullValues();
//这里就是返回一个RedisCacheManager,这里搞这么多就是把你要的数据源给你,配置信息给你。底层管你怎么搞,看看源码即可。
//想不这么写,随便。看源码,最终构建RedisCacheManager的时候,需要什么信息,你提供好就行,只是不通过人家底层的建造者模式(RedisCacheManagerBuilder)这个类来build()
//读源码是读思想,学思路。不是说怎么怎么滴【剖析有深有原理就够了:学到思想到位】
RedisCacheManager cacheManager = RedisCacheManager
.builder(redisConnectionFactory).cacheDefaults(config).build();
return cacheManager;
}
}
//【这里通过建造者模式:搞了一个复杂的对象出来】
public RedisCacheManager build() {
Assert.state(this.cacheWriter != null, "CacheWriter must not be null! You can provide one via \'RedisCacheManagerBuilder#cacheWriter(RedisCacheWriter)\'.");
RedisCacheWriter theCacheWriter = this.cacheWriter;
if(!this.statisticsCollector.equals(CacheStatisticsCollector.none())) {
theCacheWriter = this.cacheWriter.withStatisticsCollector(this.statisticsCollector);
}
RedisCacheManager cm = new RedisCacheManager(theCacheWriter, this.defaultCacheConfiguration, this.initialCaches, this.allowInFlightCacheCreation);
cm.setTransactionAware(this.enableTransactions);
return cm;
}
3、到此为止,自定义的cacheManager就在容器中存在了,然后其他自动加载类,既然容器中有了这个bean,那不会再加载了【先不考了有多个cacheManager存在的情况---实际是允许的】只是我没记性分析下去了
因为实际的工作场景中,没那么复杂。
关于配置类里面@ConditionalOnBean({CacheAspectSupport.class})的特别说明