Bootstrap

SpringBoot自动装配原理(超详细,深入理解)

简介

SpringBoot 这款框架几乎是现在企业级开发的标配,使用SpringBoot进行开发,能够大量减少xml配置文件的编写,并且能为我们提供一站式服务。SpringBoot我们只需要导入相关模块的starter,就可以使用相关功能,但是我们几乎没有写配置,那么SpringBoot是如何帮我们配置的呢?

又比如说我们可以在application.properties或者application.yml 配置文件中进行一些配置,比如设置 server.port=8081,那么我们是如何知道可以设置哪些属性呢?我们往往是通过记忆,或者通过IDEA提示,或者是官方文档,但是这些都不太行,只有我们真正研究透原理,才能随心所欲的去应用。
下面就来谈谈SpringBoot的自动装配原理。

原理

一切的开始,都要从启动类上的@SpringBootApplication注解开始。

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

这个注解又是一个合成注解,点进去看看。

@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 {}

SpringBoot启动时,加载主配置类,并且开启了自动配置功能。也就是这个注解: @EnableAutoConfiguration

那么这注解的作用是什么呢?点进去看看

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

可以看到,主要利用到了一个底层的 @Import注解,导入一个选择器,根据选择器,给Spring的容器中导入一些组件。

那么导入哪些组件呢?点进这个 AutoConfigurationImportSelector 类看看。该类实现了 ImportSelect 接口,重写了这个方法:

String[] selectImports(AnnotationMetadata importingClassMetadata);

该方法返回的是一个String类型的数组,该数组其实就是一个全类名的数组,SpringBoot会将这些全类名对应的类加入到IOC容器中。重写如下:

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
// selectImports方法中调用了getAutoConfigurationEntry方法。
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);
	}

可以看到,在 getAutoConfigurationEntry(),有这么一行代码

List configurations = getCandidateConfigurations(annotationMetadata, attributes);

通过getCandidateConfigurations方法,翻译过来是获取候选的配置,返回一个configurations对象。

那么这个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;
	}

调用了 SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader()); 这个方法传入了EnableAutoConfiguration.class以及类加载器。点进这个方法看看。

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
	}

	private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
		Map<String, List<String>> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		result = new HashMap<>();
		try {
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
			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();
					String[] factoryImplementationNames =
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
					for (String factoryImplementationName : factoryImplementationNames) {
						result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
								.add(factoryImplementationName.trim());
					}
				}
			}

			// Replace all lists with unmodifiable lists containing unique elements
			result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
					.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
			cache.put(classLoader, result);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
		return result;
	}

分析一下上述代码,在loadFactoryNames方法中,其实起作用的是loadSpringFactories方法,在loadSpringFactories方法中,先通过类加载器去获取资源:classLoader.getResources(FACTORIES_RESOURCE_LOCATION);这个FACTORIES_RESOURCE_LOCATION是一个常量,表示 META-INF/spring.factories 这个路径。

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

所以说,当项目启动时,会去扫描所有jar包中的类路径下的META-INF/spring.factories 文件,获取文件对应的url。然后再遍历每一个url,最终将这些扫描到的文件的内容,包装成一个Properties对象。然后在从Properties中获取的值加入到返回的结果里面。这个结果就是要交给容器的所有组件的全类名。

这里的factoryType就是传过来的EnableAutoConfiguration.class。所以会从properties中获取这个类名对应的值,然后把他们添加到容器中。
在这里插入图片描述
一共131个。

这131给 xxxAutoConfiguration 都是容器中的一个组件,都会加入到容器中,这些自动配置类本质都是配置类,用他们来进行自动配置。

就拿HttpEncodingAutoConfiguration为例,来解释自动配置原理。

@Configuration(proxyBeanMethods = false) // 表示是一个配置类,可以给容器中添加组件
@EnableConfigurationProperties(ServerProperties.class) // 启动指定类的 @ConfigurationProperties 功能,让指定的类和 properties 文件进行属性映射,并将ServerProperties加入到容器中,给其他组件使用。
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)// @Condition,表示如果是web应用,才生效
@ConditionalOnClass(CharacterEncodingFilter.class) // 判断当前项目有没有这个类,这个类是MVC中进行乱码解决的过滤器
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true) // 判断配置文件中是否存在 server.servlet.encoding 这个配置,即使不存在也判断成立。即使不配置,也默认生效。
public class HttpEncodingAutoConfiguration {
    // 这个properties已经和配置文件映射了。
    private final Encoding properties;
    // 只有一个有参构造器,这个参数的值就会从容器中拿,
    public HttpEncodingAutoConfiguration(ServerProperties properties) {
		this.properties = properties.getServlet().getEncoding();
	}
}

点进 ServerProperties 这个类看看,可以看到,标注了@ConfigurationProperties注解,该注解的作用就是从配置文件中获取指定的值和bean的属性进行绑定。

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {

	/**
	 * Server HTTP port.
	 */
	private Integer port;

所以说,我们想要知道配置文件中去配置什么,就可以看 @ConfigurationProperties 注解的prefix 前缀。 可以看到,我们常用的server.port就是这么来的。

也就是说,所有的在配置文件中配置的属性都是在xxxProperties中封装着。 配置文件能配什么,就可以参照某个功能对应的属性类。

HttpEncodingAutoConfiguration类上标注了很多的 @Condition 注解,如果这些条件都成立,配置类才会生效。如果生效,就会向容器中@Bean,添加组件,这些值的的某些属性,需要从properties中获取。

@Bean
	@ConditionalOnMissingBean
	public CharacterEncodingFilter characterEncodingFilter() {
		CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
		filter.setEncoding(this.properties.getCharset().name());
		filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
		filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
		return filter;
	}

我们在application.properteis 中直接点击我们配置的属性,也会跳转到绑定的properties文件中。

;