前言
缓存现已成为了项目的标配,更是面必问的知识点。若你说你的项目中还没有使用到缓存,估计你都不太好意思介绍你的项目。
Spring3.1之后就引入了基于注解的缓存技术,但是要明白Spring基于注解的缓存技术并不是一个具体的实现方案(EHCache、OSCache、Redis才是具体的缓存方案),而是对缓存使用的一个抽象。
基于注解的缓存能够在现有的代码基础上只需要加入少量的缓存注解(@Cacheable、@CachePut、@CacheEvict、@Caching)即能够达到缓存方法的返回结果的效果。(使用缓存注解实现缓存无需关心缓存具体的实现产品~)
关于直接调用API方式来使用缓存,可参考:
【小家Spring】聊聊Spring Cache的缓存抽象与JSR107缓存抽象JCache,并使用API方式使用Spring Cache
开启缓存注解的步骤
通过前面多篇文章的学习我们发现,启用Spring的一个功能模块是一件非常方便的事。自然作为Spring框架的核心功能之缓存注解,该功能自然也继承了Spring这个优良特性,使它生效只需要轻松两步:
- 配置类上开启缓存注解支持:@EnableCaching
- 向容器内至少放置一个CacheManager类型的Bean
仅仅简单的两步后,就可以开工使用Spring强大的缓存注解功能了。
简单示例
按照上面两个步骤配置如下:
@EnableCaching @Configuration public class CacheConfig { @Bean public ConcurrentMapCacheManager cacheManager() { ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager(); //cacheManager.setStoreByValue(true); //true表示缓存一份副本,否则缓存引用 return cacheManager; } }
配置好后,开工书写需要使用缓存的代码,此处以Service为例:
@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..."; } }
单元测试:
@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)); } }
输出:
模拟去db查询~~~1 --------------------------------- demoCache hello cache...
从打印结果的两个可以证明缓存生效了:
getFromDB(1)
调用了两次,但日志只输出一句,证明第二次并没有执行方法体,缓存生效- 校验代码中,可以拿到名为
demoCache
的这个Cache对象,并且该Cache内是存在key=1
这个键值对的,证明结果确实存入到缓存里了
@EnableCaching开启缓存的原理解析
@EnableCaching这注解用于开启Spring的缓存注解功能,它是一个模块注解,功能类似于xml时代的:<cache:annotation-driven>配置项。本处介绍一下这个注解具体做了哪些事~
首先来到这个注解本身:
@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事务管理的原理
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); } ... }
它完成了一件事:向容器内注入了AutoProxyRegistrar
和ProxyCachingConfiguration
这两个Bean。
AutoProxyRegistrar
关于AutoProxyRegistrar
,先看图:
看图的目的是这个类它在事务章节里也被用到,而且已在那做过详述,因此本文略过。需要了解详细的请参考:【小家Spring】从基于@Transactional全注解方式的声明式事务入手,彻底掌握Spring事务管理的原理
说明:该类向Spring容器注入了一个自动代理创建器,因此可想而知缓存的代理对象,最终是委托给自动代理创建器来完成的(和@Async不同哦~)。