前言

项目里面要增加一个应用缓存,原本想着要怎么怎么来整合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. js 时间戳与yyyy-mm-dd或yyyy-MM-dd HH-mm-ss互相转换

    首先是获取当前系统时间转换为时间戳 var timestamp = Date.parse(new Date());//获取当前时间 timestamp = timestamp / 1000; 然后是时 ...

  2. R语言实战读书笔记(七)基本统计分析

    summary() sapply(x,fun,options):对数据框或矩阵中的每一个向量进行统计 mean sd:标准差 var:方差 min: max: median: length: rang ...

  3. Codeforces 246E Blood Cousins Return(树上启发式合并)

    题目链接 Blood Cousins Return #include <bits/stdc++.h> using namespace std; #define rep(i, a, b) f ...

  4. BT中的磁力链接(转)

    注意:磁力链接不是迅雷的,而是BT网络中的一种协议. 磁力链接与种子文件 磁力链接并不是一个新概念,早在2002年,相关的标准草稿就已经制定了.但直到2012年海盗湾为规避版权问题删除了站点上的所有T ...

  5. spring aop提供了两种实现方式jdk和cglib

    Spring AOP使用了两种代理机制:一种是基于JDK的动态代理:另一种是基于CGLib的动态代理.之所以需要两种代理机制,很大程度上是因为JDK本身只提供接口的代理,而不支持类的代理. Sprin ...

  6. jquery 查找子元素的几种方法

    <div class="tm-clear tb-hidden tm_brandAttr" id="J_BrandAttr" style="dis ...

  7. awk如何区分shell脚本传进来的参数和自身的参数?awk如何获取shell脚本传进来的参数;awk中如何执行shell命令

    问题:对于shell脚本,$0表示脚本本身,$1表示脚本的第一个参数,$2……依次类推:对于awk,$1表示分割后的第一个字段,$2……依次类推.那么对于shell脚本中的awk如何区分两者呢? 答案 ...

  8. Windows系统文件详解【大全】

    这是网络上转载的一篇文章,找不到原创的出处了--详细的介绍了WINDOWS系统文件的用途,我想各位保存一份以后说不定会有用吧,呵呵..这里按A到Z为大家分好类了,查询的话可以按键盘的Ctrl+F进行搜 ...

  9. Ubuntu+Apache+PHP+Mysql环境搭建(完整版)

    Ubuntu+Apache+PHP+Mysql环境搭建(完整版) 一.操作系统Ubuntu 14.04 64位,阿里云服务器 二.Apache 1.安装Apache,安装命令:sudo apt-get ...

  10. 转:国内外著名开源b2c电子商务系统比较包括asp.net和php

    from: http://longdick.iteye.com/blog/1122879 国内外著名开源b2c电子商务系统比较包括asp.net和php 博客分类: 电子商务   国内外著名开源b2c ...