前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >玩转Spring Cache --- 开启基于注解的缓存功能@EnableCaching原理了解【享学Spring】

玩转Spring Cache --- 开启基于注解的缓存功能@EnableCaching原理了解【享学Spring】

作者头像
YourBatman
发布2019-09-03 15:21:42
4.9K0
发布2019-09-03 15:21:42
举报
文章被收录于专栏:BAT的乌托邦BAT的乌托邦
前言

缓存现已成为了项目的标配,更是面必问的知识点。若你说你的项目中还没有使用到缓存,估计你都不太好意思介绍你的项目。

Spring3.1之后就引入了基于注解的缓存技术,但是要明白Spring基于注解的缓存技术并不是一个具体的实现方案(EHCache、OSCache、Redis才是具体的缓存方案),而是对缓存使用的一个抽象。

基于注解的缓存能够在现有的代码基础上只需要加入少量的缓存注解(@Cacheable@CachePut@CacheEvict@Caching)即能够达到缓存方法的返回结果的效果。(使用缓存注解实现缓存无需关心缓存具体的实现产品~)

关于直接调用API方式来使用缓存,可参考:

【小家Spring】聊聊Spring Cache的缓存抽象与JSR107缓存抽象JCache,并使用API方式使用Spring Cache

开启缓存注解的步骤

通过前面多篇文章的学习我们发现,启用Spring的一个功能模块是一件非常方便的事。自然作为Spring框架的核心功能之缓存注解,该功能自然也继承了Spring这个优良特性,使它生效只需要轻松两步:

  1. 配置类上开启缓存注解支持:@EnableCaching
  2. 向容器内至少放置一个CacheManager类型的Bean

仅仅简单的两步后,就可以开工使用Spring强大的缓存注解功能了。

简单示例

按照上面两个步骤配置如下:

代码语言:javascript
复制
@EnableCaching
@Configuration
public class CacheConfig {

    @Bean
    public ConcurrentMapCacheManager cacheManager() {
        ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
        //cacheManager.setStoreByValue(true); //true表示缓存一份副本,否则缓存引用
        return cacheManager;
    }

}

配置好后,开工书写需要使用缓存的代码,此处以Service为例:

代码语言:javascript
复制
@Service
public class CacheDemoServiceImpl implements CacheDemoService {

    @Cacheable(cacheNames = "demoCache", key = "#id")
    @Override
    public Object getFromDB(Integer id) {
        System.out.println("模拟去db查询~~~" + id);
        return "hello cache...";
    }
}

单元测试:

代码语言:javascript
复制
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class, CacheConfig.class})
public class TestSpringBean {

    @Autowired
    private CacheDemoService cacheDemoService;
    @Autowired
    private CacheManager cacheManager;

    @Test
    public void test1() {
        cacheDemoService.getFromDB(1);
        cacheDemoService.getFromDB(1);

        // 校验缓存里的内容~~~~
        Cache demoCache = cacheManager.getCache("demoCache");
        System.out.println(demoCache.getName());
        System.out.println(demoCache.get(1, String.class));
    }

}

输出:

代码语言:javascript
复制
模拟去db查询~~~1
---------------------------------
demoCache
hello cache...

从打印结果的两个可以证明缓存生效了

  1. getFromDB(1)调用了两次,但日志只输出一句,证明第二次并没有执行方法体,缓存生效
  2. 校验代码中,可以拿到名为demoCache的这个Cache对象,并且该Cache内是存在key=1这个键值对的,证明结果确实存入到缓存里了

@EnableCaching开启缓存的原理解析

@EnableCaching这注解用于开启Spring的缓存注解功能,它是一个模块注解,功能类似于xml时代的:<cache:annotation-driven>配置项。本处介绍一下这个注解具体做了哪些事~

首先来到这个注解本身:

代码语言:javascript
复制
@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;
}

该注解的这几个属性我此处略过不解释了,相信应该没有小伙伴有意见吧。同样的,此处重点关注对象为:CachingConfigurationSelector

CachingConfigurationSelector

这个类继承自AdviceModeImportSelector。关于AdviceModeImportSelector这个抽象类,熟悉我博文的小伙伴对这个类应该都不默认了,因为在讲解@EnableAsync@EnableTransactionManagement的时候都解释过多遍,因此本文对此抽象父类不再鳌诉。

有疑惑的可参考:

【小家Spring】Spring异步处理@Async的使用以及原理、源码分析(@EnableAsync)

【小家Spring】从基于@Transactional全注解方式的声明式事务入手,彻底掌握Spring事务管理的原理

代码语言:javascript
复制
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
	
	...

	static {
		ClassLoader classLoader = CachingConfigurationSelector.class.getClassLoader();
		jsr107Present = ClassUtils.isPresent("javax.cache.Cache", classLoader);
		jcacheImplPresent = ClassUtils.isPresent(PROXY_JCACHE_CONFIGURATION_CLASS, classLoader);
	}

	// 绝大多数情况下我们不会使用ASPECTJ,若要使用它还得额外导包~
	// getAspectJImports()这个方法略
	@Override
	public String[] selectImports(AdviceMode adviceMode) {
		switch (adviceMode) {
			case PROXY:
				return getProxyImports();
			case ASPECTJ:
				return getAspectJImports();
			default:
				return null;
		}
	}

	// 向容器导入了AutoProxyRegistrar和ProxyCachingConfiguration
	// 若JSR107的包存在(导入了javax.cache:cache-api这个包),并且并且存在ProxyJCacheConfiguration这个类
	// 显然ProxyJCacheConfiguration这个类我们一般都不会导进来~~~~  所以JSR107是不生效的。   但是但是Spring是支持的,非常良心
	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);
	}
	...
}

它完成了一件事:向容器内注入了AutoProxyRegistrarProxyCachingConfiguration这两个Bean。

AutoProxyRegistrar

关于AutoProxyRegistrar,先看图:

看图的目的是这个类它在事务章节里也被用到,而且已在那做过详述,因此本文略过。需要了解详细的请参考:【小家Spring】从基于@Transactional全注解方式的声明式事务入手,彻底掌握Spring事务管理的原理

说明:该类向Spring容器注入了一个自动代理创建器,因此可想而知缓存的代理对象,最终是委托给自动代理创建器来完成的(和@Async不同哦~)。

ProxyCachingConfiguration

其实这个哥们的设计思想和ProxyTransactionManagementConfiguration如出一辙,只是各自处理各自的业务属性不同而已。

代码语言:javascript
复制
// @since 3.1  内部注入的Bean角色都是ROLE_INFRASTRUCTURE  建议先看下面的父类
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {

	// 缓存注解的增强器:重点在CacheOperationSource和CacheInterceptor
	@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;
	}

	// 关于CacheOperationSource和CacheInterceptor是理解注解原理重要的两个类,放在下一章节讲解更妥
	// 注意CacheOperationSource是给CacheInterceptor用的
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public CacheOperationSource cacheOperationSource() {
		return new AnnotationCacheOperationSource();
	}
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public CacheInterceptor cacheInterceptor() {
		CacheInterceptor interceptor = new CacheInterceptor();
		interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);
		interceptor.setCacheOperationSource(cacheOperationSource());
		return interceptor;
	}
}

// 抽象父类:定义和管理了Caching相关的API、类们
@Configuration
public abstract class AbstractCachingConfiguration implements ImportAware {

	// 这些属性都是protected的,子类可以直接访问哦~~~
	@Nullable
	protected AnnotationAttributes enableCaching;
	@Nullable
	protected Supplier<CacheManager> cacheManager;
	@Nullable
	protected Supplier<CacheResolver> cacheResolver; // 缓存注解的处理器
	@Nullable
	protected Supplier<KeyGenerator> keyGenerator; // key的生成器
	@Nullable
	protected Supplier<CacheErrorHandler> errorHandler; // 错误处理器


	// 继承此抽象类前提条件:必须标注@EnableCaching注解~
	@Override
	public void setImportMetadata(AnnotationMetadata importMetadata) {
		this.enableCaching = AnnotationAttributes.fromMap(importMetadata.getAnnotationAttributes(EnableCaching.class.getName(), false));
		if (this.enableCaching == null) {
			throw new IllegalArgumentException("@EnableCaching is not present on importing class " + importMetadata.getClassName());
		}
	}

	// 可以通过实现CachingConfigurer接口来**指定缓存使用的默认**的:
	// 缓存管理器
	// 缓存解析器
	// key生成器
	// 错误处理器
	@Autowired(required = false)
	void setConfigurers(Collection<CachingConfigurer> configurers) {
		if (CollectionUtils.isEmpty(configurers)) {
			return;
		}
		// CachingConfigurer的实现类,最多只能有一个
		if (configurers.size() > 1) {
			throw new IllegalStateException(configurers.size() + " implementations of " +
					"CachingConfigurer were found when only 1 was expected. " +
					"Refactor the configuration such that CachingConfigurer is " +
					"implemented only once or not at all.");
		}
		CachingConfigurer configurer = configurers.iterator().next();
		useCachingConfigurer(configurer);
	}

	/**
	 * Extract the configuration from the nominated {@link CachingConfigurer}.
	 */
	protected void useCachingConfigurer(CachingConfigurer config) {
		this.cacheManager = config::cacheManager;
		this.cacheResolver = config::cacheResolver;
		this.keyGenerator = config::keyGenerator;
		this.errorHandler = config::errorHandler;
	}

}

可以看到@EnableCaching这个注解的模式和@EnableTransactionManagement的效果是何其相似,所以掌握模式往往比掌握具体某一种技术更重要~

上面配置类向容器放入的三个Bean:BeanFactoryCacheOperationSourceAdvisorCacheOperationSourceCacheInterceptor它是AOP处理缓存注解的核心,这在下一章节有详细的解释说明~

总结

本文先介绍了@EnableCaching它开启缓存注解支持的基本原理,从本文更应该得到的一个道理是:掌握模式往往比掌握具体的一门技术更加重要,这也就是战略和战术的区别吧~

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年07月03日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 开启缓存注解的步骤
  • @EnableCaching开启缓存的原理解析
    • CachingConfigurationSelector
      • AutoProxyRegistrar
      • ProxyCachingConfiguration
      • 总结
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档