目录
一、引入
SpringBoot
以其强大的自动装配功能简化了应用搭建的复杂性,让开发者能够更高效地构建应用。那么,SpringBoot是如何实现这种“智能配置”的呢?本文将深入探讨SpringBoot的自动装配原理,揭示其背后的“魔法”,帮助大家更好地理解和运用这一工具。
二、基本流程
自动配置流程图
SpringBoot的自动装配主要依赖于Spring框架的条件配置(Conditional Configuration)和Java的配置类(Java Config)功能。以下是自动装配的基本原理:
- 启动类注解:SpringBoot应用的启动类上通常会有一个@SpringBootApplication注解,这是一个复合注解,它包括了@EnableAutoConfiguration,正是这个注解开启了自动装配的功能。
- 自动配置类:在SpringBoot的jar包中,包含了许多以META-INF/spring.factories文件指定的自动配置类。这些类上通常会有@Configuration注解,表明它们是用来定义Bean的配置类。
- 条件化配置:这些自动配置类使用了诸如@ConditionalOnClass、@ConditionalOnBean、@ConditionalOnMissingBean等条件注解,来决定是否创建和配置某个Bean。这些条件注解根据类路径中是否存在某个类、是否已经定义了某个Bean等条件来决定配置是否生效。
- 依赖注入:当自动配置类中的条件满足时,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));
}
}
- 第一步,我们需要获取被注解标记的类,即***Application。
- 在这个类所在的包及其子包中,我们将扫描所有的类文件。
- 当扫描到这些类时,我们将把它们导入到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;
}
获取配置集合,主要流程如下:
检查缓存:首先,程序会检查缓存(例如一个内存中的数据结构,如ConcurrentHashMap)中是否已经有需要的配置集合。如果有,则直接返回,避免重复加载和解析配置文件。
加载资源:如果缓存中没有找到配置,程序会利用ClassLoader的getResources方法从类路径(classpath)中加载所有匹配的资源文件。对于Spring
Boot来说,这通常是查找所有META-INF/spring.factories文件。解析文件:加载到资源文件后,程序会解析这些文件的内容。这通常涉及读取文件的每一行,并将每一行解析成key=value的格式,然后保存到集合中。
缓存结果:解析完所有文件后,程序会将结果保存到缓存中,以便下次可以直接从缓存中获取,无需再次加载和解析文件。
返回结果:最后,程序返回解析后的配置集合,通常是一个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的大致自动配置流程可以描述为以下几个步骤:
- 首先,它会在classpath中扫描所有的META-INF/spring.factories配置文件。
- 接着,它会从这些配置文件中提取所有以org.springframework.boot.autoconfigure.EnableAutoConfiguration为key的配置项(注意,这里的key应严格匹配,包括大小写,且不应有笔误,如EnableautoConfiguration应为EnableAutoConfiguration)。
- 最后,通过Java反射机制,它会实例化那些标注有@Configuration注解的Java配置类,这些类采用JavaConfig的方式定义了IoC容器的配置。完成实例化后,这些配置会被统一注册到Spring的IoC容器中,从而完成了自动配置的过程。