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

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

	/*** 此实现期望处理器为 {@link HandlerMethod} 类型。*/@Override@Nullablepublic 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 异常*/@Overrideprotected 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 {// 创建ServletRequestWebRequestServletWebRequest webRequest = new ServletWebRequest(request, response);// 获取WebDataBinderFactoryWebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);// 获取ModelFactoryModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);// 创建ServletInvocableHandlerMethodServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);// 设置处理器方法的参数解析器if (this.argumentResolvers != null) {invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}// 设置处理器方法的返回值解析器if (this.returnValueHandlers != null) {invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}// 设置DataBinderFactoryinvocableMethod.setDataBinderFactory(binderFactory);// 设置参数名称发现器invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);// 设置方法验证器invocableMethod.setMethodValidator(this.methodValidator);// 创建ModelAndViewContainerModelAndViewContainer mavContainer = new ModelAndViewContainer();// 添加所有属性到ModelAndViewContainermavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));// 初始化模型modelFactory.initModel(webRequest, mavContainer, invocableMethod);// 设置是否在重定向时忽略默认模型mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);// 创建AsyncWebRequestAsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);// 设置异步请求超时时间asyncWebRequest.setTimeout(this.asyncRequestTimeout);// 获取WebAsyncManagerWebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);// 设置TaskExecutorasyncManager.setTaskExecutor(this.taskExecutor);// 设置AsyncWebRequestasyncManager.setAsyncWebRequest(asyncWebRequest);// 注册Callable拦截器asyncManager.registerCallableInterceptors(this.callableInterceptors);// 注册DeferredResult拦截器asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);// 如果异步结果已并行处理if (asyncManager.hasConcurrentResult()) {// 获取并行处理的结果和ModelAndViewContainerObject 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 + "]";});// 将并行处理的结果包装到ServletInvocableHandlerMethodinvocableMethod = invocableMethod.wrapConcurrentResult(result);}// 调用处理器方法并处理invocableMethod.invokeAndHandle(webRequest, mavContainer);// 如果异步处理已开始if (asyncManager.isConcurrentHandlingStarted()) {// 返回nullreturn null;}// 返回ModelAndViewreturn 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}。*/@Overridepublic 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*/@Overridepublic 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@Nullablepublic 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")@Nullableprivate <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}。*/@Overridepublic 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() {@Overridepublic void writeTo(OutputStream outputStream) throws IOException {writeInternal(t, type, new HttpOutputMessage() {@Overridepublic OutputStream getBody() {return outputStream;}@Overridepublic HttpHeaders getHeaders() {return headers;}});}@Overridepublic 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());// 选择合适的ObjectMapperObjectMapper 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))) {// 设置JavaTypeobjectWriter = 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);}// 自定义ObjectWriterobjectWriter = 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.mzph.cn/news/612314.shtml

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

相关文章

作业:通过两台linux主机配置ssh实现互相免密登陆

做题步骤&#xff1a; 一.开启两个Linux主机&#xff0c;并且用ssh连接&#xff0c;要能够ping通 我这里是server&#xff1a;192.168.81.129 client&#xff1a;192.168.81.130 举例 步骤&#xff1a; 1.安装服务软件 2.运行软件程序 3.根据自定配置提供对应的服务/etc/chr…

期末查分系统(c,链表实现)

主要功能&#xff1a; 分为三个身份: 学生:可以通过学号查询个人分数 老师&#xff1a;可以看所有学生成绩&#xff0c;单科排名&#xff08;正序&#xff0c;倒序&#xff09;&#xff0c;统计绩点&#xff0c;查看绩点排名前百分之n的学生 管理员端&#xff1a;可以创建链…

用React给XXL-JOB开发一个新皮肤(二):目录规划和路由初始化

目录 一. 简述二. 目录规划三. Vite 配置 3.1. 配置路径别名3.2. 配置 less 四. 页面 4.1. 入口文件4.2. 骨架文件4.3. 普通页面 五. 路由配置六. 预览启动 一. 简述 上一篇文章我们介绍了项目初始化&#xff0c;此篇文章我们会先介绍下当前项目的目录规划&#xff0c;接着对…

Magics 教程

文章目录 基本流程基本操作页面的介绍基本操作 基本流程 基本操作 页面的介绍 右侧是工具页&#xff0c;可以直接进行调整&#xff0c;也可以在选项&帮助->自定义用户界面 那里进行相关的调整 基本操作 直接拖动鼠标左键&#xff1a;选中物体鼠标右键&#xff1a; 长按…

JVM工作原理与实战(十二):打破双亲委派机制-自定义类加载器

专栏导航 JVM工作原理与实战 RabbitMQ入门指南 从零开始了解大数据 目录 专栏导航 前言 一、打破双亲委派机制的方法 二、自定义类加载器 1.Tomcat自定义类加载器案例 2.自定义类加载器详解 3.案例解析 总结 前言 JVM作为Java程序的运行环境&#xff0c;其负责解释和执…

RAG:让大语言模型拥有特定的专属知识

作为一个在Chatbot领域摸爬滚打了7年的从业者&#xff0c;笔者可以诚实地说&#xff0c;在大语言模型的推动下&#xff0c;检索增强生成&#xff08;Retrieval Augmented Generation&#xff0c;RAG&#xff09;技术正在快速崛起。 RAG的搜索请求和生成式AI技术&#xff0c;为搜…

【教学类-45-06】正确 X-Y之间的三连加减题混合 (竖向排列)(44格:11题“++ ”11题“--”11题“ +-”11题“ -+” )

作品展示&#xff1a; 背景需求&#xff1a; 把以下四款3连题 混在一起&#xff0c;每种题目随机抽取11题&#xff0c;一共44格 出现问题&#xff1a; 1、- 、-里面有重复题 2、升序排列最好竖排展示 素材准备: ​ ​ 问题改正 1、单元格修改&#xff1a;确保竖列写入 …

【Docker项目实战】使用Docker部署nullboard任务管理工具

【Docker项目实战】使用Docker部署nullboard任务管理工具 一、nullboard介绍1.1 nullboard简介1.2 任务看板工具介绍 二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍2.3 注意事项 三、本地环境检查3.1 检查Docker服务状态3.2 检查Docker版本3.3 检查docker compose 版本 四…

C# WPF 数据绑定

需求 后台变量发生改变,前端对应的相关属性值也发生改变 实现 接口 INotifyPropertyChanged 用于通知客户端(通常绑定客户端)属性值已更改。 示例 示例一 官方示例代码如下 using System; using System.Collections.Generic; using System.ComponentModel; using Sys…

spring boot + mybatis + websocket + js实战

项目技术&#xff1a;spring boot mybatis websocket js 需求背景&#xff1a;当添加一个女孩时&#xff0c;页面的socket收到消息&#xff0c;打印最新的所有女生list&#xff0c;这样可以进一步在react/vue前端框架下&#xff0c;实现当A用户新增了某业务数据后&#xff…

迅为RK3568开发板Android11/12/Linux编译驱动到内核

在平时的驱动开发中&#xff0c;经常需要在内核中配置某种功能&#xff0c;为了方便大家开发和学习&#xff0c;本小 节讲解如何在内核中添加驱动。具体的讲解原理讲解请参考本手册的驱动教程。 Android11 源码如果想要修改内核&#xff0c;可以运行以下命令进行修改: cd ke…

ffmpeg 视频分辨率修改 质量压缩

随着手机像素的提高&#xff0c;拍摄视频也越来越大&#xff0c;10秒的视频动辄 二三十兆&#xff0c;这给视频传输和播放都带来了 诸多不变。一般都需要 前端或或者后端 对视频进行压缩。由于我这边前端是 H5&#xff0c;所以只能后端进行压缩&#xff0c; 采用主流压缩库采用…

centOS系统yum安装和卸载mongodb

0.1 什么是mongodb&#xff1f; 0.2 Mongodb是一个基于分布式文件存储的数据库。由C语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。 0.3 Mongodb是一个介于关系数据库和非关系数据库之间的产品&#xff0c;是非关系数据库当中功能最丰富&#xff0c;最像关系数据…

现代密码学 考点复盘

现代密码学 考点汇总&#xff08;上&#xff09; 写在最前面考试范围一、给一个简单的方案&#xff0c;判断是否cca安全二、随机预言机模型之下的简单应用 考试题目1.证明CBC方案是CPA安全的2. 证明哈希函数的抗碰撞性3. CBC-MAC安全&#xff1a;证明CPA安全的对称密钥加密方案…

使用git submodule解决高耦合度问题

引言 在开发我的笔记系统时&#xff0c;我遇到了一个问题。问题是&#xff0c;在api-gate服务中&#xff0c;我需要验证用户的access_code&#xff0c;但是access_code的生成逻辑是在auth2服务中实现的。这个问题从架构设计的层面上看&#xff0c;就是一个高耦合度问题。高耦合…

大数据 - Doris系列《三》- 数据表设计之表的基本概念

目录 &#x1f436;3.1 字段类型 &#x1f436;3.2 表的基本概念 3.2.1 Row & Column 3.2.2 分区与分桶 &#x1f959;3.2.2.1 Partition 1. Range 分区 2. List 分区 进阶&#xff1a;复合分区与单分区的选择 3.2.3 PROPERTIES &#x1f959;3.2.3.1 分片副本数 &#x1f…

正则表达式、文件访问(Python实现)

一、主要目的&#xff1a; 1.了解正则表达式的基本概念和处理过程。 2.掌握使用正则表达式模块 Re 进行字符串处理的方法。 3.了解文件的基本概念和类型。 4.掌握在 Python 中访问文本文件的方法和步骤。 5.熟悉在 Python 中访问二进制文件的方法和步骤。 二、主要内容和结…

【小白专用】C# 连接 MySQL 数据库

C# – Mysql 数据库连接 1. 配置环境 #前提&#xff1a;电脑已安装Mysql服务&#xff1b; Visual Studio 安装Mysql依赖库&#xff1a; 工具 -> NuGet 包管理器 -> 管理解决方案的 NuGet程序包 —> 搜索&#xff0c; 安装Mysql.Data (Oracle); (安装成功后&…

常用的网站

PIXEL MOTION 注册-YesPMP平台 模型下载 - Ourblender - 专业的三维素材库 Vega AI 创作平台 夏沫的AI小站 Tripo AI B站视频下载工具 | 极简纯净

视频监控录像服务器(中心录像服务器)功能详细介绍

目 录 一、概述 &#xff08;一&#xff09;定义 &#xff08;二&#xff09;视频监控中心录像服务器 二、存储策略服务 &#xff08;一&#xff09;存储策略配置 1、 录入页面 2、 选择需要进行录像的视频 3、批量选择多个通道号 4、其他关键参数…