SpringBoot条件注解原理

简介: 可以看到isPresent的逻辑是通过FilteringSpringBootCondition.resolve(className, classLoader); 来尝试加载该类,如果能正常加载,则代表该类存在,如果不能则代表该类不存在。

SpringBoot封装了很多基于Spring Framework中的Conditional类的实现。


如@ConditionalOnClass,@ConditionalOnBean …等等


这些注解是从何而来的呢?


关于Spring Framework中的Conditional


public @interface Conditional {
    Class<? extends Condition>[] value();
}

内部需要一个继承自Condition类的实现来判断是否将该Bean注入到Spring容器中

public interface Condition {
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

其中matches方法用于判断是否将该Bean注入到Spring容器中,当matches方法返回true则将该Bean注入到Spring容器中

关于SpringBootCondition(所有SpringBoot条件注解的根)

public abstract class SpringBootCondition implements Condition {
    
    public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        
        String classOrMethodName = getClassOrMethodName(metadata);
        try {
            ConditionOutcome outcome = this.getMatchOutcome(context, metadata);
            this.logOutcome(classOrMethodName, outcome);
            this.recordEvaluation(context, classOrMethodName, outcome);
            return outcome.isMatch();
        } catch (NoClassDefFoundError var5) {
            throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to " + var5.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)", var5);
        } catch (RuntimeException var6) {
            throw new IllegalStateException("Error processing condition on " + this.getName(metadata), var6);
        }
    }
}

SpringBootCondition实现了Condition接口,并为matches方法定义了一个判断的框架。


其中matches方法如上:


getClassOrMethodName()获取条件注解写在了哪个类或者哪个方法上

ConditionOutcome outcome = this.getMatchOutcome(context, metadata);

该方法用于获取条件的判断结果。其中getMatchOutcome是一个抽象方法,留给子类去实现。


ConditionOutcome中包含boolean的match属性和ConditionMessage message属性。用于记录条件判断的信息。


this.logOutcome(classOrMethodName, outcome); 用于日志记录

this.recordEvaluation(context, classOrMethodName, outcome);

将判断结果记录到ConditionEvalutionReport中


ConditionEvalutionReportLoggingListener会在收到ContextRefreshedEvent事件后把匹配结果用日志打印出来


关于ConditionalOnClass

该注解的作用是当某个特定的类存在时,该判断为true,如SpringBoot内嵌容器中,如果Tomcat的某个类存在,则默认容器为Tomcat



@Conditional({OnClassCondition.class})
public @interface ConditionalOnClass {
    Class<?>[] value() default {};

    String[] name() default {};
}

ConditionOnClass是一个组合注解,该注解是由SpringFramework 中Conditional注解标注,所以OnClassCondition必须是继承自Condition类的。


OnClassCondition类定义了该ConditionalOnClass的匹配逻辑。

value属性定义了Class类型的值,是一个数组.如果该Class在项目中存在,则判断为true

name属性Class类的全路径地址,如果ClassLoader加载该类成功,则判断为true

关于OnClassCondition

SpringBootCondition --> FilteringSpringBootCondition --> OnClassCondition


上面分析过SpringBootCondition的匹配逻辑,这里着重看OnClassCondition重载SpringBootCondition的中关于匹配的逻辑方法 ``getMatchOutcome`



public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
        ClassLoader classLoader = context.getClassLoader();
        ConditionMessage matchMessage = ConditionMessage.empty();
        //拿到ConditionalOnClass注解中的value值,要判断是否存在的类名
        List<String> onClasses = this.getCandidates(metadata, ConditionalOnClass.class);
        List onMissingClasses;
        if (onClasses != null) {
          //判断OnClass中不存在的类
            onMissingClasses = this.filter(onClasses, ClassNameFilter.MISSING, classLoader);
            //如果有类确实,则表示不匹配
            if (!onMissingClasses.isEmpty()) {
                return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class, new Object[0]).didNotFind("required class", "required classes").items(Style.QUOTE, onMissingClasses));
            }

            matchMessage = matchMessage.andCondition(ConditionalOnClass.class, new Object[0]).found("required class", "required classes").items(Style.QUOTE, this.filter(onClasses, ClassNameFilter.PRESENT, classLoader));
        }

    //下面是ConditionalOnMissingClass的逻辑
        onMissingClasses = this.getCandidates(metadata, ConditionalOnMissingClass.class);
        if (onMissingClasses != null) {
            List<String> present = this.filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);
            if (!present.isEmpty()) {
                return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class, new Object[0]).found("unwanted class", "unwanted classes").items(Style.QUOTE, present));
            }

            matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class, new Object[0]).didNotFind("unwanted class", "unwanted classes").items(Style.QUOTE, this.filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));
        }

        return ConditionOutcome.match(matchMessage);
    }

List onClasses = this.getCandidates(metadata, ConditionalOnClass.class);

拿到ConditionalOnClass注解中的value值,要判断是否存在的类名


onMissingClasses = this.filter(onClasses, ClassNameFilter.MISSING, classLoader);

判断OnClass中不存在的类


如果判断某个类不存在,filter方法




protected final List<String> filter(Collection<String> classNames, ClassNameFilter classNameFilter, ClassLoader classLoader) {
        if (CollectionUtils.isEmpty(classNames)) {
            return Collections.emptyList();
        } else {
            List<String> matches = new ArrayList(classNames.size());
            Iterator var5 = classNames.iterator();

            while(var5.hasNext()) {
                String candidate = (String)var5.next();
                if (classNameFilter.matches(candidate, classLoader)) {
                    matches.add(candidate);
                }
            }

            return matches;
        }
    }

这里ClassNameFilter传入的是Missing,在ClassNameFilter源码中,主要通过isPresent判断

  protected static enum ClassNameFilter {
        PRESENT {
            public boolean matches(String className, ClassLoader classLoader) {
                return isPresent(className, classLoader);
            }
        },
        MISSING {
            public boolean matches(String className, ClassLoader classLoader) {
                return !isPresent(className, classLoader);
            }
        };

        private ClassNameFilter() {
        }

        abstract boolean matches(String className, ClassLoader classLoader);

        static boolean isPresent(String className, ClassLoader classLoader) {
            if (classLoader == null) {
                classLoader = ClassUtils.getDefaultClassLoader();
            }

            try {
                
                FilteringSpringBootCondition.resolve(className, classLoader);
                return true;
            } catch (Throwable var3) {
                return false;
            }
        }
    }

resoleve方法如下

protected static Class<?> resolve(String className, ClassLoader classLoader) throws ClassNotFoundException {
        return classLoader != null ? Class.forName(className, false, classLoader) : Class.forName(className);
}

可以看到isPresent的逻辑是通过FilteringSpringBootCondition.resolve(className, classLoader); 来尝试加载该类,如果能正常加载,则代表该类存在,如果不能则代表该类不存在。

目录
相关文章
|
3月前
|
缓存 监控 Java
SpringBoot @Scheduled 注解详解
使用`@Scheduled`注解实现方法周期性执行,支持固定间隔、延迟或Cron表达式触发,基于Spring Task,适用于日志清理、数据同步等定时任务场景。需启用`@EnableScheduling`,注意线程阻塞与分布式重复问题,推荐结合`@Async`异步处理,提升任务调度效率。
600 128
|
3月前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
1316 0
|
3月前
|
XML 安全 Java
使用 Spring 的 @Aspect 和 @Pointcut 注解简化面向方面的编程 (AOP)
面向方面编程(AOP)通过分离横切关注点,如日志、安全和事务,提升代码模块化与可维护性。Spring 提供了对 AOP 的强大支持,核心注解 `@Aspect` 和 `@Pointcut` 使得定义切面与切入点变得简洁直观。`@Aspect` 标记切面类,集中处理通用逻辑;`@Pointcut` 则通过表达式定义通知的应用位置,提高代码可读性与复用性。二者结合,使开发者能清晰划分业务逻辑与辅助功能,简化维护并提升系统灵活性。Spring AOP 借助代理机制实现运行时织入,与 Spring 容器无缝集成,支持依赖注入与声明式配置,是构建清晰、高内聚应用的理想选择。
459 0
|
2月前
|
JavaScript Java Maven
【SpringBoot(二)】带你认识Yaml配置文件类型、SpringMVC的资源访问路径 和 静态资源配置的原理!
SpringBoot专栏第二章,从本章开始正式进入SpringBoot的WEB阶段开发,本章先带你认识yaml配置文件和资源的路径配置原理,以方便在后面的文章中打下基础
328 3
|
2月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
426 2
|
3月前
|
Java 测试技术 数据库
使用Spring的@Retryable注解进行自动重试
在现代软件开发中,容错性和弹性至关重要。Spring框架提供的`@Retryable`注解为处理瞬时故障提供了一种声明式、可配置的重试机制,使开发者能够以简洁的方式增强应用的自我恢复能力。本文深入解析了`@Retryable`的使用方法及其参数配置,并结合`@Recover`实现失败回退策略,帮助构建更健壮、可靠的应用程序。
438 1
使用Spring的@Retryable注解进行自动重试
|
3月前
|
XML Java 数据格式
常用SpringBoot注解汇总与用法说明
这些注解的使用和组合是Spring Boot快速开发和微服务实现的基础,通过它们,可以有效地指导Spring容器进行类发现、自动装配、配置、代理和管理等核心功能。开发者应当根据项目实际需求,运用这些注解来优化代码结构和服务逻辑。
317 12
|
3月前
|
传感器 Java 数据库
探索Spring Boot的@Conditional注解的上下文配置
Spring Boot 的 `@Conditional` 注解可根据不同条件动态控制 Bean 的加载,提升应用的灵活性与可配置性。本文深入解析其用法与优势,并结合实例展示如何通过自定义条件类实现环境适配的智能配置。
201 0
探索Spring Boot的@Conditional注解的上下文配置
|
3月前
|
智能设计 Java 测试技术
Spring中最大化@Lazy注解,实现资源高效利用
本文深入探讨了 Spring 框架中的 `@Lazy` 注解,介绍了其在资源管理和性能优化中的作用。通过延迟初始化 Bean,`@Lazy` 可显著提升应用启动速度,合理利用系统资源,并增强对 Bean 生命周期的控制。文章还分析了 `@Lazy` 的工作机制、使用场景、最佳实践以及常见陷阱与解决方案,帮助开发者更高效地构建可扩展、高性能的 Spring 应用程序。
153 0
Spring中最大化@Lazy注解,实现资源高效利用
|
3月前
|
安全 IDE Java
Spring 的@FieldDefaults和@Data:Lombok 注解以实现更简洁的代码
本文介绍了如何在 Spring 应用程序中使用 Project Lombok 的 `@Data` 和 `@FieldDefaults` 注解来减少样板代码,提升代码可读性和可维护性,并探讨了其适用场景与限制。
155 0
Spring 的@FieldDefaults和@Data:Lombok 注解以实现更简洁的代码