栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Springboot 整合 SpringCache

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Springboot 整合 SpringCache

简单整理一下 SpringCache + Redis的使用,更具体的说明,可以看 Spring 官方关于SpringCache的说明 。

1、引入依赖


    org.springframework.boot
    spring-boot-starter-cache

2、添加配置
  • 配置使用 Redis 作为缓存
spring:
  cache:
    type: redis 				  #指定使用redis作为缓存
3、测试使用SpringCache缓存

1、标注 @EnableCaching到启动类或者配置类上,开启缓存功能

2、使用 SpringCache缓存注解标注到业务方法上,常用的注解如下:

对于缓存声明,Spring的缓存抽象提供了一组Java注解:

  • @Cacheable:触发缓存填充的操作。
  • @CacheEvict: 触发缓存删除的操作。
  • @CachePut:在不干扰方法执行的情况下更新缓存。
  • @Caching:重新组合多个缓存操作以应用于一个方法,即使用该注解内部可以组合使用其他注解(@CacheEvict、@CachePut、@Cacheable)
  • @CacheConfig:在类级别共享一些常见的缓存相关设置。
以 @Cacheable的使用为例:
    //1、Cacheable代表当前方法的结果需要缓存,如果缓存中有,方法不用调用。如果缓存中没有会调用方法,最后将方法的结果放入缓存
	//2、每一个需要缓存的数据,都要指定要放到哪个名字的缓存分区。【缓存的分区(建议按照业务类型分,方便统一删除)】
	@Cacheable(value = {"category"}) 
    @Override
    public List getLevel1Categorys() {
        // 查db操作 ,略;
    }
测试发现, SpringCahe默认行为如下:

1)、如果缓存中有数据,该方法不用被调用;

2)、key默认自动生成: 缓存的名字::SimpLeKey[](自主生成的key值);

3)、缓存的 value 的值。默认使用 “jdk序列化机制 org.springframework.data.redis.serializer.JdkSerializationRedisSerializer”,将序列化后的数据存到 redis ;

4)、默认 ttl 时间 -1,永久;

自定义配置:

1)、指定生成的缓存使用的 key; key属性指定时,接受一个SpEL的表达式,如:@Cacheable(value = {"category"},key = "#root.methodName") ,其中 key = "#root.methodName",意思是使用方法名作为key。其他用法详见 Available Caching SpEL Evaluation Context

2)、指定缓存的数据的存活时间 ttl:配置文件中修改 ttl 即可。

spring:
  cache:
    type: redis 				  #指定使用redis作为缓存
    redis:
#      key-prefix:                # 配置所有缓存key的前缀,不配置,默认格式为  "缓存自定义的value::SimpleKey []"
      time-to-live: 3600000        # 配置 ttl 缓存过期时间,单位毫秒,这里设置了3600秒,即1个钟失效
      cache-null-values: true     # 是否缓存 Null 值,默认为 true,默认解决“缓存穿透”问题。
      use-key-prefix: true        # 是否使用key前缀,默认为 true

3)、将数据保存为json格式 :使用自定义缓存配置类,覆盖默认的配置。

import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;


@Configuration
public class MyCacheConfig {

    
    @Bean
    public RedisCacheConfiguration createConfiguration(CacheProperties cacheProperties) {
        CacheProperties.Redis redisProperties = cacheProperties.getRedis();
        //获取 默认的缓存配置,后面覆写配置。
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();

        //设置 key序列器: 使用默认的 {@link StringRedisSerializer}
        config.serializeKeysWith(
            RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
        //设置 value序列器: 使用 {@link GenericFastJsonRedisSerializer}
        config = config.serializeValuesWith(
            RedisSerializationContext.SerializationPair.fromSerializer(new GenericFastJsonRedisSerializer()));

        //如下代码 照抄默认配置源码,不变
        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;
    }
}

RedisSerializer 有很多实现类,选择一个 JSON转换的实现类作为 redis的 value 序列化实现类 即可。

@CacheEvict 、@Caching的使用:
  • @CacheEvict 代表当前方法如果执行,则删除指定的缓存;一般用于更新接口。

  • 查询接口搭配使用 @Cacheable注解,等触发查询接口时,重新将数据库中新数据缓存起来,@CacheEvict 注解的作用相当于缓存中的“失效模式”。

//1、指定删除“category"缓存分区里,某个缓存key的数据。
@CacheEvict(value = {"category"},key = "'getLevel1Categorys'")

//2、指定删除“category"缓存分区里,某个缓存 key1、key2...keyN 的数据 (批量删)。
@Caching(evict = {
    @CacheEvict(value = {"category"}, key = "'getLevel1Categorys'"),
    @CacheEvict(value = {"category"}, key = "'getCatelogJson'"),
    // @CacheEvict(....) 可以删除多个
	}
)

//3、指定删除“category"缓存分区所有数据。比 (2)的删除方式更方便快捷,批量删推荐使用这种方式。
@CacheEvict(value = {"category"},allEntries = true)
@Transactional
@Override
public void updateCascade(CategoryVo categoryVo) {
  //略
}

//代表当前方法的结果需要缓存,如果缓存中有,方法不用调用。如果缓存中没有会调用方法,最后将方法的结果放入缓存
@Cacheable(value = {"category"}, key = "#root.methodName")
@Override
public List getLevel1Categorys() {
    //略
}

@Cacheable(value = {"category"}, key = "#root.methodName")
@Override
public Map> getCatelogJson() {
    //略
}
4、Spring-Cache的不足 1)、读模式:
  • 缓存穿透:查询一个null数据。

    • 解决办法:默认缓存空数据,
    • Cache解决:默认配置支持缓存Null数据,配置属性为:spring.cache.redis.cache-null-values=true
  • 缓存击穿:大量并发进来同时查询一个正好过期的数据。

    • 解决办法:加锁;
    • Cache解决?:Cache默认是无加锁的;不过缓存查询时可以使用sync = true属性加本地进程锁。sync = true意思是查询缓存时会使用“本地进程锁”,用法如: @Cacheablesyc(sync = true) ,详情见下面的“源码分析-RedisCache”。
  • 缓存雪崩:大量的key同时过期。

    • 解决办法:加随机时间。
    • Cache解决?: 默认配置为-1永久。可以修改配置加上过期时间。配置属性为:spring.cache.redis.time-to-live
2)、写模式:(缓存与数据库一致性)
  • 读写加锁。

    • 读锁:Spring-Cache 可以使用 @Cacheable(sync = true),支持读的时候加本地锁。
    • 写锁:Spring-Cache 默认不支持。
  • 即时性、一致性:Spring-Cache 均不能保证即时性,数据库与缓存的一致性。

总结:
  • 常规数据(读多写少、即时性、一致性要求不高的数据):完全可以使用 Spring-Cache 来缓存支持。Spring-Cache 能设置缓存的过期时间,利用 @CachePut 注解实现“双写模式”;利用 @CacheEvict 注解实现“失效模式”,一般的常规场景也足够用了。

  • 特殊数据:

    • 读多写多:直接去数据库查询就行。

    • 即时性、一致性高:引入Canal,通过日志记录文件感知到MySOL的更新后,再去更新Redis缓存库。

5、源码分析

SpringBoot 框架在引入 spring-boot-starter-cache、spring-boot-starter-data-redis 依赖后,帮我们自动配置了哪些东西?

CacheAutoConfiguration
  • CacheAutoConfiguration 会自动导入 RedisCacheConfiguration ,RedisCacheConfiguration 会导入各种 xxxCacheConfiguration 缓存配置。
@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({CacheManager.class})
@ConditionalOnBean({CacheAspectSupport.class})
@ConditionalOnMissingBean(
    value = {CacheManager.class},
    name = {"cacheResolver"}
)
// 1、CacheProperties 类是 SpringCache的配置,这里导入对应的配置。
@EnableConfigurationProperties({CacheProperties.class})

@AutoConfigureAfter({CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class, HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class})
@Import({CacheAutoConfiguration.CacheConfigurationImportSelector.class, CacheAutoConfiguration.CacheManagerEntityManagerFactoryDependsOnPostProcessor.class})
public class CacheAutoConfiguration {
    //导入的组件,略,只标注重点的入口,具体可以自己看源码
    
    
   
	static class CacheConfigurationImportSelector implements ImportSelector {

		@Override
		public String[] selectImports(AnnotationMetadata importingClassMetadata) {
			CacheType[] types = CacheType.values();
			String[] imports = new String[types.length];
			for (int i = 0; i < types.length; i++) {
                //2、导入对应的缓存配置
				imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
			}
			return imports;
		}

	}
    
}
RedisCacheConfiguration
  • 我使用了 Redis,这里会导入 RedisCacheConfiguration配置。
final class CacheConfigurations {

	private static final Map> MAPPINGS;

	static {
		Map> mappings = new EnumMap<>(CacheType.class);
		mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class);
		mappings.put(CacheType.EHCACHE, EhCacheCacheConfiguration.class);
		mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class);
		mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class);
		mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class);
		mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class);
        
        //2、导入 RedisCacheConfiguration
		mappings.put(CacheType.REDIS, RedisCacheConfiguration.class);
        
		mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class);
		mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class);
		mappings.put(CacheType.NONE, NoOpCacheConfiguration.class);
		MAPPINGS = Collections.unmodifiableMap(mappings);
	}
    
    //2、导入对应的缓存,我这里用的是 Redis 缓存,对应的 RedisCacheConfiguration 配置被导入。
    static String getConfigurationClass(CacheType cacheType) {
		Class configurationClass = MAPPINGS.get(cacheType);
		Assert.state(configurationClass != null, () -> "Unknown cache type " + cacheType);
		return configurationClass.getName();
	}
    
  	
	public static RedisCacheConfiguration defaultCacheConfig(@Nullable ClassLoader classLoader) {

		DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();

		registerDefaultConverters(conversionService);

		return new RedisCacheConfiguration(Duration.ZERO, true, true, CacheKeyPrefix.simple(),
				SerializationPair.fromSerializer(RedisSerializer.string()),
				SerializationPair.fromSerializer(RedisSerializer.java(classLoader)), conversionService);
	}
}
RedisCacheManager
  • 默认自动配好了缓存管理器 RedisCacheManager (如果用户自定义了缓存管理器,则使用自定义的,不使用默认的)
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisConnectionFactory.class)
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {
	
    //3、导入 RedisCacheManager 缓存管理器。
	@Bean
	RedisCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers,
			ObjectProvider redisCacheConfiguration,
			ObjectProvider redisCacheManagerBuilderCustomizers,
			RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
        // 3.1 构建缓存配置
		RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(
				determineConfiguration(cacheProperties, redisCacheConfiguration, resourceLoader.getClassLoader()));
        
		List cacheNames = cacheProperties.getCacheNames();
		if (!cacheNames.isEmpty()) {
			builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
		}
		redisCacheManagerBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
		return cacheManagerCustomizers.customize(builder.build());
	}

    //3.2 决定缓存配置的方法
	private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration(
			CacheProperties cacheProperties,
			ObjectProvider redisCacheConfiguration,
			ClassLoader classLoader) {
        //3.3 判断是否有用户自己创建的配置,如果有就用自定义的配置,如果没有则创建默认配置
		return redisCacheConfiguration.getIfAvailable(() -> createConfiguration(cacheProperties, classLoader));
	}
    
	//3.4 创建默认配置方法:决定默认配置具体配置了什么
	private org.springframework.data.redis.cache.RedisCacheConfiguration createConfiguration(
			CacheProperties cacheProperties, ClassLoader classLoader) {
		Redis redisProperties = 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) {
            // 设置 ttl 失效时间
			config = config.entryTtl(redisProperties.getTimeToLive());
		}
		if (redisProperties.getKeyPrefix() != null) {
            // 设置 缓存key前缀
			config = config.prefixKeysWith(redisProperties.getKeyPrefix());
		}
		if (!redisProperties.isCacheNullValues()) {
            // 设置 是否禁用缓存Null值
			config = config.disableCachingNullValues();
		}
		if (!redisProperties.isUseKeyPrefix()) {
            // 设置 是否禁用缓存key前缀
			config = config.disableKeyPrefix();
		}
		return config;
	}

}

RedisCache

Cache 类如下图:

RedisCache 实现类 如下图:

有一个get方法使用了 synchronized 关键字 (重写了 Cache 接口的 get方法)

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/959630.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号