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

一、概述

在实际的工作中,我们通常会使用多级缓存机制,将本地缓存和分布式缓存结合起来,从而提高系统性能和响应速度。本文通过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密码,没有就删掉该配置

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