SpringBoot自动装配原理

23 篇文章 1 订阅
7 篇文章 0 订阅

在这里插入图片描述

一、引入

SpringBoot以其强大的自动装配功能简化了应用搭建的复杂性,让开发者能够更高效地构建应用。那么,SpringBoot是如何实现这种“智能配置”的呢?本文将深入探讨SpringBoot的自动装配原理,揭示其背后的“魔法”,帮助大家更好地理解和运用这一工具。

二、基本流程

自动配置流程图在这里插入图片描述
SpringBoot的自动装配主要依赖于Spring框架的条件配置(Conditional Configuration)和Java的配置类(Java Config)功能。以下是自动装配的基本原理:

  1. 启动类注解:SpringBoot应用的启动类上通常会有一个@SpringBootApplication注解,这是一个复合注解,它包括了@EnableAutoConfiguration,正是这个注解开启了自动装配的功能。
  2. 自动配置类:在SpringBoot的jar包中,包含了许多以META-INF/spring.factories文件指定的自动配置类。这些类上通常会有@Configuration注解,表明它们是用来定义Bean的配置类。
  3. 条件化配置:这些自动配置类使用了诸如@ConditionalOnClass、@ConditionalOnBean、@ConditionalOnMissingBean等条件注解,来决定是否创建和配置某个Bean。这些条件注解根据类路径中是否存在某个类、是否已经定义了某个Bean等条件来决定配置是否生效。
  4. 依赖注入:当自动配置类中的条件满足时,SpringBoot会自动创建和配置相应的Bean,并通过依赖注入的方式将它们注入到其他需要它们的Bean中。

三、源码解读

为了深入理解自动装配的原理,我们可以查看SpringBoot的源码。以下是一个简化的源码解析过程:

3.1. 启动类

在构建SpringBoot项目的时候,启动类一般是这样的

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

3.2.@SpringBootApplication注解:

这个注解是一个复合注解,它包括了@EnableAutoConfiguration,而@EnableAutoConfiguration又导入了AutoConfigurationImportSelector类。

@Target(ElementType.TYPE)  
@Retention(RetentionPolicy.RUNTIME)  
@Documented  
@Inherited  
@SpringBootConfiguration  
@EnableAutoConfiguration  
@ComponentScan(excludeFilters = { ... })  
public @interface SpringBootApplication {  
    ...  
}

可以发现@SpringBootApplication注解其实是对@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个注解的封装,也就是它们之间具有相互替代性。本文我们主要讲下自动配置,关注@EnableAutoConfiguration这个注解
@EnableAutoConfiguration主要是标记需要导入哪些配置,这个就是一个key(factoryType),之后依据key获取spring.facteries文件中的配置信息

3.3. @EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 自动配置包
@AutoConfigurationPackage
// 导入自动配置的组件
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
	...
}

有两个需要注意的地方,那就是@AutoConfigurationPackage、@Import(AutoConfigurationImportSelector.class),我们先来看下@AutoConfigurationPackage

3.3.1.@AutoConfigurationPackage

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 导入组件AutoConfigurationPackages.Registrar
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
	...
}

3.3.2. @AutoConfigurationImportSelector类:

这个类是自动装配的关键,它实现了DeferredImportSelector接口,用于在配置类解析时动态地导入其他配置类。

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, EnvironmentAware {  
    ...  
    @Override  
    public String[] selectImports(AnnotationMetadata annotationMetadata) {  
        // 这里会加载META-INF/spring.factories中定义的自动配置类  
        ...  
    }  
    ...  
}

进入到组件AutoConfigurationPackages.Registrar

3.3.2.1. AutoConfigurationPackages.Registrar
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			// 注册当前主程序的同级以及子集的包组件,其实就是注册了一个Bean的定义
			register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
		}

		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImports(metadata));
		}

	}
  1. 第一步,我们需要获取被注解标记的类,即***Application。
  2. 在这个类所在的包及其子包中,我们将扫描所有的类文件。
  3. 当扫描到这些类时,我们将把它们导入到Spring容器中进行管理。这意味着它们可以被其他组件使用,并且可以被配置文件spring.factories引用。
3.3.2.3. AutoConfigurationImportSelector类
 看下@Import(AutoConfigurationImportSelector.class),通过Spring底层注解@Import,给容器导入一个组件,这个组件就是AutoConfigurationImportSelector.class,它实现了DeferredImportSelector,关键方法是selectImports()
/**
	 * 获取需要导入的全限定类名数组
	 *
	 * @param annotationMetadata
	 * @return
	 */
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		// 配置参数 spring.boot.enableautoconfiguration 是否打开,默认开启
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		// 获取自动配置对象,对象包含需要配置的全限定类名列表和需要排除的列表
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

	/**
	 * spring.boot.enableautoconfiguration 是否打开,默认处于打开状态
	 *
	 * @param metadata
	 * @return
	 */
	protected boolean isEnabled(AnnotationMetadata metadata) {
		if (getClass() == AutoConfigurationImportSelector.class) {
			return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
		}
		return true;
	}

通过getAutoConfigurationEntry方法来获取的配置

/**
	 * 基于annotationMetadata发现标有{@link Configuration @Configuration}的配置类并返回{@link AutoConfigurationEntry}
	 *
	 * @param annotationMetadata 配置类的注解元数据
	 * @return 应该被导入的自动配置
	 */
	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);
	}

获取原始的配置集合方法是getCandidateConfigurations

/**
	 * 返回需要自动配置的类名列表
	 *
	 * @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) {
		// 获取需要被加载的FactoryClass,也就是key
		Class<?> clazz = getSpringFactoriesLoaderFactoryClass();
		// 获取需要配置的全限定类名集合
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(clazz, 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;
	}
	
	/**
	 * 获取需要加载的工厂类
	 * 也就是key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的配置都需要被加载到IoC
	 *
	 * @return the factory class
	 */
	protected Class<?> getSpringFactoriesLoaderFactoryClass() {
		return EnableAutoConfiguration.class;
	}

SpringFactoriesLoader类属于Spring框架私有的一种扩展方案,其主要功能就是从指定的配置文件
META-INF/spring.factories加载配置,来加载到需要自动配置的类的全限定名列表,接下来我们到Spring框架中去看下SpringFactoriesLoader.loadFactoryNames()静态方法.

	/**
	 * 使用给定的类加载器,从META-INF/spring.factories中加载给定的工厂类型实现
	 * 在spring5.3中,如果给定的工厂类型下的实现类名发现不止一次,会进行去重处理
	 *
	 * @param factoryType factoryType,eg:org.springframework.beans.BeanInfoFactory=xxx
	 * @param classLoader 用于加载资源的类加载器
	 *                    {@code null} to use the default
	 * @throws IllegalArgumentException if an error occurs while loading factory names
	 * @see #loadFactories
	 */
	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		String factoryTypeName = factoryType.getName();
		// 加载Factories文件
		return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
	}

关键方法在loadSpringFactories

/**
	 * 加载Factories文件
	 *
	 * @param classLoader factories文件解析之后的map
	 * @return
	 */
	private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
		// 现在缓存中查找,classLoader为key
		Map<String, List<String>> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		result = new HashMap<>();
		try {
			// 获取资源,这里固定目录为: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 properties = PropertiesLoaderUtils.loadProperties(resource);
				// 遍历配置
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					// 获取value的数组,一般为类的全限定名
					// 比如 org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory
					String[] factoryImplementationNames =
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
					// 添加到result
					for (String factoryImplementationName : factoryImplementationNames) {
						result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
								.add(factoryImplementationName.trim());
					}
				}
			}

			// 对value进行去重
			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;
	}

获取配置集合,主要流程如下:

  1. 检查缓存:首先,程序会检查缓存(例如一个内存中的数据结构,如ConcurrentHashMap)中是否已经有需要的配置集合。如果有,则直接返回,避免重复加载和解析配置文件。

  2. 加载资源:如果缓存中没有找到配置,程序会利用ClassLoader的getResources方法从类路径(classpath)中加载所有匹配的资源文件。对于Spring
    Boot来说,这通常是查找所有META-INF/spring.factories文件。

  3. 解析文件:加载到资源文件后,程序会解析这些文件的内容。这通常涉及读取文件的每一行,并将每一行解析成key=value的格式,然后保存到集合中。

  4. 缓存结果:解析完所有文件后,程序会将结果保存到缓存中,以便下次可以直接从缓存中获取,无需再次加载和解析文件。

  5. 返回结果:最后,程序返回解析后的配置集合,通常是一个Map<String,
    List>类型的数据结构,其中key是工厂类型(如org.springframework.boot.autoconfigure.EnableAutoConfiguration),value是与该类型关联的配置值列表。

3.4. META-INF/spring.factories文件:

这个文件位于SpringBoot的jar包中,它定义了要加载的自动配置类。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\  
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\  
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\  
...

4、使用注意事项

虽然SpringBoot的自动装配功能强大且方便,但在使用时还是需要注意以下几点:

避免自动配置冲突:有时候,自动配置可能会与你的手动配置冲突。例如,如果你在配置文件中定义了一个DataSource Bean,而SpringBoot又尝试自动配置一个DataSource Bean,这可能会导致冲突。此时,你可以使用@Primary注解来指定优先使用的Bean,或者使用@EnableAutoConfiguration(exclude={...}来排除某些自动配置类。

注意依赖版本:确保你使用的SpringBoot版本与其他依赖的版本兼容。不兼容的版本可能导致自动装配失败或出现异常行为。

仔细阅读文档:SpringBoot的官方文档非常详尽,包含了大量关于自动装配的信息和示例。在遇到问题时,首先应该查阅官方文档。

调试和日志:如果遇到自动装配相关的问题,可以开启Spring的调试日志(通过设置logging.level.org.springframework=DEBUG),这将帮助你更好地理解自动装配过程中的细节。

5、小结

配合@EnableAutoConfiguration注解使用时,它主要扮演的是配置查找器的角色。这个注解利用其自身的完整类名org.springframework.boot.autoconfigure.EnableAutoConfiguration作为搜索的关键词(Key),来定位并加载一组相关的@Configuration类。

因此,@EnableAutoConfiguration的大致自动配置流程可以描述为以下几个步骤:

  1. 首先,它会在classpath中扫描所有的META-INF/spring.factories配置文件。
  2. 接着,它会从这些配置文件中提取所有以org.springframework.boot.autoconfigure.EnableAutoConfiguration为key的配置项(注意,这里的key应严格匹配,包括大小写,且不应有笔误,如EnableautoConfiguration应为EnableAutoConfiguration)。
  3. 最后,通过Java反射机制,它会实例化那些标注有@Configuration注解的Java配置类,这些类采用JavaConfig的方式定义了IoC容器的配置。完成实例化后,这些配置会被统一注册到Spring的IoC容器中,从而完成了自动配置的过程。
  • 12
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

进朱者赤

多多支持

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

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

打赏作者

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

抵扣说明:

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

余额充值