全网最深分析SpringBoot MVC自动配置失效的原因

前言

原本没有设计这一篇文章的,只是在看完SpringBoot焦点原理后,突然想到之前开发中遇到的MVC自动失效的问题,虽然网上有许多文章以及官方文档都说明晰缘故原由,但照样想亲自看一看,本以为很简单的事情,没想到却引发出一个较庞大的问题,请教了许多人都没有获得效果,网上文章也没有写清晰的,最后照样自己搞了良久才弄明了的,此篇主要纪录自己的一个剖析历程,。

正文

引出问题

全网最深分析SpringBoot MVC自动配置失效的原因
上面是SpringBoot MVC的自动设置,问题是这样的,当我们需要自己设置MVC时,有三种选择:

  • 实现WebMvcConfigurer接口
  • 继续WebMvcConfigurerAdapter
  • 继续WebMvcConfigurationSupport

在老版本中我们常用的做法就是继续WebMvcConfigurerAdapter类,这个类自己是实现了WebMvcConfigurer接口的,由于老版本JDK接口没有默认方式,直接实现WebMvcConfigurer对照繁琐,尔后来接口可以有默认方式了,WebMvcConfigurerAdapter就被标记为过时了,以是我们现在设置MVC只需要实现WebMvcConfigurer接口或者继续WebMvcConfigurationSupport,然则后者会导致SpringBoot的设置失效,由于在自动设置类上有@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)这样一个注解,示意没有WebMvcConfigurationSupport类及其子类的实例时才会加载自动设置(另外使用@EnableWebMvc注解也会导致自动设置失效)。
MVC自动设置失效的缘故原由就是这个了,基本上所有网上的文章剖析到这一步也就完了,然则注重上图我画的红方框,在这个自动设置类中有两个静态内部类,我们知道静态内部类是优于外部类加载的(SpringBoot自动设置大量使用了此特征),而其中EnableWebMvcConfiguration这个类,我注重到它是继续自DelegatingWebMvcConfiguration,而DelegatingWebMvcConfiguration又继续自WebMvcConfigurationSupport类,信赖看到这你也应该会有疑惑了,为什么这个设置类没有导致自动设置失效,而我们自己实现的就会?

剖析历程

我知道设置类的剖析注册是在ConfigurationClassPostProcessor类中,而这个类我前面的文章多次剖析过,虽然这个类的实现流程不难,但细节异常绕,以是之前没有深挖。遇到这个问题时,我首先想的是对这个类的明白不够深刻,因此第一时间想到仔细研究这个类,在花费了大量时间断点剖析后,却没有太大的收获。
接着我又想,是不是设置类的注册顺序在自动设置的后面。这里我就犯了一个显而易见的错误,由于我思量的是注册的顺序,不是实例化。由于ConditionalOnMissingBean注解是没有指定bean的实例时才会去加载,而我脑海里那时想成了ConditionalOnMissingClass。以是我在DefaultListableBeanFactory中的registerBeanDefinitionpreInstantiateSingletons方式上打上了断点,力争确认注册顺序如我所想:

吐血推荐,想进BAT必看

	public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException {

		BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
		if (existingDefinition != null) {
			....
			this.beanDefinitionMap.put(beanName, beanDefinition);
		}
		else {
			if (hasBeanCreationStarted()) {
				// Cannot modify startup-time collection elements anymore (for stable iteration)
				synchronized (this.beanDefinitionMap) {
					this.beanDefinitionMap.put(beanName, beanDefinition);
					List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
					updatedDefinitions.addAll(this.beanDefinitionNames);
					updatedDefinitions.add(beanName);
					this.beanDefinitionNames = updatedDefinitions;
					removeManualSingletonName(beanName);
				}
			}
			else {
				// Still in startup registration phase
				this.beanDefinitionMap.put(beanName, beanDefinition);
				this.beanDefinitionNames.add(beanName);
				removeManualSingletonName(beanName);
			}
			this.frozenBeanDefinitionNames = null;
		}
	}

	public void preInstantiateSingletons() throws BeansException {
		List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
		....
	}

全网最深分析SpringBoot MVC自动配置失效的原因
但效果beanDefinitionNames中的顺序却是两个静态内部类在前,也就是说静态内部类肯定是在外部类之前就注册到IOC容器中了,这下我就傻了。但幸好也是因此,否则我就该以为这就是效果了。最终我想到了应该看类的实例化顺序,然则正常情况下类的实例化顺序就是上面的断点图中的顺序,我想会不会是有什么类依赖了WebMvcAutoConfiguration,导致它提前实例化。于是我将断点又设置到AbstractBeanFactory中的doGetBean方式并加上了条件(不得不说idea的功效异常壮大,回到上一个挪用点、给断点设置条件、挪用客栈信息大大节省了我的调试时间):
全网最深分析SpringBoot MVC自动配置失效的原因
然后启动项目就可以看到首先实例化的果然是WebMvcAutoConfiguration类,这样就搞清晰了为什么EnableWebMvcConfiguration没有导致自动设置失效。
然则还没完,为什么自动设置类会在静态内部类之前实例化呢?是由谁触发的呢?继续深入,这时我想到了看挪用栈:
全网最深分析SpringBoot MVC自动配置失效的原因
大略看一下挪用栈信息,若是对Spring源码熟悉,可以发现自动设置类的实例化是在instantiateUsingFactoryMethod中触发的:

		String factoryBeanName = mbd.getFactoryBeanName();
		if (factoryBeanName != null) {
			if (factoryBeanName.equals(beanName)) {
				throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,
						"factory-bean reference points back to the same bean definition");
			}
			factoryBean = this.beanFactory.getBean(factoryBeanName);
			if (mbd.isSingleton() && this.beanFactory.containsSingleton(beanName)) {
				throw new ImplicitlyAppearedSingletonException();
			}
			factoryClass = factoryBean.getClass();
			isStatic = false;
		}
		else {
			// It's a static factory method on the bean class.
			if (!mbd.hasBeanClass()) {
				throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,
						"bean definition declares neither a bean class nor a factory-bean reference");
			}
			factoryBean = null;
			factoryClass = mbd.getBeanClass();
			isStatic = true;
		}

这段代码在bean实例化的那一篇剖析过,这个方式的作用是通过factoryMethod实例化当前的BeanDefinition,而实例化该BD优先会实例化factoryBeanName属性指向的Bean,这里的factoryBeanName就是org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfigurationfactoryMethod则是formContentFilter,而这两个属性的设置则是在ConfigurationClassPostProcessor剖析@Configuration和@Bean就设置好了(@Bean标注的方式名会设置到factoryMethod,而该方式所在设置类的名称就是factoryBeanName),这里就不睁开剖析了。

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {

	public static final String DEFAULT_PREFIX = "";

	public static final String DEFAULT_SUFFIX = "";

	private static final String[] SERVLET_LOCATIONS = { "/" };

	@Bean
	@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
	@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
	public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
		return new OrderedHiddenHttpMethodFilter();
	}

	@Bean
	@ConditionalOnMissingBean(FormContentFilter.class)
	@ConditionalOnProperty(prefix = "spring.mvc.formcontent.filter", name = "enabled", matchIfMissing = true)
	public OrderedFormContentFilter formContentFilter() {
		return new OrderedFormContentFilter();
	}
	
	......
}

formContentFilter就是在MVC自动设置类中设置的,默认是加载的,而filter就不用多说了,在Tomcat启动后就会触发初始化,追踪挪用栈也可以看到。另外我们还看到自动设置类中还设置了一个HiddenHttpMethodFilter,不外这个默认是不加载的,以是我们只要在application.properties中设置了如下属性,自动设置类就不会实例化了,然则两个静态内部类的实例化照样不会受影响的。

spring.mvc.formcontent.filter.enabled=false

总结

该问题只是出于兴趣研究,虽然耗费了大量的时间和精神,但收获不少,加深了对Spring源码的明白,也修正了之前的一些错误明白,另外对于源码更多的是要自己去研究,不能只看一两篇文章或听别人说,只有自己亲手调试过才气知道自己的明白是否准确。

原创文章,作者:admin,如若转载,请注明出处:https://www.2lxm.com/archives/19391.html