当前位置:首页 >知识 >Springboot整合Ehcache和Redis实现多级缓存实战案例 我们通常会使用多级缓存机制

Springboot整合Ehcache和Redis实现多级缓存实战案例 我们通常会使用多级缓存机制

2024-06-30 21:31:41 [百科] 来源:避面尹邢网

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

作者:言沫东 开发 架构 Redis 整合战案本文通过springboot整合ehcache和redis实现多级缓存案例实战,从源码角度分析下多级缓存实现原理。现多

一、存实概述

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

Springboot整合Ehcache和Redis实现多级缓存实战案例 我们通常会使用多级缓存机制

Springboot整合Ehcache和Redis实现多级缓存实战案例 我们通常会使用多级缓存机制

二、整合战案实战案例

pom依赖(注意引入cache和ehcache组件依赖)。现多

Springboot整合Ehcache和Redis实现多级缓存实战案例 我们通常会使用多级缓存机制

<?存实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注解)。

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。现多

<?存实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注解,也可加在启动类上)。

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

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

启动类上的:@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;}

导入的: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);	}}

AutoProxyRegistrar类(代码有所简化):

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。

@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类(重要):

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方法。

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等实现逻辑大体相同,看的多了,则一通百通。

责任编辑:姜华 来源: 今日头条 springbootredis

(责任编辑:综合)

    推荐文章
    热点阅读