前言
首先我们来看一个redis整合springboot最简单的例子,主要包括pom依赖、配置文件和使用示例三个部分。
pom依赖
org.springframework.boot
spring-boot-starter-data-redis
配置参数
spring:
redis:
password: redis
timeout: 2000ms
cluster:
max-redirects: 5 #获取失败时最大重定向次数
nodes:
- 172.28.19.80:6380
- 172.28.19.80:6381
- 172.28.19.85:6380
- 172.28.19.85:6381
- 172.28.19.89:6380
- 172.28.19.89:6381
lettuce:
pool:
max-active: 50 #连接池最大连接数(使用负值表示没有限制)
max-idle: 10 #连接池中的最大空闲连接
max-wait: 1000ms #连接池最大阻塞等待时间(使用负值表示没有限制)
min-idle: 5 #连接池中的最小空闲连接
代码示例
@Slf4j
@RestController
public class TestController {
@Autowired
private StringRedisTemplate redisTemplate;
@GetMapping("/set")
public void set(){
redisTemplate.opsForValue().set("test.goods","电视");
}
@GetMapping("/get")
public String get(){
String result = redisTemplate.opsForValue().get("test.goods");
return result;
}
}
依赖分析
为了分析spring-boot-starter的工作原理,我们首先来看一下spring-boot-starter-data-redis
的定义
4.0.0
org.springframework.boot
spring-boot-starters
2.1.3.RELEASE
org.springframework.boot
spring-boot-starter-data-redis
2.1.3.RELEASE
******
省略部分内容
******
org.springframework.boot
spring-boot-starter
2.1.3.RELEASE
compile
org.springframework.data
spring-data-redis
2.1.5.RELEASE
compile
jcl-over-slf4j
org.slf4j
io.lettuce
lettuce-core
5.1.4.RELEASE
compile
这里比较重要的内容其实都在spring-boot-starters
中了,来看一下其内容
******
省略部分内容
******
org.springframework.boot
spring-boot
2.1.3.RELEASE
compile
org.springframework.boot
spring-boot-autoconfigure
2.1.3.RELEASE
compile
org.springframework.boot
spring-boot-starter-logging
2.1.3.RELEASE
compile
javax.annotation
javax.annotation-api
1.3.2
compile
org.springframework
spring-core
5.1.5.RELEASE
compile
org.yaml
snakeyaml
1.23
runtime
包加载机制
这里最核心的部分是引入了spring-boot-autoconfigure
包,那么这个包里的内容又是如何被加载的呢,这里就要回到我之前的文章《Springboot初始化流程解析》留下的伏笔,文章中说到springboot初始化时会调用到AbstractApplicationContext
类的refresh()
方法,其中有许多用于刷新的方法,而invokeBeanFactoryPostProcessors(beanFactory)
方法就是我们要找的加载入口
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
// Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
// (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}
}
由于这个调用链比较长,我们简单梳理一下这个调用流程,首先调用到PostProcessorRegistrationDelegate
类的invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List
方法,其部分代码片段如下
for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
BeanDefinitionRegistryPostProcessor registryProcessor =
(BeanDefinitionRegistryPostProcessor) postProcessor;
registryProcessor.postProcessBeanDefinitionRegistry(registry);
registryProcessors.add(registryProcessor);
}
else {
regularPostProcessors.add(postProcessor);
}
}
通过循环的方式调用了registryProcessor.postProcessBeanDefinitionRegistry(registry)
并将registryProcessor
对象依次加入registryProcessors
这个list中,这里的registryProcessors
的实现类为ConfigurationClassPostProcessor
,调用其postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
方法
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
int registryId = System.identityHashCode(registry);
if (this.registriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
}
if (this.factoriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + registry);
}
this.registriesPostProcessed.add(registryId);
processConfigBeanDefinitions(registry);
}
关联调用processConfigBeanDefinitions(BeanDefinitionRegistry registry)
方法
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
******
// Parse each @Configuration class
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set candidates = new LinkedHashSet<>(configCandidates);
Set alreadyParsed = new HashSet<>(configCandidates.size());
do {
parser.parse(candidates);
parser.validate();
Set configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
// Read the model and create bean definitions based on its content
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
candidates.clear();
if (registry.getBeanDefinitionCount() > candidateNames.length) {
String[] newCandidateNames = registry.getBeanDefinitionNames();
Set oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
Set alreadyParsedClasses = new HashSet<>();
for (ConfigurationClass configurationClass : alreadyParsed) {
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
for (String candidateName : newCandidateNames) {
if (!oldCandidateNames.contains(candidateName)) {
BeanDefinition bd = registry.getBeanDefinition(candidateName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
!alreadyParsedClasses.contains(bd.getBeanClassName())) {
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
}
while (!candidates.isEmpty());
******
}
这里循环调用ConfigurationClassParser
类的parse(Set
方法
public void parse(Set configCandidates) {
******
this.deferredImportSelectorHandler.process();
}
public void process() {
List deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
try {
if (deferredImports != null) {
DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
deferredImports.forEach(handler::register);
handler.processGroupImports();
}
}
finally {
this.deferredImportSelectors = new ArrayList<>();
}
}
这里的handler
对象是ConfigurationClassParser
的内部类DeferredImportSelectorGroupingHandler
的实例,会调用其对应的方法processGroupImports()
public void processGroupImports() {
for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
grouping.getImports().forEach(entry -> {
ConfigurationClass configurationClass = this.configurationClasses.get(
entry.getMetadata());
try {
processImports(configurationClass, asSourceClass(configurationClass),
asSourceClasses(entry.getImportClassName()), false);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configurationClass.getMetadata().getClassName() + "]", ex);
}
});
}
}
改方法中的grouping
为ConfigurationClassParser
类的内部类DeferredImportSelectorGrouping
的实例,因此会调用其对应的getImports()
方法
public Iterable getImports() {
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
this.group.process(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getImportSelector());
}
return this.group.selectImports();
}
当这里的group
为AutoConfigurationImportSelector
类的内部类AutoConfigurationGroup
的实例时,process
方法的调用如下
@Override
public void process(AnnotationMetadata annotationMetadata,
DeferredImportSelector deferredImportSelector) {
Assert.state(
deferredImportSelector instanceof AutoConfigurationImportSelector,
() -> String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(getAutoConfigurationMetadata(),
annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
然后调用到AutoConfigurationImportSelector
的getAutoConfigurationEntry
方法
protected AutoConfigurationEntry getAutoConfigurationEntry(
AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List configurations = getCandidateConfigurations(annotationMetadata,
attributes);
configurations = removeDuplicates(configurations);
Set exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
接着调用了getCandidateConfigurations
方法
protected List getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
到这里我们看到了一个非常熟悉的方法SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader())
,在之前的文章《Springboot初始化流程解析》中有分析过,loadFactoryNames(Class> factoryClass, @Nullable ClassLoader classLoader)
方法会扫描所有jar包下的META-INF/spring.factories
文件,并将其中的类加载到内存中,而这里的getSpringFactoriesLoaderFactoryClass()
方法返回的是EnableAutoConfiguration.class
,因此可以推断,此处会加载spring.factories
文件中EnableAutoConfiguration
下对应的类。
配置类加载
经过上述分析,我们直接找到spring-boot-autoconfigure
包下的META-INF/spring.factories
文件来看看
******
省略部分内容
******
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
******
可以看到在EnableAutoConfiguration
下springboot官方已经预先定义了许多的Configuration
,其中有3个是redis打头的,我们以其中最重要的RedisAutoConfiguration
为例来进行分析。
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate
这里用到了条件注解@ConditionalOnClass
和@ConditionalOnMissingBean
,那就一起罗列一下几个常用的条件注解:
@ConditionalOnBean:仅在当前spring上下文中存在参数中的bean时,才会实例化被注解的Bean
@ConditionalOnClass:参数中的class位于classpath上,才会实例化被注解的Bean
@ConditionalOnExpression:当表达式为true的时候,才会实例化被注解的Bean
@ConditionalOnMissingBean:仅在当前spring上下文中不存在参数中的bean时,才会实例化被注解的Bean
@ConditionalOnMissingClass:参数中的class不存在于classpath上,才会实例化被注解的Bean
@ConditionalOnNotWebApplication:不是web应用时才会实例化被注解的Bean
@AutoConfigureAfter:在参数中的bean完成自动配置后实例化被注解的bean
@AutoConfigureBefore:在参数中的bean完成自动配置前实例化被注解的bean
除了上述条件注解外,还要提一下@EnableConfigurationProperties(RedisOperations.class)
,该用法通常说明参数bean(这里指RedisOperations
)上标有@ConfigurationProperties
注解,而@EnableConfigurationProperties
能使该注解生效,从而将对应的配置文件转化为参数bean对象(这里指RedisOperations
)。
根据上述说明不难看出,类RedisAutoConfiguration
的加载依赖于RedisOperations
类,而这个类位于spring-data-redis
包中,由spring-boot-starter-data-redis
包依赖引入,所以,虽然RedisAutoConfiguration
类可能会由于spring-boot-autoconfigure
包被依赖而被提前引入,但只有依赖了spring-boot-starter-data-redis
包后它才会被真正初始化。
注:这里有一点要说明的是,RedisOperations
类是redis的基本参数类,其中有一些属性是带有默认值的,这些值就允许你不进行设置,这也就是通常所说的springboot约定大于配置特点的一个体现。
总结
本文根据redis整合springboot的简单例子分析了Springboot-starter-xxx的一般工作原理,说明了springboot启动过程中操作redis的客户端是如何根据pom文件中的依赖被实例化出来的。