為什麼我們的專案裡出現兩個配置類繼承WebMvcConfigurationSupport時,只有一個會生效。我在網上找了半天都是說結果的,沒有人分析原始碼到底是為啥,博主準備講解一下,希望可以幫到大家!
大家基本遇到過一種情況,就是我配置類中已經配置了,為什麼就是沒有生效呢?其中一種原因就是,自己寫的配置類也繼承了WebMvcConfigurationSupport,當專案出現兩個配置類都繼承該類時,只會講第一個配置類生效,至於為什麼,就是今天博主需要講解的,我們必須瞭解一些springboot的bean的建立過程也就是其生命週期:
https://www.processon.com/view/link/5f704050f346fb166d0f3e3c
雖然畫的比較簡單,有許多細節都沒有解析,但是對於當前我們的話題來講已經基本可以了;
第一步:我們的配置類是從哪裡開始建立解析的:大家可以看到圖示bean的流程中doProcessConfigurationClass(configClass, sourceClass, filter);方法,我們看一下是如何呼叫它 的:
1 protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException { 2 if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { 3 return; 4 } 5 6 ConfigurationClass existingClass = this.configurationClasses.get(configClass); 7 if (existingClass != null) { 8 if (configClass.isImported()) { 9 if (existingClass.isImported()) { 10 existingClass.mergeImportedBy(configClass); 11 } 12 // Otherwise ignore new imported config class; existing non-imported class overrides it. 13 return; 14 } 15 else { 16 // Explicit bean definition found, probably replacing an import. 17 // Let's remove the old one and go with the new one. 18 this.configurationClasses.remove(configClass); 19 this.knownSuperclasses.values().removeIf(configClass::equals); 20 } 21 } 22 23 // Recursively process the configuration class and its superclass hierarchy. 24 SourceClass sourceClass = asSourceClass(configClass, filter); 25 do { 26 //從這裡開始解析我們的當前配置類 27 sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter); 28 } 29 while (sourceClass != null); 30 31 this.configurationClasses.put(configClass, configClass); 32 }
這裡可以看到一個while迴圈,為什麼要這麼設計呢?我們再看看doProcessConfigurationClass(configClass, sourceClass, filter);方法的原始碼
1 protected final SourceClass doProcessConfigurationClass( 2 ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter) 3 throws IOException { 4 5 if (configClass.getMetadata().isAnnotated(Component.class.getName())) { 6 // Recursively process any member (nested) classes first 7 processMemberClasses(configClass, sourceClass, filter); 8 } 9 10 // Process any @PropertySource annotations 11 for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable( 12 sourceClass.getMetadata(), PropertySources.class, 13 org.springframework.context.annotation.PropertySource.class)) { 14 if (this.environment instanceof ConfigurableEnvironment) { 15 processPropertySource(propertySource); 16 } 17 else { 18 logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + 19 "]. Reason: Environment must implement ConfigurableEnvironment"); 20 } 21 } 22 23 // Process any @ComponentScan annotations 24 Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable( 25 sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); 26 if (!componentScans.isEmpty() && 27 !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { 28 for (AnnotationAttributes componentScan : componentScans) { 29 // The config class is annotated with @ComponentScan -> perform the scan immediately 30 Set<BeanDefinitionHolder> scannedBeanDefinitions = 31 this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); 32 // Check the set of scanned definitions for any further config classes and parse recursively if needed 33 for (BeanDefinitionHolder holder : scannedBeanDefinitions) { 34 BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition(); 35 if (bdCand == null) { 36 bdCand = holder.getBeanDefinition(); 37 } 38 if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { 39 parse(bdCand.getBeanClassName(), holder.getBeanName()); 40 } 41 } 42 } 43 } 44 45 // Process any @Import annotations 46 processImports(configClass, sourceClass, getImports(sourceClass), filter, true); 47 48 // Process any @ImportResource annotations 49 AnnotationAttributes importResource = 50 AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class); 51 if (importResource != null) { 52 String[] resources = importResource.getStringArray("locations"); 53 Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader"); 54 for (String resource : resources) { 55 String resolvedResource = this.environment.resolveRequiredPlaceholders(resource); 56 configClass.addImportedResource(resolvedResource, readerClass); 57 } 58 } 59 //這裡也很重要,這裡開始會解析當前配置類裡的bean,然後解析父類裡面的bean,就是這裡才會把WebMvcConfigurationSupport的所有bean 60 //都解析出來並新增到configClass裡面,不管解析當前類還是父類,configClass都是自己當前的配置類,所以WebMvcConfigurationSupport 61 // Process individual @Bean methods 62 Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass); 63 for (MethodMetadata methodMetadata : beanMethods) { 64 configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); 65 } 66 67 // Process default methods on interfaces 68 processInterfaces(configClass, sourceClass); 69 70 //最主要的就是這裡,解析當前類的父類 71 // Process superclass, if any 72 if (sourceClass.getMetadata().hasSuperClass()) { 73 String superclass = sourceClass.getMetadata().getSuperClassName(); 74 if (superclass != null && !superclass.startsWith("java") && 75 !this.knownSuperclasses.containsKey(superclass)) { 76 //如果我們第一個繼承了WebMvcConfigurationSupport的配置類,已經被掃描到,就會新增一個map快取, 77 //下一個也繼承了WebMvcConfigurationSupport的配置類,將不在解析,直接返回null。結束迴圈,這也是外面一層為什麼要新增while迴圈 78 this.knownSuperclasses.put(superclass, configClass); 79 // Superclass found, return its annotation metadata and recurse 80 return sourceClass.getSuperClass(); 81 } 82 } 83 84 // No superclass -> processing is complete 85 return null;
所以就現在來講,基本已經決定了,解析第一個配置類的時候,第二個配置類重寫的任何方法基本沒什麼用了,因為父類所有的bean已經在第一個配置類中解析掃描到了,就剩下如何去建立bean了。我們再繼續往下看會更明白;
第二步:現在當所有bean已經掃描到,並且bean定義已經完成,該開始例項化了,看一下createBeanInstance的建立過程,最後生成的時候會找到 factoryBean也就是我們自己的配置類
1 private Object instantiate(String beanName, RootBeanDefinition mbd, 2 @Nullable Object factoryBean, Method factoryMethod, Object[] args) { 3 4 try { 5 if (System.getSecurityManager() != null) { 6 return AccessController.doPrivileged((PrivilegedAction<Object>) () -> 7 this.beanFactory.getInstantiationStrategy().instantiate( 8 mbd, beanName, this.beanFactory, factoryBean, factoryMethod, args), 9 this.beanFactory.getAccessControlContext()); 10 } 11 else { 12 return this.beanFactory.getInstantiationStrategy().instantiate( 13 mbd, beanName, this.beanFactory, factoryBean, factoryMethod, args); 14 } 15 } 16 catch (Throwable ex) { 17 throw new BeanCreationException(mbd.getResourceDescription(), beanName, 18 "Bean instantiation via factory method failed", ex); 19 } 20 }
其中factoryBean就是我們的當前第一個被解析到的配置類bean,截圖為證,我自己寫了兩個配置類,第一個被載入的是MyASD,瞎寫的名,好區分,第二個配置類是WebConfiguration,我們只看WebMvcConfigurationSupport裡面的其中一個bean的建立過程,就是requestMappingHandlerAdapter,為啥要看這個,正好跟上節json自定義銜接。
https://www.cnblogs.com/guoxiaoyu/p/13667961.html
到這裡,我們可以看到在生成requestMappingHandlerAdapter時,呼叫extendMessageConverters方法時,一定會呼叫第一個配置類中的重寫方法,因為所有的WebMvcConfigurationSupport裡面 bean都被第一個配置類解析完了,所有的factoryBean都是當前第一個配置類,就算第二個配置完沒有報錯,也不會生效了。
我直接把這個問題用原始碼的方式講解清楚,方便大家明白為什麼配置兩個WebMvcConfigurationSupport類,只有一個生效。