前言

项目里面要增加一个应用缓存,原本想着要怎么怎么来整合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. cookies-判断用户是否是第一次进入页面

    在郑州美莱的活动项目中客户当时有提到该需求.尽管最后去掉了该需求,但是还是花了我不少时间研究,因为之前的项目我前端没有用到过cookies,吓死宝宝了 $(document).ready(functi ...

  2. linux time

    uint32_t midtime; static struct timeval startstart,midmid; while (ros::ok()) { gettimeofday(&sta ...

  3. qt使用

    1安装qt软件(.run文件) . chmod 777 qt.....run ./qt...run或sudo ./qt...run(安装目录不一样) 2.sudo gedit .bashrc添加以下内 ...

  4. codevs贪吃的九头龙

    传说中的九头龙是一种特别贪吃的动物.虽然名字叫“九头龙”,但这只是说它出生的时候有九个头,而在成长的过程中,它有时会长出很多的新头,头的总数会远大于九,当然也会有旧头因衰老而自己脱落.有一天,有M 个 ...

  5. 访问权限修饰符Protected专题

    上图描述:A类在a包下,m()方法被protected修饰 上图描述:B类也在a包下,B类是A类的子类. 解析:B类和A类是同包类,B类是A类的子类,因此b对象可以调用m()方法. 上图描述:C类也在 ...

  6. GOF23种设计模式-工厂模式

    • 工厂模式: – 实现了创建者和调用者的分离. – 详细分类: • 简单工厂模式 • 工厂方法模式 • 抽象工厂模式 • 面向对象设计的基本原则: – OCP(开闭原则,Open-Closed Pr ...

  7. 记录一下:关于mysql数据误删除恢复的问题

    大概看来几篇博客: 1.delete的可以通过回滚(rollback)的方式恢复;但是前提是,你的数据表引擎是InnoDB而不是MyISAM,而且操作不是自动提交的 但是这种方式不可以恢复trunca ...

  8. python的私有化

    1.双下划线(__) 由双下划线開始的属性在执行时被混淆,所以直接訪问是不同意的.实际上以双下划线開始的属性 会在解释时在前面加上下划线和类名.如self.__num会被解析为self._classN ...

  9. 【翻译自mos文章】检查$ORACLE_HOME是否是RAC的HOME的方法以及relink RAC的Oracle binary的方法

    检查$ORACLE_HOME是否是RAC的HOME的方法以及relink RAC的Oracle binary的方法 来源于: How to Check Whether Oracle Binary/In ...

  10. Scala 基础新手教程

    1.前言 近期在參加Hadoop和Spark培训.须要使用Scala,自学了一下作为入门.这里作一个记录. 2.下载 1) 在scala官网下载.地址: http://www.scala-lang.o ...