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