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); 来尝试加载该类,如果能正常加载,则代表该类存在,如果不能则代表该类不存在。

目录
相关文章
|
1月前
|
Java 开发者 Spring
【SpringBoot 异步魔法】@Async 注解:揭秘 SpringBoot 中异步方法的终极奥秘!
【8月更文挑战第25天】异步编程对于提升软件应用的性能至关重要,尤其是在高并发环境下。Spring Boot 通过 `@Async` 注解简化了异步方法的实现。本文详细介绍了 `@Async` 的基本用法及配置步骤,并提供了示例代码展示如何在 Spring Boot 项目中创建与管理异步任务,包括自定义线程池、使用 `CompletableFuture` 处理结果及异常情况,帮助开发者更好地理解和运用这一关键特性。
116 1
|
1月前
|
XML Java 测试技术
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
这篇文章介绍了Spring5框架的三个新特性:支持@Nullable注解以明确方法返回、参数和属性值可以为空;引入函数式风格的GenericApplicationContext进行对象注册和管理;以及如何整合JUnit5进行单元测试,同时讨论了JUnit4与JUnit5的整合方法,并提出了关于配置文件加载的疑问。
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
|
30天前
|
缓存 Java 数据库连接
Spring Boot奇迹时刻:@PostConstruct注解如何成为应用初始化的关键先生?
【8月更文挑战第29天】作为一名Java开发工程师,我一直对Spring Boot的便捷性和灵活性着迷。本文将深入探讨@PostConstruct注解在Spring Boot中的应用场景,展示其在资源加载、数据初始化及第三方库初始化等方面的作用。
49 0
|
4天前
|
Java 应用服务中间件 API
Vertx高并发理论原理以及对比SpringBoot
Vertx 是一个基于 Netty 的响应式工具包,不同于传统框架如 Spring,它的侵入性较小,甚至可在 Spring Boot 中使用。响应式编程(Reactive Programming)基于事件模式,通过事件流触发任务执行,其核心在于事件流 Stream。相比多线程异步,响应式编程能以更少线程完成更多任务,减少内存消耗与上下文切换开销,提高 CPU 利用率。Vertx 适用于高并发系统,如 IM 系统、高性能中间件及需要较少服务器支持大规模 WEB 应用的场景。随着 JDK 21 引入协程,未来 Tomcat 也将优化支持更高并发,降低响应式框架的必要性。
Vertx高并发理论原理以及对比SpringBoot
|
2天前
|
Java Spring 容器
Spring使用异步注解@Async正确姿势
Spring使用异步注解@Async正确姿势,异步任务,spring boot
|
1天前
|
XML Java 数据格式
spring复习03,注解配置管理bean
Spring框架中使用注解配置管理bean的方法,包括常用注解的标识组件、扫描组件、基于注解的自动装配以及使用注解后的注意事项,并提供了一个基于注解自动装配的完整示例。
spring复习03,注解配置管理bean
|
2天前
|
XML 前端开发 Java
控制spring框架注解介绍
控制spring框架注解介绍
|
15天前
|
Java 开发者 数据格式
【Java笔记+踩坑】SpringBoot基础4——原理篇
bean的8种加载方式,自动配置原理、自定义starter开发、SpringBoot程序启动流程解析
【Java笔记+踩坑】SpringBoot基础4——原理篇
|
15天前
|
Java 数据库连接 数据格式
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
IOC/DI配置管理DruidDataSource和properties、核心容器的创建、获取bean的方式、spring注解开发、注解开发管理第三方bean、Spring整合Mybatis和Junit
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
|
1月前
|
Java 数据安全/隐私保护 Spring
揭秘Spring Boot自定义注解的魔法:三个实用场景让你的代码更加优雅高效
揭秘Spring Boot自定义注解的魔法:三个实用场景让你的代码更加优雅高效