解析SpringBoot中的starter原理

2023年6月6日08:07:12

SpringBoot简介

SpringBoot其设计目的是用来简化Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置,提供自动配置的“starter”项目对象模型以简化Maven配置,所谓约定大于配置。

本文重点分析springboot的starter机制,解析它如何把需要的类加载到spring容器中的过程,希望通过一步步分析使大家能够更好的理解其中的原理。

先找starter包中要加载的对象

从run方法开始

SpringBoot启动都是通过执行run方法,debug启动从run方法开始看

解析SpringBoot中的starter原理
解析SpringBoot中的starter原理
解析SpringBoot中的starter原理
解析SpringBoot中的starter原理

SpringApplication构造方法

跟了几个方法后,来到SpringApplication的一个构造方法
解析SpringBoot中的starter原理
从方法名可以看出,这里要加载对象实例了
解析SpringBoot中的starter原理

去哪找要加载的对象

解析SpringBoot中的starter原理
解析SpringBoot中的starter原理
对象在spring.factories文件里
解析SpringBoot中的starter原理

通过SPI机制扫描到META-INF/spring.factories文件中的内容
解析SpringBoot中的starter原理
文件中的内容
解析SpringBoot中的starter原理

把对象放入Map中

构造方法执行完,META-INF/spring.factories中的类就已经被加载到一个map容器中了。
解析SpringBoot中的starter原理

处理从spring.factories文件中找到对象

@SpringBootApplication注解的作用

接下来就是@SpringBootApplication注解发挥作用的时候了。

注解点进去
解析SpringBoot中的starter原理

@Import注解

继续进入@EnableAutoConfiguration注解中,看到Import注解,了解spring的就知道spring会扫描到AutoConfigurationImportSelector这个类,这个类实现了DeferredImportSelector接口

不了解的@Import注解的可以看这个
解析SpringBoot中的starter原理

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered

DeferredImportSelector是ImportSelector的子类,实现了这个接口的类,spring就会执行到process,selectImports这个两个方法。

解析SpringBoot中的starter原理

process方法

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(annotationMetadata);
	this.autoConfigurationEntries.add(autoConfigurationEntry);
	for (String importClassName : autoConfigurationEntry.getConfigurations()) {
		this.entries.putIfAbsent(importClassName, annotationMetadata);
	}
}
/**
 * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
 * of the importing {@link Configuration @Configuration} class.
 * @param annotationMetadata the annotation metadata of the configuration class
 * @return the auto-configurations that should be imported
 */
protected AutoConfigurationEntry getAutoConfigurationEntry(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 = getConfigurationClassFilter().filter(configurations);
	fireAutoConfigurationImportEvents(configurations, exclusions);
	return new AutoConfigurationEntry(configurations, exclusions);
}
/**
 * Return the auto-configuration class names that should be considered. By default
 * this method will load candidates using {@link SpringFactoriesLoader} with
 * {@link #getSpringFactoriesLoaderFactoryClass()}.
 * @param metadata the source metadata
 * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
 * attributes}
 * @return a list of candidate configurations
 */
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
	//通过SPI的方法,加载,启动时在run方法中已经加载过了
	//getSpringFactoriesLoaderFactoryClass这个方法返回的就是EnableAutoConfiguration.class
	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;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
	return EnableAutoConfiguration.class;
}
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
	String factoryTypeName = factoryType.getName();
	//之前在构造方法中加载时,EnableAutoConfiguration已经被加载过了,所以这边直接可以从map中获取
	return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

解析SpringBoot中的starter原理

private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();

cache的key是classLoader,value又是一个map,其中key是EnableAutoConfiguration,value是spring.factories文件中对应的内容

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
	//直接从cache中获取
	MultiValueMap<String, String> result = cache.get(classLoader);
	if (result != null) {
		return result;
	}

	try {
		Enumeration<URL> urls = (classLoader != null ?
				classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
				ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
		result = new LinkedMultiValueMap<>();
		while (urls.hasMoreElements()) {
			URL url = urls.nextElement();
			UrlResource resource = new UrlResource(url);
			Properties properties = PropertiesLoaderUtils.loadProperties(resource);
			for (Map.Entry<?, ?> entry : properties.entrySet()) {
				String factoryTypeName = ((String) entry.getKey()).trim();
				for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
					result.add(factoryTypeName, factoryImplementationName.trim());
				}
			}
		}
		cache.put(classLoader, result);
		return result;
	}
	catch (IOException ex) {
		throw new IllegalArgumentException("Unable to load factories from location [" +
				FACTORIES_RESOURCE_LOCATION + "]", ex);
	}
}

解析SpringBoot中的starter原理

解析SpringBoot中的starter原理

selectImports方法

这个方法主要就是封装了,把上面从factories文件中获取到的信息,封装成一个个Entry对象。

@Override
public Iterable<Entry> selectImports() {
	if (this.autoConfigurationEntries.isEmpty()) {
		return Collections.emptyList();
	}
	Set<String> allExclusions = this.autoConfigurationEntries.stream()
			.map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
	Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
			.map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
			.collect(Collectors.toCollection(LinkedHashSet::new));
	processedConfigurations.removeAll(allExclusions);

	return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
			.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
			.collect(Collectors.toList());
}

解析SpringBoot中的starter原理

最后还是回到refresh方法

从刚刚的构造方法回来后,继续跟进run方法

public static ConfigurableApplicationContext run(Class<?>[] primarySources,
		String[] args) {
	return new SpringApplication(primarySources).run(args);
}

SpringBoot的主流程方法

public ConfigurableApplicationContext run(String... args) {
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	ConfigurableApplicationContext context = null;
	Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
	configureHeadlessProperty();
	SpringApplicationRunListeners listeners = getRunListeners(args);
	listeners.starting();
	try {
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(
				args);
		ConfigurableEnvironment environment = prepareEnvironment(listeners,
				applicationArguments);
		configureIgnoreBeanInfo(environment);
		Banner printedBanner = printBanner(environment);
		context = createApplicationContext();
		exceptionReporters = getSpringFactoriesInstances(
				SpringBootExceptionReporter.class,
				new Class[] { ConfigurableApplicationContext.class }, context);
		prepareContext(context, environment, listeners, applicationArguments,
				printedBanner);
		// 其他方法就不看了,这个方法就会进入核心的refresh()方法中
		refreshContext(context);
		afterRefresh(context, applicationArguments);
		stopWatch.stop();
		if (this.logStartupInfo) {
			new StartupInfoLogger(this.mainApplicationClass)
					.logStarted(getApplicationLog(), stopWatch);
		}
		listeners.started(context);
		callRunners(context, applicationArguments);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, listeners);
		throw new IllegalStateException(ex);
	}

	try {
		listeners.running(context);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, null);
		throw new IllegalStateException(ex);
	}
	return context;
}

好了,到此后续的流程就是由ConfigurationClassPostProcessor把这些从factories文件中收集到的类,封装成BeanDefinition并加载到spring容器中了,这完全是Spring IOC部分的知识了,入口在refresh方法 —> invokeBeanFactoryPostProcessors的方法,
invokeBeanFactoryPostProcessors这个方法主要可以完成对有@Configuration 、@Component 、@Bean 、@ComponentScan 、@Import 、@ImportResource注解的收集,此处就不做深入解析了。

  • 作者:码拉松
  • 原文链接:https://blog.csdn.net/CSDN_WYL2016/article/details/107191272
    更新时间:2023年6月6日08:07:12 ,共 7957 字。