為什么我們的項目里出現兩個配置類繼承WebMvcConfigurationSupport時,只有一個會生效。我在網上找了半天都是說結果的,沒有人分析源碼到底是為啥,博主準備講解一下,希望可以幫到大家!
大家基本遇到過一種情況,就是我配置類中已經配置了,為什么就是沒有生效呢?其中一種原因就是,自己寫的配置類也繼承了WebMvcConfigurationSupport,當項目出現兩個配置類都繼承該類時,只會講第一個配置類生效,至于為什么,就是今天博主需要講解的,我們必須了解一些springboot的bean的創建過程也就是其生命周期: https://www.processon.com/view/link/5f704050f346fb166d0f3e3c 雖然畫的比較簡單,有許多細節都沒有解析,但是對于當前我們的話題來講已經基本可以了;
第一步:我們的配置類是從哪里開始創建解析的:大家可以看到圖示bean的流程中doProcessConfigurationClass(configClass, sourceClass, filter);方法,我們看一下是如何調用它 的:
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()) {
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);
do {
//從這里開始解析我們的當前配置類
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while (sourceClass != null);
this.configurationClasses.put(configClass, configClass);
}
這里可以看到一個while循環,為什么要這么設計呢?我們再看看doProcessConfigurationClass(configClass, sourceClass, filter);方法的源碼
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// Recursively process any member (nested) classes first
processMemberClasses(configClass, sourceClass, filter);
}
// Process any @PropertySource annotations
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");
}
}
// Process any @ComponentScan annotations
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
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();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
// Process any @ImportResource annotations
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);
}
}
//這里也很重要,這里開始會解析當前配置類里的bean,然后解析父類里面的bean,就是這里才會把WebMvcConfigurationSupport的所有bean
//都解析出來并添加到configClass里面,不管解析當前類還是父類,configClass都是自己當前的配置類,所以WebMvcConfigurationSupport
// Process individual @Bean methods
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// Process default methods on interfaces
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)) {
//如果我們第一個繼承了WebMvcConfigurationSupport的配置類,已經被掃描到,就會添加一個map緩存,
//下一個也繼承了WebMvcConfigurationSupport的配置類,將不在解析,直接返回null。結束循環,這也是外面一層為什么要添加while循環
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
return sourceClass.getSuperClass();
}
}
// No superclass -> processing is complete
return null;
所以就現在來講,基本已經決定了,解析第一個配置類的時候,第二個配置類重寫的任何方法基本沒什么用了,因為父類所有的bean已經在第一個配置類中解析掃描到了,就剩下如何去創建bean了。我們再繼續往下看會更明白;
第二步:現在當所有bean已經掃描到,并且bean定義已經完成,該開始實例化了,看一下createBeanInstance的創建過程,最后生成的時候會找到 factoryBean也就是我們自己的配置類
private Object instantiate(String beanName, RootBeanDefinition mbd,
@Nullable Object factoryBean, Method factoryMethod, Object[] args) {
try {
if (System.getSecurityManager() != null) {
return AccessController.doPrivileged((PrivilegedAction<Object>) () ->
this.beanFactory.getInstantiationStrategy().instantiate(
mbd, beanName, this.beanFactory, factoryBean, factoryMethod, args),
this.beanFactory.getAccessControlContext());
}
else {
return this.beanFactory.getInstantiationStrategy().instantiate(
mbd, beanName, this.beanFactory, factoryBean, factoryMethod, args);
}
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Bean instantiation via factory method failed", ex);
}
}
其中factoryBean就是我們的當前第一個被解析到的配置類bean,截圖為證,我自己寫了兩個配置類,第一個被加載的是MyASD,瞎寫的名,好區分,第二個配置類是WebConfiguration,我們只看WebMvcConfigurationSupport里面的其中一個bean的創建過程,就是requestMappingHandlerAdapter,為啥要看這個,正好跟上節json自定義銜接。
https://www.cnblogs.com/guoxiaoyu/p/13667961.html
到這里,我們可以看到在生成requestMappingHandlerAdapter時,調用extendMessageConverters方法時,一定會調用第一個配置類中的重寫方法,因為所有的WebMvcConfigurationSupport里面 bean都被第一個配置類解析完了,所有的factoryBean都是當前第一個配置類,就算第二個配置完沒有報錯,也不會生效了。
我直接把這個問題用源碼的方式講解清楚,方便大家明白為什么配置兩個WebMvcConfigurationSupport類,只有一個生效。
【轉自:建湖網站建設 http://www.1234xp.com/jianhu.html 復制請保留原URL】