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

目录
相关文章
|
25天前
|
Java Spring 容器
如何解决spring EL注解@Value获取值为null的问题
本文探讨了在使用Spring框架时,如何避免`@Value(&quot;${xxx.xxx}&quot;)`注解导致值为null的问题。通过具体示例分析了几种常见错误场景,包括类未交给Spring管理、字段被`static`或`final`修饰以及通过`new`而非依赖注入创建对象等,提出了相应的解决方案,并强调了理解框架原理的重要性。
87 4
|
9天前
|
Java Spring
在使用Spring的`@Value`注解注入属性值时,有一些特殊字符需要注意
【10月更文挑战第9天】在使用Spring的`@Value`注解注入属性值时,需注意一些特殊字符的正确处理方法,包括空格、引号、反斜杠、新行、制表符、逗号、大括号、$、百分号及其他特殊字符。通过适当包裹或转义,确保这些字符能被正确解析和注入。
|
21天前
|
XML Java 数据格式
Spring从入门到入土(bean的一些子标签及注解的使用)
本文详细介绍了Spring框架中Bean的创建和使用,包括使用XML配置文件中的标签和注解来创建和管理Bean,以及如何通过构造器、Setter方法和属性注入来配置Bean。
56 9
Spring从入门到入土(bean的一些子标签及注解的使用)
|
11天前
|
Java Spring 容器
springboot @RequiredArgsConstructor @Lazy解决循环依赖的原理
【10月更文挑战第15天】在Spring Boot应用中,循环依赖是一个常见问题,当两个或多个Bean相互依赖时,会导致Spring容器陷入死循环。本文通过比较@RequiredArgsConstructor和@Lazy注解,探讨它们解决循环依赖的原理和优缺点。@RequiredArgsConstructor通过构造函数注入依赖,使代码更简洁;@Lazy则通过延迟Bean的初始化,打破创建顺序依赖。两者各有优势,需根据具体场景选择合适的方法。
26 4
|
14天前
|
架构师 Java 开发者
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
在40岁老架构师尼恩的读者交流群中,近期多位读者成功获得了知名互联网企业的面试机会,如得物、阿里、滴滴等。然而,面对“Spring Boot自动装配机制”等核心面试题,部分读者因准备不足而未能顺利通过。为此,尼恩团队将系统化梳理和总结这一主题,帮助大家全面提升技术水平,让面试官“爱到不能自已”。
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
|
18天前
|
XML Java 数据库
Spring boot的最全注解
Spring boot的最全注解
|
19天前
|
JSON NoSQL Java
springBoot:jwt&redis&文件操作&常见请求错误代码&参数注解 (九)
该文档涵盖JWT(JSON Web Token)的组成、依赖、工具类创建及拦截器配置,并介绍了Redis的依赖配置与文件操作相关功能,包括文件上传、下载、删除及批量删除的方法。同时,文档还列举了常见的HTTP请求错误代码及其含义,并详细解释了@RequestParam与@PathVariable等参数注解的区别与用法。
|
19天前
|
Java API Spring
springBoot:注解&封装类&异常类&登录实现类 (八)
本文介绍了Spring Boot项目中的一些关键代码片段,包括使用`@PathVariable`绑定路径参数、创建封装类Result和异常处理类GlobalException、定义常量接口Constants、自定义异常ServiceException以及实现用户登录功能。通过这些代码,展示了如何构建RESTful API,处理请求参数,统一返回结果格式,以及全局异常处理等核心功能。
|
25天前
|
Java Spring 容器
Springboot3.2.1搞定了类Service和bean注解同名同类型问题修复
这篇文章讨论了在Spring Boot 3.2.1版本中,同名同类型的bean和@Service注解类之间冲突的问题得到了解决,之前版本中同名bean会相互覆盖,但不会在启动时报错,而在配置文件中设置`spring.main.allow-bean-definition-overriding=true`可以解决这个问题。
60 0
Springboot3.2.1搞定了类Service和bean注解同名同类型问题修复
|
6天前
|
存储 Java 数据管理
强大!用 @Audited 注解增强 Spring Boot 应用,打造健壮的数据审计功能
本文深入介绍了如何在Spring Boot应用中使用`@Audited`注解和`spring-data-envers`实现数据审计功能,涵盖从添加依赖、配置实体类到查询审计数据的具体步骤,助力开发人员构建更加透明、合规的应用系统。