javascript
SpringMVC —— @ResponseBody原理
目的
@ResponseBody可以添加在控制器類或其下方法中,這樣在請求結果對象返回時能夠將其解析為JSON格式,這是如何實現的呢? 文章目的在于梳理@ResponseBody實現的原理引子
先看一段摘自官網文檔的內容,https://spring.io/guides/gs/rest-service/
The Greeting object must be converted to JSON. Thanks to Spring’s HTTP message converter support, you need not do this conversion manually.
Because Jackson 2 is on the classpath,Spring’s MappingJackson2HttpMessageConverter is automatically chosen to convert the Greeting instance to JSON.
探究
文檔中提及了具體實現Json轉換類,可以在JacksonHttpMessageConvertersConfiguration這個配置類中找到mappingJackson2HttpMessageConverter的Bean注入。
@Configuration(proxyBeanMethods = false ) class JacksonHttpMessageConvertersConfiguration {JacksonHttpMessageConvertersConfiguration() {}@Configuration(proxyBeanMethods = false)@ConditionalOnClass({XmlMapper.class})@ConditionalOnBean({Jackson2ObjectMapperBuilder.class})protected static class MappingJackson2XmlHttpMessageConverterConfiguration {protected MappingJackson2XmlHttpMessageConverterConfiguration() {}@Bean@ConditionalOnMissingBeanpublic MappingJackson2XmlHttpMessageConverter mappingJackson2XmlHttpMessageConverter(Jackson2ObjectMapperBuilder builder) {return new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build());}}@Configuration(proxyBeanMethods = false)@ConditionalOnClass({ObjectMapper.class})@ConditionalOnBean({ObjectMapper.class})@ConditionalOnProperty(name = {"spring.mvc.converters.preferred-json-mapper"},havingValue = "jackson",matchIfMissing = true)static class MappingJackson2HttpMessageConverterConfiguration {MappingJackson2HttpMessageConverterConfiguration() {}// 關鍵:這里注入了mappingJackson2HttpMessageConverter,這個類是AbstractJackson2HttpMessageConverter的子類@Bean@ConditionalOnMissingBean(value = {MappingJackson2HttpMessageConverter.class},ignoredType = {"org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter", "org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter"})MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {return new MappingJackson2HttpMessageConverter(objectMapper);}} }可以看到,上面類中的核心是注入了一個mappingJackson2HttpMessageConverter類,那么接下來就是看一下這個類在哪里被使用了。先來看看這個類的接口實現及繼承關系。
這里我們看到最頂層的接口是HttpMessageConverter,下面的問題就是弄清HttpMessageConverter的調用過程。
Debug可以發現,數據對象返回后經過了RequestResponseBodyMethodProcessor.handleReturnValue方法,其相關代碼如下,可以看到調用了this.writeWithMessageConverters來處理返回的結果。
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {mavContainer.setRequestHandled(true);ServletServerHttpRequest inputMessage = this.createInputMessage(webRequest);ServletServerHttpResponse outputMessage = this.createOutputMessage(webRequest);// 核心調用this.writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);}上面代碼中,最核心的調用方法是writeWithMessageConverters方法,其具體實現如下,該方法的核心調用是converter.write(body, selectedMediaType, outputMessage)。
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {Object body;Class valueType;Object targetType;if (value instanceof CharSequence) {body = value.toString();valueType = String.class;targetType = String.class;} else {body = value;valueType = this.getReturnValueType(value, returnType);targetType = GenericTypeResolver.resolveType(this.getGenericType(returnType), returnType.getContainingClass());}if (this.isResourceType(value, returnType)) {outputMessage.getHeaders().set("Accept-Ranges", "bytes");if (value != null && inputMessage.getHeaders().getFirst("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 var19) {outputMessage.getHeaders().set("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 (this.logger.isDebugEnabled()) {this.logger.debug("Found 'Content-Type:" + contentType + "' in response");}selectedMediaType = contentType;} else {HttpServletRequest request = inputMessage.getServletRequest();List acceptableTypes;try {acceptableTypes = this.getAcceptableMediaTypes(request);} catch (HttpMediaTypeNotAcceptableException var20) {int series = outputMessage.getServletResponse().getStatus() / 100;if (body != null && series != 4 && series != 5) {throw var20;}if (this.logger.isDebugEnabled()) {this.logger.debug("Ignoring error response content (if any). " + var20);}return;}List<MediaType> producibleTypes = this.getProducibleMediaTypes(request, valueType, (Type)targetType);if (body != null && producibleTypes.isEmpty()) {throw new HttpMessageNotWritableException("No converter found for return value of type: " + valueType);}List<MediaType> mediaTypesToUse = new ArrayList();Iterator var15 = acceptableTypes.iterator();MediaType mediaType;while(var15.hasNext()) {mediaType = (MediaType)var15.next();Iterator var17 = producibleTypes.iterator();while(var17.hasNext()) {MediaType producibleType = (MediaType)var17.next();if (mediaType.isCompatibleWith(producibleType)) {mediaTypesToUse.add(this.getMostSpecificMediaType(mediaType, producibleType));}}}if (mediaTypesToUse.isEmpty()) {if (body != null) {throw new HttpMediaTypeNotAcceptableException(producibleTypes);}if (this.logger.isDebugEnabled()) {this.logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);}return;}MediaType.sortBySpecificityAndQuality(mediaTypesToUse);var15 = mediaTypesToUse.iterator();while(var15.hasNext()) {mediaType = (MediaType)var15.next();if (mediaType.isConcrete()) {selectedMediaType = mediaType;break;}if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;break;}}if (this.logger.isDebugEnabled()) {this.logger.debug("Using '" + selectedMediaType + "', given " + acceptableTypes + " and supported " + producibleTypes);}}HttpMessageConverter converter;GenericHttpMessageConverter genericConverter;label183: {if (selectedMediaType != null) {selectedMediaType = selectedMediaType.removeQualityValue();Iterator var23 = this.messageConverters.iterator();while(var23.hasNext()) {converter = (HttpMessageConverter)var23.next();genericConverter = converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter)converter : null;if (genericConverter != null) {if (((GenericHttpMessageConverter)converter).canWrite((Type)targetType, valueType, selectedMediaType)) {break label183;}} else if (converter.canWrite(valueType, selectedMediaType)) {break label183;}}}if (body != null) {Set<MediaType> producibleMediaTypes = (Set)inputMessage.getServletRequest().getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);if (!isContentTypePreset && CollectionUtils.isEmpty(producibleMediaTypes)) {throw new HttpMediaTypeNotAcceptableException(this.getSupportedMediaTypes(body.getClass()));}throw new HttpMessageNotWritableException("No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");}return;}body = this.getAdvice().beforeBodyWrite(body, returnType, selectedMediaType, converter.getClass(), inputMessage, outputMessage);if (body != null) {LogFormatUtils.traceDebug(this.logger, (traceOn) -> {return "Writing [" + LogFormatUtils.formatValue(body, !traceOn) + "]";});this.addContentDispositionHeader(inputMessage, outputMessage);if (genericConverter != null) {genericConverter.write(body, (Type)targetType, selectedMediaType, outputMessage);} else {// 核心調用converter.write(body, selectedMediaType, outputMessage);}} else if (this.logger.isDebugEnabled()) {this.logger.debug("Nothing to write: null body");}}進一步梳理
上面代碼流程可能不是很好理解,這里放出時序圖來幫助概覽接口返回數據時進行convert的過程。
為了避免調用層級過深導致圖過于復雜,時序圖只繪制到了HttpMessageConverter接口層級,該接口具體調用哪個類我們來看看下面這張圖。
可以看到在AbstractGenericHttpMessageConverter抽象類的調用方法時,轉去調用了writeInternal方法。具體調用的實現是AbstractJackson2HttpMessageConverter的writerInternal方法,其具體實現如下
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {MediaType contentType = outputMessage.getHeaders().getContentType();JsonEncoding encoding = this.getJsonEncoding(contentType);Class<?> clazz = object instanceof MappingJacksonValue ? ((MappingJacksonValue)object).getValue().getClass() : object.getClass();ObjectMapper objectMapper = this.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);Throwable var10 = null;try {this.writePrefix(generator, object);Object value = object;Class<?> serializationView = null;FilterProvider filters = null;JavaType javaType = null;if (object instanceof MappingJacksonValue) {MappingJacksonValue container = (MappingJacksonValue)object;value = container.getValue();serializationView = container.getSerializationView();filters = container.getFilters();}if (type != null && TypeUtils.isAssignable(type, value.getClass())) {javaType = this.getJavaType(type, (Class)null);}ObjectWriter objectWriter = serializationView != null ? objectMapper.writerWithView(serializationView) : objectMapper.writer();if (filters != null) {objectWriter = objectWriter.with(filters);}if (javaType != null && javaType.isContainerType()) {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.writeValue(generator, value);this.writeSuffix(generator, object);generator.flush();} catch (Throwable var26) {var10 = var26;throw var26;} finally {if (generator != null) {if (var10 != null) {try {generator.close();} catch (Throwable var25) {var10.addSuppressed(var25);}} else {generator.close();}}}} catch (InvalidDefinitionException var28) {throw new HttpMessageConversionException("Type definition error: " + var28.getType(), var28);} catch (JsonProcessingException var29) {throw new HttpMessageNotWritableException("Could not write JSON: " + var29.getOriginalMessage(), var29);}}MappingJackson2HttpMessageConverter類直接調用父類的writeInternal方法,并未重寫,因此實際轉換邏輯即為上方代碼邏輯。
以上就是@ResponseBody的調用流程及實現原理,根據此調用流程,如果我們想自定義請求返回格式,利用HttpMessageConvert實現是一種可行方案。最后,本文如存在不正確的地方,歡迎并感謝批評指正!
總結
以上是生活随笔為你收集整理的SpringMVC —— @ResponseBody原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 四年级计算机考试反思,考试反思四年级作文
- 下一篇: 计算机房灭火器配备标准,消防灭火器配备标