我想你也在真实面试中被问过无数次这个问题了,我也是,但是不管你怎么搜,都只有那几篇八股文的答案,你问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应用程序的配置过程,提高了开发效率。