preface
I didn't plan to write this article, but after reading the core principles of SpringBoot, I suddenly thought of the MVC automatic failure problem encountered in the previous development. Although there are many articles and official documents on the Internet explaining the reasons, I still want to have a look at it in person. What I thought was a simple thing, but I didn't expect to lead to a more complex problem. Many people didn't get it To the result, the online article also did not write clearly, finally or oneself do for a long time to understand, this article mainly records one's own analysis process,.
text
elicit questions
The above is the automatic configuration of SpringBoot MVC. The problem is that when we need to configure MVC ourselves, we have three choices:
- Implementation of WebMvcConfigurer interface
- Inherits the WebMvcConfigurerAdapter class
- Inherits the WebMvcConfigurationSupport class
In the old version, we usually inherit the WebMvcConfigurerAdapter class. This class itself implements the WebMvcConfigurer interface. Because the old version JDK interface has no default method, it is cumbersome to directly implement WebMvcConfigurer. However, the interface can have a default method later, and WebMvcConfigurerAdapter is marked as obsolete, so we only need to configure MVC now Implement WebMvcConfigurer interface or inherit WebMvcConfigurationSupport, but the latter will cause configuration failure of SpringBoot, because @ conditionalonmissingbean (webmvc) exists on the automatic configuration class ConfigurationSupport.class )Such an annotation indicates that the automatic configuration will not be loaded until there are no instances of the WebMvcConfigurationSupport class and its subclasses (in addition, @ enable webmvc annotation is used Solutions can also cause automatic configuration to fail).
This is the reason for the failure of MVC automatic configuration. Basically, all articles on the Internet have analyzed this step, but pay attention to the red box I drew in the figure above. There are two static internal classes in this automatic configuration class. We know that static internal classes are better than external classes to load (spring boot automatic configuration uses this feature a lot), and among them, enable webmvcconfiguration I noticed that this class inherits from the DelegatingWebMvcConfiguration, and the DelegatingWebMvcConfiguration also inherits from the WebMvcConfigurationSupport class. I believe you will have doubts when you see this. Why does this configuration class not cause the automatic configuration to fail, but we implement it ourselves?
Analysis process
I know that the resolution and registration of the configuration class is in the ConfigurationClassPostProcessor class. I have analyzed this class many times in the previous article. Although the implementation process of this class is not difficult, the details are very complicated, so I didn't dig into it before. When I encountered this problem, my first thought was that my understanding of this class was not deep enough. Therefore, I thought of studying this class carefully at the first time. After spending a lot of time analyzing breakpoints, I didn't get much.
Then I thought, is the registration order of configuration classes after automatic configuration. I made an obvious mistake here, because I was thinking about the order of registration, not instantiation. Because the annotation of ConditionalOnMissingBean will be loaded only when there is no specified instance of bean. In my mind, I thought it was ConditionalOnMissingClass. So I hit a breakpoint on the registerBeanDefinition and preInstantiateSingletons methods in the DefaultListableBeanFactory, trying to confirm that the registration order is as I thought:
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); .... }
But as a result, the order in beanDefinitionNames is that two static inner classes come first, which means that the static inner classes must be registered in the IOC container before the external classes. I'm stupid. But fortunately, that's why, otherwise I would have thought it was the result. Finally, I thought that we should look at the instantiation order of classes. However, under normal circumstances, the instantiation order of classes is the sequence in the breakpoint diagram above. I wonder if any class depends on WebMvcAutoConfiguration and causes it to be instantiated in advance. So I set the breakpoint to the doGetBean method in AbstractBeanFactory and added conditions (I have to say that idea is very powerful. It saves me a lot of debugging time to go back to the previous call point, set conditions for breakpoints, and call stack information)
Then start the project and you can see that the first instance is the WebMvcAutoConfiguration class, which makes it clear why the enabling webmvcconfiguration does not cause the automatic configuration to fail.
But it's not over. Why are auto configuration classes instantiated before static inner classes? Who triggered it? As I go further, I think of the call stack
If you are familiar with the Spring source code, you can find that the instantiation of the automatic configuration class is triggered in the 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; }
This code has been analyzed in the article of Bean instantiation. The function of this method is to instantiate the current BeanDefinition through factoryMethod, and instantiation of this BD will first instantiate the Bean pointed to by the factoryBeanName attribute. Here, the factoryBeanName is org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration and factoryMethod are formContentFilter, The settings of these two properties are set after the ConfigurationClassPostProcessor resolves @ Configuration and @ Bean (@ Bean annotated method name will be set to factoryMethod, and the name of the Configuration class where the method is located is factoryBeanName). The analysis will not be expanded here.
@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 is configured in the MVC automatic configuration class. It is loaded by default, and the filter will trigger initialization after Tomcat is started, and the trace call stack can also be seen. In addition, we also see that a HiddenHttpMethodFilter is configured in the auto configuration class, but this is not loaded by default, so we only need to set the application.properties If the following properties are configured in, the auto configuration class will not be instantiated, but the instantiation of the two static inner classes will not be affected.
spring.mvc.formcontent.filter.enabled=false
summary
This problem is just out of interest in research. Although it has consumed a lot of time and energy, it has gained a lot. It has deepened the understanding of Spring source code and corrected some previous misunderstanding. In addition, we should study the source code by ourselves. We can't only read one or two articles or listen to others. We can only know whether our understanding is correct after debugging by ourselves.