spring-cache 源码解析(无图)

本文详细解读了SpringCache的内部工作机制,包括如何通过注解@Cachable简化缓存操作,以及与SpringBoot的Redis集成,重点讲解了AOP代理、BeanFactoryCacheOperationSourceAdvisor的配置和工作原理。
摘要由CSDN通过智能技术生成

spring-cache 源码解析(无图)

简介

spring-cache 提供了高效的缓存相关数据存储和删除,通过运用注解@Cachable就可以简单完成从缓存中读取数据,减少在业务代码中加入缓存,读取缓存的操作,相对方便快捷。

用例

spring-boot-starter-cache
spring-boot-starter-data-redis

结合 redis + spring-cache,使用分布式缓存。

解析

依赖信息
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
引入了什么
@EnableCaching

对启动类加上 @EnableCaching 注解

  1. 引入CachingConfigurationSelector, 加载 ProxyCachingConfiguration 作为代理配置类
  2. ProxyCachingConfiguration 加载了 BeanFactoryCacheOperationSourceAdvisor
  3. BeanFactoryCacheOperationSourceAdvisor 使用 cacheInterceptor 作为 advice, 使用 CacheOperationSource 作为pointcut。
  4. 至此完成了代理类的配置。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {
    // ...
}
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
	// ProxyCachingConfiguration 代理类配置加载为bean
}

@Configuration
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {

	@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor() {
		BeanFactoryCacheOperationSourceAdvisor advisor =
				new BeanFactoryCacheOperationSourceAdvisor();
		advisor.setCacheOperationSource(cacheOperationSource());
		advisor.setAdvice(cacheInterceptor());
		if (this.enableCaching != null) {
			advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
		}
		return advisor;
	}

	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public CacheOperationSource cacheOperationSource() {
		...
	}

	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public CacheInterceptor cacheInterceptor() {
        ...
	}

}
RedisAutoConfiguration
  1. RedisAutoConfiguration 加载 RedisProperties 属性类
  2. RedisAutoConfiguration 加载 LettuceConnectionConfiguration、JedisConnectionConfiguration 也就加载了 RedisConnectionFactory
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
    // 加载restemplate, StringRedisTemplate
}
CacheAutoConfiguration
  1. 在有CacheAspectSupport这个bean作为条件,加载CacheProperties属性类,导入 CacheConfigurationImportSelector
  2. CachingConfigurationSelector 会加载 全部的缓存类型配置类,由shouldSkip进行条件过滤,就会剩下 RedisCacheConfiguration 可以被加载成 bean
  3. RedisCacheConfiguration 由于引入 RedisAutoConfiguration 加载了 RedisConnectionFactory作为bean,所以条件成立,加载 cacheManager
@Configuration
@ConditionalOnClass(CacheManager.class)
@ConditionalOnBean(CacheAspectSupport.class)
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
@EnableConfigurationProperties(CacheProperties.class)
@AutoConfigureAfter({ CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class,
		HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class })
@Import(CacheConfigurationImportSelector.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++) {
				imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
			}
			return imports;
		}
	}
}


final class CacheConfigurations {

	private static final Map<CacheType, Class<?>> MAPPINGS;

	static {
		Map<CacheType, Class<?>> 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);
		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);
	}
}

@Configuration
@ConditionalOnClass(RedisConnectionFactory.class)
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {

	@Bean
	public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory,
			ResourceLoader resourceLoader) {
		RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory)
				.cacheDefaults(determineConfiguration(resourceLoader.getClassLoader()));
		List<String> cacheNames = this.cacheProperties.getCacheNames();
		if (!cacheNames.isEmpty()) {
			builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
		}
		return this.customizerInvoker.customize(builder.build());
	}
}

总结引入部分
  • @EnableCaching 提供 代理类
  • spring-boot-starter-data-redis 引入RedisConnectionFactory, 满足redis缓存配置类的condition条件
  • spring-boot-starter-cache 引入各种缓存配置, 用于加载 CacheManager
  • 代理类advisor部分
  • BeanFactoryCacheOperationSourceAdvisor, 使用 cacheInterceptor 作为 advice, 覆写 invoke方法用于调用拦截
  • BeanFactoryCacheOperationSourceAdvisor,使用 CacheOperationSource 作为 pointCut, 覆写 methodMatcher.matches 方法用于 【装载代理类 && 实际调用生成调用链】
  • 其中 methodMatcher.matches() 方法就是判断有没有几个注解,并通过 SpringCacheAnnotationParser 解析这些注解成元数据CacheOperation。
  • cacheInterceptor 实现了 MethodInterceptor,而MethodInterceptor又实现了 Advice, 因此调用 目标方法时,就会调用到 cacheInterceptor 的 invoke 方法。
  • 解析过程,也就是matches识别注解的部分:
  • @Nullable
    private Collection<CacheOperation> parseCacheAnnotations(
        DefaultCacheConfig cachingConfig, AnnotatedElement ae, boolean localOnly) {
    
      Collection<CacheOperation> ops = null;
    
      Collection<Cacheable> cacheables = (localOnly ? AnnotatedElementUtils.getAllMergedAnnotations(ae, Cacheable.class) :
          AnnotatedElementUtils.findAllMergedAnnotations(ae, Cacheable.class));
      if (!cacheables.isEmpty()) {
        ops = lazyInit(null);
        for (Cacheable cacheable : cacheables) {
          ops.add(parseCacheableAnnotation(ae, cachingConfig, cacheable));
        }
      }
      Collection<CacheEvict> evicts = (localOnly ? AnnotatedElementUtils.getAllMergedAnnotations(ae, CacheEvict.class) :
          AnnotatedElementUtils.findAllMergedAnnotations(ae, CacheEvict.class));
      if (!evicts.isEmpty()) {
        ops = lazyInit(ops);
        for (CacheEvict evict : evicts) {
          ops.add(parseEvictAnnotation(ae, cachingConfig, evict));
        }
      }
      Collection<CachePut> puts = (localOnly ? AnnotatedElementUtils.getAllMergedAnnotations(ae, CachePut.class) :
          AnnotatedElementUtils.findAllMergedAnnotations(ae, CachePut.class));
      if (!puts.isEmpty()) {
        ops = lazyInit(ops);
        for (CachePut put : puts) {
          ops.add(parsePutAnnotation(ae, cachingConfig, put));
        }
      }
      Collection<Caching> cachings = (localOnly ? AnnotatedElementUtils.getAllMergedAnnotations(ae, Caching.class) :
          AnnotatedElementUtils.findAllMergedAnnotations(ae, Caching.class));
      if (!cachings.isEmpty()) {
        ops = lazyInit(ops);
        for (Caching caching : cachings) {
          Collection<CacheOperation> cachingOps = parseCachingAnnotation(ae, cachingConfig, caching);
          if (cachingOps != null) {
            ops.addAll(cachingOps);
          }
        }
      }
    
      return ops;
    }
    
    invoke 方法
    1. contexts.get(CacheEvictOperation.class): 识别CacheEvictOperation,前置缓存删除,默认不删:processCacheEvicts【beforeInvocation() default false】;
    2. contexts.get(CacheableOperation.class):识别CacheableOperation,从缓存中获取,如果没有则使用get方法+lookup方法找到对应缓存中的数据
    3. contexts.get(CacheableOperation.class):当命中为空时,识别CacheableOperation加入到请求列表
    4. 当命中时,包装 returnValue = cacheValue; 当未命中时,执行到cacheInterceptor 注册的 invocation.proceed(); 获取返回值作为 returnValue。cacheValue解包装returnValue
    5. contexts.get(CachePutOperation.class):识别CachePutOperation,加入到请求列表中
    6. cachePutRequest.apply(): 将前面的 CacheableOperation、CachePutOperation 请求实际执行。put数据到缓存中
    7. contexts.get(CacheEvictOperation.class):识别CacheEvictOperation,后置缓存删除,默认删除。即前置删除和后置删除只会成功一个。因为入参true / false, 会用于比较 operation.isBeforeInvocation(), 所以只能成功一个,就没有延时双删。
    @Nullable
    private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
      // Special handling of synchronized invocation
      if (contexts.isSynchronized()) {
        CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
        if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
          Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
          Cache cache = context.getCaches().iterator().next();
          try {
            return wrapCacheValue(method, cache.get(key, () -> unwrapReturnValue(invokeOperation(invoker))));
          }
          catch (Cache.ValueRetrievalException ex) {
            // The invoker wraps any Throwable in a ThrowableWrapper instance so we
            // can just make sure that one bubbles up the stack.
            throw (CacheOperationInvoker.ThrowableWrapper) ex.getCause();
          }
        }
        else {
          // No caching required, only call the underlying method
          return invokeOperation(invoker);
        }
      }
    
    
      // Process any early evictions
      processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
          CacheOperationExpressionEvaluator.NO_RESULT);
    
      // Check if we have a cached item matching the conditions
      Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
    
      // Collect puts from any @Cacheable miss, if no cached item is found
      List<CachePutRequest> cachePutRequests = new LinkedList<>();
      if (cacheHit == null) {
        collectPutRequests(contexts.get(CacheableOperation.class),
            CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
      }
    
      Object cacheValue;
      Object returnValue;
    
      if (cacheHit != null && !hasCachePut(contexts)) {
        // If there are no put requests, just use the cache hit
        cacheValue = cacheHit.get();
        returnValue = wrapCacheValue(method, cacheValue);
      }
      else {
        // Invoke the method if we don't have a cache hit
        returnValue = invokeOperation(invoker);
        cacheValue = unwrapReturnValue(returnValue);
      }
    
      // Collect any explicit @CachePuts
      collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
    
      // Process any collected put requests, either from @CachePut or a @Cacheable miss
      for (CachePutRequest cachePutRequest : cachePutRequests) {
        cachePutRequest.apply(cacheValue);
      }
    
      // Process any late evictions
      processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
    
      return returnValue;
    }
    
    

    附录

    spring-cache,基于aop,需要对aop有些了解,比较容易掌握。
    补充一下 spring-aop 加载的一个大致流程

  • doCreateBean 中会进行 bean的实例化 - 放入3级缓存 - 属性注入 - 初始化,其中初始化就会调用到后置处理器,使用 AbstractAutoProxyCreator.wrapIfNecessary() 完成代理类的加载。 (applyBeanPostProcessorsAfterInitialization,实现就有 org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization)
  • wrapIfNecessary(), 就会加载容器中的 Advisor.class 类型的对象,然后执行canApply(), 基于 classFilter + 对类上的各个方法对比methodMatcher.matches方法,将满足切点的advisor都作为config加载到代理类中(org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors加载注解@Around等生成 Advisor)
  • 调用时,再调用jdk/cglib下的invoke方法。判断本地方法缓存有没有放入这个调用链,如果没有就生成一条调用链,就是通过config里的各个advisor对应的pointcut的methodMatcher.matches方法,返回值决定能不能添加到调用链中。这也就是为什么上面说 BeanFactoryCacheOperationSourceAdvisor,使用 CacheOperationSource 作为 pointCut, 覆写 methodMatcher.matches 方法用于 【装载代理类 && 实际调用生成调用链】的原因。之后将生成的调用链依次调用 MethodInterceptor.invoke,完成能力增强。 (List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); 形成调用链)
  • 26
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值