SpringBoot自动配置源码解析

@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定义才算符合条件生效

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

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

相关文章

Linux Ubuntu(玩客云) qBittorrent docker BT下载(qbittorrent 密码错误无法登录 ip地址被禁止登录等)

提示&#xff1a; 需要提前安装Docker 根据qBittorrent官网的更新日志https://www.qbittorrent.org/news &#xff0c;4.6.1.0包含一个重大更新。可以看到自4.6.1.0开始&#xff0c;qBittorrent将弃用adminadmin默认密码&#xff0c;采用随机密码&#xff0c;将在终端控制台输出…

web前端学习笔记9

9. HTML5新增元素及属性 9.1 HTML5新增结构元素 HTML5引入了几个新的结构元素,极大地改善了网页的组织和结构方式。以下是HTML5中的一些关键新结构元素: 标签说明<header>页面或页面中某一个区块的页眉,通常是一些引导和导航信息<nav>可以作为页面导航的链接组&…

Android build.prop生成过程源码分析

Android的build.prop文件是在Android编译时刻收集的各种property【LCD density/语言/编译时间, etc.】&#xff1b;编译完成之后&#xff0c;文件生成在out/target/product/<board【OK1000】>/system/目录下&#xff1b;在Android运行时刻可以通过property_get()[c/c域] …

10分钟搞懂,Python接口自动化测试-接口依赖-实战教程

一、场景说明 在面试接口自动化时&#xff0c;经常会问&#xff0c;其他接口调用的前提条件是当前用户必须是登录状态&#xff0c;如何处理接口依赖&#xff1f; 在此之前我们介绍过session管理器保存会话状态。如果接口请求需要携带token&#xff0c;那么又如果处理呢&#…

基于点灯Blinker的ESP8266远程网络遥控LED

本文介绍基于ESP8266模块实现的远程点灯操作&#xff0c;手机侧APP选用的是点灯-Blinker&#xff0c;完整资料及软件见文末链接 一、ESP8266模块简介 ESP8266是智能家居等物联网场景下常用的数传模块&#xff0c;具有强大的功能&#xff0c;通过串口转WIFI的方式可实现远距离…

一个圈圈的机制玩法

什么是一个圈圈&#xff0c;说白了就是一个撸广告的平台&#xff0c;只是引入了减产机制&#xff0c;九维机制和分成机制&#xff0c;再加上有央企背景&#xff0c;做的一个区块链平台。 玩法很简单&#xff0c;就是撸广告获取能量&#xff0c;然后获取绿色能量&#xff0c;等…

Vue3 学习 (一)

编码规范 Vue3 通常和 typescript 结合使用 采用 TypeScript 组合式 api setup 语法糖 的形式 一、Vue3 简介 和 Vue2 相比 性能得到提升 源码升级 使用 Proxy 代替 definedProperty 实现响应式 重写虚拟 DOM 的实现和Tree-Shaking 可以更好的支持 TypeScript 新的…

使用Python Pandas实现两表对应列相加(即使表头不同)

目录 引言 Pandas库简介 实现对应列相加 步骤一&#xff1a;加载数据 步骤二&#xff1a;重命名列 步骤三&#xff1a;对应列相加 步骤四&#xff1a;保存结果 案例分析 结论 引言 在数据分析和处理的日常工作中&#xff0c;我们经常会遇到需要将来自不同数据源的数据…

AR人脸美妆SDK解决方案,让妆容更加贴合个人风格

美妆行业正迎来前所未有的变革&#xff0c;为满足企业对高效、精准、创新的美妆技术需求&#xff0c;美摄科技倾力打造了一款企业级AR人脸美妆SDK解决方案&#xff0c;为企业打开美妆领域的新世界大门。 革命性的人脸美妆技术 美摄科技的AR人脸美妆SDK解决方案&#xff0c;不…

应聘项目经理,软考证书会是一个加分项吗?

加分项是必需的&#xff0c;特别是IT行业的项目经理职位。您可以在各大招聘网站上搜索项目经理职位&#xff0c;前景好、薪资高、待遇好的项目经理岗位&#xff0c;基本上都有证书的要求。非IT行业项目经理&#xff0c;可以考虑PMP证书或者其他与专业相关的证书&#xff0c;比如…

深入解析C++的auto自动类型推导(二)

目录 使用auto的好处 新标准新增功能 使用auto的限制 上一篇详细讲解了使用auto关键字进行自动类型推导时的推导规则&#xff0c;这一篇重点讲解auto的使用以及C14、C17、C20等新标准对auto的功能完善&#xff0c;最后再介绍auto的使用限制。上一篇请从这里阅读&#xff1a;…

基于Unity为Vision Pro 构建游戏的4个关键

为Vision Pro开发游戏时需要考虑的四个关键概念:输入的自然性、物理尺寸的真实匹配、交互空间的充足性以及Unity组件的有效利用。 AVP交互小游戏(Capsule Critters)作者分享了使用Unity构建的几个核心关键: Bounded - 游戏定义:Bounded(有限)是Unity的术语,指的是游戏作…