From 992e75303d598a4f3ef7ab912de588f9748961ac Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 23 Jan 2020 12:31:00 +0000 Subject: [PATCH] Improve support for generics in Jackson codecs Closes gh-23791 --- .../codec/json/AbstractJackson2Decoder.java | 14 ++++++++--- .../http/codec/json/Jackson2CodecSupport.java | 25 +++++++++++++++++-- ...pingMessageConversionIntegrationTests.java | 4 +-- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java b/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java index 8a38d77d4b..74cdbe42f5 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -153,8 +153,10 @@ public abstract class AbstractJackson2Decoder extends Jackson2CodecSupport imple private ObjectReader getObjectReader(ResolvableType elementType, @Nullable Map hints) { Assert.notNull(elementType, "'elementType' must not be null"); - MethodParameter param = getParameter(elementType); - Class contextClass = (param != null ? param.getContainingClass() : null); + Class contextClass = getContextClass(elementType); + if (contextClass == null && hints != null) { + contextClass = getContextClass((ResolvableType) hints.get(ACTUAL_TYPE_HINT)); + } JavaType javaType = getJavaType(elementType.getType(), contextClass); Class jsonView = (hints != null ? (Class) hints.get(Jackson2CodecSupport.JSON_VIEW_HINT) : null); return jsonView != null ? @@ -162,6 +164,12 @@ public abstract class AbstractJackson2Decoder extends Jackson2CodecSupport imple getObjectMapper().readerFor(javaType); } + @Nullable + private Class getContextClass(@Nullable ResolvableType elementType) { + MethodParameter param = (elementType != null ? getParameter(elementType) : null); + return (param != null ? param.getContainingClass() : null); + } + private void logValue(@Nullable Object value, @Nullable Map hints) { if (!Hints.isLoggingSuppressed(hints)) { LogFormatUtils.traceDebug(logger, traceOn -> { diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2CodecSupport.java b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2CodecSupport.java index 43eaaaa199..6162be280c 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2CodecSupport.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2CodecSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -20,6 +20,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -34,6 +35,8 @@ import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; import org.springframework.core.codec.Hints; import org.springframework.http.HttpLogging; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.MimeType; @@ -55,6 +58,15 @@ public abstract class Jackson2CodecSupport { */ public static final String JSON_VIEW_HINT = Jackson2CodecSupport.class.getName() + ".jsonView"; + /** + * The key for the hint to access the actual ResolvableType passed into + * {@link org.springframework.http.codec.HttpMessageReader#read(ResolvableType, ResolvableType, ServerHttpRequest, ServerHttpResponse, Map)} + * (server-side only). Currently set when the method argument has generics because + * in case of reactive types, use of {@code ResolvableType.getGeneric()} means no + * MethodParameter source and no knowledge of the containing class. + */ + static final String ACTUAL_TYPE_HINT = Jackson2CodecSupport.class.getName() + ".actualType"; + private static final String JSON_VIEW_HINT_ERROR = "@JsonView only supported for write hints with exactly 1 class argument: "; @@ -106,11 +118,20 @@ public abstract class Jackson2CodecSupport { protected Map getHints(ResolvableType resolvableType) { MethodParameter param = getParameter(resolvableType); if (param != null) { + Map hints = null; + if (resolvableType.hasGenerics()) { + hints = new HashMap<>(2); + hints.put(ACTUAL_TYPE_HINT, resolvableType); + } JsonView annotation = getAnnotation(param, JsonView.class); if (annotation != null) { Class[] classes = annotation.value(); Assert.isTrue(classes.length == 1, JSON_VIEW_HINT_ERROR + param); - return Hints.from(JSON_VIEW_HINT, classes[0]); + hints = (hints != null ? hints : new HashMap<>(1)); + hints.put(JSON_VIEW_HINT, classes[0]); + } + if (hints != null) { + return hints; } } return Hints.none(); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingMessageConversionIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingMessageConversionIntegrationTests.java index 266952dbc6..d967b9560a 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingMessageConversionIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingMessageConversionIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -28,7 +28,6 @@ import javax.xml.bind.annotation.XmlRootElement; import io.reactivex.Flowable; import io.reactivex.Maybe; -import org.junit.jupiter.api.Disabled; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -522,7 +521,6 @@ public class RequestMappingMessageConversionIntegrationTests extends AbstractReq assertThat(getApplicationContext().getBean(PersonCreateController.class).persons.size()).isEqualTo(2); } - @Disabled @ParameterizedHttpServerTest // gh-23791 public void personCreateViaDefaultMethodWithGenerics(HttpServer httpServer) throws Exception { startServer(httpServer);