spring cache与redis整合使用

113 阅读3分钟

1.引入spring-cache并且和redis进行整合

1.引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
    <version>2.7.5</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>2.7.5</version>
</dependency>

2.加注解

@EnableCaching
public class Application {}

3.添加配置

spring:
  cache:
    type: redis

4.使用注解操作缓存

@Cacheale @CachePut @CacheEvict @Caching

生成key的策略为cacheName::key

2.原理

通过 @EnableCaching 引入 CachingConfigurationSelector.class

CachingConfigurationSelector#selectImports()

//默认使用PROXY增强
public String[] selectImports(AdviceMode adviceMode) {
        switch (adviceMode) {
            case PROXY:
                return this.getProxyImports();
            case ASPECTJ:
                return this.getAspectJImports();
            default:
                return null;
        }
    }

private String[] getProxyImports() {
        List<String> result = new ArrayList(3);
        result.add(AutoProxyRegistrar.class.getName());
        result.add(ProxyCachingConfiguration.class.getName());
        if (jsr107Present && jcacheImplPresent) {
            result.add("org.springframework.cache.jcache.config.ProxyJCacheConfiguration");
        }

        return StringUtils.toStringArray(result);
    }

ProxyCachingConfiguration 注入了三个bean

BeanFactoryCacheOperationSourceAdvisorCacheOperationSource 作为pointCut进行拦截

private CacheOperationSource cacheOperationSource;
    private final CacheOperationSourcePointcut pointcut = new CacheOperationSourcePointcut() {
        @Nullable
        protected CacheOperationSource getCacheOperationSource() {
            return BeanFactoryCacheOperationSourceAdvisor.this.cacheOperationSource;
        }
    };

CacheInterceptor 走到CacheAspectSupport#execute()

按照规定流程执行响应注解对应的动作

  1. 存在 @CacheEvit,满足 beforeInvocation == true 先执行删除

  2. 存在@Cacheable,先判断缓存中是否有值。有值直接从缓存中拿,如果没有值则放一个CachePutRequest ,用于之后缓存值,先执行逻辑,从数据库中查询值

  3. 存在@CachePut,放CachePutRequest ,缓存值

  4. 存在 @CacheEvit,满足 beforeInvocation ==false 执行删除

    4.1 删除的时候判断allEntries是否为true,true则删除cacheName下的所有缓存,否则删除匹配的那个

private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
    if (contexts.isSynchronized()) {
        CacheOperationContext context = (CacheOperationContext)contexts.get(CacheableOperation.class).iterator().next();
        if (!this.isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
            return this.invokeOperation(invoker);
        }

        Object key = this.generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
        Cache cache = (Cache)context.getCaches().iterator().next();

        try {
            return this.wrapCacheValue(method, this.handleSynchronizedGet(invoker, key, cache));
        } catch (Cache.ValueRetrievalException var10) {
            ReflectionUtils.rethrowRuntimeException(var10.getCause());
        }
    }

    this.processCacheEvicts(contexts.get(CacheEvictOperation.class), true, CacheOperationExpressionEvaluator.NO_RESULT);
    Cache.ValueWrapper cacheHit = this.findCachedItem(contexts.get(CacheableOperation.class));
    List<CachePutRequest> cachePutRequests = new ArrayList();
    if (cacheHit == null) {
        this.collectPutRequests(contexts.get(CacheableOperation.class), CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
    }

    Object returnValue;
    Object cacheValue;
    if (cacheHit != null && !this.hasCachePut(contexts)) {
        cacheValue = cacheHit.get();
        returnValue = this.wrapCacheValue(method, cacheValue);
    } else {
        returnValue = this.invokeOperation(invoker);
        cacheValue = this.unwrapReturnValue(returnValue);
    }

    this.collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
    Iterator var8 = cachePutRequests.iterator();

    while(var8.hasNext()) {
        CachePutRequest cachePutRequest = (CachePutRequest)var8.next();
        cachePutRequest.apply(cacheValue);
    }

    this.processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
    return returnValue;
}

上述操作缓存的行为,具体要看整合进来缓存类型。例如整合的是redis,则会在redis包中实现操作缓存的行为,整合进来caffeine则会再caffeine包中实现。类似于模板模式。

3. 注解解析成CacheOperation

SpringCacheAnnotationParser#parseCacheAnnotations

static {
        CACHE_OPERATION_ANNOTATIONS.add(Cacheable.class);
        CACHE_OPERATION_ANNOTATIONS.add(CacheEvict.class);
        CACHE_OPERATION_ANNOTATIONS.add(CachePut.class);
        CACHE_OPERATION_ANNOTATIONS.add(Caching.class);
    }

private Collection<CacheOperation> parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae, boolean localOnly) {
        //拿到所有的注解
			  Collection<? extends Annotation> anns = localOnly ? AnnotatedElementUtils.getAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS) : AnnotatedElementUtils.findAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS);
        if (anns.isEmpty()) {
            return null;
        } else {
            Collection<CacheOperation> ops = new ArrayList(1);
            anns.stream().filter((ann) -> {
                return ann instanceof Cacheable;
            }).forEach((ann) -> {
                ops.add(this.parseCacheableAnnotation(ae, cachingConfig, (Cacheable)ann));
            });
            anns.stream().filter((ann) -> {
                return ann instanceof CacheEvict;
            }).forEach((ann) -> {
                ops.add(this.parseEvictAnnotation(ae, cachingConfig, (CacheEvict)ann));
            });
            anns.stream().filter((ann) -> {
                return ann instanceof CachePut;
            }).forEach((ann) -> {
                ops.add(this.parsePutAnnotation(ae, cachingConfig, (CachePut)ann));
            });
            anns.stream().filter((ann) -> {
                return ann instanceof Caching;
            }).forEach((ann) -> {
                this.parseCachingAnnotation(ae, cachingConfig, (Caching)ann, ops);
            });
            return ops;
        }
    }

4.配置生效

spring:
  cache:
    type: redis

RedisCacheConfiguration@Conditional({CacheCondition.class})

满足CacheCondition

class CacheCondition extends SpringBootCondition {
    CacheCondition() {
    }

    public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String sourceClass = "";
        if (metadata instanceof ClassMetadata) {
            sourceClass = ((ClassMetadata)metadata).getClassName();
        }

        ConditionMessage.Builder message = ConditionMessage.forCondition("Cache", new Object[]{sourceClass});
        Environment environment = context.getEnvironment();

        try {
						//将配置里的值转成枚举中的值
            BindResult<CacheType> specified = Binder.get(environment).bind("spring.cache.type", CacheType.class);
            if (!specified.isBound()) {
                return ConditionOutcome.match(message.because("automatic cache type"));
            }

            CacheType required = CacheConfigurations.getType(((AnnotationMetadata)metadata).getClassName());
            //进行类型的匹配注入
						if (specified.get() == required) {
                return ConditionOutcome.match(message.because(specified.get() + " cache type"));
            }
        } catch (BindException var8) {
        }

        return ConditionOutcome.noMatch(message.because("unknown cache type"));
    }
}

5.自定义配置

没有自定义配置会注入默认的配置

对cacheName下的key设置过期时间

public class MyRedisManager extends RedisCacheManager {
    public MyRedisManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
        super(cacheWriter, defaultCacheConfiguration);
    }

    @Override
    protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
        //对cacheName进行分隔,设置过期时间
	String[] split = name.split("=");
        if (split.length > 1) {
            Long time = new Long(split[1]);
            cacheConfig = cacheConfig.entryTtl(Duration.ofSeconds(time));
        }
        return super.createRedisCache(split[0], cacheConfig);
    }
}

进行配置

@Configuration
public class RedisConfig {

   @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        //初始化RedisCacheWriter
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
        //序列化方式
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
        RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(serializer);
        RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
        //对key设置过期时间 
        defaultCacheConfig = defaultCacheConfig.entryTtl(Duration.ofMinutes(30));
        //对固定的key设置过期时间
       Map<String, RedisCacheConfiguration> map = new HashMap<>();
       map.put("user", defaultCacheConfig.entryTtl(Duration.ofMinutes(60)));

       return MyRedisManager.builder(redisCacheWriter).cacheDefaults(defaultCacheConfig).build();
    }
}

user下的key过期时间为10s

@Cacheable(cacheNames = "user=10",key = "#id")

默认使用的redis数据结构为string,想要使用其他的数据结构需要重写底层实现。还有数据一致性和redis一些其他常见的问题需要考虑。