SpringBoot源码解析-@ConditionalOnXXX注解原理
上⼀节讲到springboot⾃动化配置是以@Conditional相关注解作为判断条件,那么这⼀节我们来了解⼀下@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 = BeanNamesForType(Controller.class);
System.out.String(beanNamesForType));
}
}
复制代码
不出意外控制台会打印出空数组[]。此时去掉Controller类上的@Conditional(ControllerConditional.class)注解,控制台⼜可以打印出[controller]
@Conditional注解的原理
经过上⾯的简单⽰例,对于@Conditional注解的使⽤⼤家应该清楚了,如果matches⽅法返回false,那么这个类就不会被扫描,反之则会被扫描进spring容器。下⾯就来了解⼀下他们的原理。
回到上⼀节我们讲解析Component,PropertySources,ComponentScan这⼏个注解的地⽅,进⼊processConfigurationClass⽅法,发现在解析之前有⼀⾏代码。
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
if (ditionEvaluator.Metadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
复制代码
shouldSkip⽅法就是判断@Conditional注解的地⽅(这个shouldSkip⽅法其他地⽅也有,但是基本原理都是⼀样的,或者说就是⼀样的),在进⼊之前,我们先了解⼀下他的参数以及conditionEvaluator。到当前类的构造函数,发现如下信息。
public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory,
ProblemReporter problemReporter, Environment environment, ResourceLoader resourceLoader,
BeanNameGenerator componentScanBeanNameGenerator, BeanDefinitionRegistry registry) {
...
}
public ConditionEvaluator(@Nullable BeanDefinitionRegistry registry,
@Nullable Environment environment, @Nullable ResourceLoader resourceLoader) {
}
复制代码
构造函数不复杂,应该没啥问题。接下来了解⼀下shouldSkip⽅法的两个参数,顺着⽅法回去。
public StandardAnnotationMetadata(Class<?> introspectedClass, boolean nestedAnnotationsAsMap) {
super(introspectedClass);
this.annotations = Annotations();
}
复制代码
metadata就是这边的StandardAnnotationMetadata,第⼆个参数是⼀个枚举。做好这些准备⼯作后,开始进⼊shouldSkip⽅法。
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationP
hase phase) {
if (metadata == null || !metadata.isAnnotated(Name())) {
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, ClassLoader());
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.t, metadata)) {
return true;
}
}
return false;
}
复制代码
shouldSkip⽅法的逻辑不复杂,获取所有conditional注解⾥的参数类,依次调⽤matches⽅法,如果任意⽅法返回false则跳过该类。所以在这⼉,我们就看到了matches⽅法的参数以及调⽤。这样的话,conditional注解的原理⼤家应该没啥问题了。
那么下⾯通过举例来看看由conditional注解衍⽣出的ConditionalOnXXX类型注解。
@ConditionalOnClass注解的原理
打开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();
}
...
}
复制代码
springboot是啥从代码不难看出,关键⽅法在getMatchOutcome⾥,所以进⼊该⽅法。
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ClassLoader classLoader = ClassLoader();
ConditionMessage matchMessage = pty();
//获取所有需要判断是否存在的类
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
.Name(), true);
if (attributes == null) {
return null;
}
List<String> candidates = new ArrayList<>();
addAll(candidates, ("value"));
addAll(candidates, ("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 = DefaultClassLoader();
}
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)) {
ptyList();
}
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相关注解的原理应该都清楚了,其他衍⽣类原理也⼤多相似,就不再⼀⼀分析。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论