Automatic configuration of spring boot web application

Automatic configuration of web application

As analyzed in previous articles, @ SpringBootApplication will use the @ Import annotation to introduce AutoConfigurationImportSelector
AutoConfigurationImportSelector will return the autoconfiguration class to be loaded through the spi mechanism
These include dispatcher servlet autoconfiguration and webmvca autoconfiguration
The former is used for servlet related configuration, while the latter is used for mvc related component configuration

DispatcherServletAutoConfiguration

First, let's take a look at the annotations used by this class

// Used to specify the order of automatic initialization
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
// Specify the current class as the configuration class
@Configuration(proxyBeanMethods = false)
// Load only when the application type is servlet
@ConditionalOnWebApplication(type = Type.SERVLET)
// Only load when there is DispatcherServlet in the classpath
@ConditionalOnClass(DispatcherServlet.class)
// Load after the ServletWebServerFactoryAutoConfiguration is loaded
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
}

In addition, there are several internal classes in this class

DispatcherServletConfiguration

// Specify the current class as the configuration class
@Configuration(proxyBeanMethods = false)
// Load only when the DefaultDispatcherServletCondition is met
@Conditional(DefaultDispatcherServletCondition.class)
// Only when ServletRegistration appears in the classpath can it be loaded
@ConditionalOnClass(ServletRegistration.class)
// Validate WebMvcProperties
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {

	// Initialize dispatcherServlet. The name of the bean is dispatcherServlet
	@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
	public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
		DispatcherServlet dispatcherServlet = new DispatcherServlet();
		// Configures dispatcherSerlvet through injected webMvcProperties
		dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
		dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
		dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
		dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
		dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
		return dispatcherServlet;
	}

	// Initialize MultipartResolver to upload files
	// The loading condition here is that there is a bean of multipartresolver in the context, but the name of the bean is not multipartresolver
	// The name of this bean will be named multipartResolver here
	@Bean
	@ConditionalOnBean(MultipartResolver.class)
	@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
	public MultipartResolver multipartResolver(MultipartResolver resolver) {
		// Detect if the user has created a MultipartResolver but named it incorrectly
		return resolver;
	}

}

Let's take a look at the class WebMvcProperties

@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties {
}

As you can see, this class is mainly used to receive spring Configuration starting with MVC

DefaultDispatcherServletCondition

Next, take a look at the DefaultDispatcherServletCondition used to represent the DispatcherServletConfiguration loading condition

@Order(Ordered.LOWEST_PRECEDENCE - 10)
private static class DefaultDispatcherServletCondition extends SpringBootCondition {

	@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
		ConditionMessage.Builder message = ConditionMessage.forCondition("Default DispatcherServlet");
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		// Get the name of the bean of type DispatcherServlet from beanFactory
		List<String> dispatchServletBeans = Arrays
				.asList(beanFactory.getBeanNamesForType(DispatcherServlet.class, false, false));
		if (dispatchServletBeans.contains(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
			// If a bean with the name dispatcherSerlet and the type DispatcherServlet is already included, there is no match at this time
			return ConditionOutcome
					.noMatch(message.found("dispatcher servlet bean").items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
		}
		if (beanFactory.containsBean(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
			// If it already contains a bean named dispatcherSerlvet, there is no match at this time
			return ConditionOutcome.noMatch(
					message.found("non dispatcher servlet bean").items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
		}
		if (dispatchServletBeans.isEmpty()) {
			// Currently, there is no bean of type DispatcherServlet, so the matching
			return ConditionOutcome.match(message.didNotFind("dispatcher servlet beans").atAll());
		}
		// Although there are bean s of type dispatcherServlet, but the name is not dispatcherServlet, they still match through
		return ConditionOutcome.match(message.found("dispatcher servlet bean", "dispatcher servlet beans")
				.items(Style.QUOTE, dispatchServletBeans)
				.append("and none is named " + DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
	}

}

DispatcherServletRegistrationConfiguration

The main function is to register dispatcherServlet and configure some properties

// The current class is a configuration class
@Configuration(proxyBeanMethods = false)
// Only when the DispatcherServletRegistrationCondition is satisfied can it be loaded
@Conditional(DispatcherServletRegistrationCondition.class)
// Only when there is ServletRegistration in the classpath can it be loaded
@ConditionalOnClass(ServletRegistration.class)
// Enable automatic property injection of WebMvcProperties
@EnableConfigurationProperties(WebMvcProperties.class)
// Import DispatcherServletConfiguration
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {

	// Returns a bean with the name dispatcherServletRegistration and the type dispatcherServletRegistration
	@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
	// Load only when there are both beans of type dispatcherservlet and name dispatcherservlet in beanFactory
	@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
	public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
			WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
		// The default path is/
		DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
				webMvcProperties.getServlet().getPath());
		registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
		registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
		// Set the configuration of uploading files
		multipartConfig.ifAvailable(registration::setMultipartConfig);
		return registration;
	}

}
public class DispatcherServletRegistrationBean extends ServletRegistrationBean<DispatcherServlet>
		implements DispatcherServletPath {

	private final String path;

	/**
	 * Create a new {@link DispatcherServletRegistrationBean} instance for the given
	 * servlet and path.
	 * @param servlet the dispatcher servlet
	 * @param path the dispatcher servlet path
	 */
	public DispatcherServletRegistrationBean(DispatcherServlet servlet, String path) {
		super(servlet);
		Assert.notNull(path, "Path must not be null");
		this.path = path;
		// Add urlMappings
		super.addUrlMappings(getServletUrlMapping());
	}

	@Override
	public String getPath() {
		return this.path;
	}
	
	// Manual modification of urlMapping is not supported
	@Override
	public void setUrlMappings(Collection<String> urlMappings) {
		throw new UnsupportedOperationException("URL Mapping cannot be changed on a DispatcherServlet registration");
	}

	@Override
	public void addUrlMappings(String... urlMappings) {
		throw new UnsupportedOperationException("URL Mapping cannot be changed on a DispatcherServlet registration");
	}

}

Let's take a look at getServletUrlMapping

default String getServletUrlMapping() {
	if (getPath().equals("") || getPath().equals("/")) {
		return "/";
	}
	if (getPath().contains("*")) {
		return getPath();
	}
	if (getPath().endsWith("/")) {
		return getPath() + "*";
	}
	return getPath() + "/*";
}

DispatcherServletRegistrationCondition

The main function of this class is to determine whether to load DispatcherServletRegistrationConfiguration

@Order(Ordered.LOWEST_PRECEDENCE - 10)
private static class DispatcherServletRegistrationCondition extends SpringBootCondition {

	@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		ConditionOutcome outcome = checkDefaultDispatcherName(beanFactory);
		if (!outcome.isMatch()) {
			return outcome;
		}
		return checkServletRegistration(beanFactory);
	}

	private ConditionOutcome checkDefaultDispatcherName(ConfigurableListableBeanFactory beanFactory) {
		// Gets the name of the bean of type DispatcherServlet in beanFactory
		List<String> servlets = Arrays
				.asList(beanFactory.getBeanNamesForType(DispatcherServlet.class, false, false));
		// Determine whether there is a bean named dispatcherServlet
		boolean containsDispatcherBean = beanFactory.containsBean(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
		// If there is a bean with the name dispatcherservlet but the type is not dispatcherservlet, there is no match
		if (containsDispatcherBean && !servlets.contains(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
			return ConditionOutcome.noMatch(
					startMessage().found("non dispatcher servlet").items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
		}
		// Otherwise, a match is returned
		return ConditionOutcome.match();
	}

	private ConditionOutcome checkServletRegistration(ConfigurableListableBeanFactory beanFactory) {
		ConditionMessage.Builder message = startMessage();
		// Gets the bean of type ServletRegistrationBean in beanFactory
		List<String> registrations = Arrays
				.asList(beanFactory.getBeanNamesForType(ServletRegistrationBean.class, false, false));
		// Determine whether there is a bean named dispatcherServletRegistration
		boolean containsDispatcherRegistrationBean = beanFactory
				.containsBean(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME);
		if (registrations.isEmpty()) {
			if (containsDispatcherRegistrationBean) {
				// There is no bean of type ServletRegistrationBean, but there is a bean named dispatcherServletRegistration, then there is no match
				return ConditionOutcome.noMatch(message.found("non servlet registration bean")
						.items(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
			}
			// If there is neither a bean of type ServletRegistrationBean nor a bean of name dispatcherServletRegistration, then the matching
			return ConditionOutcome.match(message.didNotFind("servlet registration bean").atAll());
		}
		if (registrations.contains(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)) {
			// If there is a bean with the name dispatcherServletRegistration and the type is ServletRegistrationBean, then it does not match
			return ConditionOutcome.noMatch(message.found("servlet registration bean")
					.items(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
		}
		if (containsDispatcherRegistrationBean) {
			// There is a bean with the name dispatcherServletRegistration, which does not match
			return ConditionOutcome.noMatch(message.found("non servlet registration bean")
					.items(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
		}
		return ConditionOutcome.match(message.found("servlet registration beans").items(Style.QUOTE, registrations)
				.append("and none is named " + DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
	}

	private ConditionMessage.Builder startMessage() {
		return ConditionMessage.forCondition("DispatcherServlet Registration");
	}

}

WebMvcAutoConfiguration

First, let's take a look at the annotations used by webmvcoautoconfiguration

// The current class is a configuration class
@Configuration(proxyBeanMethods = false)
// Load only if the current application is a servlet application
@ConditionalOnWebApplication(type = Type.SERVLET)
// Configure only when baokukou Servlet DispatcherServlet WebMvcConfigurer is in the classpath
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
// Configure only when WebMvcConfigurationSupport does not exist
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
// After initializing the taskexecutionconfiguration, execute the autovalidationconfiguration
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
}

This class mainly includes the following internal classes, which are analyzed below

WebMvcAutoConfigurationAdapter

// The current class is a configuration class
@Configuration(proxyBeanMethods = false)
// Introducing EnableWebMvcConfiguration
@Import(EnableWebMvcConfiguration.class)
// Perform the binding configured in WebMvcProperties and ResourceProperties
// Used to parse spring MVC and spring Configuration item with resources as prefix
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
}

viewResolver

@Bean
// Only when there is a bean of ViewResolver type can it be loaded
@ConditionalOnBean(ViewResolver.class)
// When there is no bean with the name of viewResolver and the type of ContentNegotiatingViewResolver
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
	ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
	// Settings Manager
	resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
	// ContentNegotiatingViewResolver uses all the other view resolvers to locate
	// a view so it should have a high precedence
	// set priority
	resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
	return resolver;
}

Although the ContentNegotiatingViewResolver inherits the ViewResolver, it does not directly parse the view, but finds the appropriate view parser through the context

ContentNegotiatingViewResolver

The following code will get the bean that implements the ViewResolver interface from beanFactory and put it into viewResolvers

@Override
protected void initServletContext(ServletContext servletContext) {
	// Find the bean that implements the ViewResolver interface from the spring context
	Collection<ViewResolver> matchingBeans =
			BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values();
	// Add the found bean s to viewsolvers
	if (this.viewResolvers == null) {
		this.viewResolvers = new ArrayList<>(matchingBeans.size());
		for (ViewResolver viewResolver : matchingBeans) {
			if (this != viewResolver) {
				this.viewResolvers.add(viewResolver);
			}
		}
	}
	else {
		for (int i = 0; i < this.viewResolvers.size(); i++) {
			ViewResolver vr = this.viewResolvers.get(i);
			if (matchingBeans.contains(vr)) {
				continue;
			}
			String name = vr.getClass().getName() + i;
			obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(vr, name);
		}

	}
	AnnotationAwareOrderComparator.sort(this.viewResolvers);
	this.cnmFactoryBean.setServletContext(servletContext);
}
public View resolveViewName(String viewName, Locale locale) throws Exception {
	RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
	Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
	List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
	if (requestedMediaTypes != null) {
		// Use the parser in viewResolvers to parse the current view
		List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
		// Select a best match
		View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
		if (bestView != null) {
			return bestView;
		}
	}

	String mediaTypeInfo = logger.isDebugEnabled() && requestedMediaTypes != null ?
			" given " + requestedMediaTypes.toString() : "";

	if (this.useNotAcceptableStatusCode) {
		if (logger.isDebugEnabled()) {
			logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
		}
		return NOT_ACCEPTABLE_VIEW;
	}
	else {
		logger.debug("View remains unresolved" + mediaTypeInfo);
		return null;
	}
}

It can also be seen from the above code that ContentNegotiatingViewResolver will not directly parse the view, but will hand over the parsing work to the implementation class bean of ViewResolver in beanFactory

BeanNameViewResolver

BeanNameViewResolver matches beans of View type in beanFactory by name

@Bean
// Only bean s of View type are loaded
@ConditionalOnBean(View.class)
// Beans of type bean nameviewresolver do not exist before loading
@ConditionalOnMissingBean
public BeanNameViewResolver beanNameViewResolver() {
	BeanNameViewResolver resolver = new BeanNameViewResolver();
	resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
	return resolver;
}
BeanNameViewResolver

BeanNameViewResolver also inherits the ViewResolver, so it also has the ability to resolve the View. However, unlike the ContentNegotiatingViewResolver above, this class looks for a bean with the same name and type of View in beanFactory through the name of the View

public class BeanNameViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered {

	private int order = Ordered.LOWEST_PRECEDENCE;  // default: same as non-Ordered


	/**
	 * Specify the order value for this ViewResolver bean.
	 * <p>The default value is {@code Ordered.LOWEST_PRECEDENCE}, meaning non-ordered.
	 * @see org.springframework.core.Ordered#getOrder()
	 */
	public void setOrder(int order) {
		this.order = order;
	}

	@Override
	public int getOrder() {
		return this.order;
	}


	@Override
	@Nullable
	public View resolveViewName(String viewName, Locale locale) throws BeansException {
		ApplicationContext context = obtainApplicationContext();
		// Find a bean of type View whose name matches the View name
		if (!context.containsBean(viewName)) {
			// Allow for ViewResolver chaining...
			return null;
		}
		if (!context.isTypeMatch(viewName, View.class)) {
			if (logger.isDebugEnabled()) {
				logger.debug("Found bean named '" + viewName + "' but it does not implement View");
			}
			// Since we're looking into the general ApplicationContext here,
			// let's accept this as a non-match and allow for chaining as well...
			return null;
		}
		return context.getBean(viewName, View.class);
	}

}

Static resource processing

Webmvcoautoconfigurationadapter implements the WebMvcConfigurer interface, which is mainly used as a callback to customize the configuration
The processing of static resources is mainly in the addResourceHandlers method

public void addResourceHandlers(ResourceHandlerRegistry registry) {
	// The configuration items in resourceProperties are spring Resources prefix
	// Whether to enable the default static resource processing. The default value is true. If enabled, the following code will be executed to configure the resource processing
	if (!this.resourceProperties.isAddMappings()) {
		logger.debug("Default resource handling disabled");
		return;
	}
	Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
	CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
	// Judge whether there is mapping processing for / webjars / * * or not. If there is no mapping configuration below
	if (!registry.hasMappingForPattern("/webjars/**")) {
		customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
				.addResourceLocations("classpath:/META-INF/resources/webjars/")
				.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
	}
	// The static pattern here defaults to/**
	// path can be configured here
	String staticPathPattern = this.mvcProperties.getStaticPathPattern();
	// Judge whether there is a corresponding mapping. The default mapping location is [/ META-INF/resources/, /resources/, /static/, /public /]
	// Therefore, by default, we can put static resources under the above directories
	if (!registry.hasMappingForPattern(staticPathPattern)) {
		customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
				.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
				.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
	}
}

@EnableWebMvc

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}

You can see the @ EnableWebMvc annotation, which mainly imports DelegatingWebMvcConfiguration
Let's take a look at the DelegatingWebMvcConfiguration inheritance hierarchy

WebMvcConfigurationSupport

First, take a look at WebMvcConfigurationSupport, which implements ApplicationContextAware and ServletContextAware, so you can get ApplicationContext and ServletContext
In addition, this class has many methods for injecting bean s

@Bean
public BeanNameUrlHandlerMapping beanNameHandlerMapping(
		@Qualifier("mvcConversionService") FormattingConversionService conversionService,
		@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {

	BeanNameUrlHandlerMapping mapping = new BeanNameUrlHandlerMapping();
	mapping.setOrder(2);
	mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
	mapping.setCorsConfigurations(getCorsConfigurations());
	return mapping;
}

And there are many methods of empty protected modifiers

protected void addViewControllers(ViewControllerRegistry registry) {
}

The methods of these empty protected modifiers will be called in the above bean injection methods. The methods of these empty protected modifiers are provided to subclasses for customization operations

DelegatingWebMvcConfiguration

Let's take a look at the implementation class DelegatingWebMvcConfiguration. From the following code, we can see that DelegatingWebMvcConfiguration is a configuration class first
Secondly, the bean of WebMvcConfigurer type is injected through setConfigurers method
Then it implements the extension point method of customized mvc provided by WebMvcConfigurationSupport
In the extension point method, all injected WebMvcConfigurer type bean s will be traversed, and their corresponding methods will be called to execute their respective customization logic

@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

	private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();


	@Autowired(required = false)
	public void setConfigurers(List<WebMvcConfigurer> configurers) {
		if (!CollectionUtils.isEmpty(configurers)) {
			this.configurers.addWebMvcConfigurers(configurers);
		}
	}


	@Override
	protected void configurePathMatch(PathMatchConfigurer configurer) {
		this.configurers.configurePathMatch(configurer);
	}

	@Override
	protected void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
		this.configurers.configureContentNegotiation(configurer);
	}

	@Override
	protected void configureAsyncSupport(AsyncSupportConfigurer configurer) {
		this.configurers.configureAsyncSupport(configurer);
	}

	@Override
	protected void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
		this.configurers.configureDefaultServletHandling(configurer);
	}

	@Override
	protected void addFormatters(FormatterRegistry registry) {
		this.configurers.addFormatters(registry);
	}

	@Override
	protected void addInterceptors(InterceptorRegistry registry) {
		this.configurers.addInterceptors(registry);
	}

	@Override
	protected void addResourceHandlers(ResourceHandlerRegistry registry) {
		this.configurers.addResourceHandlers(registry);
	}

	@Override
	protected void addCorsMappings(CorsRegistry registry) {
		this.configurers.addCorsMappings(registry);
	}

	@Override
	protected void addViewControllers(ViewControllerRegistry registry) {
		this.configurers.addViewControllers(registry);
	}

	@Override
	protected void configureViewResolvers(ViewResolverRegistry registry) {
		this.configurers.configureViewResolvers(registry);
	}

	@Override
	protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
		this.configurers.addArgumentResolvers(argumentResolvers);
	}

	@Override
	protected void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
		this.configurers.addReturnValueHandlers(returnValueHandlers);
	}

	@Override
	protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
		this.configurers.configureMessageConverters(converters);
	}

	@Override
	protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
		this.configurers.extendMessageConverters(converters);
	}

	@Override
	protected void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
		this.configurers.configureHandlerExceptionResolvers(exceptionResolvers);
	}

	@Override
	protected void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
		this.configurers.extendHandlerExceptionResolvers(exceptionResolvers);
	}

	@Override
	@Nullable
	protected Validator getValidator() {
		return this.configurers.getValidator();
	}

	@Override
	@Nullable
	protected MessageCodesResolver getMessageCodesResolver() {
		return this.configurers.getMessageCodesResolver();
	}

}

Customized mvc configuration

If you need to customize mvc configuration, you can use the following methods. The simplest way to retain the spring boot automatic configuration function is to implement a WebMvcConfigurer and inject

implements WebMvcConfigurer

The corresponding type of mvspring configuration will be automatically configured as mvboot, and the corresponding type of mvspring configuration will be automatically customized
Customization achieved in this way will not override the configuration of @ EnableAutoConfiguration about webmvcoautoconfiguration

@EnableWebMvc + implements WebMvcConfigurer

Because @ EnableWebMvc will introduce DelegatingWebMvcConfiguration, and DelegatingWebMvcConfiguration itself inherits WebMvcConfigurationSupport
The premise of Spring boot for mvc related automatic configuration is that there is no bean of WebMvcConfigurationSupport type
Therefore, using this method will override the configuration of @ EnableAutoConfiguration about webmvcoautoconfiguration

Extensions webmvcconfigurationsupport or extensions delegatingwebmvcconfiguration

The reason is the same as above, which will overwrite the configuration of @ EnableAutoConfiguration about webmvcoautoconfiguration

Keywords: Java Spring Spring Boot

Added by getgray on Tue, 08 Mar 2022 22:57:36 +0200