Skip to content

AOP 之 Cache

使用

maven 引入依赖

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

开启缓存功能 @EnableCaching

java
@Configuration
@EnableCaching
public class QuickBootCacheAutoConfiguration {}

缓存注解

  • @Cacheable: 触发缓存填充。
  • @CacheEvict: 触发缓存驱逐。
  • @CachePut:在不干扰方法执行的情况下更新缓存。
  • @Caching:重新组合要应用于方法的多个缓存操作。
  • @CacheConfig:在类级别共享一些常见的缓存相关设置。

@Cacheable

java
@Cacheable("books")
public Book findBook(ISBN isbn) {}

@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) {}

@Cacheable(cacheNames="books", key="#isbn.rawNumber")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) {}

@Cacheable(cacheNames="books", key="T(someType).hash(#isbn)")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) {}

/**
 * 如果负责生成密钥的算法过于具体或者需要共享,则可以keyGenerator在操作上定义自定义。
 * 为此,请指定KeyGenerator要使用的bean 实现的名称,如以下示例所示:
 */
@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) {}

/**
 * 如果程序中有多个 CacheManager, 可以指定具体的。一般项目一个就够了,业务不会设计那么复杂。
 */
@Cacheable(cacheNames="books", cacheManager="anotherCacheManager")
public Book findBook(ISBN isbn) {}

/**
 * 同步缓存:
 * 在多线程环境中,某些操作可能会为同一参数并发调用(通常在启动时)。
 * 默认情况下,缓存抽象不锁定任何内容,并且可能会多次计算相同的值,从而达不到缓存的目的。
 * 可以使用该sync属性指示底层缓存提供程序在计算值时锁定缓存条目。结果,只有一个线程忙于计算该值,而其他线程被阻塞,直到缓存中的条目被更新。
 */
@Cacheable(cacheNames="foos", sync=true)
public Foo executeExpensiveOperation(String id) { }

/**
 * 条件缓存:
 * 有时,一个方法可能并不适合一直缓存(例如,它可能取决于给定的参数)。
 * 缓存注释通过condition参数支持此类用例,该 参数采用SpEL计算为true 或的表达式false。
 * 如果true,则缓存该方法。如果没有,它的行为就好像该方法没有被缓存(也就是说,无论缓存中有什么值或使用什么参数,每次都会调用该方法)。
 * 例如,仅当参数name的长度小于 32 时,才会缓存以下方法:
 */
@Cacheable(cacheNames="book", condition="#name.length() < 32")
public Book findBook(String name){}

/**
 * 除了condition参数之外,您还可以使用unless参数来否决向缓存添加值。
 * 与 不同condition,unless在调用方法之后对表达式求值。为了扩展前面的示例,也许我们只想缓存平装书,如下例所示:
 */
@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback")
public Book findBook(String name){}

/**
 * 使用该unless属性来阻止精装书。
 * 缓存抽象支持java.util.Optional,仅当它存在时才使用其内容作为缓存值。
 * #result始终指的是业务实体,而不是受支持的包装器,因此可以将前面的示例改写如下:
 * 请注意:result仍然是指Book而不是Optional。可能是这样null,我们应该使用安全导航操作符。
 */
@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result?.hardback")
public Optional<Book> findBook(String name) {}

可用缓存 SpEL 评估上下文

每个 SpEL 表达式都针对一个专用的 context. 除了内置参数之外,框架还提供了专用的缓存相关元数据,例如参数名称。 下表描述了可用于上下文的项目,以便您可以将它们用于键和条件计算:

nameLocationDescriptionExample
methodNameRoot object被调用方法的名称#root.methodName
methodRoot object被调用的方法#root.method.name
targetRoot object被调用的目标对象#root.target
targetClassRoot object被调用目标的类#root.targetClass
argsRoot object用于调用目标的参数数组#root.args[0]
cachesRoot object运行当前方法的缓存集合#root.caches[0].name
Argument nameEvaluation context任何方法参数的名称。如果名称不可用(可能是由于没有调试信息),则参数名称也可在代表参数索引的#a<#arg> where 下#arg(从 开始 0)。#iban or #a0 (you can also use #p0 or #p<#arg> notation as an alias).
resultEvaluation context方法调用的结果(要缓存的值)。仅在 unless 表达式、cache put 表达式(用于计算 key)或 cache evict 表达式(何时 beforeInvocation 是 false)中可用。对于支持的包装器(例如 Optional),#result 指的是实际对象,而不是包装器。#result

@CachePut

当需要在不干扰方法执行的情况下更新缓存时,可以使用@CachePut 注解。也就是说,始终调用该方法并将其结果放入缓存中(根据@CachePut 选项)。它支持与@Cacheable 用于缓存填充而不是方法流优化的选项相同的选项。以下示例使用了@CachePut 注释:

java
@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor) {}

@CachePut 和 @Cacheable

通常强烈建议不要在同一方法上 使用 @CachePut 和 @Cacheable 注释,因为它们具有不同的行为。 后者导致使用缓存跳过方法调用,而前者强制调用以运行缓存更新。这会导致意外行为,应避免此类声明。

区别:

  • @Cacheable: 添加缓存。如果缓存已经存在,会跳过方法的执行,直接从缓存中拿到结果,实际不会再执行具体的方法来获取数据了。
  • @CachePut:更新缓存。不会跳过方法执行。

@CacheEvict

缓存抽象不仅允许填充缓存存储,还允许逐出。此过程对于从缓存中删除陈旧或未使用的数据很有用。 与 相对 @Cacheable,@CacheEvict 划分执行缓存逐出的方法(即充当从缓存中删除数据的触发器的方法)。 与其兄弟类似,@CacheEvict 需要指定一个或多个受操作影响的缓存,允许自定义缓存和密钥解析或指定条件,并具有一个额外的参数 ( allEntries), 指示是否需要在缓存范围内逐出执行而不仅仅是条目驱逐(基于密钥)。以下示例从 books 缓存中逐出所有条目:

java
@CacheEvict(cacheNames="books", allEntries=true)
public void loadBooks(InputStream batch) {}

@Caching

有时,需要指定多个相同类型的注解(例如 @CacheEvict 或 @CachePut)——例如,因为不同缓存之间的条件或键表达式不同。 @Caching 允许在同一方法上使用多个嵌套的 @Cacheable、@CachePut 和 @CacheEvict 注释。以下示例使用两个 @CacheEvict 注释:

java
@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
public Book importBooks(String deposit, Date date) {}

@CacheConfig

到目前为止,我们已经看到缓存操作提供了许多自定义选项,并且您可以为每个操作设置这些选项。 但是,如果某些自定义选项适用于类的所有操作,则配置它们可能会很乏味。例如,指定用于类的每个缓存操作的缓存名称可以由单个类级别定义替换。 这就是 @CacheConfig 发挥作用的地方。以下示例用于 @CacheConfig 设置缓存的名称:

java
/**
 * 使用@CacheConfig设置缓存的名称。
 */
@CacheConfig("books")
public class BookRepositoryImpl implements BookRepository {
    @Cacheable
    public Book findBook(ISBN isbn) {...}
}

CacheConfig 是一个类级别的注释,允许共享缓存名称、 custom KeyGenerator、 customCacheManager 和 custom CacheResolver。 将此注释放在类上不会打开任何缓存操作。

An operation-level customization always overrides a customization set on @CacheConfig. Therefore, this gives three levels of customizations for each cache operation:

  • 全局配置,可用于 CacheManager,KeyGenerator。
  • 在类级别,使用 @CacheConfig.
  • 在操作层面。

源码解读

SPI 读取 CacheAutoConfiguration 配置类

在 spring-boot-autoconfigure-2.5.4.jar 中通过 SPI 读取自动配置类 org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration;

CacheAutoConfiguration

主要导入了两个类:

  • CacheConfigurationImportSelector.class
  • CacheManagerEntityManagerFactoryDependsOnPostProcessor.class
java
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(CacheManager.class)
@ConditionalOnBean(CacheAspectSupport.class)
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
@EnableConfigurationProperties(CacheProperties.class)
@AutoConfigureAfter({ CouchbaseDataAutoConfiguration.class, HazelcastAutoConfiguration.class,
		HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class })
@Import({ CacheConfigurationImportSelector.class, CacheManagerEntityManagerFactoryDependsOnPostProcessor.class })
public class CacheAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public CacheManagerCustomizers cacheManagerCustomizers(ObjectProvider<CacheManagerCustomizer<?>> customizers) {
        return new CacheManagerCustomizers(customizers.orderedStream().collect(Collectors.toList()));
    }

    @Bean
    public CacheManagerValidator cacheAutoConfigurationValidator(CacheProperties cacheProperties,
                                                                 ObjectProvider<CacheManager> cacheManager) {
        return new CacheManagerValidator(cacheProperties, cacheManager);
    }
    // ......
}

CacheConfigurationImportSelector

java
static class CacheConfigurationImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 拿到所有的 CacheType 遍历
        CacheType[] types = CacheType.values();
        String[] imports = new String[types.length];
        for (int i = 0; i < types.length; i++) {
            // 拿到对应第三方缓存组件的配置类,最终把这个配置类实例化为 Spring Bean
            imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
        }
        return imports;
    }
}
java
public enum CacheType {
    GENERIC,
    JCACHE,
    EHCACHE,
    HAZELCAST,
    INFINISPAN,
    COUCHBASE,
    REDIS,
    CAFFEINE,
    SIMPLE,
    NONE;
    private CacheType() {
    }
}
java
final class CacheConfigurations {
    private static final Map<CacheType, String> MAPPINGS;
    static {
        Map<CacheType, String> mappings = new EnumMap<>(CacheType.class);
        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());
        MAPPINGS = Collections.unmodifiableMap(mappings);
    }
}

可以看出,上面拿到了所有的 CacheConfiguration 配置类,并准备将其实例化为 Spring Bean。 而我们一般使用,只会使用一个第三方缓存组件,因此,每个对应的 CacheConfiguration 配置类中,添加了 @Conditional 注解,因此, 最终只有引入了 maven 依赖的缓存组件的配置类才会被实例化为 Spring Bean, 并实例化一个 CacheManager。

如果 maven 没有依赖第三方缓存组件,默认使用 SimpleCacheConfiguration。

java
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class SimpleCacheConfiguration {
	@Bean
	ConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties,
			CacheManagerCustomizers cacheManagerCustomizers) {
		ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
		List<String> cacheNames = cacheProperties.getCacheNames();
		if (!cacheNames.isEmpty()) {
			cacheManager.setCacheNames(cacheNames);
		}
		return cacheManagerCustomizers.customize(cacheManager);
	}
}

CacheManagerEntityManagerFactoryDependsOnPostProcessor

好像是跟 JPA 有关的,没仔细看。暂不关注。

java
@ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class)
	@ConditionalOnBean(AbstractEntityManagerFactoryBean.class)
	static class CacheManagerEntityManagerFactoryDependsOnPostProcessor
			extends EntityManagerFactoryDependsOnPostProcessor {

		CacheManagerEntityManagerFactoryDependsOnPostProcessor() {
			super("cacheManager");
		}

	}

@EnableCaching

CacheAutoConfiguration 看完了,接下来再看两外一个入口地方:@EnableCaching

主要导入了 CachingConfigurationSelector.class

java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {
    /**
     * 类似于 @EnableAspectJAutoProxy 中的,具体参见源码注释。
     */
	boolean proxyTargetClass() default false;

	AdviceMode mode() default AdviceMode.PROXY;

	int order() default Ordered.LOWEST_PRECEDENCE;
}

CachingConfigurationSelector

主要导入了下面两个类,并实例化为 Spring Bean

  • AutoProxyRegistrar.class 主要为了实现开启 AOP 并生成代理对象的功能。
  • ProxyCachingConfiguration.class 主要用来处理缓存相关的 AOP 的 Advisor, PointCut, Advice
java
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {

	private static final String PROXY_JCACHE_CONFIGURATION_CLASS =
			"org.springframework.cache.jcache.config.ProxyJCacheConfiguration";

	private static final String CACHE_ASPECT_CONFIGURATION_CLASS_NAME =
			"org.springframework.cache.aspectj.AspectJCachingConfiguration";

	private static final String JCACHE_ASPECT_CONFIGURATION_CLASS_NAME =
			"org.springframework.cache.aspectj.AspectJJCacheConfiguration";


	private static final boolean jsr107Present;

	private static final boolean jcacheImplPresent;

	static {
		ClassLoader classLoader = CachingConfigurationSelector.class.getClassLoader();
        // jsr107 的支撑
		jsr107Present = ClassUtils.isPresent("javax.cache.Cache", classLoader);
        // jcache 支撑
		jcacheImplPresent = ClassUtils.isPresent(PROXY_JCACHE_CONFIGURATION_CLASS, classLoader);
	}

	/**
	 * Returns {@link ProxyCachingConfiguration} or {@code AspectJCachingConfiguration}
	 * for {@code PROXY} and {@code ASPECTJ} values of {@link EnableCaching#mode()},
	 * respectively. Potentially includes corresponding JCache configuration as well.
	 */
	@Override
	public String[] selectImports(AdviceMode adviceMode) {
		switch (adviceMode) {
			case PROXY:
                // 默认使用这个
				return getProxyImports();
			case ASPECTJ:
				return getAspectJImports();
			default:
				return null;
		}
	}

	/**
	 * Return the imports to use if the {@link AdviceMode} is set to {@link AdviceMode#PROXY}.
	 * <p>Take care of adding the necessary JSR-107 import if it is available.
	 */
	private String[] getProxyImports() {
		List<String> result = new ArrayList<>(3);
        // 主要导入了下面两个类,并实例化为 Spring Bean
		result.add(AutoProxyRegistrar.class.getName());
		result.add(ProxyCachingConfiguration.class.getName());
        // jsr107 和 JCache 的支撑
		if (jsr107Present && jcacheImplPresent) {
			result.add(PROXY_JCACHE_CONFIGURATION_CLASS);
		}
		return StringUtils.toStringArray(result);
	}

	/**
	 * Return the imports to use if the {@link AdviceMode} is set to {@link AdviceMode#ASPECTJ}.
	 * <p>Take care of adding the necessary JSR-107 import if it is available.
	 */
	private String[] getAspectJImports() {
		List<String> result = new ArrayList<>(2);
		result.add(CACHE_ASPECT_CONFIGURATION_CLASS_NAME);
		if (jsr107Present && jcacheImplPresent) {
			result.add(JCACHE_ASPECT_CONFIGURATION_CLASS_NAME);
		}
		return StringUtils.toStringArray(result);
	}
}

AutoProxyRegistrar

这个类主要为了实现开启 AOP 并生成代理对象的功能。

AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);

主要是上面这一行,注册了类 InfrastructureAdvisorAutoProxyCreator.class。

这个类类似与 AnnotationAwareAspectJAutoProxyCreator 类(用于处理以 @AspectJ 注解形式开启 AOP)。参考 AopConfigUtils

二者都实现了 AbstractAdvisorAutoProxyCreator 类,而这个类主要是用来开启 AOP 并生成代理对象的。

AbstractAdvisorAutoProxyCreator 最终实现了 BeanPostProcessor。

java
public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
	private final Log logger = LogFactory.getLog(getClass());
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		boolean candidateFound = false;
		Set<String> annTypes = importingClassMetadata.getAnnotationTypes();
		for (String annType : annTypes) {
			AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
			if (candidate == null) {
				continue;
			}
			Object mode = candidate.get("mode");
			Object proxyTargetClass = candidate.get("proxyTargetClass");
			if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&
					Boolean.class == proxyTargetClass.getClass()) {
				candidateFound = true;
				if (mode == AdviceMode.PROXY) {
                    // 主要这一行,注册了类 InfrastructureAdvisorAutoProxyCreator.class
                    // 这个类类似与 AnnotationAwareAspectJAutoProxyCreator 类(用于处理以 @AspectJ 注解形式开启AOP)
                    // 二者都实现了 AbstractAdvisorAutoProxyCreator 类,而这个类主要是用来生成代理对象的。
                    // AbstractAdvisorAutoProxyCreator 最终实现了 BeanPostProcessor
					AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
					if ((Boolean) proxyTargetClass) {
						AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
						return;
					}
				}
			}
		}
		// ......
	}
}

ProxyCachingConfiguration

可以看到这里面主要配置的东西就是跟 AOP 相关的信息,主要就是切点源以及拦截器。

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

	@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor(
			CacheOperationSource cacheOperationSource, CacheInterceptor cacheInterceptor) {
        //缓存操作的Advisor,间接继承了AbstractPointcutAdvisor,能返回指定的切点
        // BeanFactoryCacheOperationSourceAdvisor对象间接实现类PointcutAdvisor接口的getPointcut方法,
        // 这个方法在 AOP 确定 advisor 能否在代理目标类上适用的时候会调用。具体在 AopUtils 里面
		BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
        //设置事务操作源,也就是切点源, 通过这个来设置 Advisor 中持有的 AOP 的 pointcut 属性
		advisor.setCacheOperationSource(cacheOperationSource);
        //设置 advice,拦截器 CacheInterceptor
		advisor.setAdvice(cacheInterceptor);
		if (this.enableCaching != null) {
			advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
		}
		return advisor;
	}

	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public CacheOperationSource cacheOperationSource() {
        //返回注解操作缓存的操作源,AnnotationCacheOperationSource 的父类 AbstractFallbackCacheOperationSource
        // 里面包含了对注解相关的解析
		return new AnnotationCacheOperationSource();
	}

    /**
     * 在 CacheInterceptor 在创建之后调用了一个 configure 方法,而这个方法里面传入了四个参数,
     * 分别是 errorHandler,keyGenerator,cacheResolver以及cacheManager。这里说明一下四个参数的作用。
     * errorHandler	在对应的缓存操作出错的时候进行处理,可以制定错误类型
     * keyGenerator	缓存键生成器。用于根据给定的方法*(用作上下文)及其参数创建键
     * cacheResolver 确定缓存信息的缓存对象类型
     * cacheManager	缓存管理器,用于获取缓存的
     */
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public CacheInterceptor cacheInterceptor(CacheOperationSource cacheOperationSource) {
        //创建对应的拦截器,aop时候拦截的时候用到
		CacheInterceptor interceptor = new CacheInterceptor();
        //设置对应的配置
		interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);
        //为缓存的切面设置操作源
		interceptor.setCacheOperationSource(cacheOperationSource);
		return interceptor;
	}
}
java
public void configure(
        @Nullable Supplier<CacheErrorHandler> errorHandler, @Nullable Supplier<KeyGenerator> keyGenerator,
        @Nullable Supplier<CacheResolver> cacheResolver, @Nullable Supplier<CacheManager> cacheManager) {
    // 设置errorHandler,如果没有指定errorHandler则使用 SimpleCacheErrorHandler 作为默认的错误处理器
    this.errorHandler = new SingletonSupplier<>(errorHandler, SimpleCacheErrorHandler::new);

    //设置keyGenerator,如果没有指定keyGenerator则使用默认的 SimpleKeyGenerator 作为默认的缓存key生成器
    this.keyGenerator = new SingletonSupplier<>(keyGenerator, SimpleKeyGenerator::new);

    //设置 cacheResolver,如果没有指定cacheResolver则根据cacheManager来生成 SimpleCacheResolver
    this.cacheResolver = new SingletonSupplier<>(cacheResolver,
            () -> SimpleCacheResolver.of(SupplierUtils.resolve(cacheManager)));
}

BeanFactoryCacheOperationSourceAdvisor

缓存操作的 Advisor,间接继承了 AbstractPointcutAdvisor,能返回指定的切点(PointCut)。 BeanFactoryCacheOperationSourceAdvisor 对象间接实现类 PointcutAdvisor 接口的 getPointcut 方法, 这个方法在 AOP 确定 advisor 能否在代理目标类上适用的时候会调用。具体在 AopUtils 里面

├─BeanFactoryCacheOperationSourceAdvisor
│ ├─extends AbstractBeanFactoryPointcutAdvisor
│ │ ├─extends AbstractPointcutAdvisor implements BeanFactoryAware
│ │ │ ├─implements PointcutAdvisor, Ordered, Serializable
│ │ │ │ ├─PointcutAdvisor extends Advisor

关注的重要点是切点( PointCut)为 CacheOperationSourcePointcut,其中包含了 CacheOperationSource 对象,用来扫描缓存注解。

java
/**
 * BeanFactoryCacheOperationSourceAdvisor 对象间接实现类 PointcutAdvisor 接口的 getPointcut 方法,
 * 通过代码可以发现,this.pointcut 就是 CacheOperationSourcePointcut pointcut 对象,
 * 而切点 CacheOperationSourcePointcut 中包含了 CacheOperationSource 对象,用来扫描缓存注解。
 */
public class BeanFactoryCacheOperationSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {
    @Nullable
    private CacheOperationSource cacheOperationSource;
    private final CacheOperationSourcePointcut pointcut = new CacheOperationSourcePointcut() {
        @Override
        @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;
    }
}

AnnotationCacheOperationSource

├─AnnotationCacheOperationSource
│ ├─ extends AbstractFallbackCacheOperationSource implements Serializable
│ │ ├─ implements CacheOperationSource

返回注解操作缓存的操作源,里面包含了对缓存注解相关的解析。而在上面的章节 BeanFactoryCacheOperationSourceAdvisor 中正好有一个 CacheOperationSource 切点获取类。

java
public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperationSource implements Serializable {
    @Override
    public boolean isCandidateClass(Class<?> targetClass) {
        // 关注以下这个类 CacheAnnotationParser
        for (CacheAnnotationParser parser : this.annotationParsers) {
            if (parser.isCandidateClass(targetClass)) {
                return true;
            }
        }
        return false;
    }
    // ......
}

/**
 * SpringCacheAnnotationParser 实现了 CacheAnnotationParser
 * 在这个类中就能够匹配缓存注解。
 */
public class SpringCacheAnnotationParser implements CacheAnnotationParser, Serializable {
    private static final Set<Class<? extends Annotation>> CACHE_OPERATION_ANNOTATIONS = new LinkedHashSet<>(8);
    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);
    }
    // ......
}

CacheInterceptor

├─CacheInterceptor
│ ├─ extends CacheAspectSupport implements MethodInterceptor, Serializable
│ │ ├─ MethodInterceptor extends Interceptor
│ │ │ ├─ Interceptor extends Advice

可以看出,CacheInterceptor 其实是一个 AOP 的通知 Advice。

经过前面的分析已经知道,能够进入到这里的方法都是贴有那些缓存操作@Cacheable,@CacheEvict 等注解的方法。 在这里主要就是调用那些方法,获取到方法的结果然后,进入到父类 CacheAspectSupport 中进一步处理。

java
public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {
    @Override
    @Nullable
    public Object invoke(final MethodInvocation invocation) throws Throwable {
        //获取到需要调用的方法
        Method method = invocation.getMethod();
        CacheOperationInvoker aopAllianceInvoker = () -> {
            try {
                //调用方法获取到结果,这个方法就是贴有缓存操作的那些注解的方法
                return invocation.proceed();
            }
            catch (Throwable ex) {
                throw new CacheOperationInvoker.ThrowableWrapper(ex);
            }
        };

        try {
            //将方法执行的结果传入父类方法中进行缓存的操作,缓存操作的入口
            return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());
        }
        catch (CacheOperationInvoker.ThrowableWrapper th) {
            throw th.getOriginal();
        }
    }
}

下面的这个方法的作用主要就是获取当前贴有注解的方法上的所有的缓存操作的注解包括@Cacheable,@CacheEvict,@CachePut,@Caching 这些注解, 生成一个缓存操作的上下文,在生成的上下文中会包含生成的 Cache 对象,这里不对生成上下文的过程进行讲解。 生成上下文信息之后,然后调用另外的一个 execute 方法进行处理。其中的 execute 才是创建 Cache 对象的位置。

java
public abstract class CacheAspectSupport extends AbstractCacheInvoker
        implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton {
    // ......
    protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
        //检查切面是否已经准备好了
        if (this.initialized) {
            //获取目标类
            Class<?> targetClass = getTargetClass(target);
            //获取cacheOperationSource默认的是AnnotationCacheOperationSource,父类是AbstractFallbackCacheOperationSource
            CacheOperationSource cacheOperationSource = getCacheOperationSource();
            if (cacheOperationSource != null) {
                //获取目标类的目标方法上的缓存相关的注解
                Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);
                if (!CollectionUtils.isEmpty(operations)) {
                    /**
                     * invoker为贴有注解方法的调用结果
                     * method 为贴有注解的方法
                     * 重点是这里创建 Cache 对象
                     */
                    return execute(invoker, method,
                            //根据注解上的相关的信息生成CacheOperationContexts,指定 CacheResolver跟 KeyGenerator
                            new CacheOperationContexts(operations, method, args, target, targetClass));
                }
            }
        }
        return invoker.invoke();
    }
}
java
public abstract class CacheAspectSupport extends AbstractCacheInvoker
        implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton {
    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)) {
                //生成key
                Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
                //从缓存操作上下文中获取cache
                Cache cache = context.getCaches().iterator().next();
                try {
                    //调用invoke方法,然后把调用的结果保存起来
                    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
        //执行@CacheEvict注解的处理逻辑类CacheEvictOperation,如果CacheEvictOperation=true
        processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
                CacheOperationExpressionEvaluator.NO_RESULT);

        // Check if we have a cached item matching the conditions
        //执行@Cacheable注解的处理逻辑类CacheableOperation,获取对应的缓存数据
        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<>();
        //如果@Cacheable注解收集不到chache(condition 通过,且key对应的数据不在缓存)
        if (cacheHit == null) {
            //如果缓存不存在,但是存在@CachePut操作,则将缓存的值放到cachePutRequests中
            collectPutRequests(contexts.get(CacheableOperation.class),
                    CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
        }

        Object cacheValue;
        Object returnValue;
        //如果CachePutRequest不是空则说明存在缓存,并且没有CachePut操作,则直接从缓存获取。然后将缓存值包装未返回值
        if (cacheHit != null && !hasCachePut(contexts)) {
            // If there are no put requests, just use the cache hit
            cacheValue = cacheHit.get();
            returnValue = wrapCacheValue(method, cacheValue);
        } else {
            //如果没有缓存或者说存在CachePut操作,这时候调用方法获取方法的返回值,然后将返回值包装为缓存值
            returnValue = invokeOperation(invoker);
            cacheValue = unwrapReturnValue(returnValue);
        }

        // Collect any explicit @CachePuts
        //将缓存的值放到cachePutRequests中
        collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);

        // Process any collected put requests, either from @CachePut or a @Cacheable miss
        //将cachePutRequests中的值一次保存到缓存中
        for (CachePutRequest cachePutRequest : cachePutRequests) {
            cachePutRequest.apply(cacheValue);
        }

        // Process any late evictions
        //执行@CacheEvict,方法调用之后
        processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);

        return returnValue;
    }
}

上面的这些操作的步骤进行讲解一下:

  • 首先执行@CacheEvict(如果 beforeInvocation=true 且 condition 通过),如果 allEntries=true,则清空所有
  • 接着收集@Cacheable(如果 condition 通过,且 key 对应的数据不在缓存),放入 cachePutRequests(也就是说如果 cachePutRequests 为空,则数据在缓存中)
  • 如果 cachePutRequests 为空且没有@CachePut 操作,那么将查找@Cacheable 的缓存,否则 result=缓存数据(也就是说只要当没有 cache put 请求时才会查找缓存)
  • 如果没有找到缓存,那么调用实际的 API,把结果放入 result
  • 如果有@CachePut 操作(如果 condition 通过),那么放入 cachePutRequests
  • 执行 cachePutRequests,将数据写入缓存(unless 为空或者 unless 解析结果为 false);
  • 执行@CacheEvict(如果 beforeInvocation=false 且 condition 通过),如果 allEntries=true,则清空所有
  • 返回结果值

这里需要注意 2/3/4 步:

如果有@CachePut 操作,即使有@Cacheable 也不会从缓存中读取;问题很明显,如果要混合多个注解使用,不能组合使用@CachePut 和@Cacheable; 官方说应该避免这样使用(解释是如果带条件的注解相互排除的场景);不过个人感觉还是不要考虑这个好,让用户来决定如何使用,否则一会介绍的场景不能满足。

到这里@EnableCaching,@Cacheable,@CacheEvict,@CachePut 的实现原理就完毕了。