前言

项目里面要增加一个应用缓存,原本想着要怎么怎么来整合ehcache和springboot,做好准备配置这个配置那个,结果只需要做三件事:

  • pom依赖
  • 写好一个ehcache的配置文件
  • 在boot的application上加上注解@EnableCaching.
    这就完事了,是不是很魔幻。

pom依赖


<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.5</version>
</dependency>

配置文件


<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
<!-- 设定缓存的默认数据过期策略 -->
<defaultCache
maxElementsInMemory="500"
maxElementsOnDisk="2000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="90"
timeToLiveSeconds="300"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="300"/>
</ehcache>

应用上加上EnableCaching注解


@SpringBootApplication
@EnableCaching
public class EhCacheApplication { public static void main(String[] args) {
SpringApplication.run(EhCacheApplication.class, args);
}
}

然后就可以在代码里面使用cache注解了,像这样。


@CachePut(value = "fish-ehcache", key = "#person.id")
public Person save(Person person) {
System.out.println("为id、key为:" + person.getId() + "数据做了缓存");
return person;
} @CacheEvict(value = "fish-ehcache")
public void remove(Long id) {
System.out.println("删除了id、key为" + id + "的数据缓存");
} @Cacheable(value = "fish-ehcache", key = "#person.id")
public Person findOne(Person person) {
findCount.incrementAndGet();
System.out.println("为id、key为:" + person.getId() + "数据做了缓存");
return person;
}

很方便对不对。下面,我们就来挖一挖,看看spring是怎么来做到的。主要分成两部分,一是启动的时候做了什么,二是运行的时候做了什么,三是和第三方缓存组件的适配

启动的时候做了什么、

这个得从@EnableCaching标签开始,在使用缓存功能时,在springboot的Application启动类上需要添加注解@EnableCaching,这个标签引入了


@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 2147483647;
}

引入了CachingConfigurationSelector类,这个类便开启了缓存功能的配置。这个类添加了AutoProxyRegistrar.java,ProxyCachingConfiguration.java两个类。

  • AutoProxyRegistrar : 实现了ImportBeanDefinitionRegistrar接口。这里看不懂,还需要继续学习。
  • ProxyCachingConfiguration : 是一个配置类,生成了BeanFactoryCacheOperationSourceAdvisor,CacheOperationSource,和CacheInterceptor这三个bean。

CacheOperationSource封装了cache方法签名注解的解析工作,形成CacheOperation的集合。CacheInterceptor使用该集合过滤执行缓存处理。解析缓存注解的类是SpringCacheAnnotationParser,其主要方法如下


/**
由CacheOperationSourcePointcut作为注解切面,会解析
SpringCacheAnnotationParser.java
扫描方法签名,解析被缓存注解修饰的方法,将生成一个CacheOperation的子类并将其保存到一个数组中去
**/
protected Collection<CacheOperation> parseCacheAnnotations(SpringCacheAnnotationParser.DefaultCacheConfig cachingConfig, AnnotatedElement ae) {
Collection<CacheOperation> ops = null;
//找@cacheable注解方法
Collection<Cacheable> cacheables = AnnotatedElementUtils.getAllMergedAnnotations(ae, Cacheable.class);
if (!cacheables.isEmpty()) {
ops = this.lazyInit(ops);
Iterator var5 = cacheables.iterator(); while(var5.hasNext()) {
Cacheable cacheable = (Cacheable)var5.next();
ops.add(this.parseCacheableAnnotation(ae, cachingConfig, cacheable));
}
}
//找@cacheEvict注解的方法
Collection<CacheEvict> evicts = AnnotatedElementUtils.getAllMergedAnnotations(ae, CacheEvict.class);
if (!evicts.isEmpty()) {
ops = this.lazyInit(ops);
Iterator var12 = evicts.iterator(); while(var12.hasNext()) {
CacheEvict evict = (CacheEvict)var12.next();
ops.add(this.parseEvictAnnotation(ae, cachingConfig, evict));
}
}
//找@cachePut注解的方法
Collection<CachePut> puts = AnnotatedElementUtils.getAllMergedAnnotations(ae, CachePut.class);
if (!puts.isEmpty()) {
ops = this.lazyInit(ops);
Iterator var14 = puts.iterator(); while(var14.hasNext()) {
CachePut put = (CachePut)var14.next();
ops.add(this.parsePutAnnotation(ae, cachingConfig, put));
}
}
Collection<Caching> cachings = AnnotatedElementUtils.getAllMergedAnnotations(ae, Caching.class);
if (!cachings.isEmpty()) {
ops = this.lazyInit(ops);
Iterator var16 = cachings.iterator(); while(var16.hasNext()) {
Caching caching = (Caching)var16.next();
Collection<CacheOperation> cachingOps = this.parseCachingAnnotation(ae, cachingConfig, caching);
if (cachingOps != null) {
ops.addAll(cachingOps);
}
}
}
return ops;
}

解析Cachable,Caching,CachePut,CachEevict 这四个注解对应的方法都保存到了Collection<CacheOperation> 集合中。

执行方法时做了什么

执行的时候,主要使用了CacheInterceptor类。


public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {
public CacheInterceptor() {
} public Object invoke(final MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
CacheOperationInvoker aopAllianceInvoker = new CacheOperationInvoker() {
public Object invoke() {
try {
return invocation.proceed();
} catch (Throwable var2) {
throw new ThrowableWrapper(var2);
}
}
}; try {
return this.execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());
} catch (ThrowableWrapper var5) {
throw var5.getOriginal();
}
}
}

这个拦截器继承了CacheAspectSupport类和MethodInterceptor接口。其中CacheAspectSupport封装了主要的逻辑。比如下面这段。


/**
CacheAspectSupport.java
执行@CachaEvict @CachePut @Cacheable的主要逻辑代码
**/ private Object execute(final CacheOperationInvoker invoker, Method method, CacheAspectSupport.CacheOperationContexts contexts) {
if (contexts.isSynchronized()) {
CacheAspectSupport.CacheOperationContext context = (CacheAspectSupport.CacheOperationContext)contexts.get(CacheableOperation.class).iterator().next();
if (this.isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
Object key = this.generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
Cache cache = (Cache)context.getCaches().iterator().next(); try {
return this.wrapCacheValue(method, cache.get(key, new Callable&lt;Object&gt;() {
public Object call() throws Exception {
return CacheAspectSupport.this.unwrapReturnValue(CacheAspectSupport.this.invokeOperation(invoker));
}
}));
} catch (ValueRetrievalException var10) {
throw (ThrowableWrapper)var10.getCause();
}
} else {
return this.invokeOperation(invoker);
}
} else {
/**
执行@CacheEvict的逻辑,这里是当beforeInvocation为true时清缓存
**/
this.processCacheEvicts(contexts.get(CacheEvictOperation.class), true, CacheOperationExpressionEvaluator.NO_RESULT);
//获取命中的缓存对象
ValueWrapper cacheHit = this.findCachedItem(contexts.get(CacheableOperation.class));
List&lt;CacheAspectSupport.CachePutRequest&gt; cachePutRequests = new LinkedList();
if (cacheHit == null) {
//如果没有命中,则生成一个put的请求
this.collectPutRequests(contexts.get(CacheableOperation.class), CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
} Object cacheValue;
Object returnValue;
/**
如果没有获得缓存对象,则调用业务方法获得返回对象,hasCachePut会检查exclude的情况
**/
if (cacheHit != null &amp;&amp; cachePutRequests.isEmpty() &amp;&amp; !this.hasCachePut(contexts)) {
cacheValue = cacheHit.get();
returnValue = this.wrapCacheValue(method, cacheValue);
} else { returnValue = this.invokeOperation(invoker);
cacheValue = this.unwrapReturnValue(returnValue);
} this.collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
Iterator var8 = cachePutRequests.iterator(); while(var8.hasNext()) {
CacheAspectSupport.CachePutRequest cachePutRequest = (CacheAspectSupport.CachePutRequest)var8.next();
/**
执行cachePut请求,将返回对象放到缓存中
**/
cachePutRequest.apply(cacheValue);
}
/**
执行@CacheEvict的逻辑,这里是当beforeInvocation为false时清缓存
**/
this.processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
return returnValue;
}
}

上面的代码片段比较核心,均是cache的内容,对于aop的源码,这里不详细展开,应该单起一篇文章进行研究。主要的类和接口都在spring的context中,org.springframework.cache包中。

和第三方缓存组件的适配

通过以上的分析,知道了spring cache功能的来龙去脉,下面需要分析的是,为什么只需要maven声明一下依赖,spring boot 就可以自动就适配了.

在上面的执行方法中,我们看到了cachePutRequest.apply(cacheValue) ,这里会操作缓存,CachePutRequest是CacheAspectSupport的内部类。


private class CachePutRequest {
private final CacheAspectSupport.CacheOperationContext context;
private final Object key;
public CachePutRequest(CacheAspectSupport.CacheOperationContext context, Object key) {
this.context = context;
this.key = key;
}
public void apply(Object result) {
if (this.context.canPutToCache(result)) {
//从context中获取cache实例,然后执行放入缓存的操作
Iterator var2 = this.context.getCaches().iterator();
while(var2.hasNext()) {
Cache cache = (Cache)var2.next();
CacheAspectSupport.this.doPut(cache, this.key, result);
}
}
}
}

Cache是一个标准接口,其中EhCacheCache就是EhCache的实现类。这里就是SpringBoot和Ehcache之间关联的部分,那么context中的cache列表是什么时候生成的呢。答案是CacheAspectSupport的getCaches方法


protected Collection&lt;? extends Cache&gt; getCaches(CacheOperationInvocationContext&lt;CacheOperation&gt; context, CacheResolver cacheResolver) {
Collection&lt;? extends Cache&gt; caches = cacheResolver.resolveCaches(context);
if (caches.isEmpty()) {
throw new IllegalStateException("No cache could be resolved for '" + context.getOperation() + "' using resolver '" + cacheResolver + "'. At least one cache should be provided per cache operation.");
} else {
return caches;
}
}

而获取cache是在每一次进行进行缓存操作的时候执行。可以看一下调用栈

貌似有点跑题,拉回来... 在spring-boot-autoconfigure包里,有所有自动装配相关的类。这里有个EhcacheCacheConfiguration类 ,如下


@Configuration
@ConditionalOnClass({Cache.class, EhCacheCacheManager.class})
@ConditionalOnMissingBean({CacheManager.class})
@Conditional({CacheCondition.class, EhCacheCacheConfiguration.ConfigAvailableCondition.class})
class EhCacheCacheConfiguration {
......
static class ConfigAvailableCondition extends ResourceCondition {
ConfigAvailableCondition() {
super("EhCache", "spring.cache.ehcache", "config", new String[]{"classpath:/ehcache.xml"});
}
}
}

这里会直接判断类路径下是否有ehcache.xml文件

Spring Boot缓存源码分析的更多相关文章

  1. spring boot 入口源码分析

    public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); / ...

  2. Spring IOC 容器源码分析 - 循环依赖的解决办法

    1. 简介 本文,我们来看一下 Spring 是如何解决循环依赖问题的.在本篇文章中,我会首先向大家介绍一下什么是循环依赖.然后,进入源码分析阶段.为了更好的说明 Spring 解决循环依赖的办法,我 ...

  3. Spring IOC 容器源码分析 - 创建原始 bean 对象

    1. 简介 本篇文章是上一篇文章(创建单例 bean 的过程)的延续.在上一篇文章中,我们从战略层面上领略了doCreateBean方法的全过程.本篇文章,我们就从战术的层面上,详细分析doCreat ...

  4. Spring IOC 容器源码分析 - 创建单例 bean 的过程

    1. 简介 在上一篇文章中,我比较详细的分析了获取 bean 的方法,也就是getBean(String)的实现逻辑.对于已实例化好的单例 bean,getBean(String) 方法并不会再一次去 ...

  5. Spring IOC 容器源码分析 - 获取单例 bean

    1. 简介 为了写 Spring IOC 容器源码分析系列的文章,我特地写了一篇 Spring IOC 容器的导读文章.在导读一文中,我介绍了 Spring 的一些特性以及阅读 Spring 源码的一 ...

  6. Spring Developer Tools 源码分析:三、重启自动配置'

    接上文 Spring Developer Tools 源码分析:二.类路径监控,接下来看看前面提到的这些类是如何配置,如何启动的. spring-boot-devtools 使用了 Spring Bo ...

  7. Spring Developer Tools 源码分析:二、类路径监控

    在 Spring Developer Tools 源码分析一中介绍了 devtools 提供的文件监控实现,在第二部分中,我们将会使用第一部分提供的目录监控功能,实现对开发环境中 classpath ...

  8. Spring IOC 容器源码分析 - 余下的初始化工作

    1. 简介 本篇文章是"Spring IOC 容器源码分析"系列文章的最后一篇文章,本篇文章所分析的对象是 initializeBean 方法,该方法用于对已完成属性填充的 bea ...

  9. Spring IOC 容器源码分析 - 填充属性到 bean 原始对象

    1. 简介 本篇文章,我们来一起了解一下 Spring 是如何将配置文件中的属性值填充到 bean 对象中的.我在前面几篇文章中介绍过 Spring 创建 bean 的流程,即 Spring 先通过反 ...

随机推荐

  1. Windows Phone 8 适应多屏分辨率

    Windows Phone 8 比较 windows phone 7 来说有很多功能及性能上的提升例如支持多核 CUP.支持SD卡.多种分辨率. 显然WP7 WVGA - 480x800 的已经不能完 ...

  2. luogu P1510 精卫填海

    题目描述 [版权说明] 本题为改编题. [问题描述] 发鸠之山,其上多柘木.有鸟焉,其状如乌,文首,白喙,赤足,名曰精卫,其名自詨.是炎帝之少女,名曰女娃.女娃游于东海,溺而不返,故为精卫.常衔西山之 ...

  3. win7 32 c++环境

    http://jingyan.baidu.com/article/455a99509c76d8a1662778f6.html 首先我们先来到这个网址下载MinGW的下载程序,百度搜索官网即可.下载之后 ...

  4. STL源代码剖析 容器 stl_deque.h

    本文为senlie原创.转载请保留此地址:http://blog.csdn.net/zhengsenlie deque ---------------------------------------- ...

  5. 【matlab】:matlab中不断的出现计算过程怎么办

    这个问题是会常常性出的.就是matlab中不断的出现计算. 关于这个问题,我们须要考虑的是自己是不是写错了,通常会出现以下两种可能的错误 1,关于计算的函数没有写分号 :这样的是致命问题,假设函数不写 ...

  6. G - Specialized Four-Digit Numbers(1.5.2)

    Time Limit:1000MS     Memory Limit:65536KB     64bit IO Format:%I64d & %I64u Submit cid=1006#sta ...

  7. NYOJ 353 3D dungeon 【bfs】

    题意:给你一个高L长R宽C的图形.每个坐标都能够视为一个方格.你一次能够向上.下.左,右,前,后任一方向移动一个方格, 可是不能向有#标记的方格移动. 问:从S出发能不能到达E,假设能请输出最少的移动 ...

  8. 关于小程序navigator没有高的情况

    传统的web开发者进入小程序的时候,可能有几个映射疑问: div  - > view a -> navigator携带参数传值(a标签应该是根据内容来撑高,而navigator就不会根据内 ...

  9. codeforces 553 D Nudist Beach

    题意大概是.给出一个图,保证每一个点至少有一条边以及随意两点间最多一条边.非常显然这个图有众多点集,若我们给每一个点定义一个权值,那每一个点集都有一个最小权值点,如今要求出一个点集,这个点集的最小权值 ...

  10. PNG24图片兼容IE6解决的方法

    非常多人都遇到一个问题:那就是PNG不能正常显示,比方: 网上试过的非常多办法都非常难实现.要嘛就是效果不好,那如今最好的办法就是直接调用JS插件,解决! 点击下载 如今说一下怎么用这个文件吧! 首先 ...