前言 開心一刻 一名劫匪慌忙中竄上了一輛車的後座,上車後發現主駕和副駕的一男一女疑惑地回頭看著他,他立即拔出槍威脅到:“趕快開車,甩掉後面的警車,否則老子一槍崩了你!”,於是副駕上的男人轉過臉對那女的說:“大姐,別慌,聽我口令把剛纔的動作再練習一遍,掛一檔,輕鬆離合,輕踩油門,走...走,哎 走.. ...
前言
開心一刻
一名劫匪慌忙中竄上了一輛車的後座,上車後發現主駕和副駕的一男一女疑惑地回頭看著他,他立即拔出槍威脅到:“趕快開車,甩掉後面的警車,否則老子一槍崩了你!”,於是副駕上的男人轉過臉對那女的說:“大姐,別慌,聽我口令把剛纔的動作再練習一遍,掛一檔,輕鬆離合,輕踩油門,走...走,哎 走...哎,哎,對,走走... 最後,三人都躺到了醫院,劫匪的手上還戴上了銬子...
劫匪的內心路漫漫其修遠兮,吾將上下而求索!
github:https://github.com/youzhibing
碼雲(gitee):https://gitee.com/youzhibing
前情回顧
估摸著大家已經忘記了createApplicationContext的內容,本文不做過多的回顧,只是提醒大家:在AnnotationConfigServletWebServerApplicationContext的實例化過程中,實例化了AnnotatedBeanDefinitionReader,另外也將ConfigurationClassPostProcessor定義註冊到了beanFactory中,如下圖所示
看著AnnotatedBeanDefinitionReader、ConfigurationClassPostProcessor是不是隱約感覺到了什麼?
概念介紹與應用
@Configuration
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Configuration { /** * Explicitly specify the name of the Spring bean definition associated * with this Configuration class. If left unspecified (the common case), * a bean name will be automatically generated. * <p>The custom name applies only if the Configuration class is picked up via * component scanning or supplied directly to a {@link AnnotationConfigApplicationContext}. * If the Configuration class is registered as a traditional XML bean definition, * the name/id of the bean element will take precedence. * @return the suggested component name, if any (or empty String otherwise) * @see org.springframework.beans.factory.support.DefaultBeanNameGenerator */ @AliasFor(annotation = Component.class) String value() default ""; }View Code
@Configuration能夠修飾Class、interface和enum,用的最多的還是標註在類上,相當於把該類作為spring的xml配置文件中的<beans>,用於配置spring容器;@Configuration往往會結合@Bean來使用,@Bean等價於spring的xml配置文件中的<bean>,用於註冊bean對象。@Configuration和@Bean組成了基於java類的配置,是spring的推薦配置方式。最簡單的使用如下
@Configuration public class MyConfiguration { @Bean public Cat mycat() { return new Cat(); } }
如上代碼就會在spring容器中註冊一個名叫mycat的Cat類型的Bean
Condition
@FunctionalInterface public interface Condition { /** * Determine if the condition matches. * @param context the condition context * @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class} * or {@link org.springframework.core.type.MethodMetadata method} being checked * @return {@code true} if the condition matches and the component can be registered, * or {@code false} to veto the annotated component's registration */ boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); }View Code
Spring的條件化配置,當我們向spring註冊bean時,可以對這個bean添加一定的自定義條件,當滿足這個條件時註冊這個bean,否則不註冊。springboot中部分實現子類如下
springboot更多實現請查看org.springframework.boot.autoconfigure.condition包。Condition一般配合@Conditional使用,更多信息往下看
@Conditional
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Conditional { /** * All {@link Condition}s that must {@linkplain Condition#matches match} * in order for the component to be registered. */ Class<? extends Condition>[] value(); }View Code
Spring的條件註解,其value是一個Class<? extends Condition>[],只有數組中的全部Condition全部匹配成功時,被@Conditional修飾的組件才會被註冊到Spring容器中。@Conditional只是一個標誌,標示需要進行條件判斷,而具體的判斷規則則由具體的Condition來實現。
在SpringBoot源碼中很容易看到被@Conditional註解的組合註解,例如:@ConditionalOnBean、@ConditionalOnMissingBean、@ConditionalOnClass、@ConditionalOnMissingClass等,具體如下
springboot還提供了AutoConfigureAfter、AutoConfigureBefore、AutoConfigureOrder,看名字基本知道其作用,具體細節需要大家自己去跟了。
完整應用案例
介面都能訪問通,數據返回也都正確,非常完美
完整工程代碼:spring-boot-condition
當我們把MyConfiguration中的myCat方法註釋掉(ConditionWeb中的cat相關也註釋掉),再啟動應用的時候,應用報錯啟動不起來,提示如下信息:
Description: Field dog in com.lee.condition.web.ConditionWeb required a bean of type 'com.lee.condition.model.Dog' that could not be found. - Bean method 'myDog' in 'MyConfiguration' not loaded because @ConditionalOnBean (types: com.lee.condition.model.Cat; SearchStrategy: all) did not find any beans of type com.lee.condition.model.Cat Action: Consider revisiting the conditions above or defining a bean of type 'com.lee.condition.model.Dog' in your configuration.View Code
ConditionWeb中需要Dog類型的bean,而Dog實例化又依賴Cat實例,而我們沒有實例化Cat,所以應用啟動報錯,提示如上信息
源碼探究
我們要探究什麼了?不探究太細,就探究@Configuration修飾的配置類是何時解析的,@Conditional是何時生效、如何生效的
@Configuration修飾的配置類是何時解析的
ConfigurationClassPostProcessor是一個BeanFactoryPostProcessor(可以查看ConfigurationClassPostProcessor的類繼承結構圖),那麼我們從AbstractApplicationContext的refresh方法調用的invokeBeanFactoryPostProcessors(beanFactory)方法開始
來到了processConfigurationClass方法,其詳細代碼如下
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException { // ConfigurationClass是否應該被skip if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { return; } ConfigurationClass existingClass = this.configurationClasses.get(configClass); if (existingClass != null) { if (configClass.isImported()) { if (existingClass.isImported()) { existingClass.mergeImportedBy(configClass); } // Otherwise ignore new imported config class; existing non-imported class overrides it. return; } else { // Explicit bean definition found, probably replacing an import. // Let's remove the old one and go with the new one. this.configurationClasses.remove(configClass); this.knownSuperclasses.values().removeIf(configClass::equals); } } // Recursively process the configuration class and its superclass hierarchy. 遞歸處理configuration class和它的父級類 // 也就說會遞歸處理我們的應用入口類:ConditionApplication.class,以及ConditionApplication.class的父級類 SourceClass sourceClass = asSourceClass(configClass); do { sourceClass = doProcessConfigurationClass(configClass, sourceClass); } while (sourceClass != null); // 將滿足條件的ConfigurationClass都放入configurationClasses集合中 // 後續會載入configurationClasses集合中所有的ConfigurationClass中配置的bean定義 this.configurationClasses.put(configClass, configClass); }View Code
其中shouldSkip方法如下
/** * Determine if an item should be skipped based on {@code @Conditional} annotations. * @param metadata the meta data * @param phase the phase of the call * @return if the item should be skipped */ public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) { // 如果這個類沒有註解修飾,或者沒有被@Conditional註解(包括Conditional系列)所修飾,不會skip 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的value 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(); } // 條件註解的生效階段滿足,一旦有條件匹配不成功,則返回true,skip此類 if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) { return true; } } return false; }View Code
我們再回過頭去看processConfigBeanDefinitions方法
/** * Build and validate a configuration model based on the registry of * {@link Configuration} classes. * 驗證@Configuration修飾的類,滿足條件則構建成configuration model */ public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { List<BeanDefinitionHolder> configCandidates = new ArrayList<>(); String[] candidateNames = registry.getBeanDefinitionNames(); for (String beanName : candidateNames) { BeanDefinition beanDef = registry.getBeanDefinition(beanName); if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) || ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) { if (logger.isDebugEnabled()) { logger.debug("Bean definition has already been processed as a configuration class: " + beanDef); } } else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); } } // Return immediately if no @Configuration classes were found if (configCandidates.isEmpty()) { return; } // Sort by previously determined @Order value, if applicable configCandidates.sort((bd1, bd2) -> { int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition()); int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition()); return Integer.compare(i1, i2); }); // Detect any custom bean name generation strategy supplied through the enclosing application context // 檢測自定義的bean生成策略 SingletonBeanRegistry sbr = null; if (registry instanceof SingletonBeanRegistry) { sbr = (SingletonBeanRegistry) registry; if (!this.localBeanNameGeneratorSet) { BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR); if (generator != null) { this.componentScanBeanNameGenerator = generator; this.importBeanNameGenerator = generator; } } } if (this.environment == null) { this.environment = new StandardEnvironment(); } // Parse each @Configuration class // 解析每一個被@Configuration修飾的class ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates); Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size()); do { parser.parse(candidates); // 解析過程中會將滿足條件的@Configuration class存放到configurationClasses中 parser.validate(); // 滿足條件的@Configuration class 都存放在了parser的configurationClasses中 Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses()); configClasses.removeAll(alreadyParsed); // Read the model and create bean definitions based on its content // 讀取@Configuration class中的配置(各個@Bean),並創建對應的bean definition(後續創建bean實例會用到bean定義) if (this.reader == null) { this.reader = new ConfigurationClassBeanDefinitionReader( registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry()); } this.reader.loadBeanDefinitions(configClasses); // 載入全部@Configuration class中的配置 alreadyParsed.addAll(configClasses); candidates.clear(); if (registry.getBeanDefinitionCount() > candidateNames.length) { String[] newCandidateNames = registry.getBeanDefinitionNames(); Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames)); Set<String> alreadyParsedClasses = new HashSet<>(); for (ConfigurationClass configurationClass : alreadyParsed) { alreadyParsedClasses.add(configurationClass.getMetadata().getClassName()); } for (String candidateName : newCandidateNames) { if (!oldCandidateNames.contains(candidateName)) { BeanDefinition bd = registry.getBeanDefinition(candidateName); if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) && !alreadyParsedClasses.contains(bd.getBeanClassName())) { candidates.add(new BeanDefinitionHolder(bd, candidateName)); } } } candidateNames = newCandidateNames; } } while (!candidates.isEmpty()); // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) { sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry()); } if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) { // Clear cache in externally provided MetadataReaderFactory; this is a no-op // for a shared cache since it'll be cleared by the ApplicationContext. ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache(); } }View Code
@Conditional是何時生效、如何生效的
這個問題再上面已經全部得到體現,Spring不會無腦的載入所有的@Configuration class,只會載入滿足條件的@Configuration class,而@Conditional就是條件標誌,至於條件匹配規則這有Condition提供;shouldSkip方法中用到Conditional和Condition,完成條件的匹配處理。
總結
1、@Configuration和@Bean組成了基於java類的配置,與xml中的<Beans>、<Bean>功能一致,Spring推薦java類的配置;
2、Condition與@Conditional實現了條件配置,只有滿足了條件的@Configuration class和@Bean才會被註冊到Spring容器;
3、Spring以我們的應用啟動類為基礎來遞歸掃描配置類,包括我們應用中的配置類、Spring自己的以及第三方的配置類(springboot集成的各種配置類(spring-boot-autoconfigure-xxx.RELEASE.jar下的spring.factories文件中的Auto Configure),還有pageHelper的自動配置,等等);前提是需要開啟自動配置(@EnableAutoConfiguration)。
參考
Spring 工具類 ConfigurationClassParser 分析得到