spring-boot-redis-cache集成总结及源码分析

目录

缓存

基于spring的Cache进行缓存(基于注解)

Spring缓存支持

声明式缓存注解

先看看如何使用

1、要想使用Cache缓存,那首先必须的开启吧

在看看别人的例子

源码分析

@EnableCaching

由于Cache是通过CacheManager进行管理,在springboot的autoConfig包中,来看看cache的自动配置

cacheManager咋个来的

自定义(解决默认序列化)JdkSerializationRedisSerializer

如何自定义虚拟化


缓存

  • 内存的速度远远大于硬盘的速度
  • 缓存主要是在获取资源方便性能优化的关键方面
  • Redis 是缓存数据库
  • 缓存未命中解决与防止缓存击穿

基于spring的Cache进行缓存(基于注解)

Spring缓存支持

Spring定义了org.springframework.cache.CacheManagerorg.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缓存名称的别名
conditionSpring SpEL 表达式,用来确定是否缓存
keySpEL 表达式,用来动态计算key
keyGeneratorBean 名字,用来自定义key生成算法,跟key不能同时用
unlessSpEL 表达式,用来否决缓存,作用跟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灵活切换

好文章,直接学习人家的总结

官方文档对cache的说明

先看看如何使用

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})的特别说明

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值