SpringBoot自动配置原理及自定义启动器

现在 SpringBoot 框架应用越来越广泛,提供的自动化配置大大减轻了开发者的配置工作,那今天就来探究一下 SpringBoot 内部是怎么实现自动化配置的,在弄清楚自动化配置之后,也会再讲述下自定义一个 SpringBoot 启动器流程。

一、SpringBoot 自动配置原理

1、@SpringBootApplication启动类注解

一切都要从SpringBoot的启动类上的@SpringBootApplication注解说起,代码如下

@SpringBootApplication
public class SpringCloudDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringCloudDemoApplication.class, args);
    }
}

@SpringBootApplication注解类里面代码如下

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
  ...
}

其中有三个比较重要的注解要说一下,分别是@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan

2、@ComponentScan注解

该注解的作用是扫描指定的包路径及其子包路径中的Spring组件(例如被@Component@Service@Repository@Controller注解标注的类),并将它们注册为Spring应用上下文中的Bean。内部精简后的代码如下

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(ComponentScans.class) // 允许多次使用
public @interface ComponentScan {
    String[] value() default {}; // 指定基础扫描包路径
    String[] basePackages() default {}; // 等价于 value 属性
    Class<?>[] basePackageClasses() default {}; // 指定扫描包中的类作为基准
    Filter[] includeFilters() default {}; // 包含的过滤器
    Filter[] excludeFilters() default {}; // 排除的过滤器
    boolean lazyInit() default false; // 是否延迟初始化,true,则扫描到的 Bean 在第一次使用时才初始化
}

具体工作流程可归纳如下

class ComponentScanAnnotationParser {
  ...
	public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
		ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(...);
    ...
    scanner.setBeanNameGenerator..
    scanner.setScopedProxyMode...
    scanner.setScopeMetadataResolver...
    scanner.setResourcePattern...
    scanner.addIncludeFilter...
    scanner.addExcludeFilter...
    scanner.getBeanDefinitionDefaults().setLazyInit...
    ...
		return scanner.doScan(StringUtils.toStringArray(basePackages));
	}
  ...
}
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
	...
	protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		// 遍历所有指定的包路径
		for (String basePackage : basePackages) {
			// 扫描包路径,并通过 isCandidateComponent 方法过滤出符合条件的类。
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			for (BeanDefinition candidate : candidates) {
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				candidate.setScope(scopeMetadata.getScopeName());
				// 生成 Bean 的名称(通常是类名的简写或自定义名称)
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				if (candidate instanceof AnnotatedBeanDefinition) {
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				// 检查是否需要进行额外的处理(如是否符合注册条件)
				if (checkCandidate(beanName, candidate)) {
					// 创建 BeanDefinitionHolder,用于存储 BeanDefinition 和 Bean 的名称
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					// 如果需要,为 Bean 设置代理增强功能(例如 AOP 的作用)
					definitionHolder =
							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					// 将 BeanDefinitionHolder 添加到集合中
					beanDefinitions.add(definitionHolder);
					// 将当前 BeanDefinition 注册到 BeanFactory(核心注册步骤)
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}
  ...
}

3、@EnableAutoConfiguration注解

该注解作用是启用SpringBoot的自动配置机制。通过这个注解,SpringBoot可以根据应用程序的类路径依赖和环境配置自动装配Spring应用上下文中的配置Bean,从而减少了繁琐的手动配置。内部精简后的代码如下

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage // 自动注册基础包
@Import(AutoConfigurationImportSelector.class) // 导入自动配置选择器
public @interface EnableAutoConfiguration {
    String[] exclude() default {}; // 排除特定的自动配置类
    Class<?>[] excludeName() default {}; // 排除特定的自动配置类(通过类名)
}

3.1 @Import注解

@ImportSpring框架中的一个重要注解,用于将一个或多个类导入到当前Spring容器中,并根据导入类的类型执行不同的逻辑。

它支持以下几种类型:

3.2 @AutoConfigurationPackage注解

用于注册指定包及其子包为组件扫描路径,使得这些包下的类可以自动被Spring容器管理。内部代码如下

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
	String[] basePackages() default {};
	Class<?>[] basePackageClasses() default {};
}

使用@Import导入了AutoConfigurationPackages.Registrar,这是一个ImportBeanDefinitionRegistrar实现类,负责将指定包及其子包注册为组件扫描路径。

public abstract class AutoConfigurationPackages {
  ...
	static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
		}
    ...
	}
  ...
}

断点调试下看看metadata信息

register

结论:@AutoConfigurationPackage就是将主配置类(@SpringBootApplication标注的类)所在的包及其子包注册为组件扫描路径。

3.3 @Import(AutoConfigurationImportSelector.class)注解

这个是将自动配置类注入进容器的核心注解。

使用@Import导入了AutoConfigurationImportSelector.class,自动配置类注入容器的核心。内部代码如下

public class AutoConfigurationImportSelector implements ... {
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		// 检查自动配置功能是否启用
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		// 获取自动配置条目封装实体
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		// 返回自动配置条目,Spring会加载这些配置类
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
	...
	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);
		// 将条目封装 AutoConfigurationEntry 实体
		return new AutoConfigurationEntry(configurations, exclusions);
	}
	...
	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		// 加载配置类下面指定key的配置条目,key为EnableAutoConfiguration的全名(包括类路径)
		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;
	}
	...
  protected Class<?> getSpringFactoriesLoaderFactoryClass() {
		return EnableAutoConfiguration.class;
	}
}

我们看到内部关键方法调用流程如下

最后一步把EnableAutoConfiguration.class作为factoryType传了下去,下面看看SpringFactoriesLoader里面的代码

public final class SpringFactoriesLoader {
    ...
    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoader == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }

        // 获取传入进来的factoryType的全名(包括类路径)
        String factoryTypeName = factoryType.getName();
        // 首先获取配所有的配置条目,然后取出factoryType的全名为key的配置值
        return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }

    private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        Map<String, List<String>> result = (Map)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            Map<String, List<String>> result = new HashMap();

            try {
                // 获取到项目包及其所以依赖子包下面的 META-INF/spring.factories 文件
                Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");
                // 解析这些文件下面的配置,并将结果缓存到 result 里面
                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()) {
                        Map.Entry<?, ?> entry = (Map.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);
            }
        }
    }
}

方法执行流程如下

最后我们看下配置文件里面都写了些啥,首先我们直接看SpringBoot的自动配置文件

auto-configure1

auto-configure2

由上面代码我们分析出,首先加载当前项目类路径及其所有依赖子包路径下面的META-INF/spring.factories文件,解析里面的配置,然后取出里面以org.springframework.boot.autoconfigure.EnableAutoConfigurationkey的那些配置,也就是上面图中红线全部来的部分。

当然SpringBoot配置了很多配置类,截图只截了部分,大家想看的话自己点进去看。

那么这么多自动配置类,我们随便抽一个自动配置类,进去看看都是怎么写的,那就看看RedisAutoConfiguration自动配置类是怎么写的

RedisAutoConfiguration

这个配置类上面@Conditional注解来进行判断,满足条件的话才会将该配置类加载进容器中,然后再根据里面Bean上面的@Conditional来选择性加载对应的Bean进容器中

这里补充下@Conditional注解及其衍生的注解

二、自定义 SpringBoot 启动器

一般SpringBoot官方提供的启动器都是spring-boot-starter-xx,我们自定义的一般是xx-spring-boot-starter

下面我们用伪代码封装一个阿里支付 SDK 的启动器,名字就叫ali-pay-spring-boot-starter

1、创建ali-pay-spring-boot-starter工程

ali-pay-project

2、配置pom.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.ali.pay</groupId>
    <artifactId>ali-pay-spring-boot-starter</artifactId>
    <dependencies>
        <!-- Spring Boot Starter 基础库 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
    </dependencies>
</project>

3、定义Properties文件

用于在配置文件中(yml)进行配置

@Data
@ConfigurationProperties(prefix = "pay.ali")
public class AliProperties {

	/** 是否开启支付宝支付功能,不配置则默认不开启,参考AliPayConfiguration类上的@ConditionalOnProperty注解 */
	private Boolean enable;

	/** APPID */
	private String appId;
}

4、编写配置类

用于配置和注册 Bean,也就是具体的接口实现类实例

@Configuration
@EnableConfigurationProperties(AliProperties.class)
@ConditionalOnProperty(value = "pay.ali.enable", havingValue = "true")
public class AliPayConfiguration {

	private final AliProperties properties;

	public AliPayConfiguration(AliProperties properties) {
		this.properties = properties;
	}

	@Bean
	public AliPayClient aliStandardPayClient() {
		return new AliPayClientImpl(properties);
	}
}

5、编写功能接口

用于定义功能实现规范

public interface AliPayClient {
  Response createPayOrder(Request request);
}

5、编写功能类

用于实现具体功能,可以有不同的实现类

public class AliPayClientImpl {

	private final AliProperties properties;

	public AliPayClientImpl(AliProperties properties) {
		this.properties = properties;
		// 初始化相关
	}

	public Response createPayOrder(Request request) {
		// 创建支付账单
	}
}

6、创建并编写spring.factories文件

该文件放在src/main/resources/META-INF目录下

因为Starter的包名跟项目包名应该不一致,所以SpringBoot没法扫描和注入`Starter 里面的配置,

SpringBoot程序在启动时会通过@EnableAutoConfiguration注解扫描所有项目和项目引用 jar 包里面的classpath路径下的META-INF/spring.factories文件中自定义的配置类,将对应配置加载进容器中

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ali.pay.AliPayConfiguration

7、使用