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 原生方法实现重定向
posted @ 2021-08-03 19:50  Seazean  阅读(1826)  评论(0编辑  收藏  举报