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包实现起步依赖

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Bitw-QwQ

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

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

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

打赏作者

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

抵扣说明:

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

余额充值