SpringMVC源码解析——HTTP请求处理

news2024/9/24 1:24:26

在SpringMVC源码解析——DispatcherServlet的逻辑处理中,最后介绍到了org.springframework.web.servlet.DispatcherServlet的doDispatch方法中关于处理Web HTTP请求的核心代码是调用AbstractHandlerMethodAdapter类的handle方法,源码如下:

	/**
	 * 此实现期望处理器为 {@link HandlerMethod} 类型。
	 */
	@Override
	@Nullable
	public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

	    return handleInternal(request, response, (HandlerMethod) handler);
	}

上面的处理HTTP请求的逻辑主要是调用handleInternal函数来进行处理,源码如下:

	/**
	 * 处理请求并返回视图模型。
	 *
	 * @param request HTTP请求对象
	 * @param response HTTP响应对象
	 * @param handlerMethod 处理方法的对象
	 * @return 视图模型对象
	 * @throws Exception 异常
	 */
	@Override
	protected ModelAndView handleInternal(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

	    ModelAndView mav;
	    checkRequest(request);

	    // 如果需要的话,在同步块中执行invokeHandlerMethod。
	    if (this.synchronizeOnSession) {
	        HttpSession session = request.getSession(false);
	        if (session != null) {
	            Object mutex = WebUtils.getSessionMutex(session);
	            synchronized (mutex) {
	                mav = invokeHandlerMethod(request, response, handlerMethod);
	            }
	        }
	        else {
	            // 没有可用的HttpSession -> 无需互斥锁
	            mav = invokeHandlerMethod(request, response, handlerMethod);
	        }
	    }
	    else {
	        // 一点都没有要求会话同步...
	        mav = invokeHandlerMethod(request, response, handlerMethod);
	    }

	    if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
	        if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
	            applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
	        }
	        else {
	            prepareResponse(response);
	        }
	    }

	    return mav;
	}

这个函数是一个Java方法,它处理HTTP请求并返回一个ModelAndView对象。它首先检查请求,然后根据需要在同步块中执行invokeHandlerMethod方法。如果需要在会话级别进行同步,则使用互斥锁来确保线程安全。接下来,根据响应是否包含缓存控制头,它会根据处理程序的方法来处理会话属性或将响应准备发送给客户端。最后,它返回一个ModelAndView对象。

/**
 * 调用RequestMapping处理器方法,如果需要解析视图,则准备ModelAndView。
 * 
 * @since 4.2
 * @see #createInvocableHandlerMethod(HandlerMethod)
 */
@SuppressWarnings("deprecation")
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
		HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
	
	// 创建ServletRequestWebRequest
	ServletWebRequest webRequest = new ServletWebRequest(request, response);
	
	// 获取WebDataBinderFactory
	WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
	
	// 获取ModelFactory
	ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
	
	// 创建ServletInvocableHandlerMethod
	ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
	
	// 设置处理器方法的参数解析器
	if (this.argumentResolvers != null) {
		invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
	}
	
	// 设置处理器方法的返回值解析器
	if (this.returnValueHandlers != null) {
		invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
	}
	
	// 设置DataBinderFactory
	invocableMethod.setDataBinderFactory(binderFactory);
	
	// 设置参数名称发现器
	invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
	
	// 设置方法验证器
	invocableMethod.setMethodValidator(this.methodValidator);
	
	// 创建ModelAndViewContainer
	ModelAndViewContainer mavContainer = new ModelAndViewContainer();
	
	// 添加所有属性到ModelAndViewContainer
	mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
	
	// 初始化模型
	modelFactory.initModel(webRequest, mavContainer, invocableMethod);
	
	// 设置是否在重定向时忽略默认模型
	mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
	
	// 创建AsyncWebRequest
	AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
	
	// 设置异步请求超时时间
	asyncWebRequest.setTimeout(this.asyncRequestTimeout);
	
	// 获取WebAsyncManager
	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
	
	// 设置TaskExecutor
	asyncManager.setTaskExecutor(this.taskExecutor);
	
	// 设置AsyncWebRequest
	asyncManager.setAsyncWebRequest(asyncWebRequest);
	
	// 注册Callable拦截器
	asyncManager.registerCallableInterceptors(this.callableInterceptors);
	
	// 注册DeferredResult拦截器
	asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
	
	// 如果异步结果已并行处理
	if (asyncManager.hasConcurrentResult()) {
		// 获取并行处理的结果和ModelAndViewContainer
		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 + "]";
		});
		
		// 将并行处理的结果包装到ServletInvocableHandlerMethod
		invocableMethod = invocableMethod.wrapConcurrentResult(result);
	}
	
	// 调用处理器方法并处理
	invocableMethod.invokeAndHandle(webRequest, mavContainer);
	
	// 如果异步处理已开始
	if (asyncManager.isConcurrentHandlingStarted()) {
		// 返回null
		return null;
	}
	
	// 返回ModelAndView
	return getModelAndView(mavContainer, modelFactory, webRequest);
}

这个函数用于调用@RequestMapping注解的处理方法,并准备 ModelAndView对象(如果需要解析视图)。它会执行处理方法,并处理异步结果和模型视图。如果异步处理已经开始,则返回null。 上面的函数会调用ServletInvocableHandlerMethod类的函数invokeAndHandle进行处理,invokeAndHandle函数的源码如下:

    /**
     * 调用方法并处理返回值
     *
     * @param webRequest    ServletWebRequest 对象,表示当前请求的上下文
     * @param mavContainer  ModelAndViewContainer 对象,用于处理模型和视图
     * @param providedArgs  提供的参数值
     * @throws Exception 抛出异常
     */
    public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
        // 调用方法并获取返回值
        Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
        
        // 设置响应状态
        this.setResponseStatus(webRequest);
        
        if (returnValue == null) {
            // 如果请求未修改、响应状态为 null 或者已处理请求
            if (this.isRequestNotModified(webRequest) || this.getResponseStatus() != null || mavContainer.isRequestHandled()) {
                mavContainer.setRequestHandled(true);
                return;
            }
        } else if (StringUtils.hasText(this.getResponseStatusReason())) {
            // 如果响应状态原因文本不为空
            mavContainer.setRequestHandled(true);
            return;
        }
        
        mavContainer.setRequestHandled(false);
        // 断言返回值处理器不为空
        Assert.state(this.returnValueHandlers != null, "No return value handlers");

        try {
            // 处理返回值
            this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest);
        } catch (Exception var6) {
            // 如果日志级别为 TRACE,则记录处理返回值时的异常信息
            if (this.logger.isTraceEnabled()) {
                this.logger.trace(this.getReturnValueHandlingErrorMessage("Error handling return value", returnValue), var6);
            }
            
            // 抛出异常
            throw var6;
        }
    }

这个函数是一个公共方法,它调用了其他的方法来处理HTTP请求,并根据处理结果进行相应的操作。首先,它调用了invokeForRequest方法来处理请求并获取返回值。然后,它通过调用setResponseStatus方法设置响应状态。接下来,它根据一些条件判断来决定是否设置请求已处理并返回。如果返回值为null,它会判断请求是否未修改、响应状态是否为null或请求是否已处理,如果满足条件,则设置请求已处理并返回。如果返回值不为null且响应状态原因文本不为空,同样会设置请求已处理并返回。如果以上条件都不满足,则将请求标记为未处理,并通过断言来验证返回值处理器不为空。最后,它调用returnValueHandlers的handleReturnValue方法来处理返回值,并将处理结果类型、模型视图容器和HTTP请求作为参数传递给该方法。如果处理过程中出现异常,它会记录日志并抛出异常。

/**
 * 在给定请求的上下文中解析方法参数值并调用方法。
 * <p>参数值通常通过 {@link HandlerMethodArgumentResolver} 解析。
 * 但是,{@code providedArgs} 参数可以提供要直接使用的参数值,即不进行参数解析的情况。
 * 例如,提供的参数值包括 {@link WebDataBinder}、{@link SessionStatus} 或抛出的异常实例。
 * 在参数解析器之前检查提供的参数值。
 * @param request 当前请求
 * @param mavContainer 本请求的 ModelAndViewContainer
 * @param providedArgs 与类型匹配的"给定"参数,未解析
 * @return 被调用方法的原始返回值
 * @throws Exception 如果找不到合适的参数解析器,或者方法引发了异常,则抛出异常
 */
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
		if (logger.isTraceEnabled()) {
			logger.trace("调用方法 '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
					"' 与参数 " + Arrays.toString(args));
		}
		Object returnValue = doInvoke(args);
		if (logger.isTraceEnabled()) {
			logger.trace("方法 [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
					"] 返回 [" + returnValue + "]");
		}
		return returnValue;
}

这个函数用于在给定请求的上下文中解析方法参数值并调用方法。参数值通常通过HandlerMethodArgumentResolver解析,但是提供的参数值可以直接使用,无需解析。提供的参数值在解析器之前进行检查。如果找不到合适的参数解析器或方法引发了异常,则会抛出异常。函数返回调用方法的原始返回值。

doInvoke方法主要是根据HTTP请求的参数调用控制器的函数来获取最终的结果。doInvoke方法的源码如下:

	/**
	 * 使用给定的参数值调用处理方法。
	 */
	protected Object doInvoke(Object... args) throws Exception {
	    ReflectionUtils.makeAccessible(getBridgedMethod());
	    try {
	        return getBridgedMethod().invoke(getBean(), args);
	    }
	    catch (IllegalArgumentException ex) {
	        assertTargetBean(getBridgedMethod(), getBean(), args);
	        String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
	        throw new IllegalStateException(getInvocationErrorMessage(text, args), ex);
	    }
	    catch (InvocationTargetException ex) {
	        // 解封装...
	        Throwable targetException = ex.getTargetException();
	        if (targetException instanceof RuntimeException) {
	            throw (RuntimeException) targetException;
	        }
	        else if (targetException instanceof Error) {
	            throw (Error) targetException;
	        }
	        else if (targetException instanceof Exception) {
	            throw (Exception) targetException;
	        }
	        else {
	            String text = getInvocationErrorMessage("Failed to invoke handler method", args);
	            throw new IllegalStateException(text, targetException);
	        }
	    }
	}

其中getBridgedMethod()获取的是Controller中相应的函数实例,getBean函数获取对应Controller的实例,运行时的堆栈信息如下:

根据上面的Controller实例,方法信息和输入参数信息,就能够执行相应控制器的方法了,并将执行结果返回给调用者。下面就能得到返回结果了。

上面调用invokeForRequest方法获取到的结果是业务端Controller中的函数返回的结果,但是HTTP通信的协议一般是JSON、Text等形式,所以还需要调用handleReturnValue函数将返回结果进行特殊处理,handleReturnValue函数的源码如下:

	/**
	 * 迭代遍历注册的 {@link HandlerMethodReturnValueHandler} 接口,并调用支持它的处理器。
	 * @throws IllegalStateException 如果找不到合适的 {@link HandlerMethodReturnValueHandler}。
	 */
	@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

	    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
	    if (handler == null) {
	        throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
	    }
	    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
	}

上面的函数先根据返回值类型调用selectHandler函数根据返回数据和返回数据的类型获取HandlerMethodReturnValueHandler实例,因为我们测试用的HTTP接口返回的结果最终需要转换为JSON数据,所以HandlerMethodReturnValueHandler需要支持注解为ResponseBody的HandlerMethodReturnValueHandler实例,而RequestResponseBodyMethodProcessor就符合该条件。最终处理返回结果的逻辑委托给RequestResponseBodyMethodProcessor接口的handleReturnValue函数进行处理。源码如下:

	/**
	 * 处理返回值
	 *
	 * @param returnValue 返回值
	 * @param returnType 返回类型
	 * @param mavContainer ModelAndViewContainer
	 * @param webRequest  NativeWebRequest
	 * @throws IOException
	 * @throws HttpMediaTypeNotAcceptableException
	 * @throws HttpMessageNotWritableException
	 */
	@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

		mavContainer.setRequestHandled(true);
		ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
		ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

		if (returnValue instanceof ProblemDetail detail) {
			outputMessage.setStatusCode(HttpStatusCode.valueOf(detail.getStatus()));
			if (detail.getInstance() == null) {
				URI path = URI.create(inputMessage.getServletRequest().getRequestURI());
				detail.setInstance(path);
			}
		}

		// 尝试即使返回值为null。ResponseBodyAdvice可以参与进来。
		writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
	}

该函数接收一些参数,包括返回值、方法参数类型、ModelAndViewContainer和NativeWebRequest。函数首先将mavContainer的 requestHandled属性设置为true。然后,它通过createInputMessage方法获取输入消息,通过createOutputMessage方法获取输出消息。接下来,如果返回值是ProblemDetail类型的对象,它将输出消息的状态码设置为HttpStatusCode中对应的值。如果 ProblemDetail对象的实例为空,它将从输入消息中获取请求的URI,并将其设置为ProblemDetail对象的实例。最后,函数调用 writeWithMessageConverters 方法,将返回值、方法参数类型、输入消息和输出消息作为参数传递给它。writeWithMessageConverters 方法的源码如下:

	/**
	 * 将给定的返回类型写入给定的输出消息。
	 * @param value 要写入输出消息的值
	 * @param returnType 返回值的类型
	 * @param inputMessage 输入消息。用于检查请求中的'Accept'头。
	 * @param outputMessage 输出消息要写入
	 * @throws IOException 当发生I/O错误时抛出
	 * @throws HttpMediaTypeNotAcceptableException 当请求中的'Accept'头由请求的消息转换器满足不了时抛出
	 * @throws HttpMessageNotWritableException 当给定的消息无法由转换器写入,或者服务器选择的Content-type没有兼容的转换器时抛出
	 */
	@SuppressWarnings({"rawtypes", "unchecked"})
	protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
			ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

		Object body;
		Class<?> valueType;
		Type targetType;

		if (value instanceof CharSequence) {
			body = value.toString();
			valueType = String.class;
			targetType = String.class;
		}
		else {
			body = value;
			valueType = getReturnValueType(body, returnType);
			targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
		}

		if (isResourceType(value, returnType)) {
			outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
			if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&
					outputMessage.getServletResponse().getStatus() == 200) {
				Resource resource = (Resource) value;
				try {
					List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
					outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
					body = HttpRange.toResourceRegions(httpRanges, resource);
					valueType = body.getClass();
					targetType = RESOURCE_REGION_LIST_TYPE;
				}
				catch (IllegalArgumentException ex) {
					outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
					outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
				}
			}
		}

		MediaType selectedMediaType = null;
		MediaType contentType = outputMessage.getHeaders().getContentType();
		boolean isContentTypePreset = contentType != null && contentType.isConcrete();
		if (isContentTypePreset) {
			if (logger.isDebugEnabled()) {
				logger.debug("在响应中找到了'Content-Type:" + contentType + "'");
			}
			selectedMediaType = contentType;
		}
		else {
			HttpServletRequest request = inputMessage.getServletRequest();
			List<MediaType> acceptableTypes;
			try {
				acceptableTypes = getAcceptableMediaTypes(request);
			}
			catch (HttpMediaTypeNotAcceptableException ex) {
				int series = outputMessage.getServletResponse().getStatus() / 100;
				if (body == null || series == 4 || series == 5) {
					if (logger.isDebugEnabled()) {
						logger.debug("忽略错误响应内容(如果有)。" + ex);
					}
					return;
				}
				throw ex;
			}

			List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
			if (body != null && producibleTypes.isEmpty()) {
				throw new HttpMessageNotWritableException(
						"找不到适用于返回值类型的转换器: " + valueType);
			}

			List<MediaType> compatibleMediaTypes = new ArrayList<>();
			determineCompatibleMediaTypes(acceptableTypes, producibleTypes, compatibleMediaTypes);

			// 对于ProblemDetail,回退到RFC 7807格式
			if (compatibleMediaTypes.isEmpty() && ProblemDetail.class.isAssignableFrom(valueType)) {
				determineCompatibleMediaTypes(this.problemMediaTypes, producibleTypes, compatibleMediaTypes);
			}

			if (compatibleMediaTypes.isEmpty()) {
				if (logger.isDebugEnabled()) {
					logger.debug("找不到匹配的: " + acceptableTypes + ", 支持: " + producibleTypes);
				}
				if (body != null) {
					throw new HttpMediaTypeNotAcceptableException(producibleTypes);
				}
				return;
			}

			MimeTypeUtils.sortBySpecificity(compatibleMediaTypes);

			for (MediaType mediaType : compatibleMediaTypes) {
				if (mediaType.isConcrete()) {
					selectedMediaType = mediaType;
					break;
				}
				else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
					selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
					break;
				}
			}

			if (logger.isDebugEnabled()) {
				logger.debug("使用'" + selectedMediaType + "',给定: " +
						acceptableTypes + " 和支持: " + producibleTypes);
			}
		}

		if (selectedMediaType != null) {
			selectedMediaType = selectedMediaType.removeQualityValue();
			for (HttpMessageConverter<?> converter : this.messageConverters) {
				GenericHttpMessageConverter genericConverter =
						(converter instanceof GenericHttpMessageConverter ghmc ? ghmc : null);
				if (genericConverter != null ?
						((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
						converter.canWrite(valueType, selectedMediaType)) {
					body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
							(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
							inputMessage, outputMessage);
					if (body != null) {
						Object theBody = body;
						LogFormatUtils.traceDebug(logger, traceOn ->
								"正在写入 [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
						addContentDispositionHeader(inputMessage, outputMessage);
						if (genericConverter != null) {
							genericConverter.write(body, targetType, selectedMediaType, outputMessage);
						}
						else {
							((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
						}
					}
					else {
						if (logger.isDebugEnabled()) {
							logger.debug("没有内容可写: null body");
						}
					}
					return;
				}
			}
		}

		if (body != null) {
			Set<MediaType> producibleMediaTypes =
					(Set<MediaType>) inputMessage.getServletRequest()
							.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

			if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {
				throw new HttpMessageNotWritableException(
						"无法写入 [" + valueType + "],Content-Type为: '" + contentType + "'");
			}
			throw new HttpMediaTypeNotAcceptableException(getSupportedMediaTypes(body.getClass()));
		}
	}

这个Java函数用于将给定的返回值写入到输出消息中。它根据请求中的"Accept"头检查是否可以满足请求,并使用适当的消息转换器将返回值转换为适当的内容类型。如果找不到适合的内容类型,则会引发异常。最后,它将内容写入到输出响应中。

 在上面代码中的消息转换器messageConverters中会有很多种类型,由于常用的HTTP接口响应类型主要是application/json,其对应的消息转换器抽象类是AbstractJsonHttpMessageConverter,接下来会调用RequestResponseBodyAdviceChain的beforeBodyWrite函数进行响应结果写入Response前的操作。beforeBodyWrite函数的源码如下:

	@Override
	@Nullable
	public Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType contentType,
			Class<? extends HttpMessageConverter<?>> converterType,
			ServerHttpRequest request, ServerHttpResponse response) {

		return processBody(body, returnType, contentType, converterType, request, response);
	}

 beforeBodyWrite函数会直接调用processBody函数处理响应的结果,源码如下:

@SuppressWarnings("unchecked")
	@Nullable
	private <T> Object processBody(@Nullable Object body, MethodParameter returnType, MediaType contentType,
			Class<? extends HttpMessageConverter<?>> converterType,
			ServerHttpRequest request, ServerHttpResponse response) {

		for (ResponseBodyAdvice<?> advice : getMatchingAdvice(returnType, ResponseBodyAdvice.class)) {
			if (advice.supports(returnType, converterType)) {
				body = ((ResponseBodyAdvice<T>) advice).beforeBodyWrite((T) body, returnType,
						contentType, converterType, request, response);
			}
		}
		return body;
	}

在上面的writeWithMessageConverters 方法中,执行完RequestResponseBodyAdviceChain的beforeBodyWrite函数后,开始调用消息转换器的写入操作了,我们先看一下AbstractGenericHttpMessageConverter的write函数的源码如下:

	/**
	 * 此实现通过调用{@link #addDefaultHeaders}设置默认头部,然后调用{@link #writeInternal}。
	 */
	@Override
	public final void write(final T t, @Nullable final Type type, @Nullable MediaType contentType,
			HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {

		final HttpHeaders headers = outputMessage.getHeaders();
		addDefaultHeaders(headers, t, contentType);

		if (outputMessage instanceof StreamingHttpOutputMessage streamingOutputMessage) {
		    streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() {
		        @Override
		        public void writeTo(OutputStream outputStream) throws IOException {
		            writeInternal(t, type, new HttpOutputMessage() {
		                @Override
		                public OutputStream getBody() {
		                    return outputStream;
		                }
		                @Override
		                public HttpHeaders getHeaders() {
		                    return headers;
		                }
		            });
		        }
		        @Override
		        public boolean repeatable() {
		            return supportsRepeatableWrites(t);
		        }
		    });
		}
		else {
		    writeInternal(t, type, outputMessage);
		    outputMessage.getBody().flush();
		}
	}

该函数用于写入HTTP输出消息。它首先通过调用addDefaultHeaders方法设置默认头部,然后调用writeInternal方法写入内容。如果输出消息是StreamingHttpOutputMessage类型,则将内容写入StreamingHttpOutputMessage的Body中,并设置可重复写入标志。否则,直接调用writeInternal方法并将内容写入输出消息的Body中,并刷新Body。

上面最终会调用函数writeInternal将控制器中HTTP接口的返回结果写入到body中,源码如下:

/**
 * 内部写入方法,将对象写入到指定的HttpOutputMessage中。
 *
 * @param object 要写入的对象
 * @param type 要写入的对象的类型
 * @param outputMessage 要写入的HttpOutputMessage对象
 * @throws IOException 如果发生IO错误
 * @throws HttpMessageNotWritableException 如果发生消息写入错误
 */
@Override
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
        throws IOException, HttpMessageNotWritableException {

    // 获取输出消息的媒体类型
    MediaType contentType = outputMessage.getHeaders().getContentType();
    // 获取JSON编码方式
    JsonEncoding encoding = getJsonEncoding(contentType);

    // 获取对象的Class类型
    Class<?> clazz = (object instanceof MappingJacksonValue mappingJacksonValue ?
            mappingJacksonValue.getValue().getClass() : object.getClass());
    // 选择合适的ObjectMapper
    ObjectMapper objectMapper = selectObjectMapper(clazz, contentType);
    Assert.state(objectMapper != null, () -> "No ObjectMapper for " + clazz.getName());

    // 获取输出流,并关闭与之关联的输出流
    OutputStream outputStream = StreamUtils.nonClosing(outputMessage.getBody());
    try (JsonGenerator generator = objectMapper.getFactory().createGenerator(outputStream, encoding)) {
        // 写入前缀信息
        writePrefix(generator, object);

        Object value = object;
        Class<?> serializationView = null;
        FilterProvider filters = null;
        JavaType javaType = null;

        if (object instanceof MappingJacksonValue mappingJacksonValue) {
            // 处理MappingJacksonValue类型对象
            value = mappingJacksonValue.getValue();
            serializationView = mappingJacksonValue.getSerializationView();
            filters = mappingJacksonValue.getFilters();
        }
        if (type != null && TypeUtils.isAssignable(type, value.getClass())) {
            // 处理指定的类型
            javaType = getJavaType(type, null);
        }

        // 创建ObjectWriter对象
        ObjectWriter objectWriter = (serializationView != null ?
                objectMapper.writerWithView(serializationView) : objectMapper.writer());
        if (filters != null) {
            // 设置过滤器
            objectWriter = objectWriter.with(filters);
        }
        if (javaType != null && (javaType.isContainerType() || javaType.isTypeOrSubTypeOf(Optional.class))) {
            // 设置JavaType
            objectWriter = objectWriter.forType(javaType);
        }
        // 获取配置信息
        SerializationConfig config = objectWriter.getConfig();
        if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) &&
                config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
            // 设置缩进输出
            objectWriter = objectWriter.with(this.ssePrettyPrinter);
        }
        // 自定义ObjectWriter
        objectWriter = customizeWriter(objectWriter, javaType, contentType);
        // 写入对象的值
        objectWriter.writeValue(generator, value);

        // 写入后缀信息
        writeSuffix(generator, object);
        // 刷新输出流
        generator.flush();
    }
    catch (InvalidDefinitionException ex) {
        // 处理无效定义异常
        throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
    }
    catch (JsonProcessingException ex) {
        // 处理JSON处理异常
        throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getOriginalMessage(), ex);
    }
}

上面的代码主要是将控制器中HTTP接口的返回结果序列化成JSON数据后写入到Body中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1365083.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

登录验证

会话技术 会话 打开浏览器&#xff0c;访问web服务器的资源&#xff0c;会话建立&#xff0c;直到有一方断开连接&#xff0c;会话结束。在一次会话中可以包含多次请求与响应 会话跟踪 一种维护浏览器的方法 服务器需要识别多次请求是否来自于同一浏览器 以便在同一次会话的多次…

【leetcode】力扣算法之相交链表【中等难度】

题目描述 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个链式结构中不存在环。 注意&#xff0c;函数…

[VUE]3-路由

目录 路由 Vue-Router1、Vue-Router 介绍2、路由配置3、嵌套路由3.1、简介3.2、实现步骤3.3、⭐注意事项 4、⭐router-view标签详解 ​&#x1f343;作者介绍&#xff1a;双非本科大三网络工程专业在读&#xff0c;阿里云专家博主&#xff0c;专注于Java领域学习&#xff0c;擅…

Java并发 - Java中所有的锁

Java 中提供了多种锁机制&#xff0c;用于实现多线程之间的同步和互斥。 1. 乐观锁&悲观锁 1.1 特点 乐观锁&#xff1a;假定多个事务之间很少发生冲突&#xff0c;操作不加锁。发生错误的时候进行回滚或重试。 悲观锁&#xff1a;假定冲突可能频繁发生&#xff0c;先…

放大镜Scratch-第14届蓝桥杯Scratch省赛真题第3题

3. 放大镜&#xff08;50分&#xff09; 评判标准&#xff1a; 10分&#xff1a;满足"具体要求"中的1&#xff09;&#xff1b; 15分&#xff1a;满足"具体要求"中的2&#xff09;&#xff1b; 25分&#xff0c;满足"具体要求"中的3&#xff…

Cadence Editor 关于画PCB相关内容

目录 一 新建PCB文件 二 指定封装库 三 导入网表 四 放置器件 五 绘制板框 六 精准定位 七 原理图与PCB的交互 八 飞线设置 九 层管理 布局布线阶段需要显示的层 十 器件位置相关 1 器件选取的基准点 2 旋转 3 对齐 4 把器件移动到底层或顶层 5 锁定与解锁 6…

C# Entity Framework 中不同的数据的加载方式

延迟加载 延迟加载是指在访问导航属性时&#xff0c;Entity Framework 会自动查询数据库并加载相关数据。这种方式在我们需要访问导航属性时比较方便&#xff0c;因为我们无需手动加载相关数据&#xff0c;而且只会在需要时才会进行查询&#xff0c;从而减少了不必要的开销。但…

Qt6入门教程 4:Qt Creator常用技巧

在上一篇Qt6入门教程 3&#xff1a;创建Hello World项目中&#xff0c;通过创建一个Qt项目&#xff0c;对Qt Creator已经有了比较直观的认识&#xff0c;本文将介绍它的一些常用技巧。 Qt Creator启动后默认显示欢迎页面 创建项目已经用过了&#xff0c;打开项目也很简单&#…

C语言中关于函数调用的理解

理论 关于函数调用的方式有两类&#xff1a;传值调用和传址调用 传值调用&#xff1a;函数的形参和实参分别占有不同的内存块&#xff0c;对形参的修改不会影响实参。 传址调用&#xff1a;把函数外部创建变量的内存地址传递给函数参数的一种调用方式。可以让函数和函数外面…

基于 STM32 的 MPU6050 姿态计算方法及应用

基于STM32的MPU6050姿态计算方法可以通过融合陀螺仪和加速度计的数据来实现。在本文中&#xff0c;我们将介绍通过MPU6050获取姿态数据&#xff0c;并结合姿态解算算法实现姿态估计的方法&#xff0c;并提供相应的代码示例。 1. 硬件连接及库配置 首先&#xff0c;我们需要将…

uniapp中uview组件库CircleProgress 圆形进度条丰富的使用方法

目录 #内部实现 #平台差异说明 #基本使用 #设置圆环的动画时间 #API #Props 展示操作或任务的当前进度&#xff0c;比如上传文件&#xff0c;是一个圆形的进度环。 #内部实现 组件内部通过canvas实现&#xff0c;有更好的性能和通用性。 #平台差异说明 AppH5微信小程…

静态网页课程设计——贾斯汀比伯歌手介绍(HTML+CSS+JavaScript)

前言 使用技术&#xff1a;HTMLCSSJS 主要内容&#xff1a;对贾斯汀比伯这名歌手进行介绍&#xff0c;还有他的几首比较经典的歌曲以及视频观看。 简单介绍 本网站是以介绍贾斯汀比伯为主题而展开&#xff0c;主要由首页、《Changes》和音乐视频组成的。首页下面的是对他介绍…

数据在内存中的存储方式

前言&#xff1a; 期末临近&#xff0c;继续复习&#xff01; 今天要复习的内容是数据在内存中的存储&#xff0c;主要是整型与浮点两种&#xff0c;还有大小端的介绍。 提出问题 打印结果是255 -1 为什么&#xff1f; 首先我们要知道数据都是以二进制的形式存…

智能的核心依然是哲学的三个基本问题

智能的发展与哲学的三个基本问题密切相关&#xff0c;作为一个复杂领域&#xff0c;智能涉及到人类认知和行为的模拟与复制&#xff0c;因而也会涉及到哲学的核心问题。 存在论&#xff1a;智能的存在论问题涉及到什么是智能以及智能系统的本质。这包括对于意识、思维和自主性的…

酷雷曼精彩亮相CMC 2023中国元宇宙大会,助力云上VR直播

12月23日&#xff0c;2023中关村论坛系列活动——CMC 2023中国元宇宙大会在石景山首钢园冰壶馆成功举办。酷雷曼VR作为元宇宙领域代表企业之一受邀出席会议&#xff0c;分享元宇宙技术研发成果及应用方案&#xff0c;并为大会提供VR直播技术支持。 大咖云集&#xff0c;共商元宇…

js逆向第13例:猿人学第6题js混淆-回溯赛

文章目录 m是加密字符串怎么来的?浏览器环境检测本地运行的js代码任务六:采集全部5页的彩票数据,计算全部中奖的总金额(包含一、二、三等奖) 此题总体难度低于第5题,老规矩还是查看控制台请求地址https://match.yuanrenxue.cn/api/match/6?m=rPRDgpbV3Wd%252FyPfURQAkxK…

Vue知识总结-中

VUE-生命周期 生命周期概述 生命周期也常常被称为生命周期回调函数/生命周期函数/生命周期钩子生命周期是Vue在关键时刻帮我们调用的一些特殊名称的函数生命周期函数的名字不能更改,但函数的具体内容是由我们程序员自己编写的生命周期函数中的this指向是vm或组件实例对象 生命周…

[VUE]5-TypeScript

目录 1 TypeScript 介绍2、安装3、快速上手4、TypeScript 常用类型4.1 类型标注的位置4.2 字符串、数字、布尔类型4.3 字面量类型4.4 ⭐interface 类型4.5 class 类型 ​&#x1f343;作者介绍&#xff1a;双非本科大三网络工程专业在读&#xff0c;阿里云专家博主&#xff0c;…

【Leetcode】移除后集合的最多元素数

目录 &#x1f4a1;题目描述 &#x1f4a1;思路 &#x1f4a1;总结 100150. 移除后集合的最多元素数 &#x1f4a1;题目描述 给你两个下标从 0 开始的整数数组 nums1 和 nums2 &#xff0c;它们的长度都是偶数 n 。 你必须从 nums1 中移除 n / 2 个元素&#xff0c;同时从 …

江科大STM32

目录 STM32简介 STM32简介 我们主要学习的就是STM32的外设。 NVIC&#xff1a;内核里面用于管理中断的设备&#xff0c;比如配置中断优先级这些东西SysTick&#xff1a;内核里面的定时器&#xff0c;主要用来给操作系统提供定时服务的&#xff0c;STM32是可以加入操作系统的&am…