springboot自动配置原理

找到入口

在每个springboot应用中,都有一个这个注解**@SpringBootApplication**

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
  ...
}

其中**@EnableAutoConfiguration**就是这一切的开始…

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
  ...
}

看到@EnableAutoConfiguration注解又标有@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)两个注解,顾名思义,@AutoConfigurationPackage注解肯定跟自动配置的包有关,而AutoConfigurationImportSelector则是跟SpringBoot的自动配置选择导入有关(Spring中的ImportSelector是用来导入配置类的,通常是基于某些条件注解@ConditionalOnXxxx来决定是否导入某个配置类)

所以自动配置的入口也就是AutoConfigurationImportSelector

找到源头

找到了自动配置的入口,同时也要知道怎么才能调用到里面的核心方法

		at org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.selectImports(AutoConfigurationImportSelector.java:93)
	  at org.springframework.boot.autoconfigure.AutoConfigurationImportSelector$AutoConfigurationGroup.process(AutoConfigurationImportSelector.java:386)
	  at org.springframework.context.annotation.ConfigurationClassParser$DeferredImportSelectorGrouping.getImports(ConfigurationClassParser.java:828)
	  at org.springframework.context.annotation.ConfigurationClassParser.processDeferredImportSelectors(ConfigurationClassParser.java:563)
	  at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:188)
	  at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:316)
	  at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:233)
	  at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:271)
	  at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:91)
	  at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:692)
	  at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:530)

通过堆栈信息,可以看出是在刷新spring容器,并且调用bean工厂的后置处理器(invokeBeanFactoryPostProcessors)的时候执行了selectImports方法

通过debug发现执行selectImports的类是ConfigurationClassPostProcessor,改后置处理器会把工作都交给ConfigurationClassParser去做

private void processDeferredImportSelectors() {                                                   
	List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;            
	this.deferredImportSelectors = null;                                                          
	if (deferredImports == null) {                                                                
		return;                                                                                   
	}                                                                                             
                                                                                                  
	deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);                                             
	Map<Object, DeferredImportSelectorGrouping> groupings = new LinkedHashMap<>();                
	Map<AnnotationMetadata, ConfigurationClass> configurationClasses = new HashMap<>();           
	for (DeferredImportSelectorHolder deferredImport : deferredImports) {                         
		Class<? extends Group> group = deferredImport.getImportSelector().getImportGroup();       
		DeferredImportSelectorGrouping grouping = groupings.computeIfAbsent(                      
				(group != null ? group : deferredImport),                                         
				key -> new DeferredImportSelectorGrouping(createGroup(group)));                   
		grouping.add(deferredImport);                                                             
		configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),            
				deferredImport.getConfigurationClass());                                          
	} 
	for (DeferredImportSelectorGrouping grouping : groupings.values()) {1》
		grouping.getImports().forEach(entry -> {2》
			ConfigurationClass configurationClass = 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);               
			}                                                                                     
		});                                                                                       
	}                                                                                             
}                                                                                                 
  1. grouping.getImports()拿到了所有待import的class数据,这里面就会调用selectImport,去拿配置文件里面的类名和注解信息
  2. 执行import操作,会递归的判断,导入的class有没有import其他class,如果有就继续调用selectImport,没有就放入缓存configurationClasses

实现

找到待import的类

现在开始从源头往下,一直找到所有的实现原理

public Iterable<Group.Entry> getImports() {                                     
	for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {1this.group.process(deferredImport.getConfigurationClass().getMetadata(),
				deferredImport.getImportSelector());                            
	}                                                                           
	return this.group.selectImports();                                          
}                                                                               

代码的关键在《1》的位置,直接点进去看

// DefaultDeferredImportSelectorGroup
@Override                                                                          
public void process(AnnotationMetadata metadata, DeferredImportSelector selector) {
	for (String importClassName : selector.selectImports(metadata)) {              
		this.imports.add(new Entry(metadata, importClassName));                    
	}                                                                              
}                                                                                  

在内部类DefaultDeferredImportSelectorGroup中,selector.selectImports(metadata)会拿到所有待import的class,然后遍历这些class,组装成Entry,放到列表里面,这个entry的结构是class名字+该class的注解信息

最后返回上面的列表

接下来重点关注**selector.selectImports(metadata)**里面的代码

@Override                                                                                
public String[] selectImports(AnnotationMetadata annotationMetadata) {                   
	if (!isEnabled(annotationMetadata)) {                                                
		return NO_IMPORTS;                                                               
	}
  // 《1》拿到配置,某个配置类的条件
	AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
			.loadMetadata(this.beanClassLoader);                                         
	AnnotationAttributes attributes = getAttributes(annotationMetadata); 
	// 《2》拿到所有的xxxAutoConfiguration                
	List<String> configurations = getCandidateConfigurations(annotationMetadata,         
			attributes);                                                                 
	configurations = removeDuplicates(configurations);                                   
	Set<String> exclusions = getExclusions(annotationMetadata, attributes);              
	checkExcludedClasses(configurations, exclusions);                                    
	configurations.removeAll(exclusions);     
  //《2》过滤,conditional起作用的地方
	configurations = filter(configurations, autoConfigurationMetadata);                  
	fireAutoConfigurationImportEvents(configurations, exclusions);                       
	return StringUtils.toStringArray(configurations);                                    
}                                                                                        
  1. 读取配置文件,拿到类似于org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.ConditionalOnClass=javax.sql.DataSource,org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType的配置文件信息,信息包括某个配置类的注解,以及注解的条件
  2. 拿到所有的xxxAutoConfiguration
  3. 过滤配置文件中多余的配置类
private List<String> filter(List<String> configurations,                               
		AutoConfigurationMetadata autoConfigurationMetadata) {                         
	long startTime = System.nanoTime();                                                
	String[] candidates = StringUtils.toStringArray(configurations);                   
	boolean[] skip = new boolean[candidates.length];                                   
	boolean skipped = false;                                                           
	for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) { 
		invokeAwareMethods(filter);
    // 《1》
		boolean[] match = filter.match(candidates, autoConfigurationMetadata);         
		for (int i = 0; i < match.length; i++) {                                       
			if (!match[i]) {                                                           
				skip[i] = true;                                                        
				skipped = true;                                                        
			}                                                                          
		}                                                                              
	}                                                                                  
	if (!skipped) {                                                                    
		return configurations;                                                         
	}                                                                                  
	List<String> result = new ArrayList<>(candidates.length);                          
	for (int i = 0; i < candidates.length; i++) {                                      
		if (!skip[i]) {                                                                
			result.add(candidates[i]);                                                 
		}                                                                              
	}                                                                                  
	if (logger.isTraceEnabled()) {                                                     
		int numberFiltered = configurations.size() - result.size();                    
		logger.trace("Filtered " + numberFiltered + " auto configuration class in "    
				+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)         
				+ " ms");                                                              
	}                                                                                  
	return new ArrayList<>(result);                                                    
}                                                                                      
  1. 这里会传入几百个candidates(待确定需要需要配置的class)和autoConfigurationMetadata(前面几百个配置class)的条件信息,该方法里面会遍历所有的candidates,然后在autoConfigurationMetadata里面拿到他所对应的条件,进行match,如果匹配,则加入结果集

还是用上面的org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.ConditionalOnClass=javax.sql.DataSource,org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType为例

candidate = DataSourceAutoConfiguration

条件 = DataSource + EmbeddedDatabaseType

所以操作就是,去类路径下判断有没有DataSource和EmbeddedDatabaseType,如果都存在,则DataSourceAutoConfiguration就可以被放入结果集

具体的代码实现如下:

private static boolean isPresent(String className, ClassLoader classLoader) { 
	if (classLoader == null) {                                                
		classLoader = ClassUtils.getDefaultClassLoader();                     
	}                                                                         
	try {                                                                     
		forName(className, classLoader);                                      
		return true;                                                          
	}                                                                         
	catch (Throwable ex) {                                                    
		return false;                                                         
	}                                                                         
}                                                                             

中间的调用过程就省略了,感兴趣可以自己去debug跟踪

加入缓存

实现的第二步,就是遍历所有待import的类,然后放入缓存,当然期间还回去判断待import的类是否还有@import注解

ConfigurationClass configurationClass = 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);               
}                                                                                     

在processImports里面,最关键的代码如下

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {      
  //《1》
	if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
		return;                                                                                                 
	}                                                                                                                  
	ConfigurationClass existingClass = this.configurationClasses.get(configClass);                              
	if (existingClass != null) {                                                                                
		if (configClass.isImported()) {                                                                         
			if (existingClass.isImported()) {                                                                   
				existingClass.mergeImportedBy(configClass);                                                     
			}              
			return;                                                                                             
		}                                                                                                       
		else {                                                                                                
			this.configurationClasses.remove(configClass);                                                      
			this.knownSuperclasses.values().removeIf(configClass::equals);                                      
		}                                                                                                       
	}                                                                                                           
	SourceClass sourceClass = asSourceClass(configClass);                                                       
	do {                                                                                                        
		sourceClass = doProcessConfigurationClass(configClass, sourceClass);                                    
	}                                                                                                           
	while (sourceClass != null);                                                                                
  // 《2》                                                                                                              
	this.configurationClasses.put(configClass, configClass);                                                    
}                                                                                                               
  1. shouldSkip会判断这个类是否需要跳过,改方法会拿到类上的所有@Conditional注解,以及条件,然后去调用相应的ConditionOnXXX里面的方法,如果有条件不满如,就会跳过该类
  2. 如果没有被跳过,就会被加入到configurationClasses缓存中

再后来,就会进行 loadBeanDefinitionsForConfigurationClass…这里面又会调用shouldSkip…

SpringBoot自动配置原理简述 SpringBoot自动配置SpringBoot的一大特色,它提供了一种简洁的方式来配置应用程序。在SpringBoot中,我们可以通过添加特定的依赖来自动配置应用程序的各种组件,例如数据源、日志、Web服务等。使用自动配置可以大大简化应用程序的配置过程,减少开发人员的工作量,提高开发效率。 自动配置的实现原理是基于Spring的条件化配置机制。SpringBoot会根据应用程序的依赖和当前环境自动配置各种组件。条件化配置可以通过在类上添加@Conditional注解来实现,该注解可以指定一个条件类,只有满足该条件类的条件时,才会进行配置。例如,以下代码示例中,只有当classpath中存在H2数据库驱动程序时,才会自动配置H2的数据源。 @Configuration @ConditionalOnClass(org.h2.Driver.class) public class H2DataSourceAutoConfiguration { // 配置H2数据源 } 除了依赖条件外,SpringBoot还支持很多其他条件,例如环境条件、属性条件、Bean条件等。通过组合这些条件,可以实现更加灵活的自动配置。例如,以下代码示例中,只有当当前环境为开发环境时,才会自动配置开发环境的日志。 @Configuration @Profile("dev") public class DevLoggerAutoConfiguration { // 配置开发环境的日志 } 总之,SpringBoot自动配置机制是基于Spring的条件化配置机制实现的,它可以根据应用程序的依赖和当前环境自动配置各种组件,大大简化了应用程序的配置过程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值