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

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

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

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

一、概述

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

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

二、实战案例

pom依赖(注意引入cache和ehcache组件依赖)。cUB28资讯网——每日最新资讯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注解)。cUB28资讯网——每日最新资讯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。cUB28资讯网——每日最新资讯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注解,也可加在启动类上)。cUB28资讯网——每日最新资讯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。cUB28资讯网——每日最新资讯28at.com

这里可以将一级缓存、二级缓存时效设置短一些,方便进行测试。cUB28资讯网——每日最新资讯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注解。cUB28资讯网——每日最新资讯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类:cUB28资讯网——每日最新资讯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类(代码有所简化):cUB28资讯网——每日最新资讯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。cUB28资讯网——每日最新资讯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类(重要):cUB28资讯网——每日最新资讯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方法。cUB28资讯网——每日最新资讯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等实现逻辑大体相同,看的多了,则一通百通。cUB28资讯网——每日最新资讯28at.com

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

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

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

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

标签:
  • 热门焦点
Top