美文网首页
SpringBoot 中各种CacheConfiguration

SpringBoot 中各种CacheConfiguration

作者: 大猪小猪在菜盘 | 来源:发表于2019-01-05 22:42 被阅读0次

    本文使用的spring-boot版本是2.0.2.RELEASE,本文力求白话,不会引入大段晦涩的框架代码造成阅读的极度不适,但是本文会引入一定量阅读舒适的代码,而且,读者需要有一定的框架基础。

    1. 故事起源

    最近在研究Spring中的缓存机制。就拿我自己的项目来讲,最早用了EhCache,后来全部迁移至Redis。我们知道在SpringBoot中开启Redis作为缓存是比较方便的,直接引入maven依赖即可:

    pom.xml

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.9.0</version>
    </dependency>
    

    就这么简单,不需要任何多余的代码行,SpringBoot就帮我们做完了一切的集成配置。我们来一段测试代码:

    这里注意需要打开@EnableCaching功能,否则@Cacheable注解的方法不会被cglib代理。

    CacheApplication.java

    @EnableCaching
    @SpringBootApplication
    public class CacheApplication {
    
        @Autowired 
        private CacheService cacheService;
    
        public static void main(String[] args) {
            ApplicationContext ctx = SpringApplication.run(CacheApplication.class, args);
            CacheApplication app = ctx.getBean(CacheApplication.class);
            System.out.println(app.cacheService.getStr(UUID.randomUUID().toString()));
        }
    }
    

    CacheService.java

    @Service
    public class CacheService {
        @Cacheable(cacheNames="XA", sync=true)
        public String getStr(String key) {
            return "value";
        }
    }
    

    跑一下,然后在redis中执行 keys *,结果为:

    127.0.0.1:6379> keys *
    1) "XA::f17ecb4d-b0e3-4bdc-9973-7592e9c7bacc"
    

    OK,引入缓存成功!

    2. AutoConfiguration初探

    本来故事讲完了该结束了,不过作为一名有理想,有道德,有文化的攻城狮,怎么能不去了解博大精深的SpringBoot是如何帮我们引入这些自动配置的呢?

    作为一个SpringBoot的资深新手,我们知道SpringBoot的傻瓜式懒人配置都存在于spring-boot-autoconfigure包中,那么我们就去这个包里寻找下线索。

    autoconfig.png

    不看不知道,一看居然有这么多种类型的缓存配置,Spring果然是包罗万象,主流的非主流的什么都有。
    粗略的看了一下这些配置文件,大部分都有引入@Conditional的条件,比如EhCacheCacheConfiguration就需要有EhCache包提供的net.sf.ehcache.Cache定义才可以生效,而我们在上面的代码中没有引入EhCache包,自然不会生效。而RedisCacheConfiguration则需要一系列spring-boot-data-redis包提供的组件才可以生效,我们引入了,所以自然就会去使用Redis作为缓存

    EhCacheCacheConfiguration.java

    @Configuration
    @ConditionalOnClass({ Cache.class, EhCacheCacheManager.class })
    @ConditionalOnMissingBean(org.springframework.cache.CacheManager.class)
    @Conditional({ CacheCondition.class,EhCacheCacheConfiguration.ConfigAvailableCondition.class })
    

    RedisCacheConfiguration.java

    @Configuration
    @AutoConfigureAfter(RedisAutoConfiguration.class)
    @ConditionalOnBean(RedisConnectionFactory.class)
    @ConditionalOnMissingBean(CacheManager.class)
    @Conditional(CacheCondition.class)
    

    正是通过这些条件的限制,SpringBoot才会根据引入的pom包选择合适的缓存配置类。

    3. 特殊的配置类

    故事到这里结束了?没有,仔细看一看这些配置类,发现有几个特殊的小哥

    NoOpCacheConfiguration
    SimpleCacheConfiguration
    

    你问我哪里特殊了?确实很特殊,因为他们两个的生效条件都是满足的,也就是说,这两个都能作为缓存配置引入,但是SpringBoot却非常“智能”的用了Redis作为缓存。

    NoOpCacheConfiguration

    @Configuration
    @ConditionalOnMissingBean(CacheManager.class)
    @Conditional(CacheCondition.class)
    

    SimpleCacheConfiguration

    @Configuration
    @ConditionalOnMissingBean(CacheManager.class)
    @Conditional(CacheCondition.class)
    

    什么时候SpringBoot有AI功能了?囧,这不可能!

    必须不可能啊,所以SpringBoot肯定做了一些事情,那么我们就再仔细探究下为什么只有RedisCacheConfigurationspring-boot选中了,其他两个却落榜了。

    4. 再次探究选择机制

    我们知道,SpringBoot在初始化的时候,会去spring-boot-autoconfigure包中寻找配置类。因为SpringBoot的设计理念就是即插即用,傻瓜式,让开发者告别繁琐的配置,所以这个包中内容是很丰富的,基本上主流的中间件都会在这个包中留下自己的配置定义。但是spring在初始化的时候并不是将所有的配置类都会加载进来,这时候那些没在spring-boot-autoconfigure包中的spring.factories中定义的,那些不满足@Conditional的的配置,就会被SpringBoot过滤掉。所以,如我们上面的测试代码,其实只引入了Redis部分的配置。
    但是,这两个特殊的缓存配置类却是满足导入要求的,因此,我们在查看configClasses集合包含的类定义的时候,确实是有这两个配置类的定义的:

    ConfigurationClassPostProcessor.processConfigBeanDefinitions 方法中的 configClasses

    configuration.png

    到这里暂时还没思绪,问题还是SpringBoot为何选择的是Redis缓存配置,而不是选择其他缓存配置。带着这个问题再次阅读一遍三个可选的缓存配置的导入条件。我们发现
    RedisCacheConfiguration, NoOpCacheConfiguration, SimpleCacheConfiguration的条件都有一个是@ConditionalOnMissingBean(CacheManager.class)

    并且我们看到三个配置类中都有对这个CacheManager的Bean输出。那么,这个问题的思路可能就变成这样了,肯定是三个配置类中的其中之一个抢跑了,输出了CacheManager的实例Bean定义,另外两个就导致判断@ConditionalOnMissingBean(CacheManager.class)条件失败而被废弃了。

    5. 真相大白

    到这里有了基本的思路,一定是SpringBoot根据某种条件对这三个配置类的加载顺序做了定义,现在我们需要做的是看这三个配置类的加载先后顺序SpringBoot究竟是怎么定义的。

    在初始化的时候,SpringBoot首先会去寻找用户项目代码中是否包含@EnableAutoConfiguration注解,其实这个注解默认情况下已经被@SpringBootApplication所包含,找到之后会处理@EnableAutoConfiguration注解所导入的AutoConfigurationImportSelector类,这个类会根据spring-boot-autoconfigure包中预定义的spring.factories文件中去搜寻包中符合条件的配置类导入。这其中,包含了spring.factories列举的配置之一org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration

    我们看到这个CacheAutoConfiguration配置类中也有一个@Import定义,我们打开它的定义查看一下,他包含一个相当长的注解列表:

    @Configuration
    @ConditionalOnClass(CacheManager.class)
    @ConditionalOnBean(CacheAspectSupport.class)
    @ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
    @EnableConfigurationProperties(CacheProperties.class)
    @AutoConfigureBefore(HibernateJpaAutoConfiguration.class)
    @AutoConfigureAfter({ CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class,
            RedisAutoConfiguration.class })
    @Import(CacheConfigurationImportSelector.class)
    

    查看这个CacheConfigurationImportSelector类,发现如下代码

    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;
        }
    }
    

    这个CacheType就立刻映入眼帘了,查看一下发现是一个枚举,到这里就明白了,在这个枚举里,定义了配置类的加载顺序,我们看到了,REDIS是排在SIMPLENONE之前,这样子,优先被载入的肯定是Redis的配置类也就是RedisCacheConfiguration。那么载入RedisCacheConfiguration之后呢?RedisCacheConfiguration有一个CacheManager的Bean导出定义,那么,在RedisCacheConfiguration加载之后,另外两个缓存配置类的加载条件就不满足@ConditionalOnMissingBean(CacheManager.class)条件,自然被Spring所抛弃了。

    6. 总结

    到这里我们分析了SpringBoot对缓存加载选择的内部处理流程,SpringBoot由于支持多元化的缓存方案,因此在配置的时候,需要对这一块有所了解,防止掉坑。

    相关文章

      网友评论

          本文标题:SpringBoot 中各种CacheConfiguration

          本文链接:https://www.haomeiwen.com/subject/tmtdrqtx.html