springBoot实现自动装配的机制

目录

前言:

1.什么是自动装配

2.springBoot实现的原理

3.造一个Starter

1.首先,跟着造一个starter项目

 2.接着,造一个autoconfigure

 3.在autoconfigure工程的 resources 包下创建META-INF/spring.factories文件,并写好文件

 4.starter项目中引入autoconfigure项目,测试项目中引入starter项目

 5.断点观察前后集合中元素数量

总结


前言:

        Spring Boot是基于Spring框架开发的一种应用框架,它通过自动装配机制,大大简化了Spring应用的开发和部署,使开发者可以更加专注于业务逻辑的实现,而无需过多关注Bean的实例化和装配过程。本文将介绍Spring Boot自动装配的原理和实现方式。

       使用过或者学习Spring 的小伙伴,一定有被 XML 配置统治的恐惧。即使 Spring 后面引入了基于注解的配置,我们在开启某些 Spring 特性或者引入第三方依赖的时候,还是需要用 XML 或 Java 进行显式配置,繁琐的步骤和大量的代码让人心烦。 

        但是,Spring Boot 项目,我们只需要添加相关依赖,无需配置,通过启动 main 方法即可。

 通过 Spring Boot 的全局配置文件 application.propertiesapplication.yml即可对项目进行设置比如更换端口号,配置 JPA 属性等等。

至于springBoot框架如此便捷的使用方式得益于它的自动装配原理。

1.什么是自动装配

        自动装配是指在不需要显式配置的情况下,Spring Boot能够自动完成组件之间的依赖注入。这种方式通过分析应用程序中的类路径,找到可以提供所需服务的Bean,并将它们自动地注入到目标Bean中,从而消除了手动配置的麻烦。

        在Spring Framework 的时候就已经实现了这个功能。Boot只是在其基础上,通过 SPI 的方式,做了进一步优化。

SpringBoot 定义了一套接口规范,这套规范规定:SpringBoot 在启动时会扫描外部引用 jar 包中的META-INF/spring.factories文件,将文件中配置的类型信息加载到 Spring 容器(此处涉及到 JVM 类加载机制与 Spring 的容器知识),并执行类中定义的各种操作。对于外部 jar 来说,只需要按照 SpringBoot 定义的标准,就能将自己的功能装置进 SpringBoot。

        简单来说就是:通过一些简单的配置和注解就可以将自己需要的功能导入进BOOT框架。

2.springBoot实现的原理

首先一切的开始从@SpringBootApplication 注解开始:

 @SpringBootApplication 是一个合成注解,进入其中可以看到

大概可以把 @SpringBootApplication看作是 @Configuration@EnableAutoConfiguration@ComponentScan 注解的集合。根据 SpringBoot 官网,这三个注解的作用分别是:

  • @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制
  • @Configuration:允许在上下文中注册额外的 bean 或导入其他配置类
  • @ComponentScan:扫描被@Component (@Service,@Controller)注解的 bean,注解默认会扫描启动类所在的包下所有的类 ,可以自定义不扫描某些 bean。如图中所示,容器中将排除TypeExcludeFilterAutoConfigurationExcludeFilter

@EnableAutoConfiguration 实现自动装配的核心注解

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

点进这个 AutoConfigurationImportSelector 类看看。该类实现了 ImportSelect 接口,重写了这个方法:

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

这里需要重点关注一下getAutoConfigurationEntry()方法,这个方法主要负责加载自动配置类的。结合源码来分析一下:

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.getConfigurationClassFilter().filter(configurations);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }

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

List configurations = getCandidateConfigurations(annotationMetadata, attributes);

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

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中获取的值加入到返回的结果里面。这个结果就是要交给容器的所有组件的全类名。


spring.factories中这么多配置,每次启动都要全部加载么,当然不现实,@ConditionalOnXXX 中的所有条件都满足,该类才会生效。

 Spring Boot 提供的条件注解

  • @ConditionalOnBean:当容器里有指定 Bean 的条件下
  • @ConditionalOnMissingBean:当容器里没有指定 Bean 的情况下
  • @ConditionalOnSingleCandidate:当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean
  • @ConditionalOnClass:当类路径下有指定类的条件下
  • @ConditionalOnMissingClass:当类路径下没有指定类的条件下
  • @ConditionalOnProperty:指定的属性是否有指定的值
  • @ConditionalOnResource:类路径是否有指定的值
  • @ConditionalOnExpression:基于 SpEL 表达式作为判断条件
  • @ConditionalOnJava:基于 Java 版本作为判断条件
  • @ConditionalOnJndi:在 JNDI 存在的条件下差在指定的位置
  • @ConditionalOnNotWebApplication:当前项目不是 Web 项目的条件下
  • @ConditionalOnWebApplication:当前项目是 Web 项 目的条件下

3.造一个Starter

1.首先,跟着造一个starter项目

 2.接着,造一个autoconfigure

 3.在autoconfigure工程的 resources 包下创建META-INF/spring.factories文件,并写好文件

 

 这是让他读取properties或者yml文件的关键

 4.starter项目中引入autoconfigure项目,测试项目中引入starter项目

 5.断点观察前后集合中元素数量

很好,多了一个,证明成功了!

总结

Spring Boot 通过@EnableAutoConfiguration开启自动装配,通过 SpringFactoriesLoader 最终加载META-INF/spring.factories中的自动配置类实现自动装配,自动配置类其实就是通过@Conditional按需加载的配置类,想要其生效必须引入spring-boot-starter-xxx包实现起步依赖

### SpringBoot 自动装配机制原理详解 #### 1. 核心概念 SpringBoot自动装配机制是其核心特性之一,它通过一系列的设计模式和注解实现了应用程序的自动化配置。这一过程的核心目标在于减少手动配置的工作量,提升开发效率并降低出错概率。 - **@SpringBootApplication 注解** 是启动 SpringBoot 应用程序的关键入口[^2]。该注解实际上是一个组合注解,内部集成了多个其他注解,其中包括 `@SpringBootConfiguration` 和 `@EnableAutoConfiguration`。 - **@SpringBootConfiguration** 表明当前类是一个配置类,并继承了标准的 `@Configuration` 功能[^2]。这意味着它可以被 Spring 容器识别为一个用于定义 Bean 的配置源。 #### 2. 启动流程解析 当 SpringBoot 应用启动时,框架会扫描项目中的依赖项以及特定路径下的元数据文件(通常是 `META-INF/spring.factories`),并通过这些信息决定如何进行自动装配。 - **spring.factories 文件的作用** spring.factories 文件中存储了一系列全限定名形式的配置类列表。这些类通常由第三方库提供,在应用初始化阶段会被读取并注册到容器中[^4]。例如: ```properties org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,... ``` - **条件注解驱动装配逻辑** 条件注解(如 `@ConditionalOnClass`, `@ConditionalOnMissingBean` 等)决定了具体的 Bean 是否应该创建。只有满足指定条件下才会执行对应的配置操作[^3]。比如: - 如果 classpath 中存在某类,则触发相应行为 (`@ConditionalOnClass`); - 若不存在某个 bean 实例则注入默认实现(`@ConditionalOnMissingBean`)。 #### 3. 配置优先级与覆盖策略 为了支持灵活定制需求,Spring Boot 设计了一套完善的优先级处理方案: - 默认情况下采用内置推荐设置; - 用户可以通过外部属性文件(application.properties 或 application.yml)自定义参数来调整默认值; - 当两者发生冲突时,以外部显式声明为准. 此机制确保即使在高度复杂的场景下也能保持良好的可维护性和扩展性. ```java @SpringBootApplication(scanBasePackages = {"com.example"}) public class MyApplication { public static void main(String[] args){ SpringApplication.run(MyApplication.class,args); } } ``` 上述代码片段展示了最基本的 Spring Boot 应用结构,其中 scanBasePackages 参数指定了需要扫描的基础包位置以便发现候选组件. --- #### 相关问题
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Bitw-QwQ

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

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

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

打赏作者

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

抵扣说明:

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

余额充值