我想你也在真实面试中被问过无数次这个问题了,我也是,但是不管你怎么搜,都只有那几篇八股文的答案,你问GPT它都解释不清楚,我决定自己写一篇详细的,避免遗忘也想帮助一下患难中的兄弟姐妹们,能把这篇文章搞懂,这个面试题必过!!!!祝各位早日上岸
SpringBoot实现自动装配的原理是其核心特性之一,它极大地简化了Spring应用程序的配置过程,让开发者能够快速构建Spring应用。下面详细解释SpringBoot自动装配的原理,并尝试结合源代码进行说明(注意,由于直接引用完整源代码篇幅较长且涉及版权问题,这里将基于原理和关键组件进行说明)。
SpringBoot自动装配原理概述
SpringBoot自动装配主要通过以下几个关键组件和机制实现:
- @SpringBootApplication注解:
- 这是一个组合注解,包含了@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan三个核心注解。
- @SpringBootConfiguration:表明该类是一个配置类,它实际上是一个@Configuration注解的派生注解,用于定义Bean和配置应用程序。
- @EnableAutoConfiguration:开启自动配置功能,通过@Import注解引入AutoConfigurationImportSelector类,该类负责扫描并加载自动配置类。
- @ComponentScan:指定Spring Boot扫描组件的基础包,用于注册带有@Controller、@Service、@Repository、@Component等注解的类为Spring管理的Bean。
- AutoConfigurationImportSelector:
- 实现了ImportSelector接口,其selectImports方法会根据应用的上下文环境和类路径中存在的依赖,动态地选择需要导入的自动配置类。
- 这一过程主要依赖于META-INF/spring.factories文件中的配置,该文件位于spring-boot-autoconfigure项目的JAR包中,包含了大量自动配置类的全限定名。
- spring.factories文件:
- 这是一个位于JAR包META-INF目录下的文件,以key=value的形式列出了自动配置类的全限定名。
- 当@EnableAutoConfiguration注解生效时,Spring Boot会读取这个文件,并根据其中的配置加载相应的自动配置类。
- 条件注解(@Conditional注解及其扩展):
- 自动配置类中使用了大量条件注解(如@ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty等),这些注解用于控制Bean的创建和配置类的激活。
- 例如,@ConditionalOnClass注解会检查类路径中是否存在某个类,如果存在,则满足条件;@ConditionalOnMissingBean注解会检查Spring容器中是否已经存在某个Bean,如果不存在,则满足条件。
简单总结一下就是:(1)@SpringBootApplication引入了@EnableAutoConfiguration(负责启动自动配置功能)
(2)@EnableAutoConfiguration引入了@Import,里面引入了AutoConfigurationImportSelector,z这个类又实现了DeferredImportSelector(咱可以给它翻译成延迟导入选择器),它会使SpringBoot的自动配置类的顺序放在最后,方便扩展和覆盖(比如我们有很多的ContidionalOnBean,Bean解析之后,自动配置类才能准确的更好的发挥作用)
(3)DeferredImportSelector才用类似SPI的机制(SPI对应的名字应该是接口名字,另外应该在/META-INF/service下)实现导入classpath下的所有的/META-INF/spring.factories文件
源代码层面的简要说明(非完整代码)
由于直接展示完整源代码篇幅过长,这里仅对关键部分进行简要说明:
-
@EnableAutoConfiguration注解:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {/*** Environment property that can be used to override when auto-configuration is* enabled.*/String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";/*** Exclude specific auto-configuration classes such that they will never be applied.* @return the classes to exclude*/Class<?>[] exclude() default {};/*** Exclude specific auto-configuration class names such that they will never be* applied.* @return the class names to exclude* @since 1.3.0*/String[] excludeName() default {};}
注意
@Import(AutoConfigurationImportSelector.class)
,这里导入了AutoConfigurationImportSelector类,用于后续的自动配置类加载。 -
AutoConfigurationImportSelector类:
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()); }// ... selectImports方法的具体实现细节会涉及到读取spring.factories文件等 }
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);}
里面最重要的源码是调用了getCandidateConfigurations,看具体代码
/*** Return the auto-configuration class names that should be considered. By default* this method will load candidates using {@link SpringFactoriesLoader} with* {@link #getSpringFactoriesLoaderFactoryClass()}.* @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) {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;}
loadFactoryNames就是我们的主要导入配置的方法了
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {ClassLoader classLoaderToUse = classLoader;if (classLoader == null) {classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();}String factoryTypeName = factoryType.getName();return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());}
里面有几个重要的点需要我们关注一下:(1)factoryType来自我们传入的第一个参数也就是getSpringFactoriesLoaderFactoryClass(),代码如下
/*** Return the class used by {@link SpringFactoriesLoader} to load configuration* candidates.* @return the factory class*/protected Class<?> getSpringFactoriesLoaderFactoryClass() {return EnableAutoConfiguration.class;}
说明我们要导入的EnableAutoConfiguration这个类对应的value
(2)loadSpringFactroies是实际load的过程,代码一看就能知道为什么说我们导入的是classpath下的所有的spring.factories
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {Map<String, List<String>> result = (Map)cache.get(classLoader);if (result != null) {return result;} else {HashMap result = new HashMap();try {Enumeration urls = classLoader.getResources("META-INF/spring.factories");while(urls.hasMoreElements()) {URL url = (URL)urls.nextElement();UrlResource resource = new UrlResource(url);Properties properties = PropertiesLoaderUtils.loadProperties(resource);Iterator var6 = properties.entrySet().iterator();while(var6.hasNext()) {Entry<?, ?> entry = (Entry)var6.next();String factoryTypeName = ((String)entry.getKey()).trim();String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());String[] var10 = factoryImplementationNames;int var11 = factoryImplementationNames.length;for(int var12 = 0; var12 < var11; ++var12) {String factoryImplementationName = var10[var12];((List)result.computeIfAbsent(factoryTypeName, (key) -> {return new ArrayList();})).add(factoryImplementationName.trim());}}}result.replaceAll((factoryType, implementations) -> {return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));});cache.put(classLoader, result);return result;} catch (IOException var14) {throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);}}}
spring.factories在很多jar包中都有,也可以自己定义
我们要导入的就是所有这些文件里对应的key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的(截图文件是org\springframework\boot\spring-boot-autoconfigure\2.4.13\spring-boot-autoconfigure-2.4.13.jar!\META-INF\spring.factories)
讲到这我们就得继续回到开头的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);}
过滤完之后SpringBoot原生的应该有23个
我这里有自定义的,所以有74个
总结
SpringBoot通过@SpringBootApplication注解开启自动配置,利用AutoConfigurationImportSelector类动态加载spring.factories文件中指定的自动配置类,同时结合条件注解控制Bean的创建和配置类的激活,从而实现了自动装配。这种机制极大地简化了Spring应用程序的配置过程,提高了开发效率。