前言
首先我們來看一個(gè)redis整合springboot最簡(jiǎn)單的例子,主要包括pom依賴疼蛾、配置文件和使用示例三個(gè)部分黍判。
pom依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置參數(shù)
spring:
redis:
password: redis
timeout: 2000ms
cluster:
max-redirects: 5 #獲取失敗時(shí)最大重定向次數(shù)
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 #連接池最大連接數(shù)(使用負(fù)值表示沒有限制)
max-idle: 10 #連接池中的最大空閑連接
max-wait: 1000ms #連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒有限制)
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
的定義
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starters</artifactId>
<version>2.1.3.RELEASE</version>
</parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.1.3.RELEASE</version>
******
省略部分內(nèi)容
******
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.1.3.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.1.5.RELEASE</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>jcl-over-slf4j</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>5.1.4.RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
這里比較重要的內(nèi)容其實(shí)都在spring-boot-starters
中了,來看一下其內(nèi)容
******
省略部分內(nèi)容
******
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>2.1.3.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.1.3.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<version>2.1.3.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.1.5.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.23</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
包加載機(jī)制
這里最核心的部分是引入了spring-boot-autoconfigure
包掀亩,那么這個(gè)包里的內(nèi)容又是如何被加載的呢,這里就要回到我之前的文章《Springboot初始化流程解析》留下的伏筆欢顷,文章中說到springboot初始化時(shí)會(huì)調(diào)用到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()));
}
}
由于這個(gè)調(diào)用鏈比較長(zhǎng)抬驴,我們簡(jiǎn)單梳理一下這個(gè)調(diào)用流程炼七,首先調(diào)用到PostProcessorRegistrationDelegate
類的invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors)
方法,其部分代碼片段如下
for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
BeanDefinitionRegistryPostProcessor registryProcessor =
(BeanDefinitionRegistryPostProcessor) postProcessor;
registryProcessor.postProcessBeanDefinitionRegistry(registry);
registryProcessors.add(registryProcessor);
}
else {
regularPostProcessors.add(postProcessor);
}
}
通過循環(huán)的方式調(diào)用了registryProcessor.postProcessBeanDefinitionRegistry(registry)
并將registryProcessor
對(duì)象依次加入registryProcessors
這個(gè)list中布持,這里的registryProcessors
的實(shí)現(xiàn)類為ConfigurationClassPostProcessor
豌拙,調(diào)用其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);
}
關(guān)聯(lián)調(diào)用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<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
parser.parse(candidates);
parser.validate();
Set<ConfigurationClass> 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<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
Set<String> 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());
******
}
這里循環(huán)調(diào)用ConfigurationClassParser
類的parse(Set<BeanDefinitionHolder> configCandidates)
方法
public void parse(Set<BeanDefinitionHolder> configCandidates) {
******
this.deferredImportSelectorHandler.process();
}
public void process() {
List<DeferredImportSelectorHolder> 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
對(duì)象是ConfigurationClassParser
的內(nèi)部類DeferredImportSelectorGroupingHandler
的實(shí)例,會(huì)調(diào)用其對(duì)應(yīng)的方法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
類的內(nèi)部類DeferredImportSelectorGrouping
的實(shí)例题暖,因此會(huì)調(diào)用其對(duì)應(yīng)的getImports()
方法
public Iterable<Group.Entry> getImports() {
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
this.group.process(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getImportSelector());
}
return this.group.selectImports();
}
當(dāng)這里的group
為AutoConfigurationImportSelector
類的內(nèi)部類AutoConfigurationGroup
的實(shí)例時(shí)按傅,process
方法的調(diào)用如下
@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);
}
}
然后調(diào)用到AutoConfigurationImportSelector
的getAutoConfigurationEntry
方法
protected AutoConfigurationEntry getAutoConfigurationEntry(
AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
接著調(diào)用了getCandidateConfigurations
方法
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> 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;
}
到這里我們看到了一個(gè)非常熟悉的方法SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader())
,在之前的文章《Springboot初始化流程解析》中有分析過胧卤,loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader)
方法會(huì)掃描所有jar包下的META-INF/spring.factories
文件唯绍,并將其中的類加載到內(nèi)存中,而這里的getSpringFactoriesLoaderFactoryClass()
方法返回的是EnableAutoConfiguration.class
灌侣,因此可以推斷,此處會(huì)加載spring.factories
文件中EnableAutoConfiguration
下對(duì)應(yīng)的類裂问。
配置類加載
經(jīng)過上述分析侧啼,我們直接找到spring-boot-autoconfigure
包下的META-INF/spring.factories
文件來看看
******
省略部分內(nèi)容
******
# 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官方已經(jīng)預(yù)先定義了許多的Configuration
,其中有3個(gè)是redis打頭的堪簿,我們以其中最重要的RedisAutoConfiguration
為例來進(jìn)行分析痊乾。
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
這里用到了條件注解@ConditionalOnClass
和@ConditionalOnMissingBean
,那就一起羅列一下幾個(gè)常用的條件注解:
@ConditionalOnBean:僅在當(dāng)前spring上下文中存在參數(shù)中的bean時(shí)椭更,才會(huì)實(shí)例化被注解的Bean
@ConditionalOnClass:參數(shù)中的class位于classpath上哪审,才會(huì)實(shí)例化被注解的Bean
@ConditionalOnExpression:當(dāng)表達(dá)式為true的時(shí)候,才會(huì)實(shí)例化被注解的Bean
@ConditionalOnMissingBean:僅在當(dāng)前spring上下文中不存在參數(shù)中的bean時(shí)虑瀑,才會(huì)實(shí)例化被注解的Bean
@ConditionalOnMissingClass:參數(shù)中的class不存在于classpath上湿滓,才會(huì)實(shí)例化被注解的Bean
@ConditionalOnNotWebApplication:不是web應(yīng)用時(shí)才會(huì)實(shí)例化被注解的Bean
@AutoConfigureAfter:在參數(shù)中的bean完成自動(dòng)配置后實(shí)例化被注解的bean
@AutoConfigureBefore:在參數(shù)中的bean完成自動(dòng)配置前實(shí)例化被注解的bean
除了上述條件注解外,還要提一下@EnableConfigurationProperties(RedisOperations.class)
舌狗,該用法通常說明參數(shù)bean(這里指RedisOperations
)上標(biāo)有@ConfigurationProperties
注解叽奥,而@EnableConfigurationProperties
能使該注解生效,從而將對(duì)應(yīng)的配置文件轉(zhuǎn)化為參數(shù)bean對(duì)象(這里指RedisOperations
)痛侍。
根據(jù)上述說明不難看出朝氓,類RedisAutoConfiguration
的加載依賴于RedisOperations
類,而這個(gè)類位于spring-data-redis
包中,由spring-boot-starter-data-redis
包依賴引入赵哲,所以待德,雖然RedisAutoConfiguration
類可能會(huì)由于spring-boot-autoconfigure
包被依賴而被提前引入,但只有依賴了spring-boot-starter-data-redis
包后它才會(huì)被真正初始化枫夺。
注:這里有一點(diǎn)要說明的是将宪,RedisOperations
類是redis的基本參數(shù)類,其中有一些屬性是帶有默認(rèn)值的筷屡,這些值就允許你不進(jìn)行設(shè)置涧偷,這也就是通常所說的springboot約定大于配置特點(diǎn)的一個(gè)體現(xiàn)。
總結(jié)
本文根據(jù)redis整合springboot的簡(jiǎn)單例子分析了Springboot-starter-xxx的一般工作原理毙死,說明了springboot啟動(dòng)過程中操作redis的客戶端是如何根據(jù)pom文件中的依賴被實(shí)例化出來的燎潮。