SpringBoot2-番外:第一章:Spring Cache注解简单分析

2,452 阅读7分钟

番外篇?以为是小说吗?没错,在我看来代码就是小说,每个工程师都是小说作者,大体框架是类似的我们只是要适当的去按照自己的想法去完善就行了。

本项目的GitHub:https://github.com/pc859107393/Go2SpringBoot.git

有兴趣交流springboot进行快速开发的同学可以加一下下面的企鹅群。

行走的java全栈

认识Spring cache

Spring cache技术和我们前面遇到的其他Spring相关的技术类似,这里Spring并没有提供真正的cache的实现,而是提供了对缓存使用的抽象。我们只需要使用对应的cache注解就能实现cache的效果。

同样的,这样的设计有良好的可扩展性,我们可以依赖这些设置实现和专业缓存(Ehcache)的集成。

简单的cache构思

如果我们要实现一个简单缓存该怎么做呢?首先我们根据也无需要,一般来说就是简单的增删改查和清理所有缓存的接口和实现。

/**
 * 简单缓存接口
 * @author cheng
 */
interface Cache<T> {
    fun add(key: Any, @Nullable value: T?)

    fun get(key: Any): T?

    fun remove(key: Any)

    fun update(key: Any, @Nullable value: T?)

    fun clear()
}

/**
*简单的缓存,使用的时候初始化就行
*/
class MapCache<T> : Cache<T> {
    private var hashMap: HashMap<Any, T?> = hashMapOf()

    override fun add(key: Any, value: T?) {
        hashMap[key] = value
    }

    override fun get(key: Any): T? = hashMap[key]

    override fun update(key: Any, value: T?) {
        hashMap[key] = value
    }

    override fun remove(key: Any) {
        hashMap.remove(key)
    }

    override fun clear() {
        hashMap.clear()
    }
}

我相信上面这种简单的缓存处理,大家应该都是会使用的,在方法里面做一下判断为空就存入不为空直接取出。

但是这样的缓存有什么缺陷呢?

  • 耦合太高
  • 侵入性太高
  • 更换缓存方案代价太高
  • 维护成本极高

Spring的缓存抽象

在Spring-context包下面给我们提供了对应的缓存抽象,完整的路径为org.springframework.cache,我们先看看这个包下面有哪些东西。

  • org.springframework.cache

    • annotation
    • concurrent
    • config
    • interceptor
    • support
    • Cache.java(Cache接口)
    • CacheManager.java(CacheManager接口)

在这上面,我们最直观的可以看到concurrentsupport可能是cache的一些具体实现,毕竟concurrent代表的是并发,support代表的是实现。

annotation这个包下面,我们可以看到有CacheableCacheConfigCacheEvictCachePutCachingEnableCaching这些注解,他们对应都有各自的功能。

注解名称 主要作用 使用范围 参考案例
EnableCaching 缓存整个bean,可用于缓存某个service 配合Configuration注解标记某个cacheConfig类 Spring的@EnableCaching注解
CacheConfig 提供一个缓存默认设置的集合给某个被标记类的所有方法 标记在某个类上,常用于service层 1️⃣Spring缓存注解@Cacheable,@CachePut , @CacheEvict使用
Cacheable 主要作用在方法上,对该方法结果进行缓存 标记在某个方法上,缓存该方法的结果;缓存在某个类上,缓存该类下面所有方法的结果 2️⃣Spring缓存注解@Cacheable、@CacheEvict、@CachePut使用
CachePut 提供缓存,但是每次都会触发真实调用,并将结果缓存。可以同步数据库结果到缓存中 常用于标记某个方法,并更新结果到缓存中(也可缓存整个类) 1️⃣Spring缓存注解@Cacheable,@CachePut , @CacheEvict使用
CacheEvict 更够清空缓存 某个方法上面标记,可以在一定条件下清空缓存(也可以标记某个类) 1️⃣Spring缓存注解@Cacheable,@CachePut , @CacheEvict使用
Caching 对多个缓存注释(不同或相同类型)的组注释。 方法和类均可进行标记 请查看spring官方文档

紧接着我们可以看看annotation这个包下面的其他资源,具体阐述如下:

  • AbstractCachingConfiguration -> 一个抽象的公共缓存管理功能
/**
* 提供公共的缓存管理功能
*/
@Configuration
public abstract class AbstractCachingConfiguration implements ImportAware {

	@Nullable
	protected AnnotationAttributes enableCaching;   

	@Nullable
	protected CacheManager cacheManager;    //缓存管理器

	@Nullable
	protected CacheResolver cacheResolver;  //缓存解析器

	@Nullable
	protected KeyGenerator keyGenerator;    //key生成器

	@Nullable
	protected CacheErrorHandler errorHandler; //错误Handler


	@Override
	public void setImportMetadata(AnnotationMetadata importMetadata) {//代码省略···}

	@Autowired(required = false)
	void setConfigurers(Collection<CachingConfigurer> configurers) {//代码省略···}

	/**
	 * 导入CachingConfigurer,并拆箱后赋值给cacheManager、cacheResolver、keyGenerator、errorHandler
	 */
	protected void useCachingConfigurer(CachingConfigurer config) {//代码省略···}
}
  • AnnotationCacheOperationSource -> CacheOperationSource接口的实现,用于注解标记的缓存元数据
/**
* CacheOperationSource接口的实现,用于注解标记的缓存元数据。
*/
public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperationSource implements Serializable {

	private final boolean publicMethodsOnly; //是否只支持公开方法

	private final Set<CacheAnnotationParser> annotationParsers; //注解解析器


	/**
	 * Create a default AnnotationCacheOperationSource, supporting public methods
	 * that carry the {@code Cacheable} and {@code CacheEvict} annotations.
	 * 构造一个默认的AnnotationCacheOperationSource,只支持那些被@Cacheable@CacheEvict标记的公开方法。
	 */
	public AnnotationCacheOperationSource() {
		this(true);
	}
    

	@Override
	@Nullable
	protected Collection<CacheOperation> findCacheOperations(final Class<?> clazz) {
		return determineCacheOperations(parser -> parser.parseCacheAnnotations(clazz));
	}

	@Override
	@Nullable
	protected Collection<CacheOperation> findCacheOperations(final Method method) {
		return determineCacheOperations(parser -> parser.parseCacheAnnotations(method));
	}
	
	//上面的两个findCacheOperations方法,结合构造函数的注释来看,也可以找到这里对@Cacheable和@CacheEvict的注解作用域的一个可能描述


	/**
	 * 确定缓存操作,通过对全局的注解解析器的遍历,获取对应的CacheOperation集合并返回
	 */
	@Nullable
	protected Collection<CacheOperation> determineCacheOperations(CacheOperationProvider provider) {
		Collection<CacheOperation> ops = null;
		for (CacheAnnotationParser annotationParser : this.annotationParsers) {
			Collection<CacheOperation> 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}.
	 */
	@FunctionalInterface
	protected interface CacheOperationProvider {

		/**
		 * Return 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 found
		 */
		@Nullable
		Collection<CacheOperation> getCacheOperations(CacheAnnotationParser parser);
	}
	
}
  • CachingConfigurer接口和CachingConfigurerSupport是CachingConfigurer接口的实现类,CachingConfigurer提供了cache相关的一些东西入:CacheManager、CacheResolver、KeyGenerator和CacheErrorHandler。

  • CachingConfigurationSelector是CachingConfiguration的选择器,根据配置选择Proxy或AspectJ。

  • ProxyCachingConfiguration 缓存的代理管理

/**
* 首先标记为设置类,继承AbstractCachingConfiguration,说明这是抽闲缓存管理的具体实现
*/
@Configuration
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {
    
    //所有的BeanDefinition.ROLE_INFRASTRUCTURE 都是spring内部指明的后台工作的bean
	@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;
	}
    
    //值得注意的是Spring中的bean默认都是单例的,所以这里多次调用的本方法实际是同一个
	@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;
	}
}
  • SpringCacheAnnotationParser Spring的缓存注解解析器
//片段一
public class SpringCacheAnnotationParser implements CacheAnnotationParser, Serializable {
    @Override
	@Nullable
	public Collection<CacheOperation> parseCacheAnnotations(Class<?> type) {//省略代码···}

	@Override
	@Nullable
	public Collection<CacheOperation> parseCacheAnnotations(Method method) {//省略代码···}

    //上面两个parseCacheAnnotations方法分别是根据类和方法的注解来调用下面这个parseCacheAnnotations
    
    //这里再调用下面的parseCacheAnnotations,实现具体的某个
	@Nullable
	protected Collection<CacheOperation> parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae) {
		Collection<CacheOperation> ops = parseCacheAnnotations(cachingConfig, ae, false);
		if (ops != null && ops.size() > 1 && ae.getAnnotations().length > 0) {
			// More than one operation found -> local declarations override interface-declared ones...
			Collection<CacheOperation> localOps = parseCacheAnnotations(cachingConfig, ae, true);
			if (localOps != null) {
				return localOps;
			}
		}
		return ops;
	}

	@Nullable
	private Collection<CacheOperation> parseCacheAnnotations(
			DefaultCacheConfig cachingConfig, AnnotatedElement ae, boolean localOnly) {
			    //这里根据注解的不同实现不同的操作
	}
	
	CacheableOperation parseCacheableAnnotation(AnnotatedElement ae, DefaultCacheConfig defaultConfig, Cacheable cacheable) {
	    //Cacheable操作的注解解析信息组装
	}
    //下面还有类似的parseEvictAnnotation、parsePutAnnotation、parseCachingAnnotation几个方法,对应了自己的注解信息组装

    static class DefaultCacheConfig {

		@Nullable
		private final String[] cacheNames;

		@Nullable
		private final String keyGenerator;

		@Nullable
		private final String cacheManager;

		@Nullable
		private final String cacheResolver;
	    
	    //省略构造函数代码
	    
	    //应用默认缓存的设置
	    public void applyDefault(CacheOperation.Builder builder) {//省略代码···}
	}
}

在整个org.springframework.cache.annotation的包下面,我们可以看到是缓存操作的注解以及相关的解析和缓存设置的一些资源。这时候可以猜测一下大概思路是根据配置生成对应的注解配置,再扫描注解后利用注解配置指定的缓存框架来实现缓存操作。


到此为止,基本把注解包下面的东西分析了一下,其实思路还是比较清晰的,首先看那几个注解,接着看抽象类和对应实现,最后考虑接口和实现就行了。

其实真真的springcache远远不止这些,我们现在看到的仅仅是一角而已。