一、概述
在实际的工作中,我们通常会使用多级缓存机制,将本地缓存和分布式缓存结合起来,从而提高系统性能和响应速度。本文通过springboot整合ehcache和redis实现多级缓存案例实战,从源码角度分析下多级缓存实现原理。
二、实战案例
pom依赖(注意引入cache和ehcache组件依赖)。
4.0.0
org.example
cache-demo
1.0-SNAPSHOT
8
8
org.springframework.boot
spring-boot-starter-parent
2.5.0
org.springframework.boot
spring-boot-starter-web
junit
junit
4.12
org.springframework.boot
spring-boot-starter-test
test
com.baomidou
mybatis-plus-boot-starter
3.4.3
mysql
mysql-connector-java
com.alibaba
druid-spring-boot-starter
1.2.1
org.projectlombok
lombok
com.alibaba
fastjson
1.2.76
com.alibaba
druid
1.1.23
com.google.guava
guava
23.0
org.springframework.boot
spring-boot-starter-data-redis
org.springframework.boot
spring-boot-starter-cache
net.sf.ehcache
ehcache
2.10.8
org.springframework.boot
spring-boot-starter-aop
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/数据库?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
ehcache.xml。
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 {
@Override
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
// 此处走的是:PROXY
return getProxyImports();
case ASPECTJ:
return getAspectJImports();
default:
return null;
}
}
private String[] getProxyImports() {
List 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.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 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 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 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) {
// 方法没有参数,则返回空的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等实现逻辑大体相同,看的多了,则一通百通。