一、引入
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容器中,从而完成了自动配置的过程。