SpringBoot-运行原理、自动装配源码探究

本文深入剖析SpringBoot的自动配置机制,从pom.xml依赖管理到启动器原理,再到@SpringBootApplication和@EnableAutoConfiguration的作用,详细解释了SpringBoot如何智能地加载和配置组件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

初探pom.xml

SpringBoot的核心依赖再父工程中,springboot-boot-dependencies,我们在写或者引入一些springboot一类的时候,不需要指定版本,就是因为有这些版本的仓库。可以理解为springboot的每一个版本都为我们做好了所有依赖的版本控制,我们不需要再去指定。
在这里插入图片描述

了解启动器

<dependency>
	<groupId>org.springframwork.boot</groupId>
	<artifactId>spring-boot-starter</artifactId>
</dependency>

启动器就是springboot的启动场景,比如spring-boot-starter-web,他就会帮我们自动导入web环境所有依赖,springboot会将所有功能场景都变成一个个的启动器,我们要使用什么功能,找到对应的启动器就好了。

从入门开始

学习SpringBoot,起步时我们一定写过这样一段代码。没错,他就是SpringBoot的“helloWord”,这段代码短小精悍,因为它做了很多准备工作,例如开启服务、自动装配等,他既然做了这么多工作,那么他的底层内部一定大有文章,在这篇博文中进行简单剖析。

@SpringBootApplication
public class MybatisApplication {
    public static void main(String[] args) {
       SpringApplication.run(MybatisApplication.class, args);
    }
}

@SpringBootApplication

只要是被这个注解标注,那么就可以说明这个类是一个SpringBoot的主配置类,一层一层的点进去看看这个注解的内部,@SpringBootConfiguration@Configuration@Component不关注不相关的注解和元注解,可以发现他和我们学过的spring一样,使用了@Configuration,这也说明主配置类其实也是一个组件@Component

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

}

@Configuration
public @interface SpringBootConfiguration {

}

@Component
public @interface Configuration {

}

@EnableAutoConfiguration

从字面意思理解为自动配置,这个注解标志着开启了自动配置功能,和上面一样一层一层点进去看源码。
@AutoConfigurationPackage字面意思是自动配置包,
@Import(AutoConfigurationPackages.Registrar.class)是导入自动配置注册包,这里也使用了Spring的底层注解@Import,他给容器中导入一个组件;
我们可以在public static List<String> get(BeanFactory beanFactory)方法处打一个断点,debug启动,看一看他会给我们什么结果,我们发现最后他给我生成了一个我们自己的包名。
在这里插入图片描述
所以导入的组件由AutoConfigurationPackages.Registrar.class类将我们的主配置类(@SpringBootApplication标注的类)的所在包及下面所有子包里面的所有组件就扫描到Spring容器中了。
这也就是为什么我们写的controller必须和主配置类在同级目录或者其同级子包下才能起作用的原因了。

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

}

@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

public abstract class AutoConfigurationPackages {
	/**
	 * Return the auto-configuration base packages for the given bean factory.
	 * @param beanFactory the source bean factory
	 * @return a list of auto-configuration packages
	 * @throws IllegalStateException if auto-configuration is not enabled
	 */
	public static List<String> get(BeanFactory beanFactory) {
		try {
			return beanFactory.getBean(BEAN, BasePackages.class).get();
		}
		catch (NoSuchBeanDefinitionException ex) {
			throw new IllegalStateException("Unable to retrieve @EnableAutoConfiguration base packages");
		}
	}

EnableAutoConfigurationImportSelector.class

EnableAutoConfigurationImportSelector字面意思是自动导入配置组件选择器。他会将所有需要导入的组件以全类名的方式返回,这些组件就会被添加到Spring容器中;他会给容器中导入非常多的自动配置类(XXXXAutoConfiguration),即给容器中导入这个场景需要的所有组件,并且进行一些自动配置。

selectImports

这个选择器会调用selectImports方法去获取所有要导入的组件的全限定类名,这样那些组件就会被添加到Spring容器中了。具体是怎样获取的还得进一步探究,请耐心往后看。

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return NO_IMPORTS;
	}
	AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
			.loadMetadata(this.beanClassLoader);
	AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
			annotationMetadata);
	return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

selectImports会调用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);
}

getAutoConfigurationEntry方法有会去调用getCandidateConfigurations去获取候选配置。这些候选配置其实与我们一开始说的pom.xml中根据需要的组件所引的启动器starter有关,这是SpringBoot要帮我们配置哪些组件的唯一标准,我们引了哪些,他就去配置那些,很听话的。

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;
}
  • No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.这个字符串的意思为在META-INF/spring.factories中找不到自动配置类。如果使用自定义打包,请确保文件正确无误。
  • 这就更加验证了上面所说的那一点。他是怎样去加载配置的。我们先看看这个META-INF/spring.factories,他里面就是放置了所有场景的的自动配置类,可以看见他全是XXXXAutoConfiguration。
  • 类似的条件过滤还有很多,如下面这些都是条件判断
    在这里插入图片描述
  • 我们打开一个我们熟悉的WebmvcAutoConfiguration看看,其实他就是通过@ConditionalOnClass注解来作为调节筛选是否去加载这个组件。如果我们引入了对应的启动器,就相当于是引入了jar包,此时的配置类WebmvcAutoConfiguration@ConditionalOnClass条件满足不会爆红报错,SpringBoot就会去给我们自动加载这个组件。
    在这里插入图片描述
    EnableAutoConfigurationImportSelector类中的调用关系图如下。
    在这里插入图片描述

SpringFactoriesLoader

上面的EnableAutoConfigurationImportSelector类的getCandidateConfigurations会调用SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass()方法。
这个其实就是从META-INF/spring.factories中获得哪些自动配置,然后挨个遍历然后封装到properties文件中,来供我们使用。

public final class SpringFactoriesLoader {

	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
	
	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}

	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		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);
		}
	}

}

SpringFactoriesLoader 中相关方法调用关系图。
在这里插入图片描述

画了很久也很重要的两张图拿走不谢

画的好累
在这里插入图片描述
在这里插入图片描述
这个图是狂神的
在这里插入图片描述

总结

  • 以前我们需要自动配置的东西,以及j2ee的解决方案和自动配置都在spring-boot-autoconfigure-2.2.0.RELEASE.jar/META_INF/spring.factories的文件中
  • 他会把所有需要导入的组件,以类名方式返回,这些组件就会添加到容器
  • 容器中也存在非常多的XXXXAutoConfiguration的文件,就是这些给容器中导入了这个场景需要的所有组件,并自动配置

springboot所有自动配置都是在启动的时候扫描并加载的,spring.factories中所有自动配置类都在里面,但不一定都是生效的,根据@ConditionalOnClass注解判断条件是否成立,只要我们导入对应的启动器starter,那么条件就成立,就会自动装配,然后配置成功。

SpringBoot之所以操作简单,不用像ssm那样写过多的配置,主要是缘于他的这些精妙的设计,还有很长的路要走,加油!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值