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());@Overridepublic 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/falsereturn 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:

@Overridepublic 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.mzph.cn/diannao/11235.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

沃尔玛自养号测评的优势是什么?有哪些技术要求

沃尔玛自养号测评的优势主要体现在以下几个方面&#xff1a; 1. 可控性强&#xff1a;自养号测评允许卖家完全掌控测评流程&#xff0c;包括账号的创建、管理、使用等&#xff0c;可以根据需要随时调整指定测评周期&#xff0c;确保测评效果最大化。 2. 安全性高&#xff1a;…

ae如何导出mp4格式?图文教程,手把手教您搞定

在创作精彩的视频内容后&#xff0c;将其成功导出为通用的MP4格式是确保作品在不同平台上流畅播放的重要一环。Adobe After Effects作为一款专业的视频后期制作工具&#xff0c;提供了丰富的功能来实现这一目标。在本文中&#xff0c;我们将通过图文教程&#xff0c;手把手地向…

牛客 二叉树 NB20 翻转牛群结构

[原题连接](翻转牛群结构_牛客题霸_牛客网 (nowcoder.com)) 这道题简单来讲就是给着棵树翻个面, 翻面后各个节点之间不会有子节点的交换, 但是每个节点的左右节点会做交换, 所以我们采用层序遍历来遍历二叉树, 在遍历的过程中交换每个节点的左右节点即可 public class Solutio…

Linux禁用危险命令和防止误操作

禁用rm命令 编辑/etc/profile文件&#xff0c;结尾添加 ###### rm prevent ###### alias rmecho can not use rm command使用source命令生效 source /etc/profile效果 使用mv命令代替rm命令 将需要删除的文件移动到特定的目录&#xff0c;比如/home/sharedir/ 在.bashrc目…

臻奶惠:社区牛奶直供领航者

在当今中国经济转型升级的紧要关头&#xff0c;随着人口红利的逐步减弱&#xff0c;消费升级趋势日益显著&#xff0c;传统行业面临着前所未有的变革与重组。在此背景下&#xff0c;臻奶惠凭借其独到的市场洞察力和前瞻的战略布局&#xff0c;聚焦于健康消费的新蓝海&#xff0…

spring cloud alibaba、spring cloud和springboot三者的版本兼容

官方版本说明地址: 版本说明 alibaba/spring-cloud-alibaba Wiki GitHub 组件版本关系 每个 Spring Cloud Alibaba 版本及其自身所适配的各组件对应版本如下表所示(注意,Spring Cloud Dubbo 从 2021.0.1.0 起已被移除出主干,不再随主干演进): Spring Cloud Alibaba Ve…

Multsim仿真电路:(十七)DC-DC降压电路原理简单仿真

1.前言 由于日常工作中&#xff0c;降压电路用的比较多&#xff0c;所以我只对降压DC-DC进行仿真&#xff0c;本质上还是自己学习记录&#xff0c;因为发现越深入要了解的东西就会越多&#xff0c;慢慢就脱离我现在使用的范畴&#xff0c;就又会变成空空的学习&#xff0c;所以…

社交媒体数据恢复:密聊猫

一、概述 密聊猫是一款提供多种优质体验的手机社交聊天软件。通过这款软件&#xff0c;用户可以享受到多种不同的乐趣体验&#xff0c;如真人在线匹配、真实的交友体验等。同时&#xff0c;密聊猫也提供了数据恢复功能&#xff0c;帮助用户找回丢失的数据。 二、数据恢复步骤…

黑马甄选离线数仓项目day01(项目介绍)

课程介绍 项目名称 黑马甄选数仓形式 离线数仓开发业务类型 电商业务 电商介绍 B2B B2C C2C 项目属于 新零售电商 新零售 线上(网站,app,小程序&#xff09; 线下&#xff08;实体体验店&#xff09; 物流&#xff08;自营物流&#xff09; 项目行业 果蔬生鲜领域 商业模式 B…

文章解读与仿真程序复现思路——电力自动化设备EI\CSCD\北大核心《规模化屋顶光伏接入配电网的建设决策》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

数据结构---经典链表OJ

乐观学习&#xff0c;乐观生活&#xff0c;才能不断前进啊&#xff01;&#xff01;&#xff01; 我的主页&#xff1a;optimistic_chen 我的专栏&#xff1a;c语言 点击主页&#xff1a;optimistic_chen和专栏&#xff1a;c语言&#xff0c; 创作不易&#xff0c;大佬们点赞鼓…

使用 CloudFlare 后如何才能不影响搜索引擎蜘蛛爬虫

今天,明月给大家再次详细讲解一下,明月在使用 CloudFlare 后如何才能不影响搜索引擎蜘蛛爬虫对站点的抓取,因为这是很多首次使用 CloudFlare 的站长们容易忽略和触犯的问题,并不是 CloudFlare 不友好,而是 CloudFlare 的防火墙(WAF)实在是太给力。其实在【CloudFlare 如…

java项目之共享汽车管理系统(springboot+mysql+vue)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的共享汽车管理系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 共享汽车管理系统的主要…

为什么推荐将 IoTDB 服务地址配置为 HostName 而非 IP?

设置主机名启动 IoTDB 可在不修改配置情况下&#xff0c;在不同环境运行 IoTDB 并实现多次部署。 01 前言 IoTDB 在配置启动时有两种方式&#xff1a; 1. 通过设置 HostName&#xff08;主机名&#xff09;的方式来启动 IoTDB&#xff08;推荐方式&#xff09;&#xff1b; 2. …

CSS - 选择器

目录 一、CSS的基本语法格式&#xff1a; 二、常见的CSS选择器 ​编辑1.标签选择器 2.类选择器 3.id选择器 4.复合选择器 5.通用选择器 三、常见的CSS样式 1.color 2.font-size 3.border 4.width/height 5.padding 6.margin 四、CSS的引入方式 1.行内引入 …

Tableau-BI仪表盘搭建

目录 经营数据总览 经营数据详情 每日营收数据 每日流量数据 新老客占比 平台占比 门店占比 投放情况 订单分布 配送分布 汇总搭建仪表板 构思仪表盘布局 经营数据总览 数据总览表&#xff0c;显示的是数据&#xff0c;就拖入文本中&#xff0c;其他同样加入到已经…

开源免费的定时任务管理系统:Gocron

Gocron&#xff1a;精准调度未来&#xff0c;你的全能定时任务管理工具&#xff01;- 精选真开源&#xff0c;释放新价值。 概览 Gocron是github上一个开源免费的定时任务管理系统。它使用Go语言开发&#xff0c;是一个轻量级定时任务集中调度和管理系统&#xff0c;用于替代L…

JavaEE初阶-多线程5

文章目录 一、线程池1.1 线程池相关概念1.2 线程池标准类1.3 线程池工厂类1.4 实现自己的线程池 二、定时器2.1 java标准库中的定时器使用2.2 实现一个自己的定时器2.2.1 定义任务类2.2.2 定义定时器 一、线程池 1.1 线程池相关概念 池这个概念在计算机中比较常见&#xff0c…

[笔试训练](十九)

目录 055:小易的升级之路 056:礼物的最大价值 057:对称之美 055:小易的升级之路 小易的升级之路_牛客题霸_牛客网 (nowcoder.com) 题目&#xff1a; 题解&#xff1a; 根据题意简单模拟即可&#xff0c;可单独写gcd函数求最大公因数。 int gcd(int a, int b) { if (…

数字水印 | 基于小波变换的数字水印技术

&#x1f34d;原文&#xff1a; 基于小波变换的数字水印技术 &#x1f34d;写在前面&#xff1a; 本文属搬运博客&#xff0c;自己留存学习。 正文 小波变换 把一个信号分解成由基本小波经过移位和缩放后的一系列小波&#xff0c;它是一种 “时间——尺度” 信号的多分辨率分…