SpringMVC相关基础知识
1. servlet.multipart 大小配置
SpringBoot 文件上传接口中有 MultipartFile
类型的文件参数,上传较大文件时报错:
org.springframework.web.multipart.MaxUploadSizeExceededException: Maximum upload size exceeded; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException: the request was rejected because its size (95214622) exceeds the configured maximum (52428800)
at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.handleParseFailure(StandardMultipartHttpServletRequest.java:124)
Caused by: java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException: the request was rejected because its size (95214622) exceeds the configured maximum (52428800)
at org.apache.catalina.connector.Request.parseParts(Request.java:2890)
Caused by: org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException: the request was rejected because its size (95214622) exceeds the configured maximum (52428800)
at org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl.init(FileItemIteratorImpl.java:161)
原因: 文件大小超过了设置的 servlet.multipart
最大值,修改配置:
spring:servlet:multipart:# 设置单个文件最大值max-file-size: 1GB# 设置请求体数据总大小max-request-size: 1GB
2. SpringMVC 的路径匹配规则
Spring Boot 2.6 及以上默认路劲的匹配规则是 PATH_PATTERN_PARSER, Spring Fox/Swagger 使用的路径匹配是基于 ANT_PATH_MATCHER。 Spring Boot 2.6 引入枚举 MatchingStrategy
public static enum MatchingStrategy {ANT_PATH_MATCHER,PATH_PATTERN_PARSER;private MatchingStrategy() {}
}
ANT_PATH_MATCHER 对应 org.springframework.util.AntPathMatcher PATH_PATTERN_PARSER 对应 RequestMappingInfoHandlerMapping
2.1 AntPathMatcher Ant风格匹配策略
?
匹配1个字符,并且不能是代表路径分隔符的/*
匹配0个或多个字符,但是不能是路径**
匹配路径中的0个或多个目录{spring:[a-z]+}
将正则表达式[a-z]+
匹配到的值,赋值给名为 spring 的路径变量。
2.2 PATH_PATTERN_PARSER
PATH_PATTERN_PARSER是一种更复杂的匹配策略,它支持更多的条件匹配,例如: 请求方法匹配(例如 GET、POST 等)。 请求头匹配(例如 Content-Type、Accept 等)。 请求参数匹配(例如 ?name=value)。
2.3 匹配规则
当一个URL同时匹配多个模式时,只会选择最匹配的一个:
- URI模式变量的数目和通配符数量的总和最少的那个路径模式更准确。比如,
/hotels/{hotel}/*
这个路径拥有一个URI变量和一个通配符,而/hotels/{hotel}/**
这个路径则拥有一个URI变量和两个通配符,因此前者是更准确的路径模式。 - 如果两个模式的URI模式数量和通配符数量总和一致,则路径更长的那个模式更准确。举个例子,
/foo/bar*
就被认为比/foo/*
更准确,因为前者的路径更长。 - 如果两个模式的数量和长度均一致,则那个具有更少通配符的模式是更加准确的。比如,
/hotels/{hotel}
就比/hotels/*
更精确。 - 默认的通配模式
/**
比其他所有的模式都更“不准确”。比方说,/api/{a}/{b}/{c}
就比默认的通配模式/**
要更准确 - 前缀通配(比如
/public/**
)被认为比其他任何不包括双通配符的模式更不准确。比如说,/public/path3/{a}/{b}/{c}
就比/public/**
更准确
3. HandlerInterceptor Spring拦截器
SpringWebMVC 的处理器拦截器,类似于 Servlet 开发中的过滤器 Filter ,用于处理器进行预处理和后处理。
3.1 HandlerInterceptor
package org.springframework.web.servlet;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import org.springframework.lang.Nullable;
import org.springframework.web.method.HandlerMethod;public interface HandlerInterceptor {boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception;void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception;void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception;
}
preHandle(请求处理前)
调用时机:通过 HandlerMapping 找到了具体的处理器(handler),也就是 controller 类,但还没正式开始处理之前
预处理回调方法,实现处理器的预处理(如检查登陆),第三个参数为响应的处理器也就是controller类 在preHandle中,可以进行编码、安全控制等处理; 返回值true表示继续流程, false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时需要通过response来产生响应;
postHandle(视图渲染前)
调用时机:handler 完成了处理,但还没渲染视图
后处理回调方法,实现处理器的后处理(但在渲染视图之前),在postHandle中,有机会修改ModelAndView,对模型数据进行处理或对视图进行处理。
afterCompletion(视图渲染后)
调用时机:handler 完成了处理,且完成视图渲染
整个请求处理完毕回调方法,即在视图渲染完毕时回调,如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finally,但仅调用处理器执行链中 在afterCompletion中,可以根据ex是否为null判断是否发生了异常,进行日志记录。
3.2 HandlerInterceptorAdapter 拦截器适配器(5.3+废弃)
有时候可能只需要实现三个回调方法中的某一个,此时spring提供了一个HandlerInterceptorAdapter适配器(一种适配器设计模式的实现),允许我们只实现需要的回调方法。
从 Spring 5.3 开始不建议再使用 HandlerInterceptorAdapter,建议直接实现 HandlerInterceptor 接口,HandlerInterceptor 内部的3个方法都加了默认实现,所以也不需要实现全部3个方法。
Deprecated as of 5.3 in favor of implementing HandlerInterceptor and/ or AsyncHandlerInterceptor directly.
3.3 注册拦截器到Spring
有了拦截器,还需要对拦截器进行注册。需要使用 WebMvcConfigurerAdapter
下的 addInterceptors()
方法
package com.masikkk.common.config;import com.masikkk.common.web.LogInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** Spring MVC 配置*/
@ComponentScan(basePackages = {"com.xxx.common.web"})
@Configuration
public class SpringWebMvcConfig implements WebMvcConfigurer {@Autowiredprivate LogInterceptor logInterceptor;@Autowiredprivate AuthHandlerInterceptor authHandlerInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(logInterceptor);// 指定 url 模式匹配registry.addInterceptor(authHandlerInterceptor).addPathPatterns("/user/**", "/account/xx");}
}
3.4 多个拦截器的执行顺序
SpringMVC 中的 Interceptor 是链式的调用的,在一个应用中或者说是在一个请求中可以同时存在多个 Interceptor 。每个 interceptor 的调用会依据它的声明顺序依次执行,而且最先执行的都是 Interceptor 中的 preHandle 方法
- 按
registry.addInterceptor()
加入的先后顺序执行所有拦截器的preHandle
方法; - 请求处理完后,按倒序执行所有
postHandle
方法 - 按倒序执行所有
afterCompletion
方法
4. ContentCachingRequestWrapper
请求 body 输入流只能读取一次问题,Spring MVC 提供了 ContentCachingRequestWrapper 类,旨在解决请求 body 输入流只能读取一次问题的问题。它是原始 HttpServletRequest 对象的包装。 当我们读取请求正文时,ContentCachingRequestWrapper 会缓存内容供以后使用。
注意: 1、requestWrapper.getContentAsByteArray() 必须是在 request.inputStream() 的内容使用过后才能缓存请求中 body 的内容,下次需要再使用 body 只能使用此方法requestWrapper.getContentAsByteArray() 才能再次获取body中的值。 所以,在 HandlerInterceptor 拦截器中提前使用 requestWrapper.getContentAsByteArray() 是获取不到值的,因为 inputStream 还没被消费。
2、如果在 HandlerInterceptor 拦截器中,使用了 request.inputStream() 输入流中的内容,那么在控制层 @RequestBody 标记的内容就获取不到任何内容了,因为 @RequestBody 是从request.getInputStream() 中获取内容的。但是此 inputStream 已经关闭了。
所以 ContentCachingRequestWrapper 本身也不太好用。
5. @ControllerAdvice 加 @ExceptionHandler 进行异常统一处理
@Co