Spring配置類源碼分析詳解
spring配置類解析源碼解析
上一篇分析spring的啟動過程中,會把BeanDefinitionRegistryPostProcessor接口類型的子類添加到啟動的過程中,其中它的一個子類ConfigurationClassPostProcessor
是用來處理配置類。下面來分析spring如何處理配置類。
那么首先要知道什么是配置類?先簡單來說常見的配置類就是添加了@Configuration、@ComponentScan等注解的類。后續(xù)分析源碼的過程中在詳細介紹。
配置類解析源碼分析
上一篇分析過spring啟動的過程會添加一些類用于后續(xù)bean的生命周期使用,啟動過程中會執(zhí)行ConfigurationClassPostProcessor
的processConfigBeanDefinitions
方法。展示部分重要的代碼
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { List<BeanDefinitionHolder> configCandidates = new ArrayList<>(); //拿到啟動過程中注入的類的名稱 String[] candidateNames = registry.getBeanDefinitionNames(); for (String beanName : candidateNames) { BeanDefinition beanDef = registry.getBeanDefinition(beanName); if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) { 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 // 通過@Order可以排序,升序排序,order越下越靠前 configCandidates.sort((bd1, bd2) -> { int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition()); int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition()); return Integer.compare(i1, i2); }); // Parse each @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()); // 遞歸解析配置類,有可能通過解析一個配置類,得到了其他的配置類,比如掃描和Import do { StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse"); //配置類解析 parser.parse(candidates); parser.validate(); // 從解析器parse得到配置類,移除已經解析過的,剩下的就是新增的 Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses()); configClasses.removeAll(alreadyParsed); // Read the model and create bean definitions based on its content if (this.reader == null) { this.reader = new ConfigurationClassBeanDefinitionReader( registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry()); } // 把parser.parse(candidates);解析到的但是未生成BeanDefinition的配置 生成對應的BeanDefinition this.reader.loadBeanDefinitions(configClasses); alreadyParsed.addAll(configClasses); processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end(); // candidates中存的是BeanDefinition,configClasses中存的是ConfigurationClass candidates.clear(); //loadBeanDefinitions方法會增加很多BeanDefinition 如果發(fā)現(xiàn)BeanDefinition增加了,則有可能增加了配置類 //這個if對新增的BeanDefinition做處理 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()); } //遍歷新增的BeanDefinition for (String candidateName : newCandidateNames) { if (!oldCandidateNames.contains(candidateName)) { BeanDefinition bd = registry.getBeanDefinition(candidateName); //判斷新增的BeanDefinition是不是一個配置類 if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) && !alreadyParsedClasses.contains(bd.getBeanClassName())) { //如果是配置類 添加到這個集合當中 交給do while 循環(huán)解析配置類 candidates.add(new BeanDefinitionHolder(bd, candidateName)); } } } candidateNames = newCandidateNames; } } while (!candidates.isEmpty()); }
整個流程就是 判斷哪些類是配置類,根據(jù)@order注解排序,構建一個配置類的解析器,利用do while 解析配置類。先來看看spring是如何判斷哪些類是配置類的。
判斷配置類
判斷配置類的方法是ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)
public static boolean checkConfigurationClassCandidate( BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) { String className = beanDef.getBeanClassName(); if (className == null || beanDef.getFactoryMethodName() != null) { return false; } // AnnotationMetadata表示某個類的注解信息,但是并一定要加載這個類 AnnotationMetadata metadata; // 如果AnnotatedBeanDefinition,則直接取AnnotationMetadata if (beanDef instanceof AnnotatedBeanDefinition && className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) { // Can reuse the pre-parsed metadata from the given BeanDefinition... metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata(); } // 如果是AbstractBeanDefinition,則解析beanClass得到AnnotationMetadata else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) { // Check already loaded Class if present... // since we possibly can't even load the class file for this Class. Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass(); if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) || BeanPostProcessor.class.isAssignableFrom(beanClass) || AopInfrastructureBean.class.isAssignableFrom(beanClass) || EventListenerFactory.class.isAssignableFrom(beanClass)) { return false; } metadata = AnnotationMetadata.introspect(beanClass); } else { try { MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className); metadata = metadataReader.getAnnotationMetadata(); } catch (IOException ex) { if (logger.isDebugEnabled()) { logger.debug("Could not find class file for introspecting configuration annotations: " + className, ex); } return false; } } //如果該類有@Configuration注解 表示是一個配置類 Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName()); // 存在@Configuration,并且proxyBeanMethods不為false(為true或為null)時,就是Full配置類 if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL); // 存在@Configuration,并且proxyBeanMethods為false時,是lite配置類 // 或者不存在@Configuration,但是只要存在@Component、@ComponentScan、@Import、@ImportResource四個中的一個,就是lite配置類 // 或者不存在@Configuration,只要存在@Bean注解了的方法,就是lite配置類 else if (config != null || isConfigurationCandidate(metadata)) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE); } else { return false; } // It's a full or lite configuration candidate... Let's determine the order value, if any. Integer order = getOrder(metadata); if (order != null) { beanDef.setAttribute(ORDER_ATTRIBUTE, order); } return true; }
首先就是拿到這個類的BeanDefinition的注解,根據(jù)注解判斷有沒有@Configuration,如果有該注解并且其屬性proxyBeanMethods=ture,默認也是true,那么就是full配置類,如果為fasle那么就是lite配置類。這兩種配置類跟代理有關可以先不用管。同時還有個或的判斷isConfigurationCandidate(metadata)
,如果滿足這個條件那么也是lite配置類。
public static boolean isConfigurationCandidate(AnnotationMetadata metadata) { //如果是一個接口類型,那么直接返回false 就不是一個配置類 if (metadata.isInterface()) { return false; } // 只要存在@Component、@ComponentScan、@Import、@ImportResource四個中的一個,就是lite配置類 for (String indicator : candidateIndicators) { if (metadata.isAnnotated(indicator)) { return true; } } // 只要存在@Bean注解了的方法,就是lite配置類 return hasBeanMethods(metadata); } static { candidateIndicators.add(Component.class.getName()); candidateIndicators.add(ComponentScan.class.getName()); candidateIndicators.add(Import.class.getName()); candidateIndicators.add(ImportResource.class.getName()); }
其中candidateIndicators是一個集合,包含了@Component、@ComponentScan、@Import、@ImportResource這幾個注解。
因此根據(jù)上述代碼可以知道什么是配置類
類上有@Component、@ComponentScan、@Import、@ImportResource、@Configuration任意一個注解的類,或者包含有@bean注解的類都是配置類。
解析配置類
拿到了配置類,排序后,構建一個配置類的解析器就開始解析。開頭講的do while 就是用來解析配置類的。核心代碼在于parser.parse(candidates);
,這個方法中核心處理在于processConfigurationClass這個方法。
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException { // 條件注解判斷 if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { return; } ConfigurationClass existingClass = this.configurationClasses.get(configClass); if (existingClass != null) { if (configClass.isImported()) { // OrderService導入了AccountService,UserService也導入了AccountService,就會符合這個條件 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. SourceClass sourceClass = asSourceClass(configClass, filter); //循環(huán) 解析了該類后 解析父類 do { //核心方法 真正解析類 --> 解析類上的各種配置注解并做出對應處理 sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter); } while (sourceClass != null); // ConfigurationClass重寫了equals方法,只要兩個ConfigurationClass對應的className相等就可以 this.configurationClasses.put(configClass, configClass); }
其中核心代碼在于doProcessConfigurationClass(configClass, sourceClass, filter)
開始解析配置類,這里也有個do while ,這個do while 就是循環(huán)解決配置類的父類,如果配置類存在父類,那么就會執(zhí)行解析的邏輯,如果沒有就會推出循環(huán)。那么接著看如何解析配置類。
protected final SourceClass doProcessConfigurationClass( ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter) throws IOException { //對@Component注解做處理 if (configClass.getMetadata().isAnnotated(Component.class.getName())) { // 處理內部類 processMemberClasses(configClass, sourceClass, filter); } //對@PropertySource注解做處理 for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) { if (this.environment instanceof ConfigurableEnvironment) { processPropertySource(propertySource); } else { logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment"); } } //對@ComponentScan注解做處理 Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { for (AnnotationAttributes componentScan : componentScans) { // The config class is annotated with @ComponentScan -> perform the scan immediately // 底層調用doScan 進行掃描所有的bean得到BeanDefinition Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); // Check the set of scanned definitions for any further config classes and parse recursively if needed for (BeanDefinitionHolder holder : scannedBeanDefinitions) { BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition(); if (bdCand == null) { bdCand = holder.getBeanDefinition(); } // 檢查掃描出來的BeanDefinition是不是配置類(full和lite) if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { //如果是配置類遞歸執(zhí)行 解析配置類的方法 parse(bdCand.getBeanClassName(), holder.getBeanName()); } } } } // getImports(sourceClass)會拿到所有導入的類 //對@Import注解做處理 processImports(configClass, sourceClass, getImports(sourceClass), filter, true); //對@ImportResource注解做處理 @ImportResource導入一個xml作為配置文件 這里暫時沒有解析xml文件 AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class); if (importResource != null) { String[] resources = importResource.getStringArray("locations"); Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader"); for (String resource : resources) { String resolvedResource = this.environment.resolveRequiredPlaceholders(resource); configClass.addImportedResource(resolvedResource, readerClass); } } // Process individual @Bean methods // 解析配置類中的@Bean,但并沒有真正處理@Bean,只是暫時找出來 Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } // Process default methods on interfaces // 解析配置類所實現(xiàn)的接口中的@Bean,但并沒有真正處理@Bean,只是暫時找出來 processInterfaces(configClass, sourceClass); // Process superclass, if any //如果有父類就返回父類 if (sourceClass.getMetadata().hasSuperClass()) { String superclass = sourceClass.getMetadata().getSuperClassName(); if (superclass != null && !superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) { this.knownSuperclasses.put(superclass, configClass); // Superclass found, return its annotation metadata and recurse return sourceClass.getSuperClass(); } } //如果沒有父類 那么這個類就解析完了 return null; }
大致流程就是解析配置類上的注解,根據(jù)得到的注解,做出不同的處理。如何處理通過下面的流程圖來分析。doProcessConfigurationClass方法流程圖:
需要注意的是除了@ComponentScan的處理會把掃描到的類注冊成BeanDefinition以外,其他的處理都是記錄到相關屬性,后續(xù)在把這些類拿出來做處理。
那么到此parser.parse(candidates);
方法就執(zhí)行完成。
配置類的處理
解析完配置類后,會得到很多新的配置類,或者bean對象。通過this.reader.loadBeanDefinitions(configClasses);
方法創(chuàng)建這些新的類的BeanDefinition。該方法的底層核心方法是loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
private void loadBeanDefinitionsForConfigurationClass( ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) { if (trackedConditionEvaluator.shouldSkip(configClass)) { String beanName = configClass.getBeanName(); if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) { this.registry.removeBeanDefinition(beanName); } this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName()); return; } //將Importe和@component的內部類 生成BeanDefinition if (configClass.isImported()) { registerBeanDefinitionForImportedConfigurationClass(configClass); } //解析所有的@bean 生成對應的BeanDefinition for (BeanMethod beanMethod : configClass.getBeanMethods()) { loadBeanDefinitionsForBeanMethod(beanMethod); } //解析導入進來的xml文件 生成bean對應的BeanDefinition loadBeanDefinitionsFromImportedResources(configClass.getImportedResources()); //執(zhí)行實現(xiàn)了ImportBeanDefinitionRegistrar接口的方法 loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars()); }
doProcessConfigurationClass(configClass, sourceClass, filter)
方法會把根據(jù)配置類上的注解做處理,比如吧Importe導入的類和@component的內部類,收集起來,所有@bean需要創(chuàng)建的bean封裝成BeanMethod對象收集起來,ImportedResources導入的xml文件等都沒有處理。那么loadBeanDefinitionsForConfigurationClass
這個方法就是處理這些類的,把這類的對應的BeanDefinition創(chuàng)建出來。
由于創(chuàng)建出很多BeanDefinition,那么還需要判斷哪些BeanDefinition是配置類。如果是配置加入到集合當中。通過開頭講的do while去處理這個集合。
總結
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
一:在啟動Spring時,需要傳入一個配置類,例如上面?zhèn)魅階ppConfig.class。ApplicationContext會根據(jù)AppConfig類封裝為一個BeanDefinition,這種BeanDefinition我們把它稱為配置類BeanDefinition。
二:ConfigurationClassPostProcessor中會把配置類BeanDefinition取出來
三:構造一個ConfigurationClassParser用來解析配置類BeanDefinition,并且會生成一個配置類對象ConfigurationClass
四:如果配置類上存在@Component注解,那么解析配置類中的內部類(這里有遞歸,如果內部類也是配置類的話)
五:如果配置類上存在@PropertySource注解,那么則解析該注解,并得到PropertySource對象,并添加到environment中去
六:如果配置類上存在@ComponentScan注解,那么則解析該注解,進行掃描,掃描得到一系列的BeanDefinition對象,然后判斷這些BeanDefinition是不是也是配置類BeanDefinition(只要存在@Component注解就是配置類,所以基本上掃描出來的都是配置類),如果是則繼續(xù)解析該配置類,(也有遞歸),并且會生成對應的ConfigurationClass
七:如果配置類上存在@Import注解,那么則判斷Import的類的類型:
如果是ImportSelector,那么調用執(zhí)行selectImports方法得到類名,然后在把這個類當做配置類進行解析 也是遞歸如果是ImportBeanDefinitionRegistrar,那么則生成一個ImportBeanDefinitionRegistrar實例對象,并添加到配置類對象中(ConfigurationClass)的importBeanDefinitionRegistrars屬性中。
八:如果配置類上存在@ImportResource注解,那么則把導入進來的資源路徑存在配置類對象中的importedResources屬性中
九:如果配置類中存在@Bean的方法,那么則把這些方法封裝為BeanMethod對象,并添加到配置類對象中的beanMethods屬性中
十:如果配置類實現(xiàn)了某些接口,則看這些接口內是否定義了@Bean的默認方法
十一:如果配置類有父類,則把父類當做配置類進行解析
十二:AppConfig這個配置類會對應一個ConfigurationClass,同時在解析的過程中也會生成另外的一些ConfigurationClass,接下來就利用reader來進一步解析ConfigurationClass
- 如果ConfigurationClass是通過@Import注解導入進來的,則把這個類生成一個BeanDefinition,同時解析這個類上@Scope,@Lazy等注解信息,并注冊BeanDefinition
- 如果ConfigurationClass中存在一些BeanMethod,也就是定義了一些@Bean,那么則解析這些@Bean,并生成對應的BeanDefinition,并注冊
- 如果ConfigurationClass中導入了一些資源文件,比如xx.xml,那么則解析這些xx.xml文件,得到并注冊BeanDefinition
- 如果ConfigurationClass中導入了一些ImportBeanDefinitionRegistrar,那么則執(zhí)行對應的registerBeanDefinitions進行BeanDefinition的注冊
擴展點
通過spring對配置類處理,可以了解一些知識點,
- @Import 可以導入一些類,并且這些以配置類的形式注冊到spring容器中(當然也會生成Bean對象)導入的類還可以實現(xiàn)ImportSelector或ImportBeanDefinitionRegistrar這兩個接口做出額外的擴展
- @ImportResource 導入xxx.xml配置文件
- @PropertySource 導入xxx.properties文件到環(huán)境變量中
Import使用demo
//定義一個類 public class Aaaa { public void test(){ System.out.println("-----------Aaaa----------"); } } //定義一個Import類 public class ImportDemo { @Bean public Aaaa aaaa(){ return new Aaaa(); } }
測試代碼
@ComponentScan(basePackages = {"service","config","aop"}) @Configuration @Import(ImportDemo.class) public class AppConfig { } public class Test { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); Aaaa aaaa = (Aaaa)context.getBean("aaaa"); aaaa.test(); } }
這樣可以把Aaaa這個類放入到spring容器中。 通過這種形式可以控制那些bean在那些條件下才導入到spring中 例如在某個注解上加上@Import(ImportDemo.class)。這樣只有使用了該注解后才會導入這個bean。
到此這篇關于Spring配置類源碼分析詳解的文章就介紹到這了,更多相關Spring配置類內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
解決SpringBoot項目讀取yml文件中值為中文時,在視圖頁面顯示亂碼
這篇文章主要介紹了解決SpringBoot項目讀取yml文件中值為中文時,在視圖頁面顯示亂碼的問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-08-08Java、C++中子類對父類函數(shù)覆蓋的可訪問性縮小的區(qū)別介紹
這篇文章主要給大家介紹了關于Java、C++中子類對父類函數(shù)覆蓋的可訪問性縮小的區(qū)別的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧。2018-01-01Java While循環(huán) do-while循環(huán)用法
循環(huán)語句就是讓計算機根據(jù)條件做循環(huán)計算,在條件滿足時繼續(xù)循環(huán),條件不滿足時退出循環(huán),需要的朋友可以參考下2020-11-11JAVA WEB中Servlet和Servlet容器的區(qū)別
這篇文章主要介紹了JAVA WEB中Servlet和Servlet容器的區(qū)別,文中示例代碼非常詳細,供大家參考和學習,感興趣的朋友可以了解下2020-06-06SpringBoot使用Mybatis注解實現(xiàn)分頁動態(tài)sql開發(fā)教程
這篇文章主要為大家介紹了SpringBoot使用Mybatis注解實現(xiàn)分頁及動態(tài)sql開發(fā)教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-03-03