Principles of ConditionalOnClass annotations in SpringBoot

The auto-configuration classes in SpringBoot have many ConditionalOnClass annotations, and @ConditionalOnClass matches when all classes in the annotation value exist (by trying to load the specified class using the class loader),

What are the principles of these ConditionalOnClass annotations? Before you can understand the principles of the ConditionalOnClass annotations, you must first understand the principles of the Conditional annotations, because the Conditional annotations are the most fundamental

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
    Class<? extends Condition>[] value();
}

The property that can be labeled in the Contional annotation is a Class array

The one that handles this annotation is the Condition interface, which SpringBootCondition implements to handle Continental annotations

@FunctionalInterface
public interface Condition {
    boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String classOrMethodName = getClassOrMethodName(metadata);

try {
//Gets the ContionOutCome object whose properties have matching and matching information
ConditionOutcome outcome = this.getMatchOutcome(context, metadata);//The getMatchOutcome method is an abstract method in this class and its implementation is implemented in a subclass, which OnClassCondition implements.
this.logOutcome(classOrMethodName, outcome);
this.recordEvaluation(context, classOrMethodName, outcome);
return outcome.isMatch();//Returns a match result true or False
} 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);
}
}

 

Now that you understand the principles of the ConditionalOnClass annotation, you can begin by looking at the definition of the ConditionalOnClass annotation.

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnClassCondition.class})
public @interface ConditionalOnClass {
    Class<?>[] value() default {};

    String[] name() default {};
}

You can see that the ConditionalOnClass annotation actually works through the Conditional annotation, whose attribute is OnClassCondition.class. Next, look at the source code of OnClassCondition.class. From the source code, you can see that OnClassCondition.class obtains matching results through getMatchOutCome.

Matching results are used in SpringBootCondition

public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
          //Get the class loader for the container
        ClassLoader classLoader = context.getClassLoader();
        ConditionMessage matchMessage = ConditionMessage.empty();
    // Obtain@ConditionalOnClass annotation value as well as name All classes declared by attributes
        List<String> onClasses = this.getCandidates(metadata, ConditionalOnClass.class);
        List onMissingClasses;
        if (onClasses != null) {
            //Load ConditionalOnClass Annotate the specified class
            onMissingClasses = this.filter(onClasses, ClassNameFilter.MISSING, classLoader);
            // If the load succeeds, there is a ConditionalOnClasses Annotate the specified class, that is onMissingClasses Empty, load failure is onMissingClasses Is not empty, returns a result of matching failure
            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));
        }
          
        
        onMissingClasses = this.getCandidates(metadata, ConditionalOnMissingClass.class);
        if (onMissingClasses != null) {
            
            //Load ConditionalOnMissingClass Annotate the specified class
            List<String> present = this.filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);
            // If loading fails, there is no on the class path ConditionalOnMissingClasses Annotate the specified class, that is present Empty, successful loading is present Is not empty, returns a result of matching failure
            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);//Return a successful match
    }

public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String classOrMethodName = getClassOrMethodName(metadata);

try {
ConditionOutcome outcome = this.getMatchOutcome(context, metadata);//The matching results returned by the above method are used here
this.logOutcome(classOrMethodName, outcome);
this.recordEvaluation(context, classOrMethodName, outcome);
return outcome.isMatch();//A successful match returns true, adding the ConditionalOnClass tagged class to the container, and a failed match skips the labeled class
} 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);
}
}
 

You can see that the getMatchOutCome method uses the filter method to determine if there are classes labeled in the ConditionalOnClass annotation on the class path

protected final List<String> filter(Collection<String> classNames, FilteringSpringBootCondition.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();
                //classNameFilter by MISSING When loaded 
                //ConditionalOnClass Specified class, matches The return value of flase,You won'tConditionalOnClass Attributes of the note are added to the matches,That is matches Is empty
                if (classNameFilter.matches(candidate, classLoader)) {
                    matches.add(candidate);
                }
            }

            return matches;
        }
    }
The filter method uses the matches method in the ClassNameFilter enumeration class to determine if there is a class labeled in the ConditionalOnClass annotation on the class path
protected static enum ClassNameFilter {
    //Returns whether the class exists in the class path,If the class can be loaded, it will be returned True,Otherwise return False
        PRESENT {
            public boolean matches(String className, ClassLoader classLoader) {
                return isPresent(className, classLoader);
            }
        },
    //Returns whether the class does not exist in the class path,If the class can be loaded, it will be returned False,Otherwise return True
        MISSING {
            public boolean matches(String className, ClassLoader classLoader) {
                return !isPresent(className, classLoader);
            }
        };

        private ClassNameFilter() {
        }

        abstract boolean matches(String var1, ClassLoader var2);

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

            try {
                //utilize loadClass as well as forName Method to determine if there is this specified class under the class path
                //Or Return true
                FilteringSpringBootCondition.resolve(className, classLoader);
                return true;
            } catch (Throwable var3) {
                //Load Failure Return If Not false
                return false;
            }
        }
    }
}

protected static Class<?> resolve(String className, ClassLoader classLoader) throws ClassNotFoundException {
return classLoader != null ? classLoader.loadClass(className) : Class.forName(className);//Class loaders are loaded with class loaders and not with the forName method
}

From here we can see that the class properties in the ConditionalOnClass annotation are used by the class loader and forName methods to determine if there is a class on the class path. If there is a class on the class path, it is loaded successfully, that is, it can be matched successfully. After successful matching, SpringBoot will add the class marked by the ConditionalOnClass annotation to the container



Tags: Java SpringBoot Attribute

Posted on Tue, 12 May 2020 03:50:18 -0400 by I Am Chris