From b915e42c387195ed956fd908602094139148aac3 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 16 Jul 2018 19:45:35 +0200 Subject: [PATCH] Resolve target type for GenericHttpMessageConverter.canWrite/write Issue: SPR-16877 --- ...stractMessageConverterMethodProcessor.java | 41 ++++++++++--------- ...questResponseBodyMethodProcessorTests.java | 26 +++++++----- 2 files changed, 37 insertions(+), 30 deletions(-) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java index 8fff526636..e1a50eed7f 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java @@ -28,6 +28,7 @@ import java.util.Set; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.springframework.core.GenericTypeResolver; import org.springframework.core.MethodParameter; import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.ResolvableType; @@ -59,12 +60,13 @@ import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.util.UrlPathHelper; /** - * Extends {@link AbstractMessageConverterMethodArgumentResolver} with the ability to handle - * method return values by writing to the response with {@link HttpMessageConverter HttpMessageConverters}. + * Extends {@link AbstractMessageConverterMethodArgumentResolver} with the ability to handle method + * return values by writing to the response with {@link HttpMessageConverter HttpMessageConverters}. * * @author Arjen Poutsma * @author Rossen Stoyanchev * @author Brian Clozel + * @author Juergen Hoeller * @since 3.1 */ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver @@ -180,30 +182,30 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe Object body; Class valueType; - Type declaredType; + Type targetType; if (value instanceof CharSequence) { body = value.toString(); valueType = String.class; - declaredType = String.class; + targetType = String.class; } else { body = value; valueType = getReturnValueType(body, returnType); - declaredType = getGenericType(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) { + if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null && + outputMessage.getServletResponse().getStatus() == 200) { Resource resource = (Resource) value; try { List httpRanges = inputMessage.getHeaders().getRange(); outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value()); body = HttpRange.toResourceRegions(httpRanges, resource); valueType = body.getClass(); - declaredType = RESOURCE_REGION_LIST_TYPE; + targetType = RESOURCE_REGION_LIST_TYPE; } catch (IllegalArgumentException ex) { outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength()); @@ -225,7 +227,7 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe else { HttpServletRequest request = inputMessage.getServletRequest(); List requestedMediaTypes = getAcceptableMediaTypes(request); - List producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType); + List producibleMediaTypes = getProducibleMediaTypes(request, valueType, targetType); if (body != null && producibleMediaTypes.isEmpty()) { throw new HttpMessageNotWritableException( @@ -270,22 +272,22 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe if (selectedMediaType != null) { selectedMediaType = selectedMediaType.removeQualityValue(); for (HttpMessageConverter converter : this.messageConverters) { - GenericHttpMessageConverter genericConverter = - (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter) converter : null); + GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ? + (GenericHttpMessageConverter) converter : null); if (genericConverter != null ? - ((GenericHttpMessageConverter) converter).canWrite(declaredType, valueType, selectedMediaType) : + ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) : converter.canWrite(valueType, selectedMediaType)) { body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType, (Class>) converter.getClass(), inputMessage, outputMessage); if (body != null) { if (logger.isDebugEnabled()) { - Object formatted = body instanceof CharSequence ? "\"" + body + "\"" : body; + Object formatted = (body instanceof CharSequence ? "\"" + body + "\"" : body); logger.debug("Writing [" + formatted + "]"); } addContentDispositionHeader(inputMessage, outputMessage); if (genericConverter != null) { - genericConverter.write(body, declaredType, selectedMediaType, outputMessage); + genericConverter.write(body, targetType, selectedMediaType, outputMessage); } else { ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage); @@ -356,18 +358,19 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe * @since 4.2 */ @SuppressWarnings("unchecked") - protected List getProducibleMediaTypes(HttpServletRequest request, Class valueClass, - @Nullable Type declaredType) { + protected List getProducibleMediaTypes( + HttpServletRequest request, Class valueClass, @Nullable Type targetType) { - Set mediaTypes = (Set) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); + Set mediaTypes = + (Set) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); if (!CollectionUtils.isEmpty(mediaTypes)) { return new ArrayList<>(mediaTypes); } else if (!this.allSupportedMediaTypes.isEmpty()) { List result = new ArrayList<>(); for (HttpMessageConverter converter : this.messageConverters) { - if (converter instanceof GenericHttpMessageConverter && declaredType != null) { - if (((GenericHttpMessageConverter) converter).canWrite(declaredType, valueClass, null)) { + if (converter instanceof GenericHttpMessageConverter && targetType != null) { + if (((GenericHttpMessageConverter) converter).canWrite(targetType, valueClass, null)) { result.addAll(converter.getSupportedMediaTypes()); } } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java index ec2fe4fc08..1de18f5f7d 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -932,7 +932,20 @@ public class RequestResponseBodyMethodProcessorTests { } - private static class JacksonController { + private static class BaseController { + + @RequestMapping + @ResponseBody + public List handleTypeInfoList() { + List list = new ArrayList<>(); + list.add((T) new Foo("foo")); + list.add((T) new Bar("bar")); + return list; + } + } + + + private static class JacksonController extends BaseController { @RequestMapping @ResponseBody @@ -969,15 +982,6 @@ public class RequestResponseBodyMethodProcessorTests { return entity.getBody(); } - @RequestMapping - @ResponseBody - public List handleTypeInfoList() { - List list = new ArrayList<>(); - list.add(new Foo("foo")); - list.add(new Bar("bar")); - return list; - } - @RequestMapping @ResponseBody public Identifiable handleSubType() {