• 项目里出现两个配置类继承WebMvcConfigurationSupport时,为什么只有一个会生效(源码分析)


      为什么我们的项目里出现两个配置类继承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类,只有一个生效。


  • 相关阅读:
    apache安装 mod_evasive
    sshd调优
    MySQL服务器状态变量
    MySQL查询缓存
    MySQL并发调优和IO调优
    MySQL线程独享[转]
    mysql线程缓存和表缓存
    MySQL点滴
    MySQL优化常用
    apache的prefork的详解
  • 原文地址:https://www.cnblogs.com/guoxiaoyu/p/13731123.html
Copyright © 2020-2023  润新知