spring boot configures spring mvc almost entirely automatically, so it doesn't need to be configured by users at all, but it can also be configured by itself. For example, static resources, content negotiation, attempted parsers, site icons, data binders, welcome pages, and so on, are automatically registered.
1. Static Resources and Access
Catalog
Under class paths: /static (or/public or/resources or/META-INF/resources), as long as static resources are placed under these paths, they can be accessed directly in the browser (root path).
The path of the static resource mapping is /**. Then when there are dynamic resources and static resource request paths that are the same, the container processes the dynamic resources first, and if the dynamic resources do not exist, it handles them over to the static resources.
Static Resource Access Prefix (default none)
spring.mvc.static-path-pattern=/resources/**
Write this configuration, then static resources will be prefixed.
Modify folders for static resources
spring.web.resources.static-locations
Writing this configuration allows you to modify the default static resource path and accept arrays.
Webjars are also supported, that is, some css, js are packaged into a jar package and then introduced directly into the pom for direct use. The access path is /webjars/**
Welcome Page and Icons
There are two ways to handle static pages, the first is to put index.html directly under the static folder, the other is to add a controller to handle the welcome page.
When index.html configures the welcome page, it cannot configure the access prefix, otherwise the welcome page will become invalid.
Icons are also named directly into the static resource folder, and configuring a prefix can make it impossible to use site icons.
When testing, you will find that caching problems often occur.
2. The underlying principle of static resource access
- When spring boot starts, it loads the related loading classes
- The spring mvc boot autoconfiguration class is mostly in WebMvcAutoConfiguration
@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 })
- A configuration class for webmvc is configured in the container, where the properties depend on WebMvcProperties.class,ResourceProperties.class
@Configuration(proxyBeanMethods = false) @Import(EnableWebMvcConfiguration.class) @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class }) @Order(0) public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
- WebMvcProperties == spring mvc
- ResourceProperties == spring resource
- Configuration class has only one parametric constructor, so all parameters are in the container
- Default rules for resource processing
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { //Determine whether static resources open add-mappings if (!this.resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled"); return; } //Static resources, cache time Duration cachePeriod = this.resourceProperties.getCache().getPeriod(); CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl(); if (!registry.hasMappingForPattern("/webjars/**")) { //Webjars path processing, which also caches customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/") .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)); } //Static Resource Access Rule Processing String staticPathPattern = this.mvcProperties.getStaticPathPattern(); if (!registry.hasMappingForPattern(staticPathPattern)) { //At the location of the configuration, look for the static resource customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern) .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations())) .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)); } }
- Welcome Page Source
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) { if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) { //Welcome Page Exists && /** Go directly to Welcome Page logger.info("Adding welcome page: " + welcomePage.get()); setRootViewName("forward:index.html"); } else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) { //Otherwise enter a controller logger.info("Adding welcome page template: index"); setRootViewName("index"); } }
//HandlerMapping: Processor mapping that saves each handler to handle those requests //Here is a handler Mapping placed on a welcome page @Bean public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) { WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping( new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(), this.mvcProperties.getStaticPathPattern()); welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider)); welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations()); return welcomePageHandlerMapping; }
3. Request Processing
Request mapping goes without saying, offset is simple.
Briefly speaking, rest ful-style requests are now being developed in which the distinction of operations expected to be made using http method verbs, as well as data processing for users, are/user, GET-Get User, DELETE-Delete User, PUT-Modify User, POST-Save User
The core of this is to configure the core filter of the HiddenHttpMethodFilter, which allows rest ing requests with only a hidden field of'_method'. You need to open spring.mvc.hiddenmethod.filter = true in the configuration file
Rest-style principles for form submission, because forms only have post and get, so they need to be processed, while others can submit Rest-style submissions without processing.
- Require_method parameter
- Intercepted by HiddenHttpMethodFilter
- You must post to use a rest ful-style submission
- Get the value of _method
- As judged, rewrite the request using wapper packaging
- Use wapper to process requests when released
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package org.springframework.web.filter; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; import org.springframework.http.HttpMethod; import org.springframework.util.Assert; import org.springframework.util.StringUtils; public class HiddenHttpMethodFilter extends OncePerRequestFilter { private static final List<String> ALLOWED_METHODS; public static final String DEFAULT_METHOD_PARAM = "_method"; private String methodParam = "_method"; public HiddenHttpMethodFilter() { } public void setMethodParam(String methodParam) { Assert.hasText(methodParam, "'methodParam' must not be empty"); this.methodParam = methodParam; } protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { HttpServletRequest requestToUse = request; if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) { String paramValue = request.getParameter(this.methodParam); if (StringUtils.hasLength(paramValue)) { String method = paramValue.toUpperCase(Locale.ENGLISH); if (ALLOWED_METHODS.contains(method)) { requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method); } } } filterChain.doFilter((ServletRequest)requestToUse, response); } static { ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name())); } private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper { private final String method; public HttpMethodRequestWrapper(HttpServletRequest request, String method) { super(request); this.method = method; } public String getMethod() { return this.method; } } }
Request Mapping Principle
All analysis of spring mvc starts with Dispatcher Servlet. Looking at its inheritance structure, you can see that it is itself a servlet, looking for its doGet\doPost method, in the order in which it is called:
doGet\doPost(FrameworkServlet)->processRequest(FrameworkServlet)->doService(DispatcherServlet)->doDispatch(DispatcherServlet) .
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { try { ModelAndView mv = null; Object dispatchException = null; try { processedRequest = this.checkMultipart(request); multipartRequestParsed = processedRequest != request; //View the handler handling the request mappedHandler = this.getHandler(processedRequest); if (mappedHandler == null) { this.noHandlerFound(processedRequest, response); return; }
All handlers are saved in handler Mappings.
Where RequestMappingHandlerMapping is saved, which request is handled by each class:
All request mappings are saved in handler Mapping.
- spring boot automatically configures handler Mapping on the welcome page
- When the request comes in, it tries to see if all HandlerMapping s handle the request until it finds one that can handle it
- Springboot auto-configured handler Mapping, a total of five
- Users can also configure handlerMapping themselves, such as api/v1 and api/v2, to invoke business implementations of different packages
4. Processing of parameters
Sprboot follows the spring mvc approach in dealing with parameters, mainly in the following ways:
- Using the wildcard character/car/{id}/owner/{username} in the path, then @PathVariable("parameter name") can be used in the parameter, and all parameters can be accepted directly with map
- Get request headers using @RequestHeader or map to receive all request headers
- The most common way is to use @RequestParam to receive? X1=a1&x2=a2_1&x2=a2_2, or map to receive all parameters
- Use the @CookieValue annotation to get the values in a Cookie, or use map to receive the values for all cookies
- Use @RequestBody to get the entire content of the Post request body, typically using a single bean to receive all values
- Use @RequestAttribute to get attribute values in the request domain
- Get the matrix variable using @MatrixVariable, which is used in the request;Parameters to split, for example: /car;jsession=abc, which allows it to handle cookie s that are disabled without session, but in spring boot where matrix variables are disabled by default, UrlPathHepler is used for path processing, with one attribute removeSemicolonContent removed by default;After number, set to true
Principles of various parameter analysis
- Handler Mapping finds a Handler that can handle requests (actually, a method of Controller)
- Find an adapter HandlerAdapter for the current Handler - >RequestMappingHandlerAdapter
Four HandlerAdapter s:
- 0-Supports RequestMapping annotations
- 1-Supports Functional Programming
The target method is then executed:
The 26 parameter parsers, which are actually how many ways to write the receive parameters:
Return value processor in 15, that is, how many return values can be written:
True way to execute goals:
Determine each parameter value for the target method:
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { //Get parameter details MethodParameter[] parameters = this.getMethodParameters(); if (ObjectUtils.isEmpty(parameters)) { return EMPTY_ARGS; } else { //Return value Object[] args = new Object[parameters.length]; //Traversing through parameters for(int i = 0; i < parameters.length; ++i) { MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); args[i] = findProvidedArgument(parameter, providedArgs); if (args[i] == null) { //Does the parser support this parameter, traversing which of the 26 parsers supports the parameter passed in //Is to see which comment is labeled on the parameter, such as @ReqeustParam if (!this.resolvers.supportsParameter(parameter)) { throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver")); } try { //Assign a parameter to get the parameter parser for the current parameter args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); } catch (Exception var10) { if (this.logger.isDebugEnabled()) { String exMsg = var10.getMessage(); if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) { this.logger.debug(formatArgumentError(parameter, exMsg)); } } throw var10; } } } return args; } }
5. Servlet Api parameters
spring mvc is also a parameter that can be passed into the servlet api, such as request, session, header, and so on.
Like parameter parsing, it is queried in 26 parses to which parser matches, such as request, which is resolved by the parser ServletRequestMethodArgumentResolver.
6. Complex parameters
map and model
Equivalent to placing data in the middle of a request. Parameters of type map and model eventually return to the BingingAwareModelMap, which is also the map and model, inheriting the linkHashMap.
The final map and model objects are resolved into one object:
When the target method is executed, place all the data in the ModelAndViewContainer with the page address you want to go to and the associated data
All data in the model, traversed into the request domain.
RedirectAttribute
Redirect Carried Data
ServletResponse
Data carried by native responder
7. Custom Object Parameters
Sprmvc can directly encapsulate data submitted by a page into objects. Here's how data binding works.
Find a supported parser and find ServletModelAttributeMethodProcessor
Whether annotated or not simple type:
Once support is found, it is ready to be encapsulated:
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer"); Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory"); String name = ModelFactory.getNameForParameter(parameter); ModelAttribute ann = (ModelAttribute)parameter.getParameterAnnotation(ModelAttribute.class); if (ann != null) { mavContainer.setBinding(name, ann.binding()); } Object attribute = null; BindingResult bindingResult = null; if (mavContainer.containsAttribute(name)) { attribute = mavContainer.getModel().get(name); } else { try { //Create an empty pojo object attribute = this.createAttribute(name, parameter, binderFactory, webRequest); } catch (BindException var10) { if (this.isBindExceptionRequired(parameter)) { throw var10; } if (parameter.getParameterType() == Optional.class) { attribute = Optional.empty(); } bindingResult = var10.getBindingResult(); } } if (bindingResult == null) { //web Data Binder, which binds the values of request parameters to the specified bean s (attribute s) //Converting data to types in java using type converters WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); if (binder.getTarget() != null) { if (!mavContainer.isBindingDisabled(name)) { this.bindRequestParameters(binder, webRequest); } this.validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter)) { throw new BindException(binder.getBindingResult()); } } if (!parameter.getParameterType().isInstance(attribute)) { attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter); } bindingResult = binder.getBindingResult(); } Map<String, Object> bindingResultModel = bindingResult.getModel(); mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); return attribute; }
Conversion service in binder:
GenericConversionService (the value of each property, which is also used to find and convert 124 converters)
Users can also put their own converters in the webDataBind.