Springboot MVC auto configuration principle

Posted by Ali_baba on Thu, 16 Dec 2021 13:04:29 +0100

catalogue

5. MVC automatic configuration principle

5.1 official website reading

5.2 content negotiation view parser

5.3 converter and formatter

5.4 modify the default configuration of SpringBoot

5.5 take over of spring MVC

5. MVC automatic configuration principle

5.1 official website reading

Before writing the project, we also need to know what configuration SpringBoot has made for our spring MVC, including how to extend and customize.

Only by making these clear can we use them more easily in the future. Way 1: source code analysis, way 2: official documents!

Spring MVC Auto-configuration
// Spring Boot provides automatic configuration for Spring MVC, which works well with most applications.
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.
// Auto configuration adds the following functions based on Spring default settings:
The auto-configuration adds the following features on top of Spring's defaults:
// Include view parser
Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
// Support the path of static resource folder and webjars
Support for serving static resources, including support for WebJars 
// Automatically registered Converter:
// Converter, which is what we automatically encapsulate the data submitted by the web page into objects in the background, such as automatically converting the "1" string to int type
// Formatter: [formatter, for example, the page gives us a 2019-8-10, which will automatically format it as a Date object]
Automatic registration of Converter, GenericConverter, and Formatter beans.
// HttpMessageConverters
// Spring MVC is used to convert Http requests and responses. For example, if we want to convert a User object into a JSON string, you can see the official website document explanation;
Support for HttpMessageConverters (covered later in this document).
// To define error code generation rules
Automatic registration of MessageCodesResolver (covered later in this document).
// Home page customization
Static index.html support.
// icons customizing
Custom Favicon support (covered later in this document).
// Initialize the data binder: help us bind the request data to the JavaBean!
Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).
 
/*
If you want to keep the Spring Boot MVC function and add other MVC configurations (interceptors, formatters, view controllers, and other functions), you can add your own @ configuration class of type webmvcconfigurer without adding @ EnableWebMvc. If you want to provide
RequestMappingHandlerMapping,RequestMappingHandlerAdapter Or ExceptionHandlerExceptionResolver
 Instance, you can declare a webmvcreationadapter instance to provide such components.
*/
If you want to keep Spring Boot MVC features and you want to add additional MVC configuration 
(interceptors, formatters, view controllers, and other features), you can add your own 
@Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide 
custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or 
ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.
 
// If you want to fully control Spring MVC, you can add your own @ Configuration and annotate it with @ EnableWebMvc.
If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.

Take a closer look at how it is implemented. It tells us that SpringBoot has automatically configured spring MVC for us, and then what has been automatically configured?

5.2 content negotiation view parser

The ViewResolver is automatically configured, which is the view parser of spring MVC we learned earlier;

That is, the View object is obtained according to the return value of the method, and then the View object determines how to render (forward, redirect).

Let's take a look at the source code here: we find webmvca autoconfiguration and search for content negotiatingviewresolver. Find the following method!

@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
    ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
    resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
    // ContentNegotiatingViewResolver uses all the other view resolvers to locate
    // a view so it should have a high precedence
    // Content negotiatingviewresolver uses all other view parsers to locate views, so it should have higher priority
    resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return resolver;
}

We can click the ContentNegotiatingViewResolver class to find the corresponding code to parse the view

@Override
@Nullable  // Note: @ Nullable means that the parameter can be null
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) {
        // Get candidate view objects
        List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
        // Select the most appropriate view object, and then return this object
        View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
        if (bestView != null) {
            return bestView;
        }
    }
    
    // ...
}

Let's continue to click getcandidate views to see how it obtains candidate views?

In getCandidateViews, you can see that it takes all the view parsers, loops and parses them one by one!

private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
        throws Exception {
 
    List<View> candidateViews = new ArrayList<>();
    if (this.viewResolvers != null) {
        Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
        for (ViewResolver viewResolver : this.viewResolvers) {
            View view = viewResolver.resolveViewName(viewName, locale);
            if (view != null) {
                candidateViews.add(view);
            }
            for (MediaType requestedMediaType : requestedMediaTypes) {
                List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
                for (String extension : extensions) {
                    String viewNameWithExtension = viewName + '.' + extension;
                    view = viewResolver.resolveViewName(viewNameWithExtension, locale);
                    if (view != null) {
                        candidateViews.add(view);
                    }
                }
            }
        }
    }
    if (!CollectionUtils.isEmpty(this.defaultViews)) {
        candidateViews.addAll(this.defaultViews);
    }
    return candidateViews;
}

Therefore, it is concluded that the view parser content negotiation view resolver is used to combine all view parsers

Let's study his combinatorial logic and see that there is an attribute viewResolvers to see where it is assigned!

Continue searching for initServletContext in the ContentNegotiatingViewResolver class

@Override
protected void initServletContext(ServletContext servletContext) {
    // Here it is the parser that gets all the views in the container from the beanFactory tool
    // ViewRescolver.class combines all the view parsers
    Collection<ViewResolver> matchingBeans =
            BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values();
    if (this.viewResolvers == null) {
        this.viewResolvers = new ArrayList<>(matchingBeans.size());
        for (ViewResolver viewResolver : matchingBeans) {
            if (this != viewResolver) {
                this.viewResolvers.add(viewResolver);
            }
        }
    }
    // ...
}

Since it is looking for a view parser in the container, can we guess that we can implement a view parser ourselves?

We can add a view parser to the container ourselves, and this class will help us automatically combine it!

5.3 converter and formatter

Find the formattingconvertionservice in the WebMvcConfigurationSupport class

@Bean
@Override
public FormattingConversionService mvcConversionService() {
    // Get the formatting rules in the configuration file
    WebConversionService conversionService = 
        new WebConversionService(this.mvcProperties.getDateFormat());
    addFormatters(conversionService);
    return conversionService;
}
//    @Bean
//    public FormattingConversionService mvcConversionService() {
//        //Get the formatting rules in the configuration file
//        FormattingConversionService conversionService = new DefaultFormattingConversionService();
//        addFormatters(conversionService);
//        return conversionService;
//    }
 

Click getDateFormat() and find that the default format is "dd/MM/yyyy"

public String getDateFormat() {
    return this.dateFormat;
}
 
/**
* Date format to use. For instance, `dd/MM/yyyy`. default
 */
private String dateFormat;

You can see that in our Properties file, we can automatically configure it!

If you configure your own formatting method, it will be registered in the Bean and take effect. We can configure the date formatting rules in the configuration file:

# Time date formatting
spring.mvc.date-format=yyyy-MM-dd

5.4 modify the default configuration of SpringBoot

The principle of so many automatic configuration is the same. Through the analysis of the principle of automatic configuration of WebMVC, we should learn a learning method and draw a conclusion through source code exploration. This conclusion must belong to ourselves and be versatile.

The bottom layer of SpringBoot uses a lot of these design details, so you need to read the source code and draw a conclusion!

When spring boot automatically configures many components, first check whether there are user configured (user configured @ bean s) in the container. If there are, use the user configured. If not, use the automatic configuration default.

If some components can have multiple components, you can combine the user configured components with your own default components, such as our view parser!

The extension uses spring MVC. The official documents are as follows:

If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.

All we need to do is write a @ Configuration annotation class, and the type should be WebMvcConfigurer, and the @ EnableWebMvc annotation cannot be marked

Let's write one ourselves, create a new config package, and write a class MyMvcConfig under the package

package com.dzj.config;
 
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
//If you want to customize some functions, just write this component and give it to SpringBoot. SpringBoot will help us assemble it automatically
//Extend MVC dispatcherservlet
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // Browser send / index HTML, you will jump to the index page;
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index.html").setViewName("index");
        registry.addViewController("/main.html").setViewName("dashboard");
    }
 
}

 

Therefore, we want to extend spring MVC. The official recommends that we use it in this way, so that we can not only keep all the automatic configurations of spring boot, but also use our extended configurations!

We can analyze the principle:

1. Webmvcoautoconfiguration is the automatic configuration class of spring MVC, which has a class webmvcoautoconfigurationadapter

2. There is an annotation on this class, which will be imported during other automatic configuration: @ Import(EnableWebMvcConfiguration.class)

3. Let's click EnableWebMvcConfiguration to see that it inherits a parent class: delegatingwebmvccconfiguration

public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
 
	private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
 
    // Get all webmvcconfigurers from the container
	@Autowired(required = false)
	public void setConfigurers(List<WebMvcConfigurer> configurers) {
		if (!CollectionUtils.isEmpty(configurers)) {
			this.configurers.addWebMvcConfigurers(configurers);
		}
	}
    // ...
}

4. We can find a viewController we just set in the DelegatingWebMvcConfiguration class as a reference and find that it calls a

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

5. Let's go in and have a look

@Override
public void addViewControllers(ViewControllerRegistry registry) {
     // Call all WebMvcConfigurer related configurations together! Including those configured by ourselves and those configured by Spring
    for (WebMvcConfigurer delegate : this.delegates) {
        delegate.addViewControllers(registry);
    }
}

Therefore, it is concluded that all WebMvcConfiguration will be used, not only Spring's own configuration class, but also our own configuration class will be called;

5.5 take over of spring MVC

Official documents:

If you want to take complete control of Spring MVC
you can add your own @Configuration annotated with @EnableWebMvc.

Take over method: just add an annotation @ EnableWebMvc on our configuration class

Full takeover, that is, the automatic configuration of spring MVC by SpringBoot will no longer take effect. All of them are configured by ourselves!

In our development, it is not recommended to use spring MVC

Thinking? Why is the automatic configuration invalid when an annotation is added! You can see the following source code:

1. Click to enter the annotation @ EnableWebMvc. It is found that it imports a class. We can continue to see it

@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}

2. It inherits a parent class WebMvcConfigurationSupport

public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
  // ......
}

3. Let's review the Webmvc autoconfiguration class

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
// This annotation means that the autoconfiguration class takes effect only when there is no component in the container
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
    ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
    
}

To sum up: @ EnableWebMvc has imported the WebMvcConfigurationSupport component;

The imported WebMvcConfigurationSupport is only the most basic function of spring MVC!

There will be many extension configurations in SpringBoot. As long as we see this, we should pay more attention~

Topics: Spring Spring Boot mvc