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 = context.getBeanNamesForType(Controller.class); System.out.println(Arrays.toString(beanNamesForType)); } } 複製程式碼
不出意外控制檯會打印出空陣列[]。此時去掉Controller類上的@Conditional(ControllerConditional.class)註解,控制檯又可以打印出[controller]
@Conditional註解的原理
經過上面的簡單示例,對於@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註解的原理
開啟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相關注解的原理應該都清楚了,其他衍生類原理也大多相似,就不再一一分析。