springboot整合ehcache和redis实现多级缓存实战案例

一、概述

在实际的工作中,我们通常会使用多级缓存机制,将本地缓存和分布式缓存结合起来,从而提高系统性能和响应速度。本文通过springboot整合ehcache和redis实现多级缓存案例实战,从源码角度分析下多级缓存实现原理。

二、实战案例

1、pom依赖(注意引入cache和ehcache组件依赖)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>cache-demo</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.0</version></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.3</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.1</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.76</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.23</version></dependency><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>23.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency><dependency><groupId>net.sf.ehcache</groupId><artifactId>ehcache</artifactId><version>2.10.8</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency></dependencies>
</project>

2、application.properties(启动类加上:@EnableCaching注解)

server.port = 7001
spring.application.name = cache-demo#log config
logging.config = classpath:log/logback.xml
debug = false#mp config
mybatis-plus.mapper-locations = classpath*:mapper/*.xml
mybatis-plus.configuration.log-impl = org.apache.ibatis.logging.stdout.StdOutImplspring.datasource.type = com.alibaba.druid.pool.DruidDataSource
spring.datasource.druid.driver-class-name = com.mysql.cj.jdbc.Driver
spring.datasource.url = jdbc:mysql://localhost:3306/数据库?characterEncoding=utf-8
spring.datasource.username = 数据库账号
spring.datasource.password = 数据库密码#redis config
spring.redis.host = redis主机
spring.redis.port = 6379
spring.redis.password=redis密码,没有就删掉该配置# ehcache config
spring.cache.type = ehcache
spring.cache.ehcache.config = classpath:ehcache.xml

3、ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"updateCheck="false"><diskStore path="D:\ehcache"/><!--默认缓存策略 --><!-- external:是否永久存在,设置为true则不会被清除,此时与timeout冲突,通常设置为false--><!-- diskPersistent:是否启用磁盘持久化--><!-- maxElementsInMemory:最大缓存数量--><!-- overflowToDisk:超过最大缓存数量是否持久化到磁盘--><!-- timeToIdleSeconds:最大不活动间隔,设置过长缓存容易溢出,设置过短无效果,单位:秒--><!-- timeToLiveSeconds:最大存活时间,单位:秒--><!-- memoryStoreEvictionPolicy:缓存清除策略--><defaultCacheeternal="false"diskPersistent="false"maxElementsInMemory="1000"overflowToDisk="false"timeToIdleSeconds="60"timeToLiveSeconds="60"memoryStoreEvictionPolicy="LRU"/><cachename="studentCache"eternal="false"diskPersistent="false"maxElementsInMemory="1000"overflowToDisk="false"timeToIdleSeconds="100"timeToLiveSeconds="100"memoryStoreEvictionPolicy="LRU"/>
</ehcache>

4、MybatisPlusConfig类(注意:@MapperScan注解,也可加在启动类上)

@Configuration
@MapperScan("com.cache.demo.mapper")
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {//分页插件MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());return mybatisPlusInterceptor;}
}

5、测试demo

这里可以将一级缓存、二级缓存时效设置短一些,方便进行测试。

@Slf4j
@RestController
@RequestMapping("/cache")
public class CacheController {@Resourceprivate StudentMapper studentMapper;@Autowiredprivate StringRedisTemplate stringRedisTemplate;// 添加缓存注解(一级缓存:ehcache)@Cacheable(value = "studentCache", key = "#id+'getStudentById'")@GetMapping("/getStudentById")public String getStudentById(Integer id) {String key = "student:" + id;// 一级缓存中不存在,则从二级缓存:redis中查找String studentRedis = stringRedisTemplate.opsForValue().get(key);if (StringUtils.isNotBlank(studentRedis)) {return JSON.toJSONString(JSON.parseObject(studentRedis, Student.class));}// 二级缓存中不存在则查询数据库,并更新二级缓存、一级缓存Student student = studentMapper.selectStudentById(id);if (null != student) {stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(student));}return JSON.toJSONString(student);}
}

6、启动类上的:@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 Ordered.LOWEST_PRECEDENCE;
}

7、导入的:
CachingConfigurationSelector类:

public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {@Overridepublic String[] selectImports(AdviceMode adviceMode) {switch (adviceMode) {case PROXY:// 此处走的是:PROXYreturn getProxyImports();case ASPECTJ:return getAspectJImports();default:return null;}}private String[] getProxyImports() {List<String> result = new ArrayList<>(3);// 导入了AutoProxyRegistrar类和ProxyCachingConfiguration类result.add(AutoProxyRegistrar.class.getName());result.add(ProxyCachingConfiguration.class.getName());if (jsr107Present && jcacheImplPresent) {result.add(PROXY_JCACHE_CONFIGURATION_CLASS);}return StringUtils.toStringArray(result);}
}

8、AutoProxyRegistrar类(代码有所简化):

public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {private final Log logger = LogFactory.getLog(getClass());@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {// 最终注册了:InfrastructureAdvisorAutoProxyCreator(BeanPostProcessor接口实现类)// 通过重写postProcessAfterInitialization接口创建代理对象AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);}
}@Nullablepublic static BeanDefinition registerAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, @Nullable Object source) {return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);}

9、导入的第一个类看完了,接着看导入的第二个类:ProxyCachingConfiguration

@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor(CacheOperationSource cacheOperationSource, CacheInterceptor cacheInterceptor) {//  构建BeanFactoryCacheOperationSourceAdvisorBeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();// 设置缓存注解解析器advisor.setCacheOperationSource(cacheOperationSource);// 设置缓存拦截器:cacheInterceptoradvisor.setAdvice(cacheInterceptor);if (this.enableCaching != null) {advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));}return advisor;}@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public CacheOperationSource cacheOperationSource() {// 缓存注解解析器return new AnnotationCacheOperationSource();}@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public CacheInterceptor cacheInterceptor(CacheOperationSource cacheOperationSource) {// 缓存拦截器CacheInterceptor interceptor = new CacheInterceptor();interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);interceptor.setCacheOperationSource(cacheOperationSource);return interceptor;}
}

10、继续看下CacheInterceptor类(重要):

public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {@Override@Nullablepublic Object invoke(final MethodInvocation invocation) throws Throwable {Method method = invocation.getMethod();CacheOperationInvoker aopAllianceInvoker = () -> {try {return invocation.proceed();}catch (Throwable ex) {throw new CacheOperationInvoker.ThrowableWrapper(ex);}};Object target = invocation.getThis();Assert.state(target != null, "Target must not be null");try {// 缓存执行逻辑return execute(aopAllianceInvoker, target, method, invocation.getArguments());}catch (CacheOperationInvoker.ThrowableWrapper th) {throw th.getOriginal();}}
}@Nullableprotected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {if (this.initialized) {Class<?> targetClass = getTargetClass(target);CacheOperationSource cacheOperationSource = getCacheOperationSource();if (cacheOperationSource != null) {// 解析缓存相关注解,返回CacheOperation// 每个缓存注解对应一种不同的解析处理操作// CacheEvictOperation、CachePutOperation、CacheableOperation等Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);if (!CollectionUtils.isEmpty(operations)) {// 执行缓存逻辑return execute(invoker, method,new CacheOperationContexts(operations, method, args, target, targetClass));}}}return invoker.invoke();}private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {// 解析处理@CacheEvict注解processCacheEvicts(contexts.get(CacheEvictOperation.class), true,	CacheOperationExpressionEvaluator.NO_RESULT);// 解析处理@Cacheable注解Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));List<CachePutRequest> cachePutRequests = new ArrayList<>();if (cacheHit == null) {collectPutRequests(contexts.get(CacheableOperation.class),	CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);}Object cacheValue;Object returnValue;if (cacheHit != null && !hasCachePut(contexts)) {// 命中缓存,则从缓存中获取数据cacheValue = cacheHit.get();returnValue = wrapCacheValue(method, cacheValue);} else {// 未命中缓存,则通过反射执行目标方法returnValue = invokeOperation(invoker);cacheValue = unwrapReturnValue(returnValue);}// 解析处理@CachePut注解collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);// 未命中缓存时,会封装一个cachePutRequests// 然后通过反射执行目标方法后,执行该方法,最终调用EhCacheCache.put方法将数据写入缓存中for (CachePutRequest cachePutRequest : cachePutRequests) {cachePutRequest.apply(cacheValue);}// 解析处理@CacheEvict注解,和上面的方法相同,只不过第二个参数不同processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);return returnValue;}

11、接着看下findCachedItem方法

private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {Object result = CacheOperationExpressionEvaluator.NO_RESULT;for (CacheOperationContext context : contexts) {if (isConditionPassing(context, result)) {// 生成key策略:解析@Cacheable注解中的key属性// 若未配置则默认使用SimpleKeyGenerator#generateKey方法生成keyObject key = generateKey(context, result);Cache.ValueWrapper cached = findInCaches(context, key);if (cached != null) {return cached;}	else {if (logger.isTraceEnabled()) {logger.trace("No cache entry for key '" + key + "' in cache(s) " + context.getCacheNames());}}}}return null;}// SimpleKeyGenerator#generateKey
public static Object generateKey(Object... params) {// 方法没有参数,则返回空的SimpleKeyif (params.length == 0) {return SimpleKey.EMPTY;}// 方法参数只有一个,则返回该参数if (params.length == 1) {Object param = params[0];if (param != null && !param.getClass().isArray()) {return param;}}// 否则将方法参数进行封装,返回SimpleKeyreturn new SimpleKey(params);}private Cache.ValueWrapper findInCaches(CacheOperationContext context, Object key) {for (Cache cache : context.getCaches()) {// 从一级缓存中获取数据Cache.ValueWrapper wrapper = doGet(cache, key);if (wrapper != null) {if (logger.isTraceEnabled()) {logger.trace("Cache entry for key '" + key + "' found in cache '" + cache.getName() + "'");}return wrapper;}}return null;}protected Cache.ValueWrapper doGet(Cache cache, Object key) {try {// 这里我们使用的是:EhCacheCache,所以最终会调用EhCacheCache.get方法获取缓存中的数据return cache.get(key);}catch (RuntimeException ex) {getErrorHandler().handleCacheGetError(ex, cache, key);return null;}}

三、总结

@EnableCaching和@Transactional等实现逻辑大体相同,看的多了,则一通百通。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1304.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

赛效:如何将PDF文件免费转换成Word文档

1&#xff1a;在网页上打开wdashi&#xff0c;默认进入PDF转Word页面&#xff0c;点击中间的上传文件图标。 2&#xff1a;将PDF文件添加上去之后&#xff0c;点击右下角的“开始转换”。 3&#xff1a;稍等片刻转换成功后&#xff0c;点击绿色的“立即下载”按钮&#xff0c;将…

做私域选个微还是企微,哪个有优势?

做私域&#xff0c;你必须要有一个&#xff0c;引流新客户及留存老客户的地方。 于是&#xff0c;就有很多人讨论或者纠结&#xff1a;做私域&#xff0c;选择个人微信&#xff1f;还是企业微信&#xff1f; 让我们一起来看看个人微信和企业微信在功能和使用上有哪些区别&…

[SpringBoot]单点登录

关于单点登录 单点登录的基本实现思想&#xff1a; 当客户端提交登录请求时&#xff0c;服务器端在验证登录成功后&#xff0c;将生成此用户对应的JWT数据&#xff0c;并响应到客户端 客户端在后续的访问中&#xff0c;将自行携带JWT数据发起请求&#xff0c;通常&#xff0c…

一篇搞懂steam/csgo搬砖原理

接触csgo游戏搬砖项目三年了&#xff0c;也有在别的论坛交流心得。让我无语的是有些已经游戏搬砖差不多半年&#xff0c;却还告诉我没有赚到钱&#xff0c;又或者说时常到可出售的时候利润少的可怕&#xff0c;总是说这个行业说水太深了&#xff01;那么请你告诉我&#xff0c;…

快快快快快快快快快快排

作者简介&#xff1a;დ旧言~&#xff0c;目前大一&#xff0c;现在学习Java&#xff0c;c&#xff0c;Python等 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 望小伙伴们点赞&#x1f44d;收藏✨加关注哟&#x1f495;&#x1f495; C语言实现快排☺️ ℹ️…

Ceph 块存储系统 RBD 接口

-创建 Ceph 块存储系统 RBD 接口- 1、创建一个名为 rbd-demo 的专门用于 RBD 的存储池 ceph osd pool create rbd-demo 64 642、将存储池转换为 RBD 模式 ceph osd pool application enable rbd-demo rbd3、初始化存储池 rbd pool init -p rbd-demo # -p 等同于 --pool4、…

jenkins手把手教你从入门到放弃01-jenkins简介(详解)

一、简介 jenkins是一个可扩展的持续集成引擎。持续集成&#xff0c;也就是通常所说的CI&#xff08;Continues Integration&#xff09;&#xff0c;可以说是现代软件技术开发的基础。持续集成是一种软件开发实践&#xff0c; 即团队开发成员经常集成他们的工作&#xff0c;通…

STM32 Proteus仿真LCD12864火灾检测烟雾火焰温度报警器MQ2 -0064

STM32 Proteus仿真LCD12864火灾检测烟雾火焰温度报警器MQ2 -0064 Proteus仿真小实验&#xff1a; STM32 Proteus仿真LCD12864火灾检测烟雾火焰温度报警器MQ2 -0064 功能&#xff1a; 硬件组成&#xff1a;STM32F103R6单片机 LCD12864 液晶显示DS18B20 温度传感器多个按键电位…

单例模式:懒汉式和饿汉式

目录 懒汉模式和饿汉模式 区别 示例 懒汉模式线程不安全 懒汉模式线程安全 懒汉模式内部静态变量线程安全 饿汉式线程安全 指的是在系统生命周期内&#xff0c;只产生一个实例。 懒汉模式和饿汉模式 分为懒汉式和饿汉式 区别 创建时机和线程安全 线程安全&#xff1…

高时空分辨率、高精度一体化预测技术的风、光、水自动化预测技术的应用

第一章 预测平台讲解及安装 一、高精度气象预测基础理论介绍 综合气象观测数值模拟模式&#xff1b; 全球预测模式、中尺度数值模式&#xff1b; 二、自动化预测平台介绍 Linux系统 Crontab定时任务执行机制 Bash脚本自动化编程 硬件需求简介 软件系统安装 …

分享一个加载按钮动画

先看效果&#xff1a; 再看代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>加载动画按钮</title><script src"https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2…

flutter开发实战-卡片翻转动画效果Transform+IndexedStack+rotateAnimation

flutter开发实战-实现卡片翻转动画效果 之前开发中遇到了商品卡片翻转&#xff0c;商品正面是商品图片、商品名称&#xff1b;背面是商品价格&#xff0c;需要做卡片翻转动画。 动画实现即&#xff1a;在一段时间内&#xff0c;快速地多次改变UI外观&#xff1b;由于人眼会产生…

FL Studio是什么软件?FL Studio2023最新更新内容

FL Studio是什么软件 FL Studio是由比利时软件公司Image-Line开发的音乐制作软件&#xff0c;它拥有丰富的音效、合成器、采样器、鼓机等工具。FL Studio支持多种音频文件格式&#xff0c;包括MIDI、MP3、WAV、OGG等&#xff0c;可以帮助用户自由地进行音乐创作。 FL Studio界…

如何有效利用chatgpt?

如何有效地使用ChatGPT&#xff1f; 代码、诗歌、歌曲和短篇小说都可以由 ChatGPT 以特定的风格编写。您所需要的只是正确的问题和适当的提示。以下是有关如何有效使用ChatGPT的一些提示和想法&#xff1a; 头脑 风暴获取初稿解决编码问题尝试不同的提示格式查找标题寻求帮助…

WordPress作为可扩展的企业级解决方案

网络商业世界就像一片汪洋大海&#xff0c;大型企业是大海中最大的鱼。然而&#xff0c;只因为你比其他人都大&#xff0c;并不意味着你不能逆流而上。相反&#xff0c;企业业务面临的挑战更大&#xff0c;对网站的技术要求更高。 多年来&#xff0c;大型公司通常依赖最昂贵的…

Linux总线设备驱动模型

1. 简介 驱动模型中的总线可以是真是存在的物理总线&#xff08;USB总线&#xff0c;I2C总线&#xff0c;PCI总线&#xff09;&#xff0c;也可以是为了驱动模型架构设计出的虚拟总线&#xff08;Platform总线&#xff09;。为此linux设备驱动模型都将围绕"总线–设备–驱…

科普一下Elasticsearch中BM25算法的使用

首先还是先了解几个概念&#xff0c;Elasticsearch是一个开源的分布式搜索和分析引擎&#xff0c;它使用一系列算法来计算文档的相关性分数&#xff08;relevance score&#xff09;。这些算法用于确定查询与文档的匹配程度&#xff0c;以便按相关性对搜索结果进行排序。以下是…

生命在于折腾——MacOS(Inter)渗透测试环境搭建

一、前景提要 之前使用的是2022款M2芯片的MacBook Air 13寸&#xff0c;不得不说&#xff0c;是真的续航好&#xff0c;轻薄&#xff0c;刚开始我了解到M芯片的底层是ARM架构&#xff0c;我觉得可以接受&#xff0c;虚拟机用的不多&#xff0c;但在后续的使用过程中&#xff0…

换零钱II:零钱面值动态变化,class方法自动兑换最少零钱(贪心算法)

银行现存零钱面值种类动态变化但数量无限&#xff0c;类方法change()完成指定金额的最少零钱个数兑换。 (本笔记适合学透python基本数据结构&#xff0c;熟悉class的基构造&#xff0c;对类内全局变量有一定认的 coder 翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1…

RabbitMQ消息堆积问题及惰性队列

一&#xff0c;消息堆积 1&#xff0c;消费者堆积问题 当生产者生产消息的速度超过了消费者处理消息的速度&#xff0c;就会导致消息在队列中进行堆积&#xff0c;一定时间后会造成队列达到存储的上限&#xff0c;那么最开始进入队列的消息可能变成死信&#xff0c;会被丢弃&…