SpringBoot自动配置源码解析+自定义Spring Boot Starter

@SpringBootApplication

        Spring Boot应用标注 @SpringBootApplication 注解的类说明该类是Spring Boot 的主配置类,需要运行该类的main方法进行启动 Spring Boot 应用

@SpringBootConfiguration

       该注解标注表示标注的类是个配置类

@EnableAutoConfiguration

        直译:开启自动配置

@AutoConfigurationPackage

        将当前配置类所在的包保存在 basePackages 的Bean 中,提供给Spring 使用

@Import(AutoConfigurationPackages.Registrar.class)

      注册一个保存当前配置类所在包的Bean

@Import(AutoConfigurationImportSelector.class)

        使用@Import注解完成导入AutoConfigurationImportSelector类 的功能。AutoConfigurationImportSelector 实现了DeferredImportSelector类

注:在解析ImportSelector时,所导入的配置类会被直接解析,而DeferredImportSelector导入的配置类会延迟进行解析(延迟在其他配置类都解析完之后)

        Spring容器在解析@Import时会去执行DeferredImportSelector 的 selectImports方法:

/**
	 * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
	 * of the importing {@link Configuration @Configuration} class.
	 * @param annotationMetadata the annotation metadata of the configuration class
	 * @return the auto-configurations that should be imported
	 */
	protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
        // 从META-INF/spring.factories中获得候选的自动配置类
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        //去重
		configurations = removeDuplicates(configurations);
        //根据EnableAutoConfiguration注解中exclude、excludeName属性、
        //spring.autoconfigure.exclude 配置的排除项
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
        // 通过读取spring.factories 中        
        // OnBeanCondition\OnClassCondition\OnWebApplicationCondition进行过滤
		configurations = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

        springboot应用中都会引入spring-boot-autoconfigure依赖,spring.factories文件就在该包的META-INF下面。spring.factories文件是Key=Value形式,多个Value时使用“,”逗号进行分割,该文件中定义了关于初始化、监听器、过滤器等信息,而真正使自动配置生效的key是org.springframework.boot.autoconfigure.EnableAutoConfiguration,如下所示:

Spring Boot 提供的自动配置类

   https://docs.spring.io/spring-boot/docs/current/reference/html/auto-configuration-classes.html#appendix.auto-configuration-classes

Spring Boot 常用条件注解

        ConditionalOnBean:是否存在某个某类或某个名字的Bean
        ConditionalOnMissingBean:是否缺失某个某类或某个名字的Bean
        ConditionalOnSingleCandidate:是否符合指定类型的Bean只有一个
        ConditionalOnClass:是否存在某个类
        ConditionalOnMissingClass:是否缺失某个类
        ConditionalOnExpression:指定的表达式返回的是true还是false
        ConditionalOnJava:判断Java版本
        ConditionalOnWebApplication:当前应用是不是一个Web应用
        ConditionalOnNotWebApplication:当前应用不是一个Web应用
        ConditionalOnProperty:Environment中是否存在某个属性

        我们也可以使用@Conditional注解进行自定义条件注解

        条件注解可以写在类和方法上,如果某个@xxxCondition条件注解写在自动配置类上,那该自动配置类会不会生效就要看当前条件是否符合条件,或者条件注解写在某个@Bean修饰的方法上,那么Bean生不生效也要看当前的条件是否符条件。

        Spring容器在解析某个自动配置类时,会先判断该自动配置类上是否有条件注解,如果有,则进一步判断条件注解所指定的条件当前情况是否满足,如果满足,则继续解析该配置类,如果不满足则不进行解析该配置类,于是配置类所定义的Bean也都得不到解析,那么在Spring容器中就不会存在该Bean。
        同理,Spring在解析某个@Bean的方法时,也会先判断方法上是否有条件注解,然后进行解析,如果不满足条件,则该Bean也不会生效。

        Spring Boot提供的自动配置,实际上就是Spring Boot源码中预先写好准备了一些常用的配置类,预先定义好了一些Bean,在用Spring Boot时,这些配置类就已经在我们项目的依赖中了,而这些自动配置类或自动配置Bean是否生效,就需要看具体指定的条件是否满足。

        下面代码就是根据 @Conditional 确定是否应跳过忽略

/**
	 * Determine if an item should be skipped based on {@code @Conditional} annotations.
	 * @param metadata the meta data
	 * @param phase the phase of the call
	 * @return if the item should be skipped
	 */
	public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
		if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
			return false;
		}

		if (phase == null) {
			if (metadata instanceof AnnotationMetadata &&
					ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
				return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
			}
			return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
		}

		List<Condition> conditions = new ArrayList<>();
		for (String[] conditionClasses : getConditionClasses(metadata)) {
			for (String conditionClass : conditionClasses) {
				Condition condition = getCondition(conditionClass, this.context.getClassLoader());
				conditions.add(condition);
			}
		}

		AnnotationAwareOrderComparator.sort(conditions);

		for (Condition condition : conditions) {
			ConfigurationPhase requiredPhase = null;
			if (condition instanceof ConfigurationCondition) {
				requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
			}
			if ((requiredPhase == null || requiredPhase == phase) && 
// 重点判断
!condition.matches(this.context, metadata)) {
				return true;
			}
		}

		return false;
	}

以@ConditionalOnBean底层工作原理为示例

        OnBeanCondition类继承了FilteringSpringBootCondition,FilteringSpringBootCondition类又继承SpringBootCondition,而SpringBootCondition实现了Condition接口,matches()方法也是在
SpringBootCondition这个类中实现的:

public abstract class SpringBootCondition implements Condition {

	private final Log logger = LogFactory.getLog(getClass());

	@Override
	public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 获取当前解析的类名或方法名
		String classOrMethodName = getClassOrMethodName(metadata);
		try {
// 进行具体的条件匹配,ConditionOutcome表示匹配结果
			ConditionOutcome outcome = getMatchOutcome(context, metadata);
//日志记录匹配结果
			logOutcome(classOrMethodName, outcome);
			recordEvaluation(context, classOrMethodName, outcome);
// 返回匹配结果 true/false
			return outcome.isMatch();
		}
		catch (NoClassDefFoundError ex) {
			throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to "
					+ ex.getMessage() + " not found. Make sure your own configuration does not rely on "
					+ "that class. This can also happen if you are "
					+ "@ComponentScanning a springframework package (e.g. if you "
					+ "put a @ComponentScan in the default package by mistake)", ex);
		}
		catch (RuntimeException ex) {
			throw new IllegalStateException("Error processing condition on " + getName(metadata), ex);
		}
	}
//....
	public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);

}

        具体的条件匹配逻辑在getMatchOutcome方法中实现的,而SpringBootCondition类中的
getMatchOutcome方法是一个抽象方法,具体的实现逻辑就在子类OnBeanCondition:

@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
		ConditionMessage matchMessage = ConditionMessage.empty();
		MergedAnnotations annotations = metadata.getAnnotations();
        // 如果存在ConditionalOnBean注解
		if (annotations.isPresent(ConditionalOnBean.class)) {
			Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class);
			MatchResult matchResult = getMatchingBeans(context, spec);
            // 如果某个Bean不存在
			if (!matchResult.isAllMatched()) {
				String reason = createOnBeanNoMatchReason(matchResult);
				return ConditionOutcome.noMatch(spec.message().because(reason));
			}
            // 所有Bean都存在
			matchMessage = spec.message(matchMessage)
				.found("bean", "beans")
				.items(Style.QUOTE, matchResult.getNamesOfAllMatches());
		}
    // 如果存在ConditionalOnSingleCandidate注解
		if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
			Spec<ConditionalOnSingleCandidate> spec = new SingleCandidateSpec(context, metadata, annotations);
			MatchResult matchResult = getMatchingBeans(context, spec);
			if (!matchResult.isAllMatched()) {
				return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll());
			}
			Set<String> allBeans = matchResult.getNamesOfAllMatches();
			if (allBeans.size() == 1) {
				matchMessage = spec.message(matchMessage).found("a single bean").items(Style.QUOTE, allBeans);
			}
			else {
				List<String> primaryBeans = getPrimaryBeans(context.getBeanFactory(), allBeans,
						spec.getStrategy() == SearchStrategy.ALL);
				if (primaryBeans.isEmpty()) {
					return ConditionOutcome
						.noMatch(spec.message().didNotFind("a primary bean from beans").items(Style.QUOTE, allBeans));
				}
				if (primaryBeans.size() > 1) {
					return ConditionOutcome
						.noMatch(spec.message().found("multiple primary beans").items(Style.QUOTE, primaryBeans));
				}
				matchMessage = spec.message(matchMessage)
					.found("a single primary bean '" + primaryBeans.get(0) + "' from beans")
					.items(Style.QUOTE, allBeans);
			}
		}
        // 存在ConditionalOnMissingBean注解
		if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
			Spec<ConditionalOnMissingBean> spec = new Spec<>(context, metadata, annotations,
					ConditionalOnMissingBean.class);
			MatchResult matchResult = getMatchingBeans(context, spec);
			if (matchResult.isAnyMatched()) {
				String reason = createOnMissingBeanNoMatchReason(matchResult);
				return ConditionOutcome.noMatch(spec.message().because(reason));
			}
			matchMessage = spec.message(matchMessage).didNotFind("any beans").atAll();
		}
		return ConditionOutcome.match(matchMessage);
	}


protected final MatchResult getMatchingBeans(ConditionContext context, Spec<?> spec) {
		ClassLoader classLoader = context.getClassLoader();
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		boolean considerHierarchy = spec.getStrategy() != SearchStrategy.CURRENT;
		Set<Class<?>> parameterizedContainers = spec.getParameterizedContainers();
		if (spec.getStrategy() == SearchStrategy.ANCESTORS) {
			BeanFactory parent = beanFactory.getParentBeanFactory();
			Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent,
					"Unable to use SearchStrategy.ANCESTORS");
			beanFactory = (ConfigurableListableBeanFactory) parent;
		}
		MatchResult result = new MatchResult();
		Set<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, considerHierarchy,
				spec.getIgnoredTypes(), parameterizedContainers);
		for (String type : spec.getTypes()) {
			Collection<String> typeMatches = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, type,
					parameterizedContainers);
			typeMatches
				.removeIf((match) -> beansIgnoredByType.contains(match) || ScopedProxyUtils.isScopedTarget(match));
			if (typeMatches.isEmpty()) {
				result.recordUnmatchedType(type);
			}
			else {
				result.recordMatchedType(type, typeMatches);
			}
		}
		for (String annotation : spec.getAnnotations()) {
			Set<String> annotationMatches = getBeanNamesForAnnotation(classLoader, beanFactory, annotation,
					considerHierarchy);
			annotationMatches.removeAll(beansIgnoredByType);
			if (annotationMatches.isEmpty()) {
				result.recordUnmatchedAnnotation(annotation);
			}
			else {
				result.recordMatchedAnnotation(annotation, annotationMatches);
			}
		}
		for (String beanName : spec.getNames()) {
			if (!beansIgnoredByType.contains(beanName) && containsBean(beanFactory, beanName, considerHierarchy)) {
				result.recordMatchedName(beanName);
			}
			else {
				result.recordUnmatchedName(beanName);
			}
		}
		return result;
	}

        getMatchingBeans方法中会利用BeanFactory去获取指定类型的Bean,如果没有指定类型的Bean,则会将该类型记录在MatchResult对象的unmatchedTypes集合中,如果有该类型的Bean,则会把该Bean的beanName记录在MatchResult对象的matchedNames集合中,所以MatchResult对象中记录了哪些类没有对应的Bean,哪些类有对应的Bean。

        大概流程如下:

        Spring在解析某个配置类,或某个Bean定义时如果发现它们上面用到了条件注解,就会取出所有的条件注解,并生成对应的条件对象,比如OnBeanCondition对象;
        依次调用条件对象的matches方法,进行条件匹配,看是否符合条件
        条件匹配逻辑中,会拿到@ConditionalOnBean等条件注解的信息,判断哪些Bean存在
        然后利用BeanFactory来进行判断
        最后只有所有条件注解的条件都匹配,那么当前Bean定义才算符合条件生效

自定义Spring Boot Starter

        SpringBoot 最强大的功能就是把我们常用的业务场景抽取成了一个个starter(场景启动器),我们通过引入SpringBoot 提供的这些场景启动器,再进行少量的配置就能使用相应的功能。但是,SpringBoot 不能囊括我们所有的业务使用场景,往往我们需要自定义starter,来简化我们对springboot的使用。

        下面是自定义starter的示例代码地址:

lp-springboot-start: 自定义Spring Boot Start

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/608483.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何控制外部用户访问SAP表的权限

今天搞了一天&#xff0c;我就去找找找啊。我们是IDMC要访问BW的表。 Configure SAP user authorization (informatica.com) 这个informatica上面说要连SAP的数据的话&#xff0c;需要设置这些用户权限。 我也没具体看这两权限对象&#xff0c;这个别人已经设置好了。但是表权…

13 华三三层链路聚和

13 华三三层链路聚和 AI 解析 华三三层静态路由是指在华三交换机上配置的一种路由方式。它通过在交换机上手动配置路由表&#xff0c;将不同网络之间的数据进行转发。 华三三层静态路由的配置步骤如下&#xff1a; 1. 配置交换机接口的IP地址&#xff1a;在交换机上选择要配…

生产者与消费者 PV操作 与 阻塞队列

文章目录 普通方式 wait 与 notifyAll消费者生产者桌子测试类运行结果 阻塞队列Cook生产者Customer消费者测试类 普通方式 wait 与 notifyAll 消费者 package abc;public class Customer extends Thread{Overridepublic void run() {while (true) {synchronized (Desk.lock) {…

如何让加快OpenHarmony编译速度?

OpenHarmony 有两种编译方式&#xff0c;一种是通过 hb 工具编译&#xff0c;一种是通过 build.sh 脚本编译。本文笔者将提升 build.sh 方式编译速度的方法整理如下&#xff1a; 因为笔者只用 build.sh 脚本编译&#xff0c;没用过 hb 工具&#xff0c;好像下面的选项也可以用于…

可编程 IP 新星 Story Protocol 何以引领链上文艺复兴浪潮?

当前&#xff0c;随着 Web3 行业发展进入全新阶段&#xff0c;与生成式人工智能&#xff08;AIGC&#xff09;技术融合正在创造潜力新星项目。也是目前的互联网生态下&#xff0c;任何普通民众都有权利创作高质量的音乐、艺术、散文和视频内容&#xff0c;带来了用户生成内容&a…

鸿蒙开发接口Ability框架:【@ohos.application.StartOptions (StartOptions)】

StartOptions StartOptions模块对系统的基本通信组件进行查询和设置的能力。 说明&#xff1a; 本模块首批接口从API version 9 开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 本模块接口仅可在Stage模型下使用。 开发前请熟悉鸿蒙开发指导文档…

idea使用git不提示账号密码登录,而是输入token问题解决

idea 或者 webstream 等全家桶软件 使用git 推送代码时&#xff0c;不提示账号密码登录&#xff0c;而是输入token问题解决 你的代码仓库是gitlab 然后打开修改代码后推送时&#xff0c;会默认使用gitlab插件&#xff0c;所以提示数据token 解决方式就是把gitlab插件取消使用这…

Verilog复习(一)| 模块的定义

模块&#xff08;module&#xff09;是Verilog的基本描述单位&#xff0c;用于描述某个设计的功能或结构&#xff0c;及其与其他模块通信&#xff08;连接&#xff09;的外部端口。 Verilog程序由关键词module和endmodule进行定义。 定义模块的步骤&#xff1a; 定义模块的端…

Windows系统下修改文件夹和U盘图标实战

文章目录 知识学习一、修改磁盘图标第一步、新建.INF文件第二步、放置图标第三步、重新插入U盘第四步、隐藏与显示文件知识拓展 二、修改文件夹图标设置图标样式恢复图标样式 在日常办公中使用的是windows系统&#xff0c;系统默认的文件图标都一样&#xff0c;不利于分类整理&…

MQTT服务搭建及python使用示例

1、MQTT协议 1.1、MQTT介绍 MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;是一种轻量级的、基于发布/订阅模式的通信协议&#xff0c;通常用于物联网设备之间的通讯。它具有低带宽、低功耗和开放性等特点&#xff0c;适合在网络带宽有限或者网络连接不稳定…

利用智能私信软件,快速拓展潜在客户群体

在数字化营销的浪潮中&#xff0c;企业如何快速而有效地触及并吸引潜在客户&#xff0c;已成为一个不可忽视的挑战。随着人工智能技术的不断进步&#xff0c;智能私信软件作为一种新型工具&#xff0c;正逐渐改变着企业的市场拓展方式。本文将探讨如何通过这类软件&#xff0c;…

复现NerfingMVS(更新中)

按以下代码一步步操作 conda create -n NerfingMVS python3.7 conda activate NerfingMVS conda install pytorch1.7.1 torchvision0.8.2 torchaudio0.7.2 -c pytorch pip install -r requirements.txthttps://colmap.github.io/install.html Linux 中 建议的依赖&#xff1…

AI边缘计算盒子优势有哪些?如何实现低延迟处理?

AI边缘计算盒子作为一种集成人工智能技术的边缘计算设备&#xff0c;其优势主要体现在以下几个方面&#xff0c;万物纵横为您详细介绍&#xff1a; 1. 低延迟处理 AI边缘计算盒子靠近数据产生源头&#xff0c;能够即时处理数据&#xff0c;大幅减少数据传输至云端的时间&#…

深入解析算法效率核心:时间与空间复杂度概览及优化策略

算法复杂度&#xff0c;即时间复杂度与空间复杂度&#xff0c;衡量算法运行时资源消耗。时间复杂度反映执行时间随数据规模增长的关系&#xff0c;空间复杂度表明额外内存需求。优化策略&#xff0c;如选择合适数据结构、算法改进、循环展开等&#xff0c;对于提升程序效率、减…

从抖音阳哥的经验看,选品师项目是否值得你投入?

在抖音这个短视频平台上&#xff0c;无数的创作者分享着他们的知识和经验&#xff0c;其中阳哥作为一个备受关注的博主&#xff0c;他的每一次分享都能引起广大网友的热烈讨论。最近&#xff0c;阳哥分享了一个名为“选品师”的项目&#xff0c;让许多对电商和抖音运营感兴趣的…

RK3576 Android平台SD启动

RK3576 Android平台SD启动 要求 1&#xff0c;Android14及以上版本&#xff1b;2&#xff0c;SD卡制作工具&#xff1a;瑞芯微创建升级磁盘工具V1.78及以上版本&#xff1b;3&#xff0c;SD卡容量8G-32G之间&#xff1b; 软件修改 1&#xff0c;Android部分 修改PRODUCT_BO…

Android 的 Timer 和 TimerTask

Timer 简介(来自Gemini) Timer 是 Java 中用于创建定时任务的类。它位于 java.util 包中。可以使用 Timer 来安排一次性或定期执行的任务。 每个 Timer 对象都对应一个后台线程。此线程负责从任务队列中检索任务并按计划执行它们。 使用 Timer 要使用 Timer&#xff0c;首先…

3D 渲染至少需要多少显存?显存真得越大越好吗?

无论是电影制作、游戏开发还是建筑效果图设计&#xff0c;在今天的计算机图形学领域&#xff0c;都需要强大的图形处理能力。而显存&#xff08;VRAM&#xff09;作为支持这一切的重要组成部分&#xff0c;对于高效3D渲染同样至关重要。在本文中&#xff0c;我们将一起探讨显存…

Cmake-learning

可以把cmake看成一款自动生成 Makefile的工具&#xff0c;所以编译流程就变成了&#xff1a;cmake—>make–>可执行文件 在哪个目录执行cmake txt位置 就会在哪个目录生成构造文件和可执行文件 project(HELLO): 非强制&#xff0c;指明项目名称 add_executable(he…

【算法】滑动窗口——水果成篮

本篇博客是我对“水果成篮”这道题由暴力解法到滑动窗口思路的具体思路&#xff0c;有需要借鉴即可。 目录 1.题目2.暴力求解3.暴力优化3.1每次right不用回退3.2有些left长度一定不如前一个&#xff0c;不用走&#xff0c;left不回退 4.滑动窗口算法5.总结 1.题目 题目链接&am…
最新文章