springboot整合ehcache和redis實現(xiàn)多級緩存實戰(zhàn)案例
一、概述
在實際的工作中,我們通常會使用多級緩存機制,將本地緩存和分布式緩存結合起來,從而提高系統(tǒng)性能和響應速度。本文通過springboot整合ehcache和redis實現(xiàn)多級緩存案例實戰(zhàn),從源碼角度分析下多級緩存實現(xiàn)原理。
二、實戰(zhàn)案例
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.StdOutImpl spring.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/數(shù)據(jù)庫?characterEncoding=utf-8 spring.datasource.username = 數(shù)據(jù)庫賬號 spring.datasource.password = 數(shù)據(jù)庫密碼 #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:最大緩存數(shù)量--> ? ? <!-- overflowToDisk:超過最大緩存數(shù)量是否持久化到磁盤--> ? ? <!-- timeToIdleSeconds:最大不活動間隔,設置過長緩存容易溢出,設置過短無效果,單位:秒--> ? ? <!-- timeToLiveSeconds:最大存活時間,單位:秒--> ? ? <!-- memoryStoreEvictionPolicy:緩存清除策略--> ? ? <defaultCache ? ? ? ? ? ? eternal="false" ? ? ? ? ? ? diskPersistent="false" ? ? ? ? ? ? maxElementsInMemory="1000" ? ? ? ? ? ? overflowToDisk="false" ? ? ? ? ? ? timeToIdleSeconds="60" ? ? ? ? ? ? timeToLiveSeconds="60" ? ? ? ? ? ? memoryStoreEvictionPolicy="LRU"/> ? ? <cache ? ? ? ? ? ? name="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 { ? ? @Bean ? ? public MybatisPlusInterceptor mybatisPlusInterceptor() { ? ? ? ? //分頁插件 ? ? ? ? MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); ? ? ? ? mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor()); ? ? ? ? return mybatisPlusInterceptor; ? ? } }
5、測試demo
這里可以將一級緩存、二級緩存時效設置短一些,方便進行測試。
@Slf4j @RestController @RequestMapping("/cache") public class CacheController { ? ? @Resource ? ? private StudentMapper studentMapper; ? ? @Autowired ? ? private 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)); ? ? ? ? } ? ? ? ? // 二級緩存中不存在則查詢數(shù)據(jù)庫,并更新二級緩存、一級緩存 ? ? ? ? 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> { ?? ?@Override ?? ?public String[] selectImports(AdviceMode adviceMode) { ?? ??? ?switch (adviceMode) { ?? ??? ??? ?case PROXY: ? ? ? ? // 此處走的是:PROXY ?? ??? ??? ??? ?return 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()); ?? ?@Override ?? ?public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { ?? ??? ??? ??? ?// 最終注冊了:InfrastructureAdvisorAutoProxyCreator(BeanPostProcessor接口實現(xiàn)類) ? ? ?? ??? ?// 通過重寫postProcessAfterInitialization接口創(chuàng)建代理對象 ? ? ? ?? ?AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry); ?? ?} } @Nullable ?? ?public 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) { ?? ??? ?// ?構建BeanFactoryCacheOperationSourceAdvisor ? ? BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor(); ?? ??? ?// 設置緩存注解解析器 ? ? advisor.setCacheOperationSource(cacheOperationSource); ?? ??? ?// 設置緩存攔截器:cacheInterceptor ? ? advisor.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、繼續(xù)看下CacheInterceptor類(重要):
public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable { ?? ?@Override ?? ?@Nullable ?? ?public 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 { ? ? ? // 緩存執(zhí)行邏輯 ?? ??? ??? ?return execute(aopAllianceInvoker, target, method, invocation.getArguments()); ?? ??? ?} ?? ??? ?catch (CacheOperationInvoker.ThrowableWrapper th) { ?? ??? ??? ?throw th.getOriginal(); ?? ??? ?} ?? ?} } @Nullable ?? ?protected 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)) { ? ? ? ? ? // 執(zhí)行緩存邏輯 ?? ??? ??? ??? ??? ?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)) { ?? ??? ??? ?// 命中緩存,則從緩存中獲取數(shù)據(jù) ?? ??? ??? ?cacheValue = cacheHit.get(); ?? ??? ??? ?returnValue = wrapCacheValue(method, cacheValue); ?? ??? ?} else { ?? ??? ??? ?// 未命中緩存,則通過反射執(zhí)行目標方法 ?? ??? ??? ?returnValue = invokeOperation(invoker); ?? ??? ??? ?cacheValue = unwrapReturnValue(returnValue); ?? ??? ?} ?? ??? ?// 解析處理@CachePut注解 ?? ??? ?collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests); ?? ??? ?// 未命中緩存時,會封裝一個cachePutRequests ? ?? ?// 然后通過反射執(zhí)行目標方法后,執(zhí)行該方法,最終調用EhCacheCache.put方法將數(shù)據(jù)寫入緩存中 ?? ??? ?for (CachePutRequest cachePutRequest : cachePutRequests) { ?? ??? ??? ?cachePutRequest.apply(cacheValue); ?? ??? ?} ?? ??? ?// 解析處理@CacheEvict注解,和上面的方法相同,只不過第二個參數(shù)不同 ?? ??? ?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方法生成key ?? ??? ??? ??? ?Object 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) { ? ?? ?// 方法沒有參數(shù),則返回空的SimpleKey ?? ??? ?if (params.length == 0) { ?? ??? ??? ?return SimpleKey.EMPTY; ?? ??? ?} ? ?? ?// 方法參數(shù)只有一個,則返回該參數(shù) ?? ??? ?if (params.length == 1) { ?? ??? ??? ?Object param = params[0]; ?? ??? ??? ?if (param != null && !param.getClass().isArray()) { ?? ??? ??? ??? ?return param; ?? ??? ??? ?} ?? ??? ?} ? ?? ?// 否則將方法參數(shù)進行封裝,返回SimpleKey ?? ??? ?return new SimpleKey(params); ?? ?} private Cache.ValueWrapper findInCaches(CacheOperationContext context, Object key) { ?? ??? ?for (Cache cache : context.getCaches()) { ? ? ? // 從一級緩存中獲取數(shù)據(jù) ?? ??? ??? ?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方法獲取緩存中的數(shù)據(jù) ?? ??? ??? ?return cache.get(key); ?? ??? ?} ?? ??? ?catch (RuntimeException ex) { ?? ??? ??? ?getErrorHandler().handleCacheGetError(ex, cache, key); ?? ??? ??? ?return null; ?? ??? ?} ?? ?}
三、總結
@EnableCaching和@Transactional等實現(xiàn)邏輯大體相同,看的多了,則一通百通。
到此這篇關于springboot整合ehcache和redis實現(xiàn)多級緩存實戰(zhàn)案例的文章就介紹到這了,更多相關springboot多級緩存內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java操作minio刪除文件夾及其文件方法(MinIO基本使用)
MinIO是一個高性能、無限擴展的開源對象存儲服務器,它以對象的形式存儲數(shù)據(jù),并兼容Amazon S3接口,它適用于大規(guī)模數(shù)據(jù)存儲、大數(shù)據(jù)分析、文件共享和備份等應用場景,這篇文章主要介紹了java操作minio刪除文件夾及其文件方法,需要的朋友可以參考下2024-02-02Java數(shù)據(jù)結構之鏈表、棧、隊列、樹的實現(xiàn)方法示例
這篇文章主要介紹了Java數(shù)據(jù)結構之鏈表、棧、隊列、樹的實現(xiàn)方法,結合實例形式分析了Java數(shù)據(jù)結構中鏈表、棧、隊列、樹的功能、定義及使用方法,需要的朋友可以參考下2019-03-03spring cloud實現(xiàn)前端跨域問題的解決方案
這篇文章主要介紹了 spring cloud實現(xiàn)前端跨域問題的解決方案,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-01-01