【1】SpringBoot接收到請求
① springboot接收到一個請求返回json格式的列表
將要調用的目標方法如下:
@ApiOperation(value="分頁查詢")
@RequestMapping(value = "/listPage",method = RequestMethod.POST)
@ResponseBody
public ResponseBean listPage(@RequestBody JSONObject params){
Integer pageNum = params.getInteger("pageNum");
Integer pageSize = params.getInteger("pageSize");
String vagueParam = params.getString("vagueParam");
IPage<TbSysGoodsCategory> indexPage = new Page<>(pageNum, pageSize);
QueryWrapper<TbSysGoodsCategory> queryWrapper = new QueryWrapper<>();
if (!StringUtils.isEmpty(vagueParam)){
queryWrapper.like("name",vagueParam).or().like("code",vagueParam);
}
//排序
queryWrapper.orderByDesc("id");
indexPage = tbSysGoodsCategoryService.page(indexPage,queryWrapper);
return new ResponseBean<>(true, indexPage, CommonEnum.SUCCESS_OPTION);
}
如下所示,首先進入DispatcherServlet使用RequestMappingHandlerAdapter進行處理。
而RequestMappingHandlerAdapter (extends AbstractHandlerMethodAdapter)會調用父類AbstractHandlerMethodAdapter的handle方法進行處理:
② RequestMappingHandlerAdapter.handleInternal
這裏首先在this.checkRequest(request)對請求進行了檢測,HttpRequestMethodNotSupportedException異常就是這裏拋出的。
/**
* Check the given request for supported methods and a required session, if any.
* @param request current HTTP request
* @throws ServletException if the request cannot be handled because a check failed
* @since 4.2
*/
protected final void checkRequest(HttpServletRequest request) throws ServletException {
// Check whether we should support the request method.
String method = request.getMethod();
if (this.supportedMethods != null && !this.supportedMethods.contains(method)) {
throw new HttpRequestMethodNotSupportedException(method, this.supportedMethods);
}
// Check whether a session is required.
if (this.requireSession && request.getSession(false) == null) {
throw new HttpSessionRequiredException("Pre-existing session required but none found");
}
}
其他沒有什麼需要特殊說明的,直接調用了invokeHandlerMethod方法進行實際業務處理。
【2】RequestMappingHandlerAdapter.invokeHandlerMethod核心處理
RequestMappingHandlerAdapter.invokeHandlerMethod
這個方法十分重要,是請求處理流程中的核心方法。這個方法會根據handlerMethod獲取一個ServletInvocableHandlerMethod 並對其進行各種屬性設置然後調用其invokeAndHandle方法進行處理。
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
// 對應 2
ServletWebRequest webRequest = new ServletWebRequest(request, response);
Object result;
try {
// 對應 3
WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod);
// 對應 4
ModelFactory modelFactory = this.getModelFactory(handlerMethod, binderFactory);
// 對應 5
ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
// 對應 6
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
//對應 7
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer)asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(this.logger, (traceOn) -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
//這裏會跳到【3】ServletInvocableHandlerMethod.invokeAndHandle
invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);
if (!asyncManager.isConcurrentHandlingStarted()) {
//這裏會跳到【4】RequestMappingHandlerAdapter.getModelAndView
ModelAndView var15 = this.getModelAndView(mavContainer, modelFactory, webRequest);
return var15;
}
result = null;
} finally {
webRequest.requestCompleted();
}
return (ModelAndView)result;
}
① 此時的handlerMethod是什麼?
如下圖所示:
② 此時的ServletWebRequest webRequest是什麼?
如下圖所示:
③ 此時的WebDataBinderFactory binderFactory
WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod);
這個很關鍵。SpringBoot請求處理流程中最重要的一步就是數據綁定,即將參數寫到目標對象上面。那麼這裏就涉及到參數校驗、數據格式轉換、綁定結果對象、錯誤對象等。
其屬性ConfigurableWebBindingInitializer對象提供了基礎功能,該對象中WebConversionService中轉換器實例如下:
④ 根據handlerMethod和binderFactory獲取到ModelFactory modelFactory
ModelFactory modelFactory = this.getModelFactory(handlerMethod, binderFactory);
可以看到modelFactory主要屬性dataBindFactory和sessionAttributeHandler都是爲參數綁定數據服務的。
⑤ 創建核心處理對象ServletInvocableHandlerMethod invocableMethod併爲其屬性賦值
ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
argumentResolvers 看名字就應該知道是爲參數解析進行服務的,其值列表如下:
我想你現在應該知道爲什麼方法參數使用@RequestBody就可以進行參數綁定了吧!
繼續看returnValueHandlers,也就是返回結果處理器。其中returnValueHandlers是HandlerMethodReturnValueHandlerComposite實例,就像HandlermethodArgumentResolverComposite一樣,它包含了所有HandlerMethodReturnValueHandler的列表,並在Spring啓動時完成註冊。其值列表如下:
ok,我們的主題來了。就是這個RequestResponseBodyMethodProcessor後置處理器對@ResponseBody註解進行的處理!
繼續往下走,給invocableMethod設置了DataBinderFactory。這個同上都是爲數據參數綁定服務,繼續往下看invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
parameterNameDiscoverer這裏值列表如下:
⑥ 創建mavContainer進行數據的初步處理
//創建ModelAndViewContainer 實例對象
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
//從請求中獲取InputFlashMap並把其數據放入defaultModel中,flashmap的作用是在redirect中傳遞參數
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
//調用modelFactory對model進行初始化
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
//重定向時忽略默認Model
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
首先看下ModelAndViewContainer,其核心有三個屬性view-視圖對象,defaultModel-默認的數據存放地方以及redirectModel-重定向時數據存放地方。
modelFactory.initModel(webRequest, mavContainer, invocableMethod);,這裏對model做了處理。也可以說是對目標方法實際調用前對數據做的最後一次處理:
public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod)
throws Exception {
//嘗試獲取會話中保存的屬性值
Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
//對model中屬性值進行合併處理:稱之爲補缺更合適
container.mergeAttributes(sessionAttributes);
//調用標註了@ModelAttribute註解的方法
invokeModelAttributeMethods(request, container);
//如果handlerMethod的方法參數標註了@ModelAttribute註解並且在sessionAttributetes存在,則對其進行遍歷
//嘗試獲取值,如果獲取不到值就會拋出異常;如果獲取到值就會放到model-defaultModel中
for (String name : findSessionAttributeArguments(handlerMethod)) {
if (!container.containsAttribute(name)) {
Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
if (value == null) {
throw new HttpSessionRequiredException("Expected session attribute '" + name + "'", name);
}
container.addAttribute(name, value);
}
}
}
關於mergeAttributes合併屬性方法源碼如下:
#也就是說遍歷sessionAttributes ,如果model中不存在,就放入。如果存在,就跳過!注意,不會進行值覆蓋
public ModelMap mergeAttributes(@Nullable Map<String, ?> attributes) {
if (attributes != null) {
attributes.forEach((key, value) -> {
if (!this.containsKey(key)) {
this.put(key, value);//this這裏值的是modelMap,也就是defaultModel
}
});
}
return this;
}
關於findSessionAttributeArguments方法源碼如下:
//從方法參數中找到在(@SessionAttributes註解的屬性/參數)中存在的
// 且方法參數上標註了@ModelAttribute註解的
private List<String> findSessionAttributeArguments(HandlerMethod handlerMethod) {
List<String> result = new ArrayList<>();
for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
if (parameter.hasParameterAnnotation(ModelAttribute.class)) {
String name = getNameForParameter(parameter);
Class<?> paramType = parameter.getParameterType();
if (this.sessionAttributesHandler.isHandlerSessionAttribute(name, paramType)) {
result.add(name);
}
}
}
return result;
}
⑦ 異步請求
這一塊先不用管,後續分析
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
接下來調用invocableMethod.invokeAndHandle(webRequest, mavContainer);就到了ServletInvocableHandlerMethod.invokeAndHandle。
【3】調用目標方法並對返回值進行處理ServletInvocableHandlerMethod.invokeAndHandle
方法源碼如下:
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//調用目標方法並獲取返回值,這裏對應 【3.1】 InvocableHandlerMethod.invokeForRequest調用目標方法
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
【3.1】 InvocableHandlerMethod.invokeForRequest調用目標方法
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//解析參數--這裏對應 1
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
//根據上面得到的參數值調用目標方法 這裏對應2
return doInvoke(args);
}
① 解析參數getMethodArgumentValues
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//獲取到方法的參數對象 MethodParameter[]數組
MethodParameter[] parameters = getMethodParameters();
//如果爲空,返回空參數組
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
Object[] args = new Object[parameters.length];
//遍歷MethodParameter[] parameters,對每一個方法參數對象獲取到具體參數並解析得到參數值
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
//綁定參數名稱發現器
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
//從providedArgs中嘗試獲取到參數名
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
//如果方法參數解析器不支持parameter,則拋出異常
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
//
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled...
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
}
MethodParameter[] parameters = getMethodParameters();
這裏獲取的 MethodParameter[] parameters如下圖所示:
參數解析器組合對象( this.resolvers)列表如下所示:
爲什麼稱之爲參數解析器組合對象?其實這裏的this.resolvers並不是具體的參數解析器而是argumentResolvers、argumentResolverCache組合而成的HandlerMethodArgumentResolverComposite!
可以看到起還有argumentResolverCache屬性,其值列表如下:
默認argumentResolverCache是一個容量爲256的ConcurrentHashMap,是HandlerMethodArgumentResolverComposite的成員變量:
private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =
new ConcurrentHashMap<>(256);
這個argumentResolverCache是在動態改變,其在判斷是否支持paramter的方法中會改變,HandlerMethodArgumentResolverComposite.getArgumentResolver源碼如下:
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
//如果緩存中有,則直接返回
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
//如果緩存中沒有就嘗試從解析器列表中獲取一個支持parameter的,並將解析器 parameter放入緩存
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
爲什麼要有argumentResolverCache ?你可以沒有,但是你就需要每次從argumentResolvers遍歷尋找支持當前MethodParameter的參數解析器!之所以保存一份鍵值對數據到argumentResolverCache ,就是爲了下次不用尋找,就是爲了更快!