專案裡出現兩個配置類繼承WebMvcConfigurationSupport時,為什麼只有一個會生效(原始碼分析)

努力的小雨發表於2020-09-29

  為什麼我們的專案裡出現兩個配置類繼承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類,只有一個生效。

 


相關文章