Spring 之 @Cacheable 源码解析(上)

一、@EnableCaching 源码解析

当要使用 @Cacheable 注解时需要引入 @EnableCaching 注解开启缓存功能。为什么呢?现在就来看看为什么要加入 @EnableCaching 注解才能开启缓存切面呢?源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {
    
    

	boolean proxyTargetClass() default false;
	
	AdviceMode mode() default AdviceMode.PROXY;

	int order() default Ordered.LOWEST_PRECEDENCE;
}

可以看出是通过 @Import 注解来导入一个类 CachingConfigurationSelector,猜测下,这个类肯定是一个入口类,也可以说是个触发类。注意此处 mode() 默认是 AdviceMode.PROXY

进入 CachingConfigurationSelector 类,源码如下:

public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
    
    

	@Override
	public String[] selectImports(AdviceMode adviceMode) {
    
    
		switch (adviceMode) {
    
    
			case PROXY:
				return getProxyImports();
			case ASPECTJ:
				return 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(PROXY_JCACHE_CONFIGURATION_CLASS);
		}
		return StringUtils.toStringArray(result);
	}
}

从 getProxyImports() 方法可知导入两个类:AutoProxyRegistrar 类、ProxyCachingConfiguration 类。那么接下来就重点分析这两个类是用来干啥的?

1、AutoProxyRegistrar

看到 XxxRegistrar 就可以确定要注册一个类。进入核心源码如下:

public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
    
    

	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
    
		AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
	}

	@Nullable
	public static BeanDefinition registerAutoProxyCreatorIfNecessary(
			BeanDefinitionRegistry registry, @Nullable Object source) {
    
    
		// 缓存和事物都是通过这个 InfrastructureAdvisorAutoProxyCreator 基础增强类来生成代理对象
		return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
	}
}

很明显在这注册了一个 InfrastructureAdvisorAutoProxyCreator 类,这个类和事务切面入口类就是同一个。而这个类并不会做什么逻辑,所有的逻辑都在其父类 AbstractAutoProxyCreator 抽象类中,该类仅仅充当一个入口类。

AbstractAutoProxyCreator 类又是一个 BeanPostProcessor 接口的应用。所以关注这个接口的两个方法。这里只需要关注第二个方法 postProcessAfterInitialization(),源码如下:

	@Override
	public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    
    
		if (bean != null) {
    
    
			/** 获取缓存 key */
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			if (this.earlyProxyReferences.remove(cacheKey) != bean) {
    
    
				/** 是否有必要创建代理对象 */
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}

很明显在 AbstractAutoProxyCreator 抽象类中会调用 wrapIfNecessary() 方法去判断对当前 bean 是否需要创建代理对象。那么这里根据什么来判断呢?进入该方法,核心源码如下:

	protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    
    

		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);

		 /** 合适的通知不为空 */
		if (specificInterceptors != DO_NOT_PROXY) {
    
    
		
			Object proxy = createProxy(
					bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
			return proxy;
		}
		
		return bean;
	}

在上面这段逻辑,很明显 getAdvicesAndAdvisorsForBean() 方法就是判断依据。如果有值就需要创建代理,反之不需要。那么重点关注该方法内部逻辑,核心源码如下:

	@Override
	@Nullable
	protected Object[] getAdvicesAndAdvisorsForBean(
			Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
    
    

		/** tc_tag-99: 查找适合这个类的 advisors 切面通知 */
		List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
		if (advisors.isEmpty()) {
    
    
			return DO_NOT_PROXY;
		}
		return advisors.toArray();
	}

继续进入 findEligibleAdvisors() 内部逻辑,核心源码如下:

	protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
    
    

		List<Advisor> candidateAdvisors = findCandidateAdvisors();

		List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);

		extendAdvisors(eligibleAdvisors);

		if (!eligibleAdvisors.isEmpty()) {
    
    
			eligibleAdvisors = sortAdvisors(eligibleAdvisors);
		}
		return eligibleAdvisors;
	}

看到 findCandidateAdvisors() 方法,内部逻辑如下:

	protected List<Advisor> findCandidateAdvisors() {
    
    
		return this.advisorRetrievalHelper.findAdvisorBeans();
	}
	
	public List<Advisor> findAdvisorBeans() {
    
    
		String[] advisorNames = this.cachedAdvisorBeanNames;
		if (advisorNames == null) {
    
    
			advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
					this.beanFactory, Advisor.class, true, false);
			this.cachedAdvisorBeanNames = advisorNames;
		}
		
		List<Advisor> advisors = new ArrayList<>();

		for (String name : advisorNames) {
    
    
			if (isEligibleBean(name)) {
    
    
				advisors.add(this.beanFactory.getBean(name, Advisor.class));
			}
		}
		return advisors;
	}

从上述代码中可以得知,Spring 通过调用 beanNamesForTypeIncludingAncestors(Advisor.class) 方法来获取所有 Spring 容器中实现 Advsior 接口的实现类。

那么现在先看到 ProxyCachingConfiguration 类,源码如下:

@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {
    
    

	// cache_tag: 缓存方法增强器
	@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor(
			CacheOperationSource cacheOperationSource, CacheInterceptor cacheInterceptor) {
    
    

		BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
		// 缓存用来解析一些属性封装的对象 CacheOperationSource
		advisor.setCacheOperationSource(cacheOperationSource);
		// 缓存拦截器执行对象
		advisor.setAdvice(cacheInterceptor);
		if (this.enableCaching != null) {
    
    
			advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
		}
		return advisor;
	}
	// 先省略一部分...
}

很明显 BeanFactoryCacheOperationSourceAdvisor 实现了 Advisor 接口。那么在上面调用 beanNamesForTypeIncludingAncestors(Advisor.class) 方法时就可以获取到该实例。该实例就会被提添加到候选集合 advisors 中返回。

okay,看完 findCandidateAdvisors() 方法, 再看到 findAdvisorsThatCanApply() 方法,前一个方法获取到了 Spring 容器中所有实现了 Advisor 接口的实现。然后再调用 findAdvisorsThatCanApply() 方法,去判断有哪些 Advisor 适用于当前 bean。进入 findAdvisorsThatCanApply() 内部逻辑,核心源码如下:

	public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
    
    

		/** 没有切面,匹配个屁 */
		if (candidateAdvisors.isEmpty()) {
    
    
			return candidateAdvisors;
		}

		List<Advisor> eligibleAdvisors = new ArrayList<>();
		for (Advisor candidate : candidateAdvisors) {
    
    
			if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
    
    
				eligibleAdvisors.add(candidate);
			}
		}
		boolean hasIntroductions = !eligibleAdvisors.isEmpty();
		for (Advisor candidate : candidateAdvisors) {
    
    
			if (candidate instanceof IntroductionAdvisor) {
    
    
				// already processed
				continue;
			}
			// tc_tag-96: 开始 for 循环 candidateAdvisors 每个增强器,看是否能使用与这个 bean
			if (canApply(candidate, clazz, hasIntroductions)) {
    
    
				eligibleAdvisors.add(candidate);
			}
		}
		return eligibleAdvisors;
	}

继续进入 canApply() 核心逻辑如下:

	public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
    
    

		/**
		 * 通过 Pointcut 获取到 ClassFilter 类的匹配器
		 * 然后匹配 targetClass 是否在 Pointcut 配置的包路径下面么?具体实现看 AspectJExpressionPointcut
		 */
		if (!pc.getClassFilter().matches(targetClass)) {
    
    
			return false;
		}
		/**
		 * 通过 Pointcut 获取到 MethodMatcher 类的匹配器
		 * 然后判断这个类下面的方法是否在 Pointcut 配置的包路径下面么?
		 * 或者是这个方法上是否标注了 @Transactional、@Cacheable等注解呢?
		 */
		MethodMatcher methodMatcher = pc.getMethodMatcher();
		if (methodMatcher == MethodMatcher.TRUE) {
    
    
			// No need to iterate the methods if we're matching any method anyway...
			return true;
		}

		IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
		if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
    
    
			introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
		}

		Set<Class<?>> classes = new LinkedHashSet<>();
		if (!Proxy.isProxyClass(targetClass)) {
    
    
			classes.add(ClassUtils.getUserClass(targetClass));
		}
		classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
		for (Class<?> clazz : classes) {
    
    
			/**
			 * tc_tag1: 获取这个 targetClass 类下所有的方法,开始挨个遍历是否满 Pointcut 配置的包路径下面么?
			 * 或者是这个方法上是否标注了 @Transactional、@Cacheable等注解呢?
			 */
			Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
			for (Method method : methods) {
    
    
				if (introductionAwareMethodMatcher != null ?
						introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
						/**
						 * tc_tag2: 注意对于 @Transactional 注解的 Pointcut 匹配还是比较复杂的,匹配逻辑在 TransactionAttributeSourcePointcut
						 */
						methodMatcher.matches(method, targetClass)) {
    
    
					return true;
				}
			}
		}

		return false;
	}

从上面源码中放眼过去,就能看到两个熟悉的老朋友:ClassFilter 类、MethodMatcher 类,ClassFilter 类是用来判断当前 bean 所在的 Class 上面是否有标注 @Caching@Cacheable@CachePut@CacheEvict 注解。MethodMatcher 类是用来判断当前 bean 的方法上是否有标注 @Caching@Cacheable@CachePut@CacheEvict 注解。

那么这两个过滤器对象是在哪里创建的呢?

回到 ProxyCachingConfiguration 类,源码如下:

@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {
    
    

	// cache_tag: 缓存方法增强器
	@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor(
			CacheOperationSource cacheOperationSource, CacheInterceptor cacheInterceptor) {
    
    

		BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
		// 缓存用来解析一些属性封装的对象 CacheOperationSource
		advisor.setCacheOperationSource(cacheOperationSource);
		// 缓存拦截器执行对象
		advisor.setAdvice(cacheInterceptor);
		if (this.enableCaching != null) {
    
    
			advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
		}
		return advisor;
	}
}

看到 BeanFactoryCacheOperationSourceAdvisor 类,我们知道一个 Advisor 必然是由 Advice 和 Pointcut 组成的。
而 Pointcut 肯定是由 ClassFilter 和 MethodMatcher 组成的
。进入该类,源码如下:

public class BeanFactoryCacheOperationSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {
    
    

	@Nullable
	private CacheOperationSource cacheOperationSource;

	private final CacheOperationSourcePointcut pointcut = new CacheOperationSourcePointcut() {
    
    

		@Nullable
		protected CacheOperationSource getCacheOperationSource() {
    
    
			return cacheOperationSource;
		}
	};

	public void setCacheOperationSource(CacheOperationSource cacheOperationSource) {
    
    
		this.cacheOperationSource = cacheOperationSource;
	}

	public void setClassFilter(ClassFilter classFilter) {
    
    
		this.pointcut.setClassFilter(classFilter);
	}

	@Override
	public Pointcut getPointcut() {
    
    
		return this.pointcut;
	}
}

关注到 CacheOperationSourcePointcut 类,进入源码如下:

abstract class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
    
    

	protected CacheOperationSourcePointcut() {
    
    
		setClassFilter(new CacheOperationSourceClassFilter());
	}

	@Override
	public boolean matches(Method method, Class<?> targetClass) {
    
    
		CacheOperationSource cas = getCacheOperationSource();
		return (cas != null && !CollectionUtils.isEmpty(cas.getCacheOperations(method, targetClass)));
	}

	@Nullable
	protected abstract CacheOperationSource getCacheOperationSource();

	private class CacheOperationSourceClassFilter implements ClassFilter {
    
    

		@Override
		public boolean matches(Class<?> clazz) {
    
    
			if (CacheManager.class.isAssignableFrom(clazz)) {
    
    
				return false;
			}
			CacheOperationSource cas = getCacheOperationSource();
			return (cas == null || cas.isCandidateClass(clazz));
		}
	}
}

从源码中可以看出 ClassFilter = CacheOperationSourceClassFilter,MethodMatcher = CacheOperationSourcePointcut。这里 ClassFilter 的 matches() 方法不对类进行过滤。这个类过滤放到了 MethodMatcher 的 matches() 方法中,和对方法的过滤集成在一起。

所以这里关注到 MethodMatcher#matches() 匹配方法,看看它是怎么进行匹配的。核心源码如下:

abstract class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
    
    

	@Override
	public boolean matches(Method method, Class<?> targetClass) {
    
    
		CacheOperationSource cas = getCacheOperationSource();
		return (cas != null && !CollectionUtils.isEmpty(cas.getCacheOperations(method, targetClass)));
	}
}

进入 getCacheOperations() 方法,核心源码如下:

	@Override
	@Nullable
	public Collection<CacheOperation> getCacheOperations(Method method, @Nullable Class<?> targetClass) {
    
    
		if (method.getDeclaringClass() == Object.class) {
    
    
			return null;
		}

		Object cacheKey = getCacheKey(method, targetClass);
		Collection<CacheOperation> cached = this.attributeCache.get(cacheKey);

		if (cached != null) {
    
    
			return (cached != NULL_CACHING_ATTRIBUTE ? cached : null);
		}
		else {
    
    
			// cache_tag: 计算缓存注解上面的配置的值,然后封装成 CacheOperation 缓存属性对象,基本和事物的一样
			// 注意每个缓存注解对应一种不同的解析处理
			Collection<CacheOperation> cacheOps = computeCacheOperations(method, targetClass);
			if (cacheOps != null) {
    
    
				if (logger.isTraceEnabled()) {
    
    
					logger.trace("Adding cacheable method '" + method.getName() + "' with attribute: " + cacheOps);
				}
				this.attributeCache.put(cacheKey, cacheOps);
			}
			else {
    
    
				this.attributeCache.put(cacheKey, NULL_CACHING_ATTRIBUTE);
			}
			return cacheOps;
		}
	}

发现这段代码和 @Transactional 注解匹配一模一样,其实就是用的同一套逻辑,只不过注解不同而已。匹配过程是一模一样的,看到 computeCacheOperations() 方法,核心源码如下:

	@Nullable
	private Collection<CacheOperation> computeCacheOperations(Method method, @Nullable Class<?> targetClass) {
    
    
		if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
    
    
			return null;
		}

		Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);

		Collection<CacheOperation> opDef = findCacheOperations(specificMethod);
		if (opDef != null) {
    
    
			return opDef;
		}

		opDef = findCacheOperations(specificMethod.getDeclaringClass());
		if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
    
    
			return opDef;
		}

		if (specificMethod != method) {
    
    
			opDef = findCacheOperations(method);
			if (opDef != null) {
    
    
				return opDef;
			}
			// Last fallback is the class of the original method.
			opDef = findCacheOperations(method.getDeclaringClass());
			if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
    
    
				return opDef;
			}
		}
		return null;
	}
	
	@Nullable
	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;
		}
		// cache_tag: 熟悉不能再熟悉的缓存注解 Cacheable/CacheEvict/CachePut/Caching
		// 注意每一种类型的注解解析是不太一样的哦,具体看 parseCacheableAnnotation() 解析方法
		final Collection<CacheOperation> ops = new ArrayList<>(1);
		anns.stream().filter(ann -> ann instanceof Cacheable).forEach(
				ann -> ops.add(parseCacheableAnnotation(ae, cachingConfig, (Cacheable) ann)));
		anns.stream().filter(ann -> ann instanceof CacheEvict).forEach(
				ann -> ops.add(parseEvictAnnotation(ae, cachingConfig, (CacheEvict) ann)));
		anns.stream().filter(ann -> ann instanceof CachePut).forEach(
				ann -> ops.add(parsePutAnnotation(ae, cachingConfig, (CachePut) ann)));
		anns.stream().filter(ann -> ann instanceof Caching).forEach(
				ann -> parseCachingAnnotation(ae, cachingConfig, (Caching) ann, ops));
		return ops;
	}

从上面源码可以看到匹配逻辑,会去匹配当前这个 bean 的方法上是否有 @Cacheable 等注解。方法没找到,就找当前 bean 所在的类上,在没找到就找接口方法,在没找到,继续找接口类上,还灭找到直接返回 null。这个就是他的 MethodMatcher#matches() 匹配过程。

所以 canApply() 方法调用的底层就是调用 MethodMatcher 去进行匹配。findAdvisorsThatCanApply() 方法调用的就是 canApply(),就是这样一个匹配过程。如果能匹配成功,表示 Advisor 可以用于加强当前 bean。那么就需要去调用 createProxy() 方法创建代理对象。让代理对象去调用 Advisor 里面的增强逻辑,执行完之后再调用目标方法。返回到上层调用 getAdvicesAndAdvisorsForBean(),源码如下:

	@Override
	@Nullable
	protected Object[] getAdvicesAndAdvisorsForBean(
			Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
    
    

		/** tc_tag-99: 查找适合这个类的 advisors 切面通知 */
		List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
		if (advisors.isEmpty()) {
    
    
			return DO_NOT_PROXY;
		}
		return advisors.toArray();
	}

返回到最上层调用 wrapIfNecessary() ,源码如下:

	protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    
    

		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);

		 /** 合适的通知不为空 */
		if (specificInterceptors != DO_NOT_PROXY) {
    
    
		
			Object proxy = createProxy(
					bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
			return proxy;
		}
		
		return bean;
	}

最后调用 createProxy() 创建代理对象。至此在 Spring 启动时创建代理对象环节完成。接下来要分析调用流程。

2、ProxyCachingConfiguration

对于该类就是提供缓存需要的支持类。比如 Advisor,通过 @Bean 方式提前注册到 Spring 容器中。源码如下:

@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {
    
    

	// cache_tag: 缓存方法增强器
	@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor(
			CacheOperationSource cacheOperationSource, CacheInterceptor cacheInterceptor) {
    
    

		BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
		// 缓存用来解析一些属性封装的对象 CacheOperationSource
		advisor.setCacheOperationSource(cacheOperationSource);
		// 缓存拦截器执行对象
		advisor.setAdvice(cacheInterceptor);
		if (this.enableCaching != null) {
    
    
			advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
		}
		return advisor;
	}

	// cache_tag: Cache 注解解析器
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public CacheOperationSource cacheOperationSource() {
    
    
		return new AnnotationCacheOperationSource();
	}

	// cache_tag: 缓存拦截器执行器
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public CacheInterceptor cacheInterceptor(CacheOperationSource cacheOperationSource) {
    
    
		CacheInterceptor interceptor = new CacheInterceptor();
		interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);
		interceptor.setCacheOperationSource(cacheOperationSource);
		return interceptor;
	}

}

这里只需要关注 Advisor。而 CacheOperationSource 对象只是对 @Cacheable 等注解解析之后的属性封装而已。

Advisor = Advice + Pointcut

Advisor = BeanFactoryCacheOperationSourceAdvisor

Advice = CacheInterceptor

Pointcut = CacheOperationSourcePointcut

Pointcut = ClassFilter + MethodMatcher

ClassFilter = CacheOperationSourceClassFilter

MethodMatcher = CacheOperationSourcePointcut

调用流程请跳转到下篇 Spring 之 @Cacheable 源码解析(下)

最终你会发现 @EnableCaching 注解和 @EnableTransactionManagement 基本一模一样,只是换了个注解,把 @Transactional 换成了 @Cacheable 等注解。

猜你喜欢

转载自blog.csdn.net/qq_35971258/article/details/128727311