spring源码分析之cache注解
Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如EHCache 或者 OSCache),而是一个对缓存使用的抽象,通过在既有代码中添加少量它定义的各种 annotation,即能够达到缓存方法的返回对象的效果。
Spring 的缓存技术还具备相当的灵活性,不仅能够使用 SpEL(Spring Expression Language)来定义缓存的 key 和各种 condition,还提供开箱即用的缓存临时存储方案,也支持和主流的专业缓存例如 EHCache 集成。
其特点总结如下:
- 通过少量的配置 annotation 注释即可使得既有代码支持缓存
- 支持开箱即用 Out-Of-The-Box,即不用安装和部署额外第三方组件即可使用缓存
- 支持 Spring Express Language,能使用对象的任何属性或者方法来定义缓存的 key 和 condition
- 支持 AspectJ,并通过其实现任何方法的缓存支持
- 支持自定义 key 和自定义缓存管理者,具有相当的灵活性和扩展性
1.EnableCaching
开启spring注解驱动的cache管理能力,类似于spring xml命名空间<cache:*>的支持。将会和org.springframework.context.annotation.Configuration一起使用,示例:
@Configuration
@EnableCaching
public class AppConfig {
@Bean
public MyService myService() {
// configure and return a class having @Cacheable methods
return new MyService();
} @Bean
public CacheManager cacheManager() {
// configure and return an implementation of Spring's CacheManager SPI
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.addCaches(Arrays.asList(new ConcurrentMapCache("default")));
return cacheManager;
}
}
相应的xml配置如下:
<beans>
<cache:annotation-driven/>
<bean id="myService" class="com.foo.MyService"/>
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
<property name="name" value="default"/>
</bean>
</set>
</property>
</bean>
</beans>
在上述的场景中,@EnableCaching和<cache:annotation-driven/>负责注册spring所需的管理注解驱动的cache管理组件,如org.springframework.cache.interceptor.CacheInterceptor,当org.springframework.cache.annotation.Cacheable方法触发时基于proxy-或者基于AspectJ的advice 会将interceptor织入调用栈中。
必须注册org.springframework.cache.CacheManager类型的bean,原因是框架没有默认的可用。不同点在于<cache:annotation-driven>元素假定了一个名称为cacheManager的bean,而@EnableCaching通过类型检索一个cache manage bean。因此cache manager bean方法的命名不是显式的。
如果希望使用一种@EnableCaching与cache manager bean更直接的方式,spring提供了CachingConfigurer回调接口的实现,注意实现语句和@override注解方法:
@Configuration
@EnableCaching
public class AppConfig extends CachingConfigurerSupport { @Bean
public MyService myService() {
// configure and return a class having @Cacheable methods
return new MyService();
} @Bean
@Override
public CacheManager cacheManager() {
// configure and return an implementation of Spring's CacheManager SPI
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("default")));
return cacheManager;
} @Bean
@Override
public KeyGenerator keyGenerator() {
// configure and return an implementation of Spring's KeyGenerator SPI
return new MyKeyGenerator();
}
}
注意,上述示例中的keyGenerator方法,它允许自定义一个cache key产生策略。每个spring的org.springframework.cache.interceptor.KeyGenerator spi,通常将配置spring的org.springframework.cache.interceptor.SimpleKeyGenerator,一个key生成器必须显示指明。如果不需要定制,则直接返回new SimpleKeyGenerator()。
1.1 方法
boolean proxyTargetClass() default false;
表明是创建基于子类代理(cglib)还是基于接口的标准java代理,默认是false。当且仅当mode()设置成Advice#PROXY时其作用。
注意,这个属性设置为true时会影响到所有spring管理bean的代理,而不仅仅是被标识为@Cacheable的接口或者类。例如,spring标注的其它bean如@Transactional将会同时升级到子类代理。这种方式实际上没有负面影响,除非显示期望一种代理类型,例如测试。
1.2 源码
1.2.1 xml源码解析
CacheNamespaceHandler.java
public class CacheNamespaceHandler extends NamespaceHandlerSupport { static final String CACHE_MANAGER_ATTRIBUTE = "cache-manager"; static final String DEFAULT_CACHE_MANAGER_BEAN_NAME = "cacheManager"; static String extractCacheManager(Element element) {
return (element.hasAttribute(CacheNamespaceHandler.CACHE_MANAGER_ATTRIBUTE) ? element
.getAttribute(CacheNamespaceHandler.CACHE_MANAGER_ATTRIBUTE)
: CacheNamespaceHandler.DEFAULT_CACHE_MANAGER_BEAN_NAME);
} static BeanDefinition parseKeyGenerator(Element element, BeanDefinition def) {
String name = element.getAttribute("key-generator");
if (StringUtils.hasText(name)) {
def.getPropertyValues().add("keyGenerator", new RuntimeBeanReference(name.trim()));
}
return def;
} @Override
public void init() {
registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenCacheBeanDefinitionParser());
registerBeanDefinitionParser("advice", new CacheAdviceParser());
}
}
1.2.1.1 注册了AnnotationDrivenCacheBeanDefinitionParser
/**
* Parses the '{@code <cache:annotation-driven>}' tag. Will
* {@link AopNamespaceUtils#registerAutoProxyCreatorIfNecessary
* register an AutoProxyCreator} with the container as necessary.
*/
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
String mode = element.getAttribute("mode");
if ("aspectj".equals(mode)) {
// mode="aspectj"
registerCacheAspect(element, parserContext);
}
else {
// mode="proxy"
AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext);
} return null;
}
aspectJ方式
/**
* Registers a
* <pre class="code">
* <bean id="cacheAspect" class="org.springframework.cache.aspectj.AnnotationCacheAspect" factory-method="aspectOf">
* <property name="cacheManager" ref="cacheManager"/>
* <property name="keyGenerator" ref="keyGenerator"/>
* </bean>
* </pre>
*/
private void registerCacheAspect(Element element, ParserContext parserContext) {
if (!parserContext.getRegistry().containsBeanDefinition(AnnotationConfigUtils.CACHE_ASPECT_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition();
def.setBeanClassName(AnnotationConfigUtils.CACHE_ASPECT_CLASS_NAME);
def.setFactoryMethodName("aspectOf");
parseCacheManagerProperty(element, def);
CacheNamespaceHandler.parseKeyGenerator(element, def);
parserContext.registerBeanComponent(new BeanComponentDefinition(def, AnnotationConfigUtils.CACHE_ASPECT_BEAN_NAME));
}
}
aop方式:
/**
* Inner class to just introduce an AOP framework dependency when actually in proxy mode.
*/
private static class AopAutoProxyConfigurer { public static void configureAutoProxyCreator(Element element, ParserContext parserContext) {
AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element); if (!parserContext.getRegistry().containsBeanDefinition(AnnotationConfigUtils.CACHE_ADVISOR_BEAN_NAME)) {
Object eleSource = parserContext.extractSource(element); // Create the CacheOperationSource definition.
RootBeanDefinition sourceDef = new RootBeanDefinition("org.springframework.cache.annotation.AnnotationCacheOperationSource");
sourceDef.setSource(eleSource);
sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef); // Create the CacheInterceptor definition.
RootBeanDefinition interceptorDef = new RootBeanDefinition(CacheInterceptor.class);
interceptorDef.setSource(eleSource);
interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
parseCacheManagerProperty(element, interceptorDef);
CacheNamespaceHandler.parseKeyGenerator(element, interceptorDef);
interceptorDef.getPropertyValues().add("cacheOperationSources", new RuntimeBeanReference(sourceName));
String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef); // Create the CacheAdvisor definition.
RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryCacheOperationSourceAdvisor.class);
advisorDef.setSource(eleSource);
advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
advisorDef.getPropertyValues().add("cacheOperationSource", new RuntimeBeanReference(sourceName));
advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);
if (element.hasAttribute("order")) {
advisorDef.getPropertyValues().add("order", element.getAttribute("order"));
}
parserContext.getRegistry().registerBeanDefinition(AnnotationConfigUtils.CACHE_ADVISOR_BEAN_NAME, advisorDef); CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(),
eleSource);
compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName));
compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName));
compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, AnnotationConfigUtils.CACHE_ADVISOR_BEAN_NAME));
parserContext.registerComponent(compositeDef);
}
}
1.2.1.2 注册了CacheAdviceParser,处理子注解
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
builder.addPropertyReference("cacheManager", CacheNamespaceHandler.extractCacheManager(element));
CacheNamespaceHandler.parseKeyGenerator(element, builder.getBeanDefinition()); List<Element> cacheDefs = DomUtils.getChildElementsByTagName(element, DEFS_ELEMENT);
if (cacheDefs.size() >= 1) {
// Using attributes source.
List<RootBeanDefinition> attributeSourceDefinitions = parseDefinitionsSources(cacheDefs, parserContext);
builder.addPropertyValue("cacheOperationSources", attributeSourceDefinitions);
}
else {
// Assume annotations source.
builder.addPropertyValue("cacheOperationSources",
new RootBeanDefinition("org.springframework.cache.annotation.AnnotationCacheOperationSource"));
}
}
调用解析方法,包括
cacheable,
cache-evict,
cache-put,
method,
caching
private static final String CACHEABLE_ELEMENT = "cacheable"; private static final String CACHE_EVICT_ELEMENT = "cache-evict"; private static final String CACHE_PUT_ELEMENT = "cache-put"; private static final String METHOD_ATTRIBUTE = "method"; private static final String DEFS_ELEMENT = "caching"; private RootBeanDefinition parseDefinitionSource(Element definition, ParserContext parserContext) {
Props prop = new Props(definition);
// add cacheable first ManagedMap<TypedStringValue, Collection<CacheOperation>> cacheOpMap = new ManagedMap<TypedStringValue, Collection<CacheOperation>>();
cacheOpMap.setSource(parserContext.extractSource(definition)); List<Element> cacheableCacheMethods = DomUtils.getChildElementsByTagName(definition, CACHEABLE_ELEMENT); for (Element opElement : cacheableCacheMethods) {
String name = prop.merge(opElement, parserContext.getReaderContext());
TypedStringValue nameHolder = new TypedStringValue(name);
nameHolder.setSource(parserContext.extractSource(opElement));
CacheableOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CacheableOperation());
op.setUnless(getAttributeValue(opElement, "unless", "")); Collection<CacheOperation> col = cacheOpMap.get(nameHolder);
if (col == null) {
col = new ArrayList<CacheOperation>(2);
cacheOpMap.put(nameHolder, col);
}
col.add(op);
} List<Element> evictCacheMethods = DomUtils.getChildElementsByTagName(definition, CACHE_EVICT_ELEMENT); for (Element opElement : evictCacheMethods) {
String name = prop.merge(opElement, parserContext.getReaderContext());
TypedStringValue nameHolder = new TypedStringValue(name);
nameHolder.setSource(parserContext.extractSource(opElement));
CacheEvictOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CacheEvictOperation()); String wide = opElement.getAttribute("all-entries");
if (StringUtils.hasText(wide)) {
op.setCacheWide(Boolean.valueOf(wide.trim()));
} String after = opElement.getAttribute("before-invocation");
if (StringUtils.hasText(after)) {
op.setBeforeInvocation(Boolean.valueOf(after.trim()));
} Collection<CacheOperation> col = cacheOpMap.get(nameHolder);
if (col == null) {
col = new ArrayList<CacheOperation>(2);
cacheOpMap.put(nameHolder, col);
}
col.add(op);
} List<Element> putCacheMethods = DomUtils.getChildElementsByTagName(definition, CACHE_PUT_ELEMENT); for (Element opElement : putCacheMethods) {
String name = prop.merge(opElement, parserContext.getReaderContext());
TypedStringValue nameHolder = new TypedStringValue(name);
nameHolder.setSource(parserContext.extractSource(opElement));
CachePutOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CachePutOperation());
op.setUnless(getAttributeValue(opElement, "unless", "")); Collection<CacheOperation> col = cacheOpMap.get(nameHolder);
if (col == null) {
col = new ArrayList<CacheOperation>(2);
cacheOpMap.put(nameHolder, col);
}
col.add(op);
} RootBeanDefinition attributeSourceDefinition = new RootBeanDefinition(NameMatchCacheOperationSource.class);
attributeSourceDefinition.setSource(parserContext.extractSource(definition));
attributeSourceDefinition.getPropertyValues().add("nameMap", cacheOpMap);
return attributeSourceDefinition;
}
其中,
- @Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
- @CachePut 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用
- @CachEvict 主要针对方法配置,能够根据一定的条件对缓存进行清空
@Cacheable、@CachePut、@CacheEvict 注释介绍
通过上面的源码,我们可以看到 spring cache 主要使用两个注释标签,即 @Cacheable、@CachePut 和 @CacheEvict,我们总结一下其作用和配置方法。
表 1. @Cacheable 作用和配置方法
@Cacheable 的作用 | 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存 | |
---|---|---|
@Cacheable 主要的参数 | ||
value | 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 | 例如: @Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”} |
key | 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 | 例如: @Cacheable(value=”testcache”,key=”#userName”) |
condition | 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 | 例如: @Cacheable(value=”testcache”,condition=”#userName.length()>2”) |
表 2. @CachePut 作用和配置方法
@CachePut 的作用 | 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用 | |
---|---|---|
@CachePut 主要的参数 | ||
value | 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 | 例如: @Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”} |
key | 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 | 例如: @Cacheable(value=”testcache”,key=”#userName”) |
condition | 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 | 例如: @Cacheable(value=”testcache”,condition=”#userName.length()>2”) |
表 3. @CacheEvict 作用和配置方法
@CachEvict 的作用 | 主要针对方法配置,能够根据一定的条件对缓存进行清空 | |
---|---|---|
@CacheEvict 主要的参数 | ||
value | 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 | 例如: @CachEvict(value=”mycache”) 或者 @CachEvict(value={”cache1”,”cache2”} |
key | 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 | 例如: @CachEvict(value=”testcache”,key=”#userName”) |
condition | 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才清空缓存 | 例如: @CachEvict(value=”testcache”, condition=”#userName.length()>2”) |
allEntries | 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存 | 例如: @CachEvict(value=”testcache”,allEntries=true) |
beforeInvocation | 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存 | 例如: @CachEvict(value=”testcache”,beforeInvocation=true) |
小结
总之,注释驱动的 spring cache 能够极大的减少我们编写常见缓存的代码量,通过少量的注释标签和配置文件,即可达到使代码具备缓存的能力。且具备很好的灵活性和扩展性。但是我们也应该看到,spring cache 由于基于 spring AOP 技术,尤其是动态的 proxy 技术,导致其不能很好的支持方法的内部调用或者非 public 方法的缓存设置,当然这都是可以解决的问题,通过学习这个技术,我们能够认识到,AOP 技术的应用还是很广泛的,如果有兴趣,我相信你也能基于 AOP 实现自己的缓存方案。
参考文献
【1】https://www.ibm.com/developerworks/cn/opensource/os-cn-spring-cache/
spring源码分析之cache注解的更多相关文章
- Spring源码分析-从@ComponentScan注解配置包扫描路径到IoC容器中的BeanDefinition,经历了什么(一)?
阅前提醒 全文较长,建议沉下心来慢慢阅读,最好是打开Idea,点开Spring源码,跟着下文一步一步阅读,更加便于理解.由于笔者水平优先,编写时间仓促,文中难免会出现一些错误或者不准确的地方,恳请各位 ...
- spring源码分析之cache demo
spring提供了对echache.guava.jcache的支持,先看一个echache的示例: import org.springframework.cache.CacheManager; imp ...
- 【spring源码分析】@Value注解原理
class org.springframework.context.support.PropertySourcesPlaceholderConfigurer 该类实现了的接口:1.org.spring ...
- Spring Ioc源码分析系列--@Autowired注解的实现原理
Spring Ioc源码分析系列--@Autowired注解的实现原理 前言 前面系列文章分析了一把Spring Ioc的源码,是不是云里雾里,感觉并没有跟实际开发搭上半毛钱关系?看了一遍下来,对我的 ...
- Spring源码分析(十八)创建bean
本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 目录 一.创建bean的实例 1. autowireConstructor 2 ...
- 框架-spring源码分析(一)
框架-spring源码分析(一) 参考: https://www.cnblogs.com/heavenyes/p/3933642.html http://www.cnblogs.com/BINGJJF ...
- spring源码分析之spring-core总结篇
1.spring-core概览 spring-core是spring框架的基石,它为spring框架提供了基础的支持. spring-core从源码上看,分为6个package,分别是asm,cgli ...
- Spring源码分析——BeanFactory体系之抽象类、类分析(二)
上一篇分析了BeanFactory体系的2个类,SimpleAliasRegistry和DefaultSingletonBeanRegistry——Spring源码分析——BeanFactory体系之 ...
- spring源码分析(二)Aop
创建日期:2016.08.19 修改日期:2016.08.20-2016.08.21 交流QQ:992591601 参考资料:<spring源码深度解析>.<spring技术内幕&g ...
随机推荐
- 使用post方式提交表单如何获取图片数据及其他文本参数[NodeJS]
当POST方式提交包含图片的表单时,如上传图片时,需要在<form>字段需要添加参数enctype="multipart/form-data",表明以二进制方式传输数据 ...
- python setup.py 管理
发布项目遇到了坑……特此记录. How to write setup.py: https://docs.python.org/2/distutils/setupscript.html Setup.py ...
- Android事件分发小结
******** ******** 第一部分: 介绍说明 ******** ******** 个人感觉在做交互的时候, 对于Android的按键分发的理解还是比较重要的. 这些内容在 ...
- SQL 去掉某字段括号中的值
今天在数据库匹配数据的时候,发现一些数据存在别名,导致我的数据匹配不上.在这里记录分享一下 例如: 李钟硕 (Lee Jong Suk),这里我匹配的是 “李钟硕” 示例1: SELECT rever ...
- XCod5 SVN
近日开始学习IOS开发, 涉及版本管理问题,老大说要使用SVN, 在Windows系统中使用SVN的经验使得俺以为需要首先安装SVN,然后再配置等等, 殊不知XCode5中已经内置了SVN, 在百度一 ...
- CMS模板引擎:XHtmlAction
前言: 先说说大伙关心的工作上的事,在上家公司任了一个多月的技术经理后,和公司中止了合作关系. 主要原因在于一开始的待遇没谈的太清楚: 1:没有合同,没有公积金,连社保也没交. 2:工资的30%变成了 ...
- Mono 3.0.12 支持可移植类库
Mono 3.0.12已于6月19日发布.对跨平台开发者而言,对可移植类库的支持可能是该版本最重要的变化.该技术可以使一个DLL支持.NET.Windows Store.Windows Phone.S ...
- 挡不住的好奇心:ASP.NET 5是如何通过XRE实现跨平台的
.NET程序员也有自己的幸福,.NET的跨平台是一种幸福,.NET的开源也是一种幸福,而更幸福的是可以通过开源的.NET了解.NET是如何一步步走向跨平台的,所以幸福是一种过程. 在.NET跨平台的进 ...
- 免费图片存储和图话【提供demo下载】
我们不管是做博客系统还是其他网站,图片是免不了要使用到的.但是,我们都知道图片的访问是很耗资源的,同时也是很占磁盘空间的,且还特别占带宽. 所以,我们一般都会用到特定的图片服务器.不过,像我等屌丝平时 ...
- 探索C#之布隆过滤器(Bloom filter)
阅读目录: 背景介绍 算法原理 误判率 BF改进 总结 背景介绍 Bloom filter(后面简称BF)是Bloom在1970年提出的二进制向量数据结构.通俗来说就是在大数据集合下高效判断某个成员是 ...