当前位置:首页 > 科技  > 软件

Springboot整合Ehcache和Redis实现多级缓存实战案例

来源: 责编: 时间:2023-10-13 14:36:50 325观看
导读一、概述在实际的工作中,我们通常会使用多级缓存机制,将本地缓存和分布式缓存结合起来,从而提高系统性能和响应速度。本文通过springboot整合ehcache和redis实现多级缓存案例实战,从源码角度分析下多级缓存实现原理。二、

Eb328资讯网——每日最新资讯28at.com

一、概述

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

Eb328资讯网——每日最新资讯28at.com

二、实战案例

pom依赖(注意引入cache和ehcache组件依赖)。Eb328资讯网——每日最新资讯28at.com

<?xml versinotallow="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>

application.properties(启动类加上:@EnableCaching注解)。Eb328资讯网——每日最新资讯28at.com

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

ehcache.xml。Eb328资讯网——每日最新资讯28at.com

<?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:缓存清除策略-->    <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>

MybatisPlusConfig类(注意:@MapperScan注解,也可加在启动类上)。Eb328资讯网——每日最新资讯28at.com

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

测试demo。Eb328资讯网——每日最新资讯28at.com

这里可以将一级缓存、二级缓存时效设置短一些,方便进行测试。Eb328资讯网——每日最新资讯28at.com

@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));        }        // 二级缓存中不存在则查询数据库,并更新二级缓存、一级缓存        Student student = studentMapper.selectStudentById(id);        if (null != student) {            stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(student));        }        return JSON.toJSONString(student);    }}

启动类上的:@EnableCaching注解。Eb328资讯网——每日最新资讯28at.com

@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;}

导入的:CachingConfigurationSelector类:Eb328资讯网——每日最新资讯28at.com

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);	}}

AutoProxyRegistrar类(代码有所简化):Eb328资讯网——每日最新资讯28at.com

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

导入的第一个类看完了,接着看导入的第二个类:ProxyCachingConfiguration。Eb328资讯网——每日最新资讯28at.com

@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;	}}

继续看下CacheInterceptor类(重要):Eb328资讯网——每日最新资讯28at.com

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 {      // 缓存执行逻辑			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)) {          // 执行缓存逻辑					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;	}

接着看下findCachedItem方法。Eb328资讯网——每日最新资讯28at.com

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#generateKeypublic static Object generateKey(Object... params) {  	// 方法没有参数,则返回空的SimpleKey		if (params.length == 0) {			return SimpleKey.EMPTY;		}  	// 方法参数只有一个,则返回该参数		if (params.length == 1) {			Object param = params[0];			if (param != null && !param.getClass().isArray()) {				return param;			}		}  	// 否则将方法参数进行封装,返回SimpleKey		return 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等实现逻辑大体相同,看的多了,则一通百通。Eb328资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-13530-0.htmlSpringboot整合Ehcache和Redis实现多级缓存实战案例

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 分享10+可视图表库, 助你轻松制作精美可视化大屏

下一篇: 四款.NET开源的Redis客户端驱动库

标签:
  • 热门焦点
  • 5月iOS设备好评榜:iPhone 14仅排第43?

    来到新的一月,安兔兔的各个榜单又重新汇总了数据,像安卓阵营的榜单都有着比较大的变动,不过iOS由于设备的更新换代并没有那么快,所以相对来说变化并不大,特别是iOS好评榜,老款设
  • 六大权益!华为8月服务日开启:手机免费贴膜、维修免人工费

    8月5日消息,一年一度的华为开发者大会2023(Together)日前在松山湖拉开帷幕,与此同时,华为8月服务日也式开启,到店可享六大专属权益。华为用户可在华为商城Ap
  • 企业采用CRM系统的11个好处

    客户关系管理(CRM)软件可以为企业提供很多的好处,从客户保留到提高生产力。  CRM软件用于企业收集客户互动,以改善客户体验和满意度。  CRM软件市场规模如今超过580
  • 得物效率前端微应用推进过程与思考

    一、背景效率工程随着业务的发展,组织规模的扩大,越来越多的企业开始意识到协作效率对于企业团队的重要性,甚至是决定其在某个行业竞争中突围的关键,是企业长久生存的根本。得物
  • 猿辅导与新东方的两种“归途”

    作者|卓心月 出品|零态LT(ID:LingTai_LT)如何成为一家伟大企业?答案一定是对&ldquo;势&rdquo;的把握,这其中最关键的当属对企业战略的制定,且能够站在未来看现在,即使这其中的
  • 8月见!小米MIX Fold 3获得3C认证:支持67W快充

    这段时间以来,包括三星、一加、荣耀等等有不少品牌旗下的最新折叠屏旗舰都得到了不少爆料,而小米新一代折叠屏旗舰——小米MIX Fold 3此前也屡屡被传
  • 三星电子Q2营收60万亿韩元 存储业务营收同比仍下滑超过50%

    7月27日消息,据外媒报道,从三星电子所发布的财报来看,他们主要利润来源的存储芯片业务在今年二季度仍不乐观,营收同比仍在大幅下滑,所在的设备解决方案
  • iQOO 11S评测:行业唯一的200W标准版旗舰

    【Techweb评测】去年底,iQOO推出了“电竞旗舰”iQOO 11系列,作为一款性能强机,该机不仅全球首发2K 144Hz E6全感屏,搭载了第二代骁龙8平台及144Hz电竞
  • 2299元起!iQOO Pad开启预售:性能最强天玑平板

    5月23日,iQOO如期举行了新品发布会,除了首发安卓最强旗舰处理器的iQOO Neo8系列新机外,还在发布会上推出了旗下首款平板电脑——iQOO Pad,其搭载了天玑
Top