SpringBoot条件注解原理

SpringBoot条件注解原理

文章目录

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

如@ConditionalOnClass,@ConditionalOnBean ...等等

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

关于Spring Framework中的Conditional

java 复制代码
public @interface Conditional {
    Class<? extends Condition>[] value();
}

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

java 复制代码
public interface Condition {
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

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

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

java 复制代码
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

java 复制代码
@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`

java 复制代码
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方法
java 复制代码
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判断

java 复制代码
	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方法如下

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

相关推荐
rzl026 分钟前
java web5(黑马)
java·开发语言·前端
君爱学习11 分钟前
RocketMQ延迟消息是如何实现的?
后端
guojl25 分钟前
深度解读jdk8 HashMap设计与源码
java
Falling4229 分钟前
使用 CNB 构建并部署maven项目
后端
guojl31 分钟前
深度解读jdk8 ConcurrentHashMap设计与源码
java
程序员小假39 分钟前
我们来讲一讲 ConcurrentHashMap
后端
爱上语文1 小时前
Redis基础(5):Redis的Java客户端
java·开发语言·数据库·redis·后端
A~taoker1 小时前
taoker的项目维护(ng服务器)
java·开发语言
萧曵 丶1 小时前
Rust 中的返回类型
开发语言·后端·rust