SpringCache使用Spring AOP来实现,当我们在Configuration类打上@EnableCaching注释时,该标注通过ImportSelector机制启动AbstractAdvisorAutoProxyCreator的一个实例,该实例本身是一个Ordered BeanPostProcessor,BeanPostProcessor的作用是在bean创建及初始化的前后对其进行一些额外的操作(包括创建代理),关于BeanPostProcessor及其对bean创建时产生影响的介绍请参照我的另一片文章《BeanPostProcessor加载次序及其对Bean造成的影响分析》。而Spring AOP就是通过AbstractAdvisorAutoProxyCreator来实现的。
因此可以认为当在Configuration类打上@EnableCaching注释时,做的第一件事情就是启用Spring AOP机制。下面简单介绍一下Spring AOP机制的实现原理和核心概念,详细的实现原理与源码分析我会另写一篇文章专门说明。
从Spring2.0开始,引入了AspectJ的注释,使得定义切面变得非常简单。Spring采用AspectJ注释对POJO进行标注,从而定义一个包含切点信息和增强横切面逻辑的切面,而Spring的底层负责将这个切面织入到匹配的目标bean中。
AspectJ注释使用AspectJ切点表达式语法进行切点的定义,可以通过切点函数、运算符、通配符等高级功能进行切点定义,拥有强大的连接点描述能力。
在运行中,当创建并初始化一个bean的时候,Spring会判断这个bean上打的注释,若是通过Aspect注释定义的切点,则将切点信息以及增强逻辑织入到这个新创建的bean中,切点及增强逻辑会放置在一个Advisor中,而每个bean有可能有很多个Advisor。织入的方式有两种:动态代理和静态织入。在此我们使用动态代理的方式。
而在实际调用到某个方法时,其实是通过创建代理时设置的回调函数作为调用入口(JdkProxy和cglib大同小异),其中会过滤出可以应用于本方法的Advisor并生成Interceptor或者InterceptorAndDynamicMethodMatcher数组,以类似于FilterChain的方式按照
interceptor1.pre()
interceptor2.pre()
.....
interceptorn.pre()
method.invoke()
interceptorn.aft()
......
interceptor2.aft()
interceptor1.aft()
的顺序来执行。
Spring AOP整体实现逻辑如下:@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());
advisor.setOrder(this.enableCaching.getNumber("order"));
return advisor;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public CacheOperationSource cacheOperationSource() {
return new AnnotationCacheOperationSource();
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public CacheInterceptor cacheInterceptor() {
CacheInterceptor interceptor = new CacheInterceptor();
interceptor.setCacheOperationSources(cacheOperationSource());
if (this.cacheResolver != null) {
interceptor.setCacheResolver(this.cacheResolver);
}
else if (this.cacheManager != null) {
interceptor.setCacheManager(this.cacheManager);
}
if (this.keyGenerator != null) {
interceptor.setKeyGenerator(this.keyGenerator);
}
if (this.errorHandler != null) {
interceptor.setErrorHandler(this.errorHandler);
}
return interceptor;
}
}
我们可以看到在其中配置了三个bean:BeanFactoryCacheOperationSourceAdvisor、AnnotationCacheOperationSource、CacheInterceptor。
首先分析一下AnnotationCacheOperationSource的源码,其继承路径是
AnnotationCacheOperationSource->AbstractFallbackCacheOperationSource->CacheOperationSource
其中CacheOperationSource接口定义了一个方法:
Collection getCacheOperations(Method method, Class> targetClass);
该方法用于根据指定类上的指定方法上打的SpringCache注释来得到对应的CacheOperation集合。
AbstractFallbackCacheOperationSource是CacheOperationSource的抽象实现类,采用模板模式将获取某类的某方法上的CacheOperation的业务流程固化。该固化的流程可将方法上的属性缓存,并实现了一个获取CacheOperation的fallback策略,执行的顺序为:1目标方法、2目标类、3声明方法、4声明类/接口。当方法被调用过一次之后,其上的属性就会被缓存。它提供了两个抽象模板方法,供具体的子类类来实现:
protected abstract Collection findCacheOperations(Method method);
protected abstract Collection findCacheOperations(Class> clazz);
AnnotationCacheOperationSource内部持有一个Set
@Override
protected Collection findCacheOperations(final Class> clazz) {
return determineCacheOperations(new CacheOperationProvider() {
@Override
public Collection getCacheOperations(CacheAnnotationParser parser) {
return parser.parseCacheAnnotations(clazz);
}
});
}
@Override
protected Collection findCacheOperations(final Method method) {
return determineCacheOperations(new CacheOperationProvider() {
@Override
public Collection getCacheOperations(CacheAnnotationParser parser) {
return parser.parseCacheAnnotations(method);
}
});
}
/**
* Determine the cache operation(s) for the given {@link CacheOperationProvider}.
* This implementation delegates to configured
* {@link CacheAnnotationParser}s for parsing known annotations into
* Spring's metadata attribute class.
*
Can be overridden to support custom annotations that carry
* caching metadata.
* @param provider the cache operation provider to use
* @return the configured caching operations, or {@code null} if none found
*/
protected Collection determineCacheOperations(CacheOperationProvider provider) {
Collection ops = null;
for (CacheAnnotationParser annotationParser : this.annotationParsers) {
Collection annOps = provider.getCacheOperations(annotationParser);
if (annOps != null) {
if (ops == null) {
ops = new ArrayList();
}
ops.addAll(annOps);
}
}
return ops;
}
/**
* Callback interface providing {@link CacheOperation} instance(s) based on
* a given {@link CacheAnnotationParser}.
*/
protected interface CacheOperationProvider {
/**
* Returns the {@link CacheOperation} instance(s) provided by the specified parser.
*
* @param parser the parser to use
* @return the cache operations or {@code null} if none is found
*/
Collection getCacheOperations(CacheAnnotationParser parser);
}
具体实现使用回调模式,用Set
接着看一看其中使用的CacheAnnotaionParser,该接口定义了两个方法:
Collection parseCacheAnnotations(Class> type); //解析类上的标注,并相应创建CacheOperation
Collection parseCacheAnnotations(Method method); //解析方法上的标注,并相应创建CacheOperation
protected Collection parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae) {
Collection ops = null;
Collection cacheables = AnnotatedElementUtils.findAllMergedAnnotations(ae, Cacheable.class);
if (!cacheables.isEmpty()) {
ops = lazyInit(ops);
for (Cacheable cacheable : cacheables) {
ops.add(parseCacheableAnnotation(ae, cachingConfig, cacheable));
}
}
Collection evicts = AnnotatedElementUtils.findAllMergedAnnotations(ae, CacheEvict.class);
if (!evicts.isEmpty()) {
ops = lazyInit(ops);
for (CacheEvict evict : evicts) {
ops.add(parseEvictAnnotation(ae, cachingConfig, evict));
}
}
Collection puts = AnnotatedElementUtils.findAllMergedAnnotations(ae, CachePut.class);
if (!puts.isEmpty()) {
ops = lazyInit(ops);
for (CachePut put : puts) {
ops.add(parsePutAnnotation(ae, cachingConfig, put));
}
}
Collection cachings = AnnotatedElementUtils.findAllMergedAnnotations(ae, Caching.class);
if (!cachings.isEmpty()) {
ops = lazyInit(ops);
for (Caching caching : cachings) {
Collection cachingOps = parseCachingAnnotation(ae, cachingConfig, caching);
if (cachingOps != null) {
ops.addAll(cachingOps);
}
}
}
return ops;
}
参数中AnnotatedElement是Class和Method的父类,代表了一个可被注释的元素,该参数可以是一个Class也可以是一个Method。该方法业务逻辑很清晰:首先查找该类/方法上的Cacheable标注并进行合并。针对合并后的每个Cacheable创建对应的CacheableOperation;然后同样逻辑执行CacheEvict和CachePut。最后处理Caching,Caching表示的是若干组Cache标注的集合,将其解析成一组CacheOperation并添加到Collection
@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
CacheOperationInvoker aopAllianceInvoker = new CacheOperationInvoker() {
@Override
public Object invoke() {
try {
return invocation.proceed();
}
catch (Throwable ex) {
throw new ThrowableWrapper(ex);
}
}
};
try {
return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());
}
catch (CacheOperationInvoker.ThrowableWrapper th) {
throw th.getOriginal();
}
}
这个增强逻辑的核心功能是在CacheAspectSupport中实现的,其中首先调用AnnotationCacheOperationSource.getCacheOperations(method, targetClass)方法得到被调用方法的Collectionprivate Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts)
该方法封装了SpringCache核心的处理逻辑,也就是使用Cache配合来完成用户的方法调用,并返回结果。该核心处理逻辑将在下一篇详细分析。
private CacheOperationSource cacheOperationSource;
private final CacheOperationSourcePointcut pointcut = new CacheOperationSourcePointcut() {
@Override
protected CacheOperationSource getCacheOperationSource() {
return cacheOperationSource;
}
};
//逻辑抽取,非实际源码
advisor.getPointcut().getMethodMatcher().matches(method, targetClass)
来判断该advisor是否适合用于被创建的Bean。因此最终会调用到CacheOperationSourcePointcut的matches方法,代码如下:
@Override
public boolean matches(Method method, Class> targetClass) {
CacheOperationSource cas = getCacheOperationSource();
return (cas != null && !CollectionUtils.isEmpty(cas.getCacheOperations(method, targetClass)));
}
结合上面的代码,最终会调用AnnotationCacheOperationSource.getCacheOperations(method, targetClass)方法。因此matches方法的意思就是:如果bean目标类的任何一个方法存在SpringCache相关的标注从而可以获得List
@Configuration
@ConditionalOnClass(CacheManager.class)
@ConditionalOnBean(CacheAspectSupport.class)
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
.....................
@Import({ CacheManagerCustomizers.class, CacheConfigurationImportSelector.class })
public class CacheAutoConfiguration {
..................
/**
* {@link ImportSelector} to add {@link CacheType} configuration classes.
*/
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;
}
}
}
意思是当满足这些条件:时CacheAutoConfiguration配置类就生效。其中CacheAspectSupport我们在上面已经介绍过了,它是CacheInterceptor的父类,SpringCache真正的核心业务逻辑是由它实现的。当打上@CacheEnable注释时,自动配置了CacheInterceptor的bean,也就是CacheAspectSupport的bean,因此肯定存在。
CacheAutoConfiguration通过@Import机制引入CacheManagerCustomizers.class和CacheConfigurationImportSelector.class。其中CacheConfigurationImportSelector使用CacheType.values()作为Key,遍历并创建将要加载的配置类全名的字符串数组。枚举CacheType的代码为:
public enum CacheType {
/**
* Generic caching using 'Cache' beans from the context.
*/
GENERIC,
/**
* JCache (JSR-107) backed caching.
*/
JCACHE,
/**
* EhCache backed caching.
*/
EHCACHE,
/**
* Hazelcast backed caching.
*/
HAZELCAST,
/**
* Infinispan backed caching.
*/
INFINISPAN,
/**
* Couchbase backed caching.
*/
COUCHBASE,
/**
* Redis backed caching.
*/
REDIS,
/**
* Caffeine backed caching.
*/
CAFFEINE,
/**
* Guava backed caching.
*/
GUAVA,
/**
* Simple in-memory caching.
*/
SIMPLE,
/**
* No caching.
*/
NONE;
}
我们可以看到,其中预定义了11种不同的cache类型。同时在CacheConfigurations类中定义了各不同类型的cache对应的配置类,代码如下:
final class CacheConfigurations {
private static final Map> MAPPINGS;
static {
Map> mappings = new HashMap>();
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.GUAVA, GuavaCacheConfiguration.class);
mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class);
mappings.put(CacheType.NONE, NoOpCacheConfiguration.class);
MAPPINGS = Collections.unmodifiableMap(mappings);
}
private CacheConfigurations() {
}
public static String getConfigurationClass(CacheType cacheType) {
Class> configurationClass = MAPPINGS.get(cacheType);
Assert.state(configurationClass != null, "Unknown cache type " + cacheType);
return configurationClass.getName();
}
public static CacheType getType(String configurationClassName) {
for (Map.Entry> entry : MAPPINGS.entrySet()) {
if (entry.getValue().getName().equals(configurationClassName)) {
return entry.getKey();
}
}
throw new IllegalStateException(
"Unknown configuration class " + configurationClassName);
}
}
这些配置类各自生效的条件并不相同:@Configuration
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class SimpleCacheConfiguration {
private final CacheProperties cacheProperties;
private final CacheManagerCustomizers customizerInvoker;
SimpleCacheConfiguration(CacheProperties cacheProperties,
CacheManagerCustomizers customizerInvoker) {
this.cacheProperties = cacheProperties;
this.customizerInvoker = customizerInvoker;
}
@Bean
public ConcurrentMapCacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
List cacheNames = this.cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return this.customizerInvoker.customize(cacheManager);
}
}
该配置类指明使用ConcurrentMapCacheManager作为CacheManager bean,在其内部使用缓存方法的注释中指明的cache名称来创建ConcurrentMapCache类型的cache,创建cache的代码如下:
public void setCacheNames(Collection cacheNames) {
if (cacheNames != null) {
for (String name : cacheNames) {
this.cacheMap.put(name, createConcurrentMapCache(name));
}
this.dynamic = false;
}
else {
this.dynamic = true;
}
}
如此一来,由于SpringBoot的自动配置机制, 我们只需要打上@EnableCaching标注就可以启动SpringCache机制,使用其开箱即用的缓存实现方案。
小结: 本篇首先介绍了Spring AOP的实现原理,然后介绍了SpringCache如何通过Spring AOP的面向切面编程模式来实现通过打注释就能使用缓存机制的实现原理,最后介绍了一下SpringBoot对于SpringCache的约定大于配置的自动配置实现原理。希望通过本篇介绍,可以让大家对于SpringCache、Spring AOP、SpringBoot autoconfiguration约定大于配置等机制有更深入的了解。
相关文章:SpringCache实现原理及核心业务逻辑(一)
相关文章:SpringCache实现原理及核心业务逻辑(三)