JetCache源码(一)——简介与注解实现

一.为何不用Map作为本地缓存库

  • 内存管理
    没有对使用的内存做限制,需要手动编写代码来控制内存,没有自动的内存管理机制。
  • 缓存过期策略
    没有缓存过期策略,如:LRU,LFU,FIFO
  • 容量规划
    没有容量限制
  • Map是否是线程安全的
    可能会出现并发控制问题
  • 持久化
    服务器重启,Map中数据消失
  • 多实例数据同步及一致性
    Map是在服务器的一个实例中的,部署多个实例时,Map中数据会产生不一致的情况

二.本地缓存相较于第三方缓存有什么优势

  • 不经过网络传输,响应速度快

其中本地缓存无法进行持久化,以及保证多实例数据一致性,第三方缓存相比于本地缓存又存在响应速度上的劣势。因此我们引入了JetCache,可以灵活的选择本地、第三方、或多级缓存来解决这个问题。

三、jetCache

1.功能简介
  • 有接口与api两种缓存实现,接口使用方便,api使用类似于map,可以更细粒度的控制缓存。
    • @CreateCache
    • @Cached
  • 可选择内存缓存,分布式缓存,或者同时存在。同时存在时优先访问内存
  • 自动刷新策略,防止某个缓存失效,突然访问量增大,导致数据库挂掉(缓存雪崩)
  • 有不严格的分布式锁,对同一key,全局只有一台机器自动刷新
2.缓存实现流程

2.1 配置部分

  • 首先在spring.factories文件中配置了
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=
    com.alicp.jetcache.autoconfigure.JetCacheAutoConfiguration
    作用是:由于jetcache是jar包引入,不在我们项目包下,因此需要通过@EnableAutoConfiguration来扫描解析spring.factories文件,完成自动装配。
  • 在JetCacheAutoConfiguration配置类中完成了GlobalCacheConfig的配置注入,主要注入了两个实体类的参数
    • JetCacheProperties 存储了springboot.yml中的配置信息,通过 @ConfigurationProperties(prefix = “jetcache”)注解注入属性。
    • AutoConfigureBeans
      存储了jetcache的local和remote配置,
      是在AbstractCacheAutoInit类中完成注入,定义在afterPropertiesSet()方法中,这个方法是在初始化Bean时执行。
	//在InitializingBean接口中定义的方法,这个方法将在所有的属性被初始化后,init-method方法前调用
    @Override
    public void afterPropertiesSet() {
        if (!inited) {
            synchronized (this) {
                if (!inited) {
                    //注入local
                    process("jetcache.local.", autoConfigureBeans.getLocalCacheBuilders(), true);
                    process("jetcache.remote.", autoConfigureBeans.getRemoteCacheBuilders(), false);
                    inited = true;
                }
            }
        }
    }
    
    private void process(String prefix, Map cacheBuilders, boolean local) {
        ConfigTree resolver = new ConfigTree(environment, prefix);
        Map<String, Object> m = resolver.getProperties();
        Set<String> cacheAreaNames = resolver.directChildrenKeys();
        for (String cacheArea : cacheAreaNames) {
            final Object configType = m.get(cacheArea + ".type");
            boolean match = Arrays.stream(typeNames).anyMatch((tn) -> tn.equals(configType));
            if (!match) {
                continue;
            }
            ConfigTree ct = resolver.subTree(cacheArea + ".");
            logger.info("init cache area {} , type= {}", cacheArea, typeNames[0]);
            CacheBuilder c = initCache(ct, local ? "local." + cacheArea : "remote." + cacheArea);
            cacheBuilders.put(cacheArea, c);
        }
    }

也就是说当我们实例化AutoConfigureBeans的子类时(如CaffeineAutoConfiguration或RedisAutoInit),会调用afterPropertiesSet()方法,完成相应属性的注入。
注入的配置信息存入到localCacheBuilders和remoteCacheBuilders两个属性中,map中的key对应配置的default。

    //AutoConfigureBeans的属性
    private Map<String, CacheBuilder> localCacheBuilders = new HashMap<>();

    private Map<String, CacheBuilder> remoteCacheBuilders = new HashMap<>();
//springboot.yml中的jetcache配置实例
jetcache:
  statIntervalMinutes: 15
  areaInCacheName: false
  local:
    default:
      type: caffeine
      keyConvertor: fastjson
  remote:
    default:
      type: redis.lettuce
      keyConvertor: fastjson
      valueEncoder: java
      valueDecoder: java
      uri: redis://${spring.redis.password}@${spring.redis.host}:${spring.redis.port}/

2.2 注解部分
注解部分

  • AOP Advisor是AOP的顶层抽象,用来管理Advice和PointCut。
    Advisor有两个实现类,PointcutAdvisor和IntroductionAdvisor,其中IntroductionAdvisor只能应用于类级别的拦截,只能使用Introduction型的Advice。PointcutAdvisor可使用任何类型的PointCut,和几乎任何类型的Advice。
    CacheAdvisor继承了AbstractBeanFactoryPointcutAdvisor。
  • CachePointcut继承了抽象类StaticMethodMatcherPointcut,这里使用了静态方法切入点。
    静态切入点只在代理创建时执行一次,而不是在运行期间每次调用方法时都执行,所以性能比动态切入点要好,是首选的切入点方式。
  • 注解具体生效,是在CachePointcut的matchesImpl()方法中,调用了CacheUtils的parse方法。

    public static boolean parse(CacheInvokeConfig cac, Method method) {
        boolean hasAnnotation = false;
        CachedAnnoConfig cachedConfig = parseCached(method);//反射获取@Cached注解信息
        if (cachedConfig != null) {
            cac.setCachedAnnoConfig(cachedConfig);
            hasAnnotation = true;
        }
        boolean enable = parseEnableCache(method);
        if (enable) {
            cac.setEnableCacheContext(true);
            hasAnnotation = true;
        }
        List<CacheInvalidateAnnoConfig> invalidateAnnoConfigs = parseCacheInvalidates(method);
        if (invalidateAnnoConfigs != null) {
            cac.setInvalidateAnnoConfigs(invalidateAnnoConfigs);
            hasAnnotation = true;
        }
        CacheUpdateAnnoConfig updateAnnoConfig = parseCacheUpdate(method);
        if (updateAnnoConfig != null) {
            cac.setUpdateAnnoConfig(updateAnnoConfig);
            hasAnnotation = true;
        }

        if (cachedConfig != null && (invalidateAnnoConfigs != null || updateAnnoConfig != null)) {
            throw new CacheConfigException("@Cached can't coexists with @CacheInvalidate or @CacheUpdate: " + method);
        }

        return hasAnnotation;
    }

在这里分别判断了,@Cached,@CacheInvalidate,@CacheUpdate注解是否存在,@CacheRefresh注解的解析在parseCached()方法中,因为它是基于@Cached注解的。

2.3 缓存注解生效

  • JetCacheInterceptor会对代理的方法进行拦截,来完成注解对应的操作。
  • 被拦截的方法最终会通过AOP的实现来调用JetCacheInterceptor的invoke方法来实现策略,最终会调用CacheHandler.doInvoke()方法。

    private static Object doInvoke(CacheInvokeContext context) throws Throwable {
        CacheInvokeConfig cic = context.getCacheInvokeConfig();
        CachedAnnoConfig cachedConfig = cic.getCachedAnnoConfig();
        if (cachedConfig != null && (cachedConfig.isEnabled() || CacheContextSupport._isEnabled())) {
            return invokeWithCached(context);
        } else if (cic.getInvalidateAnnoConfigs() != null || cic.getUpdateAnnoConfig() != null) {
            return invokeWithInvalidateOrUpdate(context);
        } else {
            return invokeOrigin(context);
        }
    }

这里根据不同的注解,调用不同的invoke方法。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值