作者筆記倉庫:https://github.com/seazean/javanotes
歡迎各位關注我的筆記倉庫,clone 倉庫到本地后使用 Typora 閱讀效果更好。
筆記參考視頻:https://www.bilibili.com/video/BV19K4y1L7MT
一、調度函數
請求進入原生的 HttpServlet 的 doGet() 方法處理,調用子類 FrameworkServlet 的 doGet() 方法,最終調用 DispatcherServlet 的 doService() 方法,為請求設置相關屬性后調用 doDispatch(),請求和響應的以參數的形式傳入
//request 和 response 為 Java 原生的類
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false; //文件上傳請求
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);// 異步管理器
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request); //文件上傳請求
multipartRequestParsed = (processedRequest != request);
// 找到當前請求使用哪個 HandlerMapping(Controller的方法)處理,返回執行鏈
mappedHandler = getHandler(processedRequest);
// 沒有合適的處理請求的方式 HandlerMapping 直接返回
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 根據映射器獲取當前 handler 處理器適配器,用來處理當前的請求
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 獲取發出此次請求的方法
String method = request.getMethod();
// 判斷請求是不是 GET 方法
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 攔截器鏈的前置處理
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 執行處理方法,返回的是 ModelAndView 對象,封裝了所有的返回值數據
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 設置視圖名字
applyDefaultViewName(processedRequest, mv);
// 執行攔截器鏈中的后置處理方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception ex) {
dispatchException = ex;
}
// 處理 程序調用的結果,進行結果派發
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
//....
}
二、請求映射
2.1、映射器
doDispatch() 中調用 getHandler 方法獲取所有的映射器
總體流程:
-
所有的請求映射都在 HandlerMapping 中,RequestMappingHandlerMapping 處理 @RequestMapping 注解的映射規則
-
遍歷所有的 HandlerMapping 看是否可以匹配當前請求,匹配成功后返回,匹配失敗設置 HTTP 404 響應碼
-
用戶可以自定義的映射處理,也可以給容器中放入自定義 HandlerMapping
訪問 URL:http://localhost:8080/user
@GetMapping("/user")
public String getUser(){
return "GET";
}
@PostMapping("/user")
public String postUser(){
return "POST";
}
//。。。。。
HandlerMapping 處理器映射器,保存了所有 @RequestMapping
和 handler
的映射規則
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
// 遍歷所有的 HandlerMapping
for (HandlerMapping mapping : this.handlerMappings) {
// 嘗試去每個 HandlerMapping 中匹配當前請求的處理
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
-
mapping.getHandler(request)
:調用 AbstractHandlerMapping#getHandler-
Object handler = getHandlerInternal(request)
:獲取映射器,底層調用 RequestMappingInfoHandlerMapping 類的方法,又調用 AbstractHandlerMethodMapping#getHandlerInternal-
String lookupPath = initLookupPath(request)
:地址欄的 uri,這里的 lookupPath 為 /user -
this.mappingRegistry.acquireReadLock()
:防止並發 -
handlerMethod = lookupHandlerMethod(lookupPath, request)
:獲取當前 HandlerMapping 中的映射規則AbstractHandlerMethodMapping.lookupHandlerMethod():
-
directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath)
:獲取當前的映射器與當前請求的 URI 有關的所有映射規則 -
addMatchingMappings(directPathMatches, matches, request)
:匹配某個映射規則for (T mapping : mappings)
:遍歷所有的映射規則match = getMatchingMapping(mapping, request)
:去匹配每一個映射規則,匹配失敗返回 nullmatches.add(new Match())
:匹配成功后封裝成匹配器添加到匹配集合中
-
Match bestMatch = matches.get(0)
:匹配完成只剩一個,直接獲取返回對應的處理方法 -
if (matches.size() > 1)
:當有多個映射規則符合請求時,報錯 -
return bestMatch.getHandlerMethod()
:返回匹配器中的處理方法
-
-
-
executionChain = getHandlerExecutionChain(handler, request)
:為當前請求和映射器的構建一個攔截器鏈for (HandlerInterceptor interceptor : this.adaptedInterceptors)
:遍歷所有的攔截器chain.addInterceptor(interceptor)
:把所有的攔截器添加到 HandlerExecutionChain 中,形成攔截器鏈
-
return executionChain
:返回攔截器鏈,包含 HandlerMapping 和攔截方法
-
2.2、適配器
doDispatch() 中 調用 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler())
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
// 遍歷所有的 HandlerAdapter
for (HandlerAdapter adapter : this.handlerAdapters) {
// 判斷當前適配器是否支持當前 handle
// return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler))
// 這里返回的是True,
if (adapter.supports(handler)) {
// 返回的是 RequestMappingHandlerAdapter
return adapter;
}
}
}
throw new ServletException();
}
2.3、方法執行
2.3.1、執行流程
實例代碼:
@GetMapping("/params")
public String param(Map<String, Object> map, Model model, HttpServletRequest request) {
map.put("k1", "v1"); //都可以向請求域中添加數據
model.addAttribute("k2", "v2"); //它們兩個都在數據封裝在 BindingAwareModelMap
request.setAttribute("m", "HelloWorld");
return "forward:/success";
}
doDispatch() 中調用 mv = ha.handle(processedRequest, response, mappedHandler.getHandler())
執行目標方法
AbstractHandlerMethodAdapter#handle
→ RequestMappingHandlerAdapter#handleInternal
→ invokeHandlerMethod
:
//使用適配器執行方法
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response,
HandlerMethod handlerMethod) throws Exception {
//封裝成 SpringMVC 的接口,用於通用 Web 請求攔截器,使能夠訪問通用請求元數據,而不是用於實際處理請求
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
// WebDataBinder 用於從 Web 請求參數到 JavaBean 對象的數據綁定,獲取創建該實例的工廠
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
// 創建 Model 實例,用於向模型添加屬性
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
// 方法執行器
ServletInvocableHandlerMethod invocableMethod = 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);
// 新建一個 ModelAndViewContainer 並進行初始化和一些屬性的填充
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
// 設置一些屬性
// 【執行目標方法】
invocableMethod.invokeAndHandle(webRequest, mavContainer);
// 異步請求
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
// 【獲取 ModelAndView 對象,封裝了 ModelAndViewContainer】
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
ServletInvocableHandlerMethod#invokeAndHandle:執行目標方法
-
returnValue = invokeForRequest(webRequest, mavContainer, providedArgs)
:執行自己寫的 controller 方法,返回的就是自定義方法中 return 的值Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs)
:參數處理的邏輯,遍歷所有的參數解析器解析參數或者將 URI 中的參數進行綁定,綁定完成后開始執行目標方法-
parameters = getMethodParameters()
:獲取此處理程序方法的方法參數的詳細信息 -
Object[] args = new Object[parameters.length]
:存放所有的參數 -
for (int i = 0; i < parameters.length; i++)
:遍歷所有的參數 -
args[i] = findProvidedArgument(parameter, providedArgs)
:獲取調用方法時提供的參數,一般是空 -
if (!this.resolvers.supportsParameter(parameter))
:獲取可以解析當前參數的參數解析器return getArgumentResolver(parameter) != null
:獲取參數的解析是否為空-
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers)
:遍歷容器內所有的解析器if (resolver.supportsParameter(parameter))
:是否支持當前參數PathVariableMethodArgumentResolver#supportsParameter
:解析標注 @PathVariable 注解的參數ModelMethodProcessor#supportsParameter
:解析 Map 類型的參數ModelMethodProcessor#supportsParameter
:解析 Model 類型的參數,Model 和 Map 的作用一樣ExpressionValueMethodArgumentResolver#supportsParameter
:解析標注 @Value 注解的參數RequestParamMapMethodArgumentResolver#supportsParameter
:解析標注 @RequestParam 注解RequestPartMethodArgumentResolver#supportsParameter
:解析文件上傳的信息ModelAttributeMethodProcessor#supportsParameter
:解析標注 @ModelAttribute 注解或者不是簡單類型- 子類 ServletModelAttributeMethodProcessor 是解析自定義類型 JavaBean 的解析器
- 簡單類型有 Void、Enum、Number、CharSequence、Date、URI、URL、Locale、Class
-
-
args[i] = this.resolvers.resolveArgument()
:開始解析參數,每個參數使用的解析器不同resolver = getArgumentResolver(parameter)
:獲取參數解析器return resolver.resolveArgument()
:開始解析PathVariableMapMethodArgumentResolver#resolveArgument
:@PathVariable,包裝 URI 中的參數為 MapMapMethodProcessor#resolveArgument
:調用mavContainer.getModel()
返回默認 BindingAwareModelMap 對象ModelAttributeMethodProcessor#resolveArgument
:自定義的 JavaBean 的綁定封裝,下一小節詳解
return doInvoke(args)
:真正的執行方法Method method = getBridgedMethod()
:從 HandlerMethod 獲取要反射執行的方法ReflectionUtils.makeAccessible(method)
:破解權限method.invoke(getBean(), args)
:執行方法,getBean 獲取的是標記 @Controller 的 Bean 類,其中包含執行方法
-
-
進行返回值的處理,響應部分詳解,處理完成進入下面的邏輯
RequestMappingHandlerAdapter#getModelAndView:獲取 ModelAndView 對象
-
modelFactory.updateModel(webRequest, mavContainer)
:Model 數據升級到會話域(請求域中的數據在重定向時丟失)updateBindingResult(request, defaultModel)
:把綁定的數據添加到 Model 中 -
if (mavContainer.isRequestHandled())
:判斷請求是否已經處理完成了 -
ModelMap model = mavContainer.getModel()
:獲取包含 Controller 方法參數的 BindingAwareModelMap 對象(本節開頭) -
mav = new ModelAndView()
:把 ModelAndViewContainer 和 ModelMap 中的數據封裝到 ModelAndView -
if (!mavContainer.isViewReference())
:視圖是否是通過名稱指定視圖引用 -
if (model instanceof RedirectAttributes)
:判斷 model 是否是重定向數據,如果是進行重定向邏輯 -
return mav
:任何方法執行都會返回 ModelAndView 對象
2.3.2、參數解析
解析自定義的 JavaBean 為例
-
Person.java:
@Data @Component //加入到容器中 public class Person { private String userName; private Integer age; private Date birth; }
-
Controller:
@RestController //返回的數據不是頁面 public class ParameterController { // 數據綁定:頁面提交的請求數據(GET、POST)都可以和對象屬性進行綁定 @GetMapping("/saveuser") public Person saveuser(Person person){ return person; } }
-
訪問 URL:http://localhost:8080/saveuser?userName=zhangsan&age=20
進入源碼:ModelAttributeMethodProcessor#resolveArgument
-
name = ModelFactory.getNameForParameter(parameter)
:獲取名字,此例就是 person -
ann = parameter.getParameterAnnotation(ModelAttribute.class)
:是否有 ModelAttribute 注解 -
if (mavContainer.containsAttribute(name))
:ModelAndViewContainer 中是否包含 person 對象 -
attribute = createAttribute()
:創建一個實例,空的 Person 對象 -
binder = binderFactory.createBinder(webRequest, attribute, name)
:Web 數據綁定器,可以利用 Converters 將請求數據轉成指定的數據類型,綁定到 JavaBean 中 -
bindRequestParameters(binder, webRequest)
:利用反射向目標對象填充數據servletBinder = (ServletRequestDataBinder) binder
:類型強轉servletBinder.bind(servletRequest)
:綁定數據-
mpvs = new MutablePropertyValues(request.getParameterMap())
:獲取請求 URI 參數中的 KV 鍵值對 -
addBindValues(mpvs, request)
:子類可以用來為請求添加額外綁定值 -
doBind(mpvs)
:真正的綁定的方法,調用applyPropertyValues
應用參數值,然后調用setPropertyValues
方法AbstractPropertyAccessor#setPropertyValues()
:-
List<PropertyValue> propertyValues
:獲取到所有的參數的值,就是 URI 上的所有的參數值 -
for (PropertyValue pv : propertyValues)
:遍歷所有的參數值 -
setPropertyValue(pv)
:填充到空的 Person 實例中-
nestedPa = getPropertyAccessorForPropertyPath(propertyName)
:獲取屬性訪問器 -
tokens = getPropertyNameTokens()
:獲取元數據的信息 -
nestedPa.setPropertyValue(tokens, pv)
:填充數據 -
processLocalProperty(tokens, pv)
:處理屬性-
if (!Boolean.FALSE.equals(pv.conversionNecessary))
:數據是否需要轉換了 -
if (pv.isConverted())
:數據已經轉換過了,轉換了直接賦值,沒轉換進行轉換 -
oldValue = ph.getValue()
:獲取未轉換的數據 -
valueToApply = convertForProperty()
:進行數據轉換TypeConverterDelegate#convertIfNecessary
:進入該方法的邏輯-
if (conversionService.canConvert(sourceTypeDesc, typeDescriptor))
:判斷能不能轉換GenericConverter converter = getConverter(sourceType, targetType)
:獲取類型轉換器-
converter = this.converters.find(sourceType, targetType)
:尋找合適的轉換器-
sourceCandidates = getClassHierarchy(sourceType.getType())
:原數據類型 -
targetCandidates = getClassHierarchy(targetType.getType())
:目標數據類型for (Class<?> sourceCandidate : sourceCandidates) { //雙重循環遍歷,尋找合適的轉換器 for (Class<?> targetCandidate : targetCandidates) {
-
GenericConverter converter = getRegisteredConverter(..)
:匹配類型轉換器 -
return converter
:返回轉換器
-
-
-
conversionService.convert(newValue, sourceTypeDesc, typeDescriptor)
:開始轉換converter = getConverter(sourceType, targetType)
:獲取可用的轉換器result = ConversionUtils.invokeConverter()
:執行轉換方法converter.convert()
:調用轉換器的轉換方法(GenericConverter#convert)
return handleResult(sourceType, targetType, result)
:返回結果
-
-
ph.setValue(valueToApply)
:設置 JavaBean 屬性(BeanWrapperImpl.BeanPropertyHandler)Method writeMethod
:獲取 set 方法Class<?> cls = getClass0()
:獲取 Class 對象writeMethodName = Introspector.SET_PREFIX + getBaseName()
:set 前綴 + 屬性名writeMethod = Introspector.findMethod(cls, writeMethodName, 1, args)
:獲取只包含一個參數的 set 方法setWriteMethod(writeMethod)
:加入緩存
ReflectionUtils.makeAccessible(writeMethod)
:設置訪問權限writeMethod.invoke(getWrappedInstance(), value)
:執行方法
-
-
-
-
-
bindingResult = binder.getBindingResult()
:獲取綁定的結果 -
mavContainer.addAllAttributes(bindingResultModel)
:把所有填充的參數放入 ModelAndViewContainer -
return attribute
:返回填充后的 Person 對象
三、響應處理
3.1、響應數據
以 Person 為例:
@ResponseBody //利用返回值處理器里面的消息轉換器進行處理
@GetMapping(value = "/person")
public Person getPerson(){
Person person = new Person();
person.setAge(28);
person.setBirth(new Date());
person.setUserName("zhangsan");
return person;
}
直接進入方法執行完后的邏輯 ServletInvocableHandlerMethod#invokeAndHandle:
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 【執行目標方法】,return person 對象
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) {}
}
- 沒有加 @ResponseBody 注解的返回數據按照視圖(頁面)處理的邏輯,ViewNameMethodReturnValueHandler(視圖詳解)
- 此例是加了注解的,返回的數據不是視圖,HandlerMethodReturnValueHandlerComposite#handleReturnValue:
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) {
// 獲取合適的返回值處理器
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException();
}
// 使用處理器處理返回值(詳解源碼中的這兩個函數)
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
HandlerMethodReturnValueHandlerComposite#selectHandler:獲取合適的返回值處理器
-
boolean isAsyncValue = isAsyncReturnValue(value, returnType)
:是否是異步請求 -
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers)
:遍歷所有的返回值處理器RequestResponseBodyMethodProcessor#supportsReturnType
:處理標注 @ResponseBody 注解的返回值ModelAndViewMethodReturnValueHandler#supportsReturnType
:處理返回值類型是 ModelAndView 的處理器ModelAndViewResolverMethodReturnValueHandler#supportsReturnType
:直接返回 true,處理所有數據
RequestResponseBodyMethodProcessor#handleReturnValue:處理返回值,要進行內容協商
-
mavContainer.setRequestHandled(true)
:設置請求處理完成 -
inputMessage = createInputMessage(webRequest)
:獲取輸入的數據 -
outputMessage = createOutputMessage(webRequest)
:獲取輸出的數據 -
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage)
:使用消息轉換器進行寫出-
if (value instanceof CharSequence)
:判斷返回的數據是不是字符類型 -
body = value
:把 value 賦值給 body,此時 body 中就是自定義方法執行完后的 Person 對象 -
if (isResourceType(value, returnType))
:當前數據是不是流數據 -
MediaType selectedMediaType
:內容協商后選擇使用的類型,瀏覽器和服務器都支持的媒體(數據)類型 -
MediaType contentType = outputMessage.getHeaders().getContentType()
:獲取響應頭的數據 -
if (contentType != null && contentType.isConcrete())
:判斷當前響應頭中是否已經有確定的媒體類型selectedMediaType = contentType
:說明前置處理已經使用了媒體類型,直接繼續使用該類型 -
acceptableTypes = getAcceptableMediaTypes(request)
:獲取瀏覽器支持的媒體類型,請求頭字段this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request))
:調用該方法for(ContentNegotiationStrategy strategy:this.strategies)
:默認策略是提取請求頭的字段的內容,策略類為HeaderContentNegotiationStrategy,可以配置添加其他類型的策略List<MediaType> mediaTypes = strategy.resolveMediaTypes(request)
:解析 Accept 字段存儲為 ListheaderValueArray = request.getHeaderValues(HttpHeaders.ACCEPT)
:獲取請求頭中 Accept 字段List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerValues)
:解析成 List 集合MediaType.sortBySpecificityAndQuality(mediaTypes)
:按照相對品質因數 q 降序排序
-
producibleTypes = getProducibleMediaTypes(request, valueType, targetType)
:服務器能生成的媒體類型request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE)
:從請求域獲取默認的媒體類型for (HttpMessageConverter<?> converter : this.messageConverters)
:遍歷所有的消息轉換器converter.canWrite(valueClass, null)
:是否支持當前的類型result.addAll(converter.getSupportedMediaTypes())
:把當前 MessageConverter 支持的所有類型放入 result
-
List<MediaType> mediaTypesToUse = new ArrayList<>()
:存儲最佳匹配 -
內容協商:
for (MediaType requestedType : acceptableTypes) { // 遍歷所有的瀏覽器能接受的媒體類型 for (MediaType producibleType : producibleTypes) { // 遍歷所有服務器能產出的 if (requestedType.isCompatibleWith(producibleType)) { // 判斷類型是否匹配,最佳匹配 // 數據協商匹配成功,一般有多種 mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType)); } } }
-
MediaType.sortBySpecificityAndQuality(mediaTypesToUse)
:按照相對品質因數 q 排序,降序排序,越大的越好 -
for (MediaType mediaType : mediaTypesToUse)
:遍歷所有的最佳匹配selectedMediaType = mediaType
:選擇一種賦值給選擇的類型 -
selectedMediaType = selectedMediaType.removeQualityValue()
:媒體類型去除相對品質因數 -
for (HttpMessageConverter<?> converter : this.messageConverters)
:遍歷所有的 HTTP 數據轉換器 -
GenericHttpMessageConverter genericConverter
:MappingJackson2HttpMessageConverter 可以將對象寫為 JSON -
((GenericHttpMessageConverter) converter).canWrite()
:判斷轉換器是否可以寫出給定的類型AbstractJackson2HttpMessageConverter#canWrit
if (!canWrite(mediaType))
:是否可以寫出指定類型MediaType.ALL.equalsTypeAndSubtype(mediaType)
:是不是*/*
類型getSupportedMediaTypes()
:支持application/json
和application/*+json
兩種類型return true
:返回 true
objectMapper = selectObjectMapper(clazz, mediaType)
:選擇可以使用的 objectMappercauseRef = new AtomicReference<>()
:獲取並發安全的引用if (objectMapper.canSerialize(clazz, causeRef))
:objectMapper 可以序列化當前類return true
:返回 true
-
body = getAdvice().beforeBodyWrite()
:要響應的所有數據,Person 對象 -
addContentDispositionHeader(inputMessage, outputMessage)
:檢查路徑 -
genericConverter.write(body, targetType, selectedMediaType, outputMessage)
:調用消息轉換器的 write 方法AbstractGenericHttpMessageConverter#write
:該類的方法-
addDefaultHeaders(headers, t, contentType)
:設置響應頭中的數據類型 -
writeInternal(t, type, outputMessage)
:數據寫出為 JSON 格式Object value = object
:value 引用 Person 對象ObjectWriter objectWriter = objectMapper.writer()
:獲取 ObjectWriter 對象objectWriter.writeValue(generator, value)
:使用 ObjectWriter 寫出數據為 JSON
-
-
3.2、協商策略
開啟基於請求參數的內容協商模式:(SpringBoot 方式)
spring.mvc.contentnegotiation:favor-parameter: true #開啟請求參數內容協商模式
發請求: http://localhost:8080/person?format=json,解析 format
策略類為 ParameterContentNegotiationStrategy,運行流程如下:
-
acceptableTypes = getAcceptableMediaTypes(request)
:獲取瀏覽器支持的媒體類型mediaTypes = strategy.resolveMediaTypes(request)
:解析請求 URL 參數中的數據-
return resolveMediaTypeKey(webRequest, getMediaTypeKey(webRequest))
:getMediaTypeKey(webRequest)
:request.getParameter(getParameterName())
:獲取 URL 中指定的需求的數據類型getParameterName()
:獲取參數的屬性名 formatgetParameter()
:獲取 URL 中 format 對應的數據
resolveMediaTypeKey()
:解析媒體類型,封裝成集合
-
自定義內容協商策略:
public class WebConfig implements WebMvcConfigurer {
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override //自定義內容協商策略
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
Map<String, MediaType> mediaTypes = new HashMap<>();
mediaTypes.put("json", MediaType.APPLICATION_JSON);
mediaTypes.put("xml",MediaType.APPLICATION_XML);
mediaTypes.put("person",MediaType.parseMediaType("application/x-person"));
//指定支持解析哪些參數對應的哪些媒體類型
ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);
//請求頭解析
HeaderContentNegotiationStrategy headStrategy = new HeaderContentNegotiationStrategy();
//添加到容器中,即可以解析請求頭 又可以解析請求參數
configurer.strategies(Arrays.asList(parameterStrategy,headStrategy));
}
@Override //自定義消息轉換器
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new GuiguMessageConverter());
}
}
}
}
也可以自定義 HttpMessageConverter,實現 HttpMessageConverter
四、視圖解析
4.1、返回解析
請求處理:
@GetMapping("/params")
public String param(){
return "forward:/success";
//return "redirect:/success";
}
進入執行方法邏輯 ServletInvocableHandlerMethod#invokeAndHandle,進入 this.returnValueHandlers.handleReturnValue
:
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) {
//獲取合適的返回值處理器:調用 if (handler.supportsReturnType(returnType))判斷是否支持
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException();
}
//使用處理器處理返回值
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
-
ViewNameMethodReturnValueHandler#supportsReturnType:
public boolean supportsReturnType(MethodParameter returnType) { Class<?> paramType = returnType.getParameterType(); // 返回值是否是void 或者 是 CharSequence 字符序列 return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType)); }
-
ViewNameMethodReturnValueHandler#handleReturnValue:
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { // 返回值是字符串,是 return "forward:/success" if (returnValue instanceof CharSequence) { String viewName = returnValue.toString(); // 把視圖名稱設置進入 ModelAndViewContainer 中 mavContainer.setViewName(viewName); // 判斷是否是重定向數據 `viewName.startsWith("redirect:")` if (isRedirectViewName(viewName)) { // 如果是重定向,設置是重定向指令 mavContainer.setRedirectModelScenario(true); } } else if (returnValue != null) { // should not happen throw new UnsupportedOperationException(); } }
4.2、結果派發
doDispatch()中的 processDispatchResult:處理派發結果
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler,
@Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
}
// mv 是 ModelAndValue
if (mv != null && !mv.wasCleared()) {
// 渲染視圖
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {}
}
DispatcherServlet#render:
-
Locale locale = this.localeResolver.resolveLocale(request)
:國際化相關 -
String viewName = mv.getViewName()
:視圖名字,是請求轉發 forward:/success(響應數據部分解析了該名字存入 ModelAndView 是通過 ViewNameMethodReturnValueHandler) -
view = resolveViewName(viewName, mv.getModelInternal(), locale, request)
:解析視圖-
for (ViewResolver viewResolver : this.viewResolvers)
:遍歷所有的視圖解析器view = viewResolver.resolveViewName(viewName, locale)
:根據視圖名字解析視圖,調用內容協商視圖處理器 ContentNegotiatingViewResolver 的方法-
attrs = RequestContextHolder.getRequestAttributes()
:獲取請求的相關屬性信息 -
requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest())
:獲取最佳匹配的媒體類型,函數內進行了匹配的邏輯 -
candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes)
:獲取候選的視圖對象-
for (ViewResolver viewResolver : this.viewResolvers)
:遍歷所有的視圖解析器 -
View view = viewResolver.resolveViewName(viewName, locale)
:解析視圖AbstractCachingViewResolver#resolveViewName
:-
returnview = createView(viewName, locale)
:UrlBasedViewResolver#createView請求轉發:實例為 InternalResourceView
-
if (viewName.startsWith(FORWARD_URL_PREFIX))
:視圖名字是否是forward:
的前綴 -
forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length())
:名字截取前綴 -
view = new InternalResourceView(forwardUrl)
:新建 InternalResourceView 對象並返回 -
return applyLifecycleMethods(FORWARD_URL_PREFIX, view)
:Spring 中的初始化操作
重定向:實例為 RedirectView
if (viewName.startsWith(REDIRECT_URL_PREFIX))
:視圖名字是否是redirect:
的前綴redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length())
:名字截取前綴RedirectView view = new RedirectView()
:新建 RedirectView 對象並返回
-
-
-
-
bestView = getBestView(candidateViews, requestedMediaTypes, attrs)
:選出最佳匹配的視圖對象
-
-
-
view.render(mv.getModelInternal(), request, response)
:頁面渲染-
mergedModel = createMergedOutputModel(model, request, response)
:把請求域中的數據封裝到 model -
prepareResponse(request, response)
:響應前的准備工作,設置一些響應頭 -
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response)
:渲染輸出的數據getRequestToExpose(request)
:獲取 Servlet 原生的方式請求轉發 InternalResourceView 的邏輯:請求域中的數據不丟失
exposeModelAsRequestAttributes(model, request)
:暴露 model 作為請求域的屬性model.forEach()
:遍歷 Model 中的數據request.setAttribute(name, value)
:設置到請求域中
exposeHelpers(request)
:自定義接口dispatcherPath = prepareForRendering(request, response)
:確定調度分派的路徑,此例是 /successrd = getRequestDispatcher(request, dispatcherPath)
:獲取 Servlet 原生的 RequestDispatcher 實現轉發rd.forward(request, response)
:實現請求轉發
重定向 RedirectView 的邏輯:請求域中的數據會丟失
-
targetUrl = createTargetUrl(model, request)
:獲取目標 URLenc = request.getCharacterEncoding()
:設置編碼 UTF-8appendQueryProperties(targetUrl, model, enc)
:添加一些屬性,比如url + ?name=123&&age=324
-
sendRedirect(request, response, targetUrl, this.http10Compatible)
:重定向response.sendRedirect(encodedURL)
:使用 Servlet 原生方法實現重定向
-