SpringMVC 源碼解析筆記


作者筆記倉庫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 處理器映射器,保存了所有 @RequestMappinghandler 的映射規則

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):去匹配每一個映射規則,匹配失敗返回 null
          • matches.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#handleRequestMappingHandlerAdapter#handleInternalinvokeHandlerMethod

//使用適配器執行方法
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 中的參數為 Map
      • MapMethodProcessor#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 字段存儲為 List
        • headerValueArray = 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 genericConverterMappingJackson2HttpMessageConverter 可以將對象寫為 JSON

    • ((GenericHttpMessageConverter) converter).canWrite():判斷轉換器是否可以寫出給定的類型

      AbstractJackson2HttpMessageConverter#canWrit

      • if (!canWrite(mediaType)):是否可以寫出指定類型
        • MediaType.ALL.equalsTypeAndSubtype(mediaType):是不是 */* 類型
        • getSupportedMediaTypes():支持 application/jsonapplication/*+json 兩種類型
        • return true:返回 true
      • objectMapper = selectObjectMapper(clazz, mediaType):選擇可以使用的 objectMapper
      • causeRef = 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():獲取參數的屬性名 format
        • getParameter()獲取 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):確定調度分派的路徑,此例是 /success
      • rd = getRequestDispatcher(request, dispatcherPath)獲取 Servlet 原生的 RequestDispatcher 實現轉發
      • rd.forward(request, response):實現請求轉發

      重定向 RedirectView 的邏輯:請求域中的數據會丟失

      • targetUrl = createTargetUrl(model, request):獲取目標 URL

        • enc = request.getCharacterEncoding():設置編碼 UTF-8
        • appendQueryProperties(targetUrl, model, enc):添加一些屬性,比如 url + ?name=123&&age=324
      • sendRedirect(request, response, targetUrl, this.http10Compatible):重定向

        • response.sendRedirect(encodedURL)使用 Servlet 原生方法實現重定向


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM