上一节讲到springboot自动化配置是以@Conditional相关注解作为判断条件,那么这一节我们来了解一下@Conditional相关注解的原理。
新建一个ControllerConditional类,实现Condition接口,实现matches方法,返回false
public class ControllerConditional implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return false; } } 复制代码
在Controller类上添加@Conditional(ControllerConditional.class)注解
@RestController @Conditional(ControllerConditional.class) public class Controller { @RequestMapping("/hello") public String hello(){ return "hello"; } } 复制代码
在main函数中尝试获取Controller类。
@SpringBootApplication public class Application { public static void main(String[] args) { ApplicationContext context = SpringApplication.run(Application.class, args); String[] beanNamesForType = context.getBeanNamesForType(Controller.class); System.out.println(Arrays.toString(beanNamesForType)); } } 复制代码
不出意外控制台会打印出空数组[]。此时去掉Controller类上的@Conditional(ControllerConditional.class)注解,控制台又可以打印出[controller]
经过上面的简单示例,对于@Conditional注解的使用大家应该清楚了,如果matches方法返回false,那么这个类就不会被扫描,反之则会被扫描进spring容器。下面就来了解一下他们的原理。
回到上一节我们讲解析Component,PropertySources,ComponentScan这几个注解的地方,进入processConfigurationClass方法,发现在解析之前有一行代码。
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException { if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { return; } 复制代码
shouldSkip方法就是判断@Conditional注解的地方(这个shouldSkip方法其他地方也有,但是基本原理都是一样的,或者说就是一样的),在进入之前,我们先了解一下他的参数以及conditionEvaluator。找到当前类的构造函数,发现如下信息。
public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory, ProblemReporter problemReporter, Environment environment, ResourceLoader resourceLoader, BeanNameGenerator componentScanBeanNameGenerator, BeanDefinitionRegistry registry) { ... this.conditionEvaluator = new ConditionEvaluator(registry, environment, resourceLoader); } public ConditionEvaluator(@Nullable BeanDefinitionRegistry registry, @Nullable Environment environment, @Nullable ResourceLoader resourceLoader) { this.context = new ConditionContextImpl(registry, environment, resourceLoader); } 复制代码
构造函数不复杂,应该没啥问题。接下来了解一下shouldSkip方法的两个参数,顺着方法找回去。
this.metadata = new StandardAnnotationMetadata(beanClass, true); public StandardAnnotationMetadata(Class<?> introspectedClass, boolean nestedAnnotationsAsMap) { super(introspectedClass); this.annotations = introspectedClass.getAnnotations(); this.nestedAnnotationsAsMap = nestedAnnotationsAsMap; } 复制代码
metadata就是这边的StandardAnnotationMetadata,第二个参数是一个枚举。做好这些准备工作后,开始进入shouldSkip方法。
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) { if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) { return false; } //递归调用,确保扫描到每个类 if (phase == null) { if (metadata instanceof AnnotationMetadata && ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) { return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION); } return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN); } //获取该类的所有@Conditional注解里面的参数类 List<Condition> conditions = new ArrayList<>(); for (String[] conditionClasses : getConditionClasses(metadata)) { for (String conditionClass : conditionClasses) { Condition condition = getCondition(conditionClass, this.context.getClassLoader()); conditions.add(condition); } } AnnotationAwareOrderComparator.sort(conditions); for (Condition condition : conditions) { ConfigurationPhase requiredPhase = null; if (condition instanceof ConfigurationCondition) { requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase(); } //依次判断每个类的matches方法,有一个方法返回false则跳过这个类 if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) { return true; } } return false; } 复制代码
shouldSkip方法的逻辑不复杂,获取所有conditional注解里的参数类,依次调用matches方法,如果任意方法返回false则跳过该类。所以在这儿,我们就看到了matches方法的参数以及调用。这样的话,conditional注解的原理大家应该没啥问题了。
那么下面通过举例来看看由conditional注解衍生出的ConditionalOnXXX类型注解。
打开ConditionalOnClass注解的源代码,本身带有两个属性,一个class类型的value,一个String类型的name。同时ConditionalOnClass注解本身还带了一个@Conditional(OnClassCondition.class)注解。所以,其实ConditionalOnClass注解的判断条件就在于OnClassCondition这个类的matches方法。
@Conditional(OnClassCondition.class) public @interface ConditionalOnClass { Class<?>[] value() default {}; String[] name() default {}; } 复制代码
所以没啥好说的,直接进入OnClassCondition类,寻找matches方法。最终,在他的父类SpringBootCondition中,找到了matches方法。代码如下:
@Override public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { //获取加上了@ConditionalOnClass注解的类或者方法的名称(我们就以类分析,加在方法上是一个原理) String classOrMethodName = getClassOrMethodName(metadata); try { //获取匹配结果 ConditionOutcome outcome = getMatchOutcome(context, metadata); logOutcome(classOrMethodName, outcome); recordEvaluation(context, classOrMethodName, outcome); return outcome.isMatch(); } ... } 复制代码
从代码不难看出,关键方法在getMatchOutcome里,所以进入该方法。
@Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { ClassLoader classLoader = context.getClassLoader(); ConditionMessage matchMessage = ConditionMessage.empty(); //获取所有需要判断是否存在的类 List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class); if (onClasses != null) { //筛选这些类,判断条件为ClassNameFilter.MISSING List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader); if (!missing.isEmpty()) { return ConditionOutcome .noMatch(ConditionMessage.forCondition(ConditionalOnClass.class) .didNotFind("required class", "required classes") .items(Style.QUOTE, missing)); } matchMessage = matchMessage.andCondition(ConditionalOnClass.class) .found("required class", "required classes").items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader)); } ... return ConditionOutcome.match(matchMessage); } 复制代码
该方法并不复杂,和ConditionalOnClass有关的代码主要有两行,getCandidates和filter。 首先看看getCandidates:
private List<String> getCandidates(AnnotatedTypeMetadata metadata, Class<?> annotationType) { MultiValueMap<String, Object> attributes = metadata .getAllAnnotationAttributes(annotationType.getName(), true); if (attributes == null) { return null; } List<String> candidates = new ArrayList<>(); addAll(candidates, attributes.get("value")); addAll(candidates, attributes.get("name")); return candidates; } 复制代码
主要是获取了ConditionalOnClass的name属性和value属性。
接下来看看filter方法,在进入filter方法前,先看一下判断条件ClassNameFilter.MISSING
MISSING { @Override public boolean matches(String className, ClassLoader classLoader) { return !isPresent(className, classLoader); } }; public static boolean isPresent(String className, ClassLoader classLoader) { if (classLoader == null) { classLoader = ClassUtils.getDefaultClassLoader(); } try { forName(className, classLoader); return true; } catch (Throwable ex) { return false; } } private static Class<?> forName(String className, ClassLoader classLoader) throws ClassNotFoundException { if (classLoader != null) { return classLoader.loadClass(className); } return Class.forName(className); } 复制代码
逻辑很清晰,如果该类能被加载则判断成功,否则判断失败。现在进入filter方法。
protected List<String> filter(Collection<String> classNames, ClassNameFilter classNameFilter, ClassLoader classLoader) { if (CollectionUtils.isEmpty(classNames)) { return Collections.emptyList(); } List<String> matches = new ArrayList<>(classNames.size()); for (String candidate : classNames) { //逐个判断我们添加的判断条件,如果有不符合的即添加进list if (classNameFilter.matches(candidate, classLoader)) { matches.add(candidate); } } return matches; } 复制代码
filter方法就是利用刚刚的判断条件进行判断,发现不符合的添加进list一并返回,最后生成结果。
所以到这儿,conditional相关注解的原理应该都清楚了,其他衍生类原理也大多相似,就不再一一分析。