前言

项目里面要增加一个应用缓存,原本想着要怎么怎么来整合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. Android四大核心组件之Service

    实验内容 启动Service 绑定Service 与Service进行通信 实验要求 启动Service 绑定Service 与Service进行通信 实验步骤 Service概述 Service通常 ...

  2. nvmw安装,用于控制node版本;

    之前一直使用的是node v2.2.0版本,挺说新版本的node解决了npm安装插件产生文件夹结构过深的问题,所以就想更新试试: 上网一看才发现,尼玛的node已经到了6.+版本了,好吧,看来还是得跟 ...

  3. python 内建类型

    ''' 数值 numbers 字符串 strings 列表 lists 字典 dictionaries 元组 tuples 文件 files 集合 sets ''' 1.1 序列的操作 所有序列类型都 ...

  4. Openwrt安装软件的方法

    转自:http://blog.csdn.net/jk110333/article/details/8753825     web软件: Naken Web Openwrt安装软件 简介 Openwrt ...

  5. node工具--connect

    HTTP构建一个网站: var http = require('http'); var fs = require('fs'); var server = http.createServer(funct ...

  6. imageView--转-如何代码动态的获取和设置ImageView的宽度和高度?

    11- 12- 13-android获取图片原始长宽; ========= 11--国内某金融企业的一道面试题:如何在代码中获得一个ImageVIew的高度和宽度? 相信很大一部分人,会直接想到用im ...

  7. 转:assert()函数用法总结

    assert宏的原型定义在<assert.h>中,其作用是如果它的条件返回错误,则终止程序执行,原型定义: #include <assert.h>void assert( in ...

  8. 转:Java IO流学习总结

    Java流操作有关的类或接口: Java流类图结构: 流的概念和作用 流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象.即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输 ...

  9. onload 和 domready

    博客地址:https://ainyi.com/46 window.onload 事件会在页面或图像加载完成后触发(即所有元素的资源都下载完毕)如果页面上有许多图片.音乐或 falsh 还没加载完成,o ...

  10. pycharm激活方式

    进入C:\Windows\System32\drivers\etc替换host文件,或者在host文件后加入0.0.0.0 account.jetbrains.com然后断网,断网,断网!最后输入K7 ...