SpringCache源码解析(一)

一、springCache如何实现自动装配

SpringBoot 确实是通过 spring.factories 文件实现自动配置的。Spring Cache 也是遵循这一机制来实现自动装配的。

具体来说,Spring Cache 的自动装配是通过 org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration 这个类来实现的。这个类位于 spring-boot-autoconfigure 包下。

在 spring-boot-autoconfigure 包的 META-INF/spring.factories 文件中,可以找到 CacheAutoConfiguration 类的配置:

bash 复制代码
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration

二、CacheAutoConfiguration

java 复制代码
@Configuration
@ConditionalOnClass(CacheManager.class)
@ConditionalOnBean(CacheAspectSupport.class)
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
@EnableConfigurationProperties(CacheProperties.class)
@AutoConfigureAfter({ CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class,
		HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class })
@Import(CacheConfigurationImportSelector.class)
public class CacheAutoConfiguration {

 /**
     * 创建CacheManagerCustomizers Bean
     * XxxCacheConfiguration中创建CacheManager时会用来装饰CacheManager
     */
	@Bean
	@ConditionalOnMissingBean
	public CacheManagerCustomizers cacheManagerCustomizers(ObjectProvider<CacheManagerCustomizer<?>> customizers) {
		return new CacheManagerCustomizers(customizers.orderedStream().collect(Collectors.toList()));
	}

/**
     * 创建CacheManagerValidator Bean
     * 实现了InitializingBean,在afterPropertiesSet方法中校验CacheManager
     */
	@Bean
	public CacheManagerValidator cacheAutoConfigurationValidator(CacheProperties cacheProperties,
			ObjectProvider<CacheManager> cacheManager) {
		return new CacheManagerValidator(cacheProperties, cacheManager);
	}

 /**
     * 后置处理器
     * 内部类,动态声明EntityManagerFactory实例需要依赖"cacheManager"实例
     */
	@Configuration
	@ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class)
	@ConditionalOnBean(AbstractEntityManagerFactoryBean.class)
	protected static class CacheManagerJpaDependencyConfiguration extends EntityManagerFactoryDependsOnPostProcessor {

		public CacheManagerJpaDependencyConfiguration() {
			super("cacheManager");
		}

	}

	/**
     * 缓存管理器校验器
     * 内部类,实现了InitializingBean接口,实现afterPropertiesSet用于校验缓存管理器
     */
	static class CacheManagerValidator implements InitializingBean {

		private final CacheProperties cacheProperties;

		private final ObjectProvider<CacheManager> cacheManager;

		CacheManagerValidator(CacheProperties cacheProperties, ObjectProvider<CacheManager> cacheManager) {
			this.cacheProperties = cacheProperties;
			this.cacheManager = cacheManager;
		}

 /**
         * 当依赖注入后处理
         */
		@Override
		public void afterPropertiesSet() {
			Assert.notNull(this.cacheManager.getIfAvailable(),
					() -> "No cache manager could " + "be auto-configured, check your configuration (caching "
							+ "type is '" + this.cacheProperties.getType() + "')");
		}

	}

	/**
     * 缓存配置导入选择器
     */
	static class CacheConfigurationImportSelector implements ImportSelector {

		@Override
		public String[] selectImports(AnnotationMetadata importingClassMetadata) {
			CacheType[] types = CacheType.values();
			String[] imports = new String[types.length];
			for (int i = 0; i < types.length; i++) {
				imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
			}
			return imports;
		}

	}

}

借助Conditional机制实现自动配置条件:

  1. CacheManager.class在类路径上存在;
  2. CacheAspectSupport实例存在(即CacheInterceptor);
  3. CacheManager实例不存在,且cacheResolver Bean不存在;
    当满足以上要求时,就会触发SpringCache的自动配置逻辑,CacheAutoConfiguration会引入其它的Bean,具体如下:
  4. 通过@EnableConfigurationProperties使CacheProperties生效;
  5. 借助Import机制导入内部类:CacheConfigurationImportSelector和CacheManagerEntityManagerFactoryDependsOnPostProcessor;
  6. 创建了CacheManagerCustomizers、CacheManagerValidator Bean;

2.1 关于的一些疑问

关于@ConditionalOnClass,我有一个疑问:

  • value值是类名,如果类找不到,会不会编译报错;
    ------会! 那value有什么意义?只要能编译过的,肯定都存在。
    ------@ConditionalOnClass注解之前一直让我感到困惑,类存不存在,编译器就会体现出来,还要这个注解干嘛?后来想了很久,感觉java语法并不是一定要寄生在idea上,所以语法上限制和工具上限制,这是两码事,理论上就应该彼此都要去做。

@ConditionalOnClass还可以用name属性:

bash 复制代码
@ConditionalOnClass(name="com.xxx.Component2")

可以看下这篇文章:
SpringBoot中的@ConditionOnClass注解

2.2 CacheProperties

java 复制代码
// 接收以"spring.cache"为前缀的配置参数
@ConfigurationProperties(prefix = "spring.cache")
public class CacheProperties {

    // 缓存实现类型
    // 未指定,则由环境自动检测,从CacheConfigurations.MAPPINGS自上而下加载XxxCacheConfiguration
    // 加载成功则检测到缓存实现类型
    private CacheType type;

    // 缓存名称集合
    // 通常,该参数配置了则不再动态创建缓存
    private List<String> cacheNames = new ArrayList<>();

    private final Caffeine caffeine = new Caffeine();

    private final Couchbase couchbase = new Couchbase();

    private final EhCache ehcache = new EhCache();

    private final Infinispan infinispan = new Infinispan();

    private final JCache jcache = new JCache();

    private final Redis redis = new Redis();


    // getter and setter
    ...

    /**
     * Caffeine的缓存配置参数
     */
    public static class Caffeine {
        ...
    }

    /**
     * Couchbase的缓存配置参数
     */
    public static class Couchbase {
        ...
    }

    /**
     * EhCache的缓存配置参数
     */
    public static class EhCache {
        ...
    }

    /**
     * Infinispan的缓存配置参数
     */
    public static class Infinispan {
        ...
    }

    /**
     * JCache的缓存配置参数
     */
    public static class JCache {
        ...
    }

    /**
     * Redis的缓存配置参数
     */
    public static class Redis {

        // 过期时间,默认永不过期
        private Duration timeToLive;

        // 支持缓存空值标识,默认支持
        private boolean cacheNullValues = true;

        // 缓存KEY前缀
        private String keyPrefix;

        // 使用缓存KEY前缀标识,默认使用
        private boolean useKeyPrefix = true;

        // 启用缓存统计标识
        private boolean enableStatistics;

        // getter and setter
        ...
    }
}

2.3 CacheConfigurationImportSelector

CacheAutoConfiguration的静态内部类,实现了ImportSelector接口的selectImports方法,导入Cache配置类;

java 复制代码
/**
 * 挑选出CacheType对应的配置类
 */
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
    CacheType[] types = CacheType.values();
    String[] imports = new String[types.length];
    // 遍历CacheType
    for (int i = 0; i < types.length; i++) {
        // 获取CacheType的配置类
        imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
    }
    return imports;
}

CacheConfigurationImportSelector,把所有缓存的配置类拿出来,加入到spring中被加载。

有的开发者可能就会问:

为什么要重载所有的配置类?而不是配置了哪种缓存类型就加载哪种缓存类型?

------这里只是加载所有的配置类,比如Redis,只是加载RedisCacheConfiguration.class配置类,后续会根据条件判断具体加载到哪个配置,如下图:

2.4 CacheConfigurations

维护了CacheType和XxxCacheConfiguration配置类的映射关系;

java 复制代码
/**
 * CacheType和配置类的映射关系集合
 * 无任何配置条件下,从上而下,默认生效为SimpleCacheConfiguration
 */
static {
    Map<CacheType, String> mappings = new EnumMap<>(CacheType.class);
    mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class.getName());
    mappings.put(CacheType.EHCACHE, EhCacheCacheConfiguration.class.getName());
    mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class.getName());
    mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class.getName());
    mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class.getName());
    mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class.getName());
    mappings.put(CacheType.REDIS, RedisCacheConfiguration.class.getName());
    mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class.getName());
    mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class.getName());
    mappings.put(CacheType.NONE, NoOpCacheConfiguration.class.getName());
    MAPPINGS = Collections.unmodifiableMap(mappings);
}

/**
 * 根据CacheType获取配置类
 */
static String getConfigurationClass(CacheType cacheType) {
    String configurationClassName = MAPPINGS.get(cacheType);
    Assert.state(configurationClassName != null, () -> "Unknown cache type " + cacheType);
    return configurationClassName;
}

/**
 * 根据配置类获取CacheType
 */
static CacheType getType(String configurationClassName) {
    for (Map.Entry<CacheType, String> entry : MAPPINGS.entrySet()) {
        if (entry.getValue().equals(configurationClassName)) {
            return entry.getKey();
        }
    }
    throw new IllegalStateException("Unknown configuration class " + configurationClassName);
}

2.5 CacheType

缓存类型的枚举,按照优先级定义;

java 复制代码
GENERIC     // Generic caching using 'Cache' beans from the context.
JCACHE      // JCache (JSR-107) backed caching.
EHCACHE     // EhCache backed caching.
HAZELCAST   // Hazelcast backed caching.
INFINISPAN  // Infinispan backed caching.
COUCHBASE   // Couchbase backed caching.
REDIS       // Redis backed caching.
CAFFEINE    // Caffeine backed caching.
SIMPLE      // Simple in-memory caching.
NONE        // No caching.

2.4 CacheCondition

它是所有缓存配置使用的通用配置条件;

2.4.1 准备工作

介绍之前先看一些CacheCondition类使用的地方,可以看到是各种缓存类型类型的配置类使用了它。

@Conditional注解的作用是什么?

------请看这篇文章@Conditional 注解有什么用?

说穿了,就是根据注解参数Condition实现类(这里是CacheCondition类)的matches方法返回,来判断是否加载这个配置类。

2.4.2 CacheCondition核心代码

java 复制代码
/**
 * 匹配逻辑
 */
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
    String sourceClass = "";
    if (metadata instanceof ClassMetadata) {
        sourceClass = ((ClassMetadata) metadata).getClassName();
    }
    ConditionMessage.Builder message = ConditionMessage.forCondition("Cache", sourceClass);
    Environment environment = context.getEnvironment();
    try {
        // 获取配置文件中指定的CacheType
        BindResult<CacheType> specified = Binder.get(environment).bind("spring.cache.type", CacheType.class);
        if (!specified.isBound()) {
            // 不存在则根据CacheConfiguration.mappings自上而下匹配合适的CacheConfiguration
            return ConditionOutcome.match(message.because("automatic cache type"));
        }
        CacheType required = CacheConfigurations.getType(((AnnotationMetadata) metadata).getClassName());
        // 存在则比较CacheType是否一致
        if (specified.get() == required) {
            return ConditionOutcome.match(message.because(specified.get() + " cache type"));
        }
    }
    catch (BindException ex) {
    }
    return ConditionOutcome.noMatch(message.because("unknown cache type"));
}

三、常见的CacheConfiguration

3.1 RedisCacheConfiguration

它是Redis缓存配置;

java 复制代码
// Bean方法不被代理
@Configuration(proxyBeanMethods = false)
// RedisConnectionFactory在类路径上存在
@ConditionalOnClass(RedisConnectionFactory.class)
// 在RedisAutoConfiguration之后自动配置
@AutoConfigureAfter(RedisAutoConfiguration.class)
// RedisConnectionFactory实例存在
@ConditionalOnBean(RedisConnectionFactory.class)
// CacheManager实例不存在
@ConditionalOnMissingBean(CacheManager.class)
// 根据CacheCondition选择导入
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {

    /**
     * 创建RedisCacheManager
     */
    @Bean
    RedisCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers,
            ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
            ObjectProvider<RedisCacheManagerBuilderCustomizer> redisCacheManagerBuilderCustomizers,
            RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
        // 使用RedisCachemanager
        RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(
                determineConfiguration(cacheProperties, redisCacheConfiguration, resourceLoader.getClassLoader()));
        List<String> cacheNames = cacheProperties.getCacheNames();
        if (!cacheNames.isEmpty()) {
            // 设置CacheProperties配置的值
            builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
        }
        if (cacheProperties.getRedis().isEnableStatistics()) {
            // 设置CacheProperties配置的值
            builder.enableStatistics();
        }
        // 装饰redisCacheManagerBuilder
        redisCacheManagerBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
        // 装饰redisCacheManager
        return cacheManagerCustomizers.customize(builder.build());
    }

    /**
     * 如果redisCacheConfiguration不存在则使用默认值
     */
    private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration(
            CacheProperties cacheProperties,
            ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
            ClassLoader classLoader) {
        return redisCacheConfiguration.getIfAvailable(() -> createConfiguration(cacheProperties, classLoader));
    }

    /**
     * 根据CacheProperties创建默认RedisCacheConfigutation
     */
    private org.springframework.data.redis.cache.RedisCacheConfiguration createConfiguration(
            CacheProperties cacheProperties, ClassLoader classLoader) {
        Redis redisProperties = cacheProperties.getRedis();
        org.springframework.data.redis.cache.RedisCacheConfiguration config =     org.springframework.data.redis.cache.RedisCacheConfiguration
                .defaultCacheConfig();
        config = config.serializeValuesWith(
                SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));

        // 优先使用CacheProperties的值,若存在的话
        if (redisProperties.getTimeToLive() != null) {
            config = config.entryTtl(redisProperties.getTimeToLive());
        }
        if (redisProperties.getKeyPrefix() != null) {
            config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());
        }
        if (!redisProperties.isCacheNullValues()) {
            config = config.disableCachingNullValues();
        }
        if (!redisProperties.isUseKeyPrefix()) {
            config = config.disableKeyPrefix();
        }
        return config;
    }
}

3.2 SimpleCacheConfiguration

java 复制代码
// Bean方法不被代理
@Configuration(proxyBeanMethods = false)
// 不存在CacheManager实例
@ConditionalOnMissingBean(CacheManager.class)
// 根据CacheCondition选择导入
@Conditional(CacheCondition.class)
class SimpleCacheConfiguration {

    /**
     * 创建CacheManager
     */
    @Bean
    ConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties,
            CacheManagerCustomizers cacheManagerCustomizers) {
        // 使用ConcurrentMapCacheManager
        ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
        List<String> cacheNames = cacheProperties.getCacheNames();
        // 如果CacheProperties配置了cacheNames,则使用
        if (!cacheNames.isEmpty()) {
            // 配置了cacheNames,则不支持动态创建缓存了
            cacheManager.setCacheNames(cacheNames);
        }
        // 装饰ConcurrentMapCacheManager
        return cacheManagerCustomizers.customize(cacheManager);
    }
}
相关推荐
一嘴一个橘子27 分钟前
mybatis - 动态语句、批量注册mapper、分页插件
java
组合缺一28 分钟前
Json Dom 怎么玩转?
java·json·dom·snack4
危险、44 分钟前
一套提升 Spring Boot 项目的高并发、高可用能力的 Cursor 专用提示词
java·spring boot·提示词
kaico20181 小时前
JDK11新特性
java
钊兵1 小时前
java实现GeoJSON地理信息对经纬度点的匹配
java·开发语言
jiayong231 小时前
Tomcat性能优化面试题
java·性能优化·tomcat
爬山算法1 小时前
Hibernate(51)Hibernate的查询缓存如何使用?
spring·缓存·hibernate
秋刀鱼程序编程1 小时前
Java基础入门(五)----面向对象(上)
java·开发语言
sunnyday04261 小时前
基于Netty构建WebSocket服务器实战指南
服务器·spring boot·websocket·网络协议
纪莫1 小时前
技术面:MySQL篇(InnoDB的锁机制)
java·数据库·java面试⑧股