美文网首页
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