【Spring】深究SpringBoot自动装配原理

前言

早期的Spring项目需要添加需要配置繁琐的xml,比如MVC、事务、数据库连接等繁琐的配置。Spring Boot的出现就无需这些繁琐的配置,因为Spring Boot基于约定大于配置的理念,在项目启动时候,将约定的配置类自动装配到IOC容器里。

这些都因为Spring Boot有自动装配的特性。

接下来将会逐步从源码跟踪进去,一步步掀开自动装配的面纱。

1、main入口

在SpringBoot项目的启动类中,注解SpringBootApplication是必须需要添加的,既然是从这里启动的,那么自动装配的操作应该也是在启动的时候去执行的吧,我们一步步挖进去一探究竟。

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

2、@SpringBootApplication

点进来@SpringBootApplication注解之后会发现,这玩意头顶怎么挂着这么多注解的。不要着急,与Bean注入也只有三个相关,而自动装配的核心注解也是只有一个:

  • @SpringBootConfiguration:继承自Spring的 @Configuration 注解,作用也大致相同,支持在入口处通过@Bean等注解手动配置一下 Bean 加入到容器中;
  • @EnableAutoConfiguration:顾名思义,这玩意就是用来自动装配的,都写在人家脸上了,接下来主要的介绍核心也是该注解;
  • @ComponentScan:告诉Spring需要扫描哪些包或类,如果不设值的话默认扫描@ComponentScan注解所在类的同级类和同级目录下的所有类,这也是为什么放置启动类位置有要求的原因。
@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 {
    // 省略该注解内属性和方法
    // ......
}

3、@EnableAutoConfiguration

点进来@EnableAutoConfiguration注解,去掉那些有的没的注解,可以初步发现与自动装配有关的应该是有两个,分别是注解@AutoConfigurationPackage和导入的类AutoConfigurationImportSelector

点进去注解@AutoConfigurationPackage发现里面没有什么有用的信息,其作用是将添加该注解的类所在的package 作为自动装配 package 进行管理,感兴趣的小伙伴可以自行点进去查看。

AutoConfigurationImportSelector作为被导入的类,也是实现自动装配的核心类之一,接下来将会点进去查看其细节。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    // 省略该注解内属性和方法
    // ......
}

4、AutoConfigurationImportSelector

4.1、selectImports()

AutoConfigurationImportSelector类中,自动装配核心方法为selectImports(),在其文档中是这么介绍该方法的:

Select and return the names of which class(es) should be imported based on the AnnotationMetadata of the importing @Configuration class.

Returns: the class names, or an empty array if none

根据@Configuration中的AnnotationMetadata,选择并返回应该被导入的类的名称。

返回:类名,如果不存在则返回空数组

可以看到从这里便已经获取被设置需要自动装配的类的信息了,从源码中可以看到在selectImports()中的核心方法应该是getAutoConfigurationEntry()

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    // 省略类中成员属性...
            
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
    
    // 省略类中其余方法...
}

4.2、getAutoConfigurationEntry()

getAutoConfigurationEntry()方法同样处于AutoConfigurationImportSelector类中,在刚方法中主要返回的是已经配置好的配置数组,该配置数组被包装在类中,其中AutoConfigurationImportSelector.AutoConfigurationEntry的介绍如下:

Create an entry with the configurations that were contributed and their exclusions.

Params: configurations – the configurations that should be imported exclusions – the exclusions that were applied to the original list

使用所提供的配置及其排除项创建一个条目。

参数:configurations ——应该导入的配置 exclusions – 应用于原始列表的排除项

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);
}

4.3、getCandidateConfigurations()

方法getCandidateConfigurations()同样处于AutoConfigurationImportSelector类中,该方法主要用于获取应考虑的自动装配类名称,而获取候选项的方法为其中的loadFactoryNames()方法。

Return the auto-configuration class names that should be considered. By default this method will load candidates using ImportCandidates with getSpringFactoriesLoaderFactoryClass().

返回应考虑的自动配置类名称。默认情况下,此方法将使用ImportCandidates和getSpringFactoriesLoaderFactoryClass()加载候选项。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
	List<String> configurations = new ArrayList<>(
        	// 获取候选项
			SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
	ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
	Assert.notEmpty(configurations,
			"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
					+ "are using a custom packaging, make sure that file is correct.");
	return configurations;
}

4.4、loadFactoryNames()

挖到这里其实就差不多到头了,再往下就不太礼貌了。

在这里干的活在官方给出的文档中都说的很简单明了了:

Load the fully qualified class names of factory implementations of the given type from “META-INF/spring.factories”, using the given class loader.

As of Spring Framework 5.3, if a particular implementation class name is discovered more than once for the given factory type, duplicates will be ignored.

使用给定的类加载器,从"META-INF/spring.factories"加载给定类型的工厂实现的完全限定类名。

从 Spring Framework 5.3 开始,如果针对给定工厂类型多次发现特定实现类名称,则将忽略重复项。

从介绍中就可以看到所谓的自动装配,就是将META-INF/spring.factories中写明白的内容给加载出来,同时连上面提及到的排除项都写明白在这里了,而源码也是对官方文档进行直接翻译了,猜测是出于封装性和代码简洁度考虑,开发人员将核心逻辑封装成了私有方法loadSpringFactories()

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 {
        // 获取资源,常量FACTORIES_RESOURCE_LOCATION的值为META-INF/spring.factories
		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;
}

5、META-INF/spring.factories

spring-boot-autoconfigure.jar 包中的 META-INF/spring.factories 里面默认配置了很多aoto-configuration,如下:

image-20230802165739779

WebMvcAutoConfiguration为例:

package org.springframework.boot.autoconfigure.web.servlet;

@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurerAdapter.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
    public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new OrderedHiddenHttpMethodFilter();
    }

    @Bean
    @ConditionalOnMissingBean(HttpPutFormContentFilter.class)
    @ConditionalOnProperty(prefix = "spring.mvc.formcontent.putfilter", name = "enabled", matchIfMissing = true)
    public OrderedHttpPutFormContentFilter httpPutFormContentFilter() {
        return new OrderedHttpPutFormContentFilter();
    }
    
    ......etc
}

这里有一个地方不知道大家有没有注意到,有大部分的注解前面都有Conditional字样的,这其实也是SpringBoot的强大之处之一,其使用了 Spring 4 框架的新特性:@Conditional注释,此注解使得只有在特定条件满足时才启用一些配置。这也 Spring Boot “智能” 的关键注解。Conditional大家族如下:

注解描述
@ConditionalOnBean当容器中存在指定的Bean时生效
@ConditionalOnClass当类路径中存在指定的类时生效
@ConditionalOnExpression通过SpEL表达式指定条件,满足条件时生效
@ConditionalOnMissingBean当容器中不存在指定的Bean时生效
@ConditionalOnMissingClass当类路径中不存在指定的类时生效
@ConditionalOnNotWebApplication当应用程序不是Web应用时生效
@ConditionalOnResource当指定的资源存在于类路径中时生效
@ConditionalOnWebApplication当应用程序是Web应用时生效

6、总结

SpringBoot自动配置原理如下:

  1. @EnableAutoConfiguration 注解导入 AutoConfigurationImportSelector 类;
  2. 执行 selectImports() 方法调用 SpringFactoriesLoader.loadFactoryNames() 扫描所有 jar 下面的对应的 META-INF/spring.factories 文件;
  3. 限定为 @EnableAutoConfiguration 对应的 value,将这些装配条件的装配到 IOC 容器中。

自动装配简单来说就是自动将第三方的组件的 bean 装载到 IOC 容器内,不需要再去写 bean 相关的配置,符合约定大于配置理念。SpringBoot 基于约定大于配置的理念,配置如果没有额外的配置的话,就给按照默认的配置使用约定的默认值,按照约定配置到 IOC 容器中,无需开发人员手动添加配置,加快开发效率。

同时,对于自己开发SDK时,也可利用 SpringBoot 自动装配原理,编写自己的 META-INF/spring.factories 文件,从而将某些特定需求的类生成 Bean 放入容器中。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈宝子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值