JetCache源码解析——配置加载和初始化

JetCache自动化配置加载

JetCache的配置加载主要是在jetcache-autoconfigure模块中完成的,其中加载配置的核心类是JetCacheAutoConfiguration,主要是用于创建全局性的一些Bean,例如全局缓存配置类GlobalCacheConfig,AutoConfigureBeans和SpringConfigProvider等,源码如下:

@Configuration
@ConditionalOnClass(GlobalCacheConfig.class)
@ConditionalOnMissingBean(GlobalCacheConfig.class)
@EnableConfigurationProperties(JetCacheProperties.class)
@Import({RedisAutoConfiguration.class,
        CaffeineAutoConfiguration.class,
        MockRemoteCacheAutoConfiguration.class,
        LinkedHashMapAutoConfiguration.class,
        RedisLettuceAutoConfiguration.class,
        RedisSpringDataAutoConfiguration.class,
        RedissonAutoConfiguration.class})
public class JetCacheAutoConfiguration {

    public static final String GLOBAL_CACHE_CONFIG_NAME = "globalCacheConfig";

    @Bean(destroyMethod = "shutdown")
    @ConditionalOnMissingBean
    public SpringConfigProvider springConfigProvider(
            @Autowired ApplicationContext applicationContext,
            @Autowired GlobalCacheConfig globalCacheConfig,
            @Autowired(required = false) EncoderParser encoderParser,
            @Autowired(required = false) KeyConvertorParser keyConvertorParser,
            @Autowired(required = false) Consumer metricsCallback) {
        return new JetCacheBaseBeans().springConfigProvider(applicationContext, globalCacheConfig,
                encoderParser, keyConvertorParser, metricsCallback);
    }

    @Bean(name = "jcCacheManager",destroyMethod = "close")
    @ConditionalOnMissingBean
    public SimpleCacheManager cacheManager(@Autowired SpringConfigProvider springConfigProvider) {
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        cacheManager.setCacheBuilderTemplate(springConfigProvider.getCacheBuilderTemplate());
        return cacheManager;
    }

    @Bean
    @ConditionalOnMissingBean
    public AutoConfigureBeans autoConfigureBeans() {
        return new AutoConfigureBeans();
    }

    @Bean
    public static BeanDependencyManager beanDependencyManager() {
        return new BeanDependencyManager();
    }

    @Bean(name = GLOBAL_CACHE_CONFIG_NAME)
    public GlobalCacheConfig globalCacheConfig(AutoConfigureBeans autoConfigureBeans, JetCacheProperties props) {
        GlobalCacheConfig _globalCacheConfig = new GlobalCacheConfig();
        _globalCacheConfig = new GlobalCacheConfig();
        _globalCacheConfig.setHiddenPackages(props.getHiddenPackages());
        _globalCacheConfig.setStatIntervalMinutes(props.getStatIntervalMinutes());
        _globalCacheConfig.setAreaInCacheName(props.isAreaInCacheName());
        _globalCacheConfig.setPenetrationProtect(props.isPenetrationProtect());
        _globalCacheConfig.setEnableMethodCache(props.isEnableMethodCache());
        _globalCacheConfig.setLocalCacheBuilders(autoConfigureBeans.getLocalCacheBuilders());
        _globalCacheConfig.setRemoteCacheBuilders(autoConfigureBeans.getRemoteCacheBuilders());
        return _globalCacheConfig;
    }
}

无论是使用内存缓存LinkedHashMap和caffeine,亦或是通过lettuce、redisson和spring-data-redis来操作Redis服务缓存数据,其自动加载配置的操作基本上都是相似的。

例如LinkedHashMapAutoConfiguration完成LinkedHashMap类型的缓存初始化操作,源码如下:

/**
 * LinkedHashMap自动配置类
 */
@Component
@Conditional(LinkedHashMapAutoConfiguration.LinkedHashMapCondition.class)
public class LinkedHashMapAutoConfiguration extends EmbeddedCacheAutoInit {
    public LinkedHashMapAutoConfiguration() {
        super("linkedhashmap");
    }

    @Override
    /**
     * 初始化缓存
     * @param ct            配置树
     * @param cacheAreaWithPrefix    缓存区域名称(包括前缀)
     * @return              初始化后的缓存
     */
    protected CacheBuilder initCache(ConfigTree ct, String cacheAreaWithPrefix) {
        LinkedHashMapCacheBuilder builder = LinkedHashMapCacheBuilder.createLinkedHashMapCacheBuilder();
        parseGeneralConfig(builder, ct);
        return builder;
    }

    /**
     * LinkedHashMap条件类
     */
    public static class LinkedHashMapCondition extends JetCacheCondition {
        public LinkedHashMapCondition() {
            super("linkedhashmap");
        }
    }
}

其中LinkedHashMapCondition是一个自定义的条件类,如果spring boot项目的application.yml文件中jetcache.local.${areaName} .type或jetcache.remote.${areaName}.type为linkedhashmap时,就会构建LinkedHashMapAutoConfiguration对应的bean,JetCacheCondition类的源码如下:

public abstract class JetCacheCondition extends SpringBootCondition {

    /**
     * 缓存类型数组
     */
    private String[] cacheTypes;

    /**
     * 构造方法
     * @param cacheTypes 缓存类型数组
     */
    protected JetCacheCondition(String... cacheTypes) {
        Objects.requireNonNull(cacheTypes, "cacheTypes can't be null");
        Assert.isTrue(cacheTypes.length > 0, "cacheTypes length is 0");
        this.cacheTypes = cacheTypes;
    }

    /**
     * 判断条件是否匹配
     * @param conditionContext 条件上下文
     * @param annotatedTypeMetadata 注解类型元数据
     * @return 匹配结果
     */
    @Override
    public ConditionOutcome getMatchOutcome(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        ConfigTree ct = new ConfigTree((ConfigurableEnvironment) conditionContext.getEnvironment(), "jetcache.");
        if (match(ct, "local.") || match(ct, "remote.")) {
            return ConditionOutcome.match();
        } else {
            return ConditionOutcome.noMatch("no match for " + cacheTypes[0]);
        }
    }

    /**
     * 判断是否匹配指定前缀
     * @param ct 配置树
     * @param prefix 前缀
     * @return 是否匹配
     */
    private boolean match(ConfigTree ct, String prefix) {
        Map m = ct.subTree(prefix).getProperties();
        Set cacheAreaNames = m.keySet().stream().map((s) -> s.substring(0, s.indexOf('.'))).collect(Collectors.toSet());
        final List cacheTypesList = Arrays.asList(cacheTypes);
        return cacheAreaNames.stream().anyMatch((s) -> cacheTypesList.contains(m.get(s + ".type")));
    }
}

同理,如果想要使用redisson或lettuce中间件来进行远程缓存的处理,则对应的自动化配置类的源码如下:

@Configuration
@Conditional(RedissonAutoConfiguration.RedissonCondition.class)
public class RedissonAutoConfiguration {
    private static final String CACHE_TYPE = "redisson";

    public static class RedissonCondition extends JetCacheCondition {
        public RedissonCondition() {
            super(CACHE_TYPE);
        }
    }

    @Bean
    public RedissonAutoInit redissonAutoInit() {
        return new RedissonAutoInit();
    }
}

在上面的RedissonAutoConfiguration.RedissonCondition条件类中对应的cacheTypes为redisson,即当spring boot项目的application.yml文件中jetcache.local.${areaName} .type或jetcache.remote.${areaName}.type为redisson时,就会构建RedissonAutoConfiguration对应的bean。

同理,加载lettuce对应的自动化加载的源码如下:

@Configuration
@Conditional(RedisLettuceAutoConfiguration.RedisLettuceCondition.class)
public class RedisLettuceAutoConfiguration {
    public static final String AUTO_INIT_BEAN_NAME = "redisLettuceAutoInit";

    public static class RedisLettuceCondition extends JetCacheCondition {
        public RedisLettuceCondition() {
            super("redis.lettuce");
        }
    }

    @Bean(name = {AUTO_INIT_BEAN_NAME})
    public RedisLettuceAutoInit redisLettuceAutoInit() {
        return new RedisLettuceAutoInit();
    }
}

JetCache自动初始化

上面介绍的都是各种缓存方式的自动化配置的类,可以发现当自动化配置的Bean被创建后,都会创建AbstractCacheAutoInit对应的Bean。JetCache的自动化的类图如下:

JetCache源码解析——配置加载和初始化_第1张图片

上面所有类型的缓存初始化类最终都继承了InitializingBean,InitializingBean是Spring框架中的一个接口,它定义了一个方法 afterPropertiesSet()。当一个bean实例化后,Spring容器会自动调用该方法来完成一些初始化操作。

抽象类AbstractCacheAutoInit在实现InitializingBean接口时,重写了afterPropertiesSet() 方法来实现一些特定的初始化逻辑。在 Spring 容器完成AbstractCacheAutoInit类型的bean的属性注入后,会调用AbstractCacheAutoInit类的afterPropertiesSet() 方法来执行这些初始化逻辑。

我们先看一下AbstractCacheAutoInit类的afterPropertiesSet()方法,源码如下:

/**
 * 在属性设置之后调用的方法
 */
@Override
public void afterPropertiesSet() {
    // 如果还未初始化
    if (!inited) {
        // 上锁
        reentrantLock.lock();
        try{
            // 如果还未初始化
            if (!inited) {
                // 处理本地缓存构建器
                process("jetcache.local.", autoConfigureBeans.getLocalCacheBuilders(), true);
                // 处理远程缓存构建器
                process("jetcache.remote.", autoConfigureBeans.getRemoteCacheBuilders(), false);
                // 设置已初始化标志为true
                inited = true;
            }
        }finally {
            // 解锁
            reentrantLock.unlock();
        }
    }
}

上面的代码相对比较简单,首先会判断是否已经进行初始化了,如果没有就先用锁,然后处理本地和远程缓存的处理器。实例autoConfigureBeans中的方法getLocalCacheBuilders()和getRemoteCacheBuilders()是用于获取本地和远程的缓存构建者,类AutoConfigureBeans的源码很简单,我们简单的看一下即可,不做详细介绍。AutoConfigureBeans源码如下:

public class AutoConfigureBeans {

    private Map localCacheBuilders = new HashMap<>();

    private Map remoteCacheBuilders = new HashMap<>();

    private Map customContainer = Collections.synchronizedMap(new HashMap<>());

    public Map getLocalCacheBuilders() {
        return localCacheBuilders;
    }

    public void setLocalCacheBuilders(Map localCacheBuilders) {
        this.localCacheBuilders = localCacheBuilders;
    }

    public Map getRemoteCacheBuilders() {
        return remoteCacheBuilders;
    }

    public void setRemoteCacheBuilders(Map remoteCacheBuilders) {
        this.remoteCacheBuilders = remoteCacheBuilders;
    }

    public Map getCustomContainer() {
        return customContainer;
    }

    public void setCustomContainer(Map customContainer) {
        this.customContainer = customContainer;
    }
}

其中AutoConfigureBeans类的Bean是在JetCacheAutoConfiguration类中被创建的。我们继续看AbstractCacheAutoInit类中process方法的逻辑,其源码如下:

    /**
     * 处理缓存区域
     *
     * @param prefix 缓存前缀
     * @param cacheBuilders 缓存构建器的映射
     * @param local 是否为本地缓存
     */
    private void process(String prefix, Map cacheBuilders, boolean local) {
        // 创建配置树解析器
        ConfigTree resolver = new ConfigTree(environment, prefix);
        
        // 获取缓存区域的属性集合
        Map m = resolver.getProperties();
        
        // 获取缓存区域的名称集合
        Set 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);
        }
    }

这个函数用于处理缓存区域。首先根据给定的前缀和环境创建一个配置树解析器,并获取缓存区域的属性和名称集合。然后遍历缓存区域名称集合,获取每个区域的类型,并与给定的类型名称进行比较。如果类型匹配,则初始化该缓存区域的缓存,并将缓存构建器添加到给定的映射中。

上面的方法会针对每一个areaName分别去调用initCache方法进行初始化操作,接下来,我们先看一下initCache方法的源码:

protected abstract CacheBuilder initCache(ConfigTree ct, String cacheAreaWithPrefix);

AbstractCacheAutoInit类的initCache方法是一个抽象方法,这就需要在子类中实现该方法,我们接下来就开始分析主要的几个内存和Redis缓存的自动初始化类的源码了。

内存缓存自动初始化

LinkedHashMapAutoConfiguration类是基于LinkedHashMap来创建的内存缓存自动配置类,该类继承自EmbeddedCacheAutoInit抽象类,该类重写了initCache方法,用于内存缓存的初始化操作,该方法会创建用于创建LinkedHashMap类型的内存缓存的创建者,并调用parseGeneralConfig方法解析JetCache内存缓存的配置。

为了便于理解,我们先看一下JetCache中关于内存缓存的配置信息:

jetcache:
  statIntervalMinutes: 15
  areaInCacheName: false
  hidePackages: com.alibaba
  local:
    default:
      type: caffeine
      limit: 100
      keyConvertor: fastjson2 #其他可选:fastjson/jackson
      expireAfterWriteInMillis: 100000
    otherArea:
      type: linkedhashmap
      limit: 100
      keyConvertor: none
      expireAfterWriteInMillis: 100000

配置通用说明如下:

属性 默认值 说明
jetcache.statIntervalMinutes 0 统计间隔,0表示不统计
jetcache.areaInCacheName true(2.6-) false(2.7+) jetcache-anno把cacheName作为远程缓存key前缀,2.4.3以前的版本总是把areaName加在cacheName中,因此areaName也出现在key前缀中。2.4.4以后可以配置,为了保持远程key兼容默认值为true,但是新项目的话false更合理些,2.7默认值已改为false。
jetcache.hiddenPackages @Cached和@CreateCache自动生成name的时候,为了不让name太长,hiddenPackages指定的包名前缀被截掉
jetcache.local.${area}.type 缓存类型。tair、redis为当前支持的远程缓存;linkedhashmap、caffeine为当前支持的本地缓存类型
jetcache.local.${area}.keyConvertor fastjson2 key转换器的全局配置,2.6.5+已经支持的keyConvertor:fastjson2/jackson
2.6.5-只有一个已经实现的keyConvertor:fastjson。仅当使用@CreateCache且缓存类型为LOCAL时可以指定为none,此时通过equals方法来识别key。方法缓存必须指定keyConvertor
jetcache.local.${area}.limit 100 每个缓存实例的最大元素的全局配置,仅local类型的缓存需要指定。注意是每个缓存实例的限制,而不是全部,比如这里指定100,然后用@CreateCache创建了两个缓存实例(并且注解上没有设置localLimit属性),那么每个缓存实例的限制都是100
jetcache.local.${area}.expireAfterWriteInMillis 无穷大 以毫秒为单位指定超时时间的全局配置(以前为defaultExpireInMillis)
jetcache.local.${area}.expireAfterAccessInMillis 0 需要jetcache2.2以上,以毫秒为单位,指定多长时间没有访问,就让缓存失效,当前只有本地缓存支持。0表示不使用这个功能。

内存缓存中的常用的配置主要是type、keyConvertor、limit、expireAfterWriteInMillis和expireAfterAccessInMillis。由于系统所在的服务器对每个服务的占用内存比较敏感,所以需要对内存缓存中的数据量进行限制,这就需要配置limit,如果不配置则每个缓存实例就默认是100个,一旦某个缓存实例的缓存达到limit的限制后,新的缓存数据就无法保存到内存缓存中了,所以合理配置limit的值很重要,鉴于可以在查询函数中通过配置@Cached注解中的localLimit来设置每个缓存实例的limit,且@Cached注解中的localLimit优先于全局配置中的limit。keyConvertor可以使用默认的配置,即linkedhashmap类型的缓存使用none,caffeine类型的缓存使用fastjson2。@Cached注解中的keyConvertor来设置每个缓存实例的keyConvertor,且@Cached注解中的keyConvertor优先于全局配置中的keyConvertor。

initCache方法的源码如下:

    /**
     * 重写方法,用于初始化缓存
     * @param ct 缓存的配置树
     * @param cacheAreaWithPrefix 缓存区服(包括前缀)
     * @return 返回构建好的缓存
     */
    @Override
    protected CacheBuilder initCache(ConfigTree ct, String cacheAreaWithPrefix) {
        LinkedHashMapCacheBuilder builder = LinkedHashMapCacheBuilder.createLinkedHashMapCacheBuilder();
        parseGeneralConfig(builder, ct);
        return builder;
    }

创建LinkerHashMap类型的缓存构建者的源码如下:

public class LinkedHashMapCacheBuilder> extends EmbeddedCacheBuilder {
    /**
     * LinkedHashMapCacheBuilder的内部类
     */
    public static class LinkedHashMapCacheBuilderImpl extends LinkedHashMapCacheBuilder {
        
    }

    /**
     * 创建一个LinkedHashMapCacheBuilderImpl实例
     * 
     * @return 返回LinkedHashMapCacheBuilderImpl实例
     */
    public static LinkedHashMapCacheBuilderImpl createLinkedHashMapCacheBuilder() {
        return new LinkedHashMapCacheBuilderImpl();
    }

    /**
     * 私有构造方法
     */
    protected LinkedHashMapCacheBuilder() {
        buildFunc((c) -> new LinkedHashMapCache((EmbeddedCacheConfig) c));
    }
}

在上面构建的LinkedHashMapCacheBuilder实例时,LinkedHashMapCacheBuilder的构造函数会初始化相应的缓存类LinkedHashMapCache。这里紧急简单介绍一下,后面会详细就缓存构建者进行描述。

CaffeineAutoConfiguration类是基于Caffeine来创建的内存缓存自动配置类,该类继承自EmbeddedCacheAutoInit抽象类,该类重写了initCache方法,该方法会创建用于创建Caffeine类型的内存缓存的创建者,并调用parseGeneralConfig方法解析JetCache的配置。源码如下:

@Override
    protected CacheBuilder initCache(ConfigTree ct, String cacheAreaWithPrefix) {
        CaffeineCacheBuilder builder = CaffeineCacheBuilder.createCaffeineCacheBuilder();
        parseGeneralConfig(builder, ct);
        return builder;
    }

同样,关于构建CaffeineCacheBuilder相应的代码逻辑也放在后面再进行介绍。

内存缓存LinkedHashMap和caffeine的自动初始化类都是继承自EmbeddedCacheAutoInit抽象类,主要是解析配置的内存缓存的最大缓存数量。EmbeddedCacheAutoInit类的源码如下:

public abstract class EmbeddedCacheAutoInit extends AbstractCacheAutoInit {

    public EmbeddedCacheAutoInit(String... cacheTypes) {
        super(cacheTypes);
    }

    @Override
    protected void parseGeneralConfig(CacheBuilder builder, ConfigTree ct) {
        super.parseGeneralConfig(builder, ct);
        EmbeddedCacheBuilder ecb = (EmbeddedCacheBuilder) builder;

        ecb.limit(Integer.parseInt(ct.getProperty("limit", String.valueOf(CacheConsts.DEFAULT_LOCAL_LIMIT))));
    }
}

解析配置文件的逻辑放到下面的JetCache配置解析章节进行讲述,这里不做过多描述。

EmbeddedCacheAutoInit

我们上面提到内存缓存的自动化配置类的父类都是EmbeddedCacheAutoInit,所以这里我们需要先来看一下类EmbeddedCacheAutoInit的源码。

public abstract class EmbeddedCacheAutoInit extends AbstractCacheAutoInit {

    public EmbeddedCacheAutoInit(String... cacheTypes) {
        super(cacheTypes);
    }

    @Override
    protected void parseGeneralConfig(CacheBuilder builder, ConfigTree ct) {
        super.parseGeneralConfig(builder, ct);
        EmbeddedCacheBuilder ecb = (EmbeddedCacheBuilder) builder;

        ecb.limit(Integer.parseInt(ct.getProperty("limit", String.valueOf(CacheConsts.DEFAULT_LOCAL_LIMIT))));
    }
}

上面的parseGeneralConfig方法会先调用父类的parseGeneralConfig方法解析常用配置,然后将配置中的limit同步到EmbeddedCacheBuilder实例的limit属性中。

接下来,我们再来看一下EmbeddedCacheAutoInit的父类AbstractCacheAutoInit的parseGeneralConfig方法的源码如下:

    // 解析通用配置
    protected void parseGeneralConfig(CacheBuilder builder, ConfigTree ct) {
        // 获得缓存构建器
        AbstractCacheBuilder acb = (AbstractCacheBuilder) builder;
        // 设置key转换器
        acb.keyConvertor(new ParserFunction(ct.getProperty("keyConvertor", KeyConvertor.FASTJSON2)));

        // 获取expireAfterWriteInMillis配置
        String expireAfterWriteInMillis = ct.getProperty("expireAfterWriteInMillis");
        // 保持与2.1版本的兼容性,如果未找到expireAfterWriteInMillis,尝试获取defaultExpireInMillis配置
        if (expireAfterWriteInMillis == null) {
            expireAfterWriteInMillis = ct.getProperty("defaultExpireInMillis");
        }
        // 如果expireAfterWriteInMillis配置存在,则将其转换为long型并设置到acb对象中
        if (expireAfterWriteInMillis != null) {
            acb.setExpireAfterWriteInMillis(Long.parseLong(expireAfterWriteInMillis));
        }

        // 获取expireAfterAccessInMillis配置
        String expireAfterAccessInMillis = ct.getProperty("expireAfterAccessInMillis");
        // 如果expireAfterAccessInMillis配置存在,则将其转换为long型并设置到acb对象中
        if (expireAfterAccessInMillis != null) {
            acb.setExpireAfterAccessInMillis(Long.parseLong(expireAfterAccessInMillis));
        }
    }

这个函数用于解析通用配置信息。首先,将传入的builder对象转换为AbstractCacheBuilder类型,并设置keyConvertor为通过ct对象获取的属性值或默认值。然后,从ct对象获取expireAfterWriteInMillis属性值,如果该属性值为空,则使用defaultExpireInMillis属性值。最后,将获取到的expireAfterWriteInMillis和expireAfterAccessInMillis属性值转换为long类型,并设置到acb对象中。

Redis缓存自动初始化

在RedissonAutoConfiguration类中redissonAutoInit()函数会创建RedissonAutoInit对应的bean;在RedisLettuceAutoConfiguration类中redisLettuceAutoInit()函数会创建RedisLettuceAutoInit对应的bean。同理RedisSpringDataAutoConfiguration类中springDataRedisAutoInit()函数会创建SpringDataRedisAutoInit对应的bean。根据上面的JetCache的自动化的类图可以发现,RedissonAutoInit、RedisLettuceAutoInit和SpringDataRedisAutoInit都是ExternalCacheAutoInit的子类,所以主要是初始化函数initCache方法的实现会有所不同,我们在下面会着重的就RedissonAutoInit、RedisLettuceAutoInit和SpringDataRedisAutoInit的initCache方法进行详细的介绍一下。

鉴于下面的初始化方法中会获取JetCache的配置创建Redis连接,所以我们优先看一下JetCache中关于远程缓存的配置说明,会便于下面的源码的理解。

jetcache:
  statIntervalMinutes: 15
  areaInCacheName: false
  hidePackages: com.alibaba
  remote:
    default:
      type: redis.lettuce
      keyConvertor: fastjson2
      broadcastChannel: projectA
      mode: cluster    #redis模式,cluster:集群模式
      #readFrom: slavePreferred
      uri:
        - redis://127.0.0.1:7000 #redis服务的//IP:Port
        - redis://127.0.0.1:7001
        - redis://127.0.0.1:7002
    otherArea:
      type: redis.lettuce
      keyConvertor: fastjson2 #其他可选:fastjson/jackson
      broadcastChannel: projectA
      valueEncoder: java #其他可选:kryo/kryo5
      valueDecoder: java #其他可选:kryo/kryo5
      poolConfig:
        minIdle: 5
        maxIdle: 20
        maxTotal: 50
      host: ${redis.host}
      port: ${redis.port}

配置通用说明如下

属性 默认值 说明
jetcache.remote.${area}.type 缓存类型。tair、redis为当前支持的远程缓存;linkedhashmap、caffeine为当前支持的本地缓存类型
jetcache.remote.${area}.keyConvertor fastjson2 key转换器的全局配置,2.6.5+已经支持的keyConvertor:fastjson2/jackson
2.6.5-只有一个已经实现的keyConvertor:fastjson。仅当使用@CreateCache且缓存类型为LOCAL时可以指定为none,此时通过equals方法来识别key。方法缓存必须指定keyConvertor
jetcache.remote.${area}.valueEncoder java 序列化器的全局配置。仅remote类型的缓存需要指定,2.7+可选java/kryo/kryo5;2.6-可选java/kryo
jetcache.remote.${area}.valueDecoder java 序列化器的全局配置。仅remote类型的缓存需要指定,2.7+可选java/kryo/kryo5;2.6-可选java/kryo
jetcache.remote.${area}.expireAfterWriteInMillis 无穷大 以毫秒为单位指定超时时间的全局配置(以前为defaultExpireInMillis)
jetcache.remote.${area}.broadcastChannel

jetcahe2.7的两级缓存支持更新以后失效其他JVM中的local cache,但多个服务共用redis同一个channel可能会造成广播风暴,需要在这里指定channel,你可以决定多个不同的服务是否共用同一个channel。如果没有指定则不开启。

jetcache.remote.${area}.mode 缓存模式,cluster:集群模式
jetcache.remote.${area}.uri 缓存服务的URI,可以支持多个
jetcache.remote.${area}.ip 缓存服务的IP地址
jetcache.remote.${area}.port 缓存服务的端口

上表中${area}对应@Cached和@CreateCache的area属性。注意如果注解上没有指定area,默认值是"default"。

RedissonAutoInit

RedissonAutoInit继承自抽象类ExternalCacheAutoInit,在initCache方法中主要是获取所有RedissonClient的bean映射,如果找到了多个RedissonClient的bean,则根据配置决定使用哪个RedissonClient。解析spring boot项目的application.yml文件中jetcache.remote .${areaName}中的Redis配置,创建用于创建Redisson类型的内存缓存的创建者RedissonCacheBuilder,并调用parseGeneralConfig方法解析JetCache的配置。源码如下:

/**
 * 初始化缓存
 *
 * @param ct            缓存配置树
 * @param cacheAreaWithPrefix 缓存区域名称(包括前缀)
 * @return 缓存构建器
 */
@Override
protected CacheBuilder initCache(final ConfigTree ct, final String cacheAreaWithPrefix) {
    // 获取RedissonClient的bean
    final Map beans = this.context.getBeansOfType(RedissonClient.class);
    if (beans.isEmpty()) {
        throw new CacheConfigException("no RedissonClient in spring context");
    }
    
    RedissonClient client = beans.values().iterator().next();
    if (beans.size() > 1) {
        // 获取配置树中的redissonClient属性值
        final String redissonClientName = ct.getProperty("redissonClient");
        if (Objects.isNull(redissonClientName) || redissonClientName.isEmpty()) {
            throw new CacheConfigException("redissonClient is required, because there is multiple RedissonClient in Spring context");
        }
        
        if (!beans.containsKey(redissonClientName)) {
            throw new CacheConfigException("there is no RedissonClient named " + redissonClientName + " in Spring context");
        }
        
        client = beans.get(redissonClientName);
    }
    
    // 创建外部缓存构建器并设置RedissonClient
    final ExternalCacheBuilder builder = RedissonCacheBuilder.createBuilder().redissonClient(client);
    
    // 解析通用配置并添加到构建器中
    parseGeneralConfig(builder, ct);
    
    return builder;
}

函数的功能是初始化缓存,具体实现如下:

  • 获取所有RedissonClient的bean映射,如果找到了多个RedissonClient的bean,则根据配置决定使用哪个RedissonClient。
  • 创建一个ExternalCacheBuilder对象,并将RedissonClient设置为其属性。
  • 解析通用配置并添加到CacheBuilder中。

RedisLettuceAutoInit

RedisLettuceAutoInit继承自抽象类ExternalCacheAutoInit,在initCache方法相较于RedissonAutoInit类的initCache方法方法的逻辑也比较复杂,解析spring boot项目的application.yml文件中jetcache.remote .${areaName}中的Redis配置,创建Redis连接,如果配置了集群模式,则需要创建集群模式的Redis连接;如果配置的是单节点模式,则会创建单节点的Redis连接,创建用于创建Lettuce类型的内存缓存的创建者RedisLettuceCacheBuilder,并调用parseGeneralConfig方法解析JetCache的配置。最后将客户端和连接对象存入自动配置的自定义容器中,并返回一个包含相关配置的ExternalCacheBuilder对象。源码如下:

@Override
protected CacheBuilder initCache(ConfigTree ct, String cacheAreaWithPrefix) {
    // 获取Lettuce缓存的URI配置
    Map map = ct.subTree("uri"/*there is no dot*/).getProperties();
    // 获取读取模式
    String readFromStr = ct.getProperty("readFrom");
    String mode = ct.getProperty("mode");
    // 获取异步结果超时时间
    long asyncResultTimeoutInMillis = ct.getProperty("asyncResultTimeoutInMillis", CacheConsts.ASYNC_RESULT_TIMEOUT.toMillis());
    // 判断是否启用广播通道
    boolean enablePubSub = parseBroadcastChannel(ct) != null;
    ReadFrom readFrom = null;
    // 根据读取模式字符串获取ReadFrom枚举类型
    if (readFromStr != null) {
        readFrom = ReadFrom.valueOf(readFromStr.trim());
    }

    AbstractRedisClient client;
    StatefulConnection connection;
    StatefulRedisPubSubConnection pubSubConnection = null;
    // 判断是否配置了URI
    if (map == null || map.size() == 0) {
        // 如果未配置URI,则抛出异常
        throw new CacheConfigException("lettuce uri is required");
    } else {
        // 将配置的URI转换为RedisURI对象列表
        List uriList = map.values().stream()
            .map((k) -> RedisURI.create(URI.create(k.toString())))
            .collect(Collectors.toList());

        if ("Cluster".equalsIgnoreCase(mode)) {
            // 配置为集群模式
            client = RedisClusterClient.create(uriList);
            // 链接集群节点并获取连接对象
            connection = clusterConnection(ct, readFrom, (RedisClusterClient) client, false);
            if (enablePubSub) {
                // 如果启用了广播通道,则获取与集群节点的连接对象
                pubSubConnection = (StatefulRedisPubSubConnection) clusterConnection(ct, readFrom, (RedisClusterClient) client, true);
            }
        } else {
            // 配置为单节点模式
            client = RedisClient.create();
            ((RedisClient) client).setOptions(ClientOptions.builder().
            disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS).build());
            // 链接单节点并获取连接对象
            StatefulRedisMasterReplicaConnection c = MasterReplica.connect(
            (RedisClient) client, new JetCacheCodec(), uriList);
            if (readFrom != null) {
                // 如果指定了读取模式,则设置连接对象的读取模式
                c.setReadFrom(readFrom);
            }
            connection = c;
            if (enablePubSub) {
                // 如果启用了广播通道,则获取与单节点的连接对象
                pubSubConnection = ((RedisClient) client).connectPubSub(new JetCacheCodec(), uriList.get(0));
            }
        }
    }

    // 创建外部缓存构建器对象
    ExternalCacheBuilder externalCacheBuilder = RedisLettuceCacheBuilder.createRedisLettuceCacheBuilder()
    .connection(connection)
    .pubSubConnection(pubSubConnection)
    .redisClient(client)
    .asyncResultTimeoutInMillis(asyncResultTimeoutInMillis);
    // 解析通用配置
    parseGeneralConfig(externalCacheBuilder, ct);

    // eg: "remote.default.client"
    // 将Redis客户端对象存入自动配置的自定义容器中
    autoConfigureBeans.getCustomContainer().put(cacheAreaWithPrefix + ".client", client);
    // 获取Lettuce连接管理器对象
    LettuceConnectionManager m = LettuceConnectionManager.defaultManager();
    // 初始化Lettuce连接管理器
    m.init(client, connection);
    // 将Lettuce连接管理器中的连接对象存入自动配置的自定义容器中
    autoConfigureBeans.getCustomContainer().put(cacheAreaWithPrefix + ".connection", m.connection(client));
    // 将Lettuce连接管理器中的命令对象存入自动配置的自定义容器中
    autoConfigureBeans.getCustomContainer().put(cacheAreaWithPrefix + ".commands", m.commands(client));
    // 将Lettuce连接管理器中的异步命令对象存入自动配置的自定义容器中
    autoConfigureBeans.getCustomContainer().put(cacheAreaWithPrefix + ".asyncCommands", m.asyncCommands(client));
    // 将Lettuce连接管理器中的反应式命令对象存入自动配置的自定义容器中
    autoConfigureBeans.getCustomContainer().put(cacheAreaWithPrefix + ".reactiveCommands", m.reactiveCommands(client));
    // 返回外部缓存构建器对象
    return externalCacheBuilder;
}

在上面的方法中,如果配置的Redis配置是集群模式则会调用clusterConnection方法建立集群模式的Redis连接,相应的源码如下:

/**
 * 创建一个与Redis集群连接的状态保持连接对象。
 * @param ct 配置树对象,用于获取配置信息
 * @param readFrom 读取来源,用于设置连接的读取来源
 * @param client Redis集群客户端对象
 * @param pubsub 是否创建一个订阅连接
 * @return 状态保持连接对象
 */
private StatefulConnection clusterConnection(ConfigTree ct, ReadFrom readFrom, RedisClusterClient client, boolean pubsub) {
    int enablePeriodicRefresh = ct.getProperty("enablePeriodicRefresh", 60);
    boolean enableAllAdaptiveRefreshTriggers = ct.getProperty("enableAllAdaptiveRefreshTriggers", true);
    ClusterTopologyRefreshOptions.Builder topologyOptionBuilder = ClusterTopologyRefreshOptions.builder();
    if (enablePeriodicRefresh > 0) {
        topologyOptionBuilder.enablePeriodicRefresh(Duration.ofSeconds(enablePeriodicRefresh));
    }
    if (enableAllAdaptiveRefreshTriggers) {
        topologyOptionBuilder.enableAllAdaptiveRefreshTriggers();
    }

    ClusterClientOptions options = ClusterClientOptions.builder()
            .topologyRefreshOptions(topologyOptionBuilder.build())
            .disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS)
            .build();
    client.setOptions(options);
    if (pubsub) {
        return client.connectPubSub(new JetCacheCodec());
    } else {
        StatefulRedisClusterConnection c = client.connect(new JetCacheCodec());
        if (readFrom != null) {
            c.setReadFrom(readFrom);
        }
        return c;
    }
}

SpringDataRedisAutoInit

SpringDataRedisAutoInit继承自抽象类ExternalCacheAutoInit,initCache方法的实现比较简单,先是获取RedisConnectionFactory的Bean,如果有多个就获取jetCache配置中connectionFactory来决定使用哪一个RedisConnectionFactory实例,创建用于创建Lettuce类型的内存缓存的创建者RedisLettuceCacheBuilder,并调用parseGeneralConfig方法解析JetCache的配置。最后将客户端和连接对象存入自动配置的自定义容器中,并返回一个包含相关配置的ExternalCacheBuilder对象。源码如下:

/**
 * 初始化缓存
 *
 * @param ct 配置树
 * @param cacheAreaWithPrefix 缓存区域名称(包括前缀)
 * @return 缓存构建器
 */
@Override
protected CacheBuilder initCache(ConfigTree ct, String cacheAreaWithPrefix) {
    // 从应用上下文中获取 RedisConnectionFactory 的 bean 映射
    Map beans = applicationContext.getBeansOfType(RedisConnectionFactory.class);

    // 如果 bean 映射为空或为空,抛出异常
    if (beans == null || beans.isEmpty()) {
        throw new CacheConfigException("no RedisConnectionFactory in spring context");
    }

    // 获取一个可用的 RedisConnectionFactory 实例
    RedisConnectionFactory factory = beans.values().iterator().next();

    // 如果 Spring 上下文中有多个 RedisConnectionFactory,抛出异常
    if (beans.size() > 1) {
        String connectionFactoryName = ct.getProperty("connectionFactory");

        // 如果 connectionFactoryName 为空,抛出异常
        if (connectionFactoryName == null) {
            throw new CacheConfigException(
                    "connectionFactory is required, because there is multiple RedisConnectionFactory in Spring context");
        }

        // 检查 bean 映射中是否包含指定的 connectionFactoryName,如果不包含,抛出异常
        if (!beans.containsKey(connectionFactoryName)) {
            throw new CacheConfigException("there is no RedisConnectionFactory named "
                    + connectionFactoryName + " in Spring context");
        }

        // 使用指定的 connectionFactoryName 获取 RedisConnectionFactory 实例
        factory = beans.get(connectionFactoryName);
    }

    // 创建一个 ExternalCacheBuilder 实例,并使用 RedisSpringDataCacheBuilder 工具类创建构建器,设置 connectionFactory
    ExternalCacheBuilder builder = RedisSpringDataCacheBuilder.createBuilder().connectionFactory(factory);

    // 解析通用配置到构建器中
    parseGeneralConfig(builder, ct);

    // 返回构建器
    return builder;
}

ExternalCacheAutoInit

类ExternalCacheAutoInit作为Redis缓存自动化初始类的父类,实现逻辑还是比较简单的,源码如下:

/**
 * 外部缓存自动初始化抽象类
 */
public abstract class ExternalCacheAutoInit extends AbstractCacheAutoInit {
    public ExternalCacheAutoInit(String... cacheTypes) {
        super(cacheTypes);
    }

    /**
     * 解析通用配置
     *
     * @param builder 缓存构建器
     * @param ct      配置树
     */
    @Override
    protected void parseGeneralConfig(CacheBuilder builder, ConfigTree ct) {
        super.parseGeneralConfig(builder, ct);
        ExternalCacheBuilder ecb = (ExternalCacheBuilder) builder;
        ecb.setKeyPrefix(ct.getProperty("keyPrefix"));
        ecb.setBroadcastChannel(parseBroadcastChannel(ct));
        ecb.setValueEncoder(new ParserFunction(ct.getProperty("valueEncoder", CacheConsts.DEFAULT_SERIAL_POLICY)));
        ecb.setValueDecoder(new ParserFunction(ct.getProperty("valueDecoder", CacheConsts.DEFAULT_SERIAL_POLICY)));
    }

    /**
     * 解析广播通道
     *
     * @param ct 配置树
     * @return 解析后的广播通道,若为空则返回null
     */
    protected String parseBroadcastChannel(ConfigTree ct) {
        String broadcastChannel = ct.getProperty("broadcastChannel");
        if (broadcastChannel != null && !"".equals(broadcastChannel.trim())) {
            return broadcastChannel.trim();
        } else {
            return null;
        }
    }
}

你可能感兴趣的:(mybatis,spring,java)