Skip to content

Commit 86c0129

Browse files
committed
Fix context class resolution for nested types
This commit introduces ContextClassRequestBodyAdvice which adds a "contextClass" hint allowing to resolve generics for Optional, HttpEntity or ServerSentEvent container types. Closes spring-projectsgh-36111
1 parent aaff59a commit 86c0129

5 files changed

Lines changed: 71 additions & 8 deletions

File tree

spring-web/src/main/java/org/springframework/http/converter/AbstractJacksonHttpMessageConverter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,8 @@ public boolean canWrite(ResolvableType type, Class<?> valueClass, @Nullable Medi
313313
public Object read(ResolvableType type, HttpInputMessage inputMessage, @Nullable Map<String, Object> hints)
314314
throws IOException, HttpMessageNotReadableException {
315315

316-
Class<?> contextClass = (type.getSource() instanceof MethodParameter parameter ? parameter.getContainingClass() : null);
316+
Class<?> contextClass = (type.getSource() instanceof MethodParameter parameter ? parameter.getContainingClass() :
317+
(hints != null ? (Class<?>) hints.get("contextClass") : null));
317318
JavaType javaType = getJavaType(type.getType(), contextClass);
318319
return readJavaType(javaType, inputMessage, hints);
319320
}

spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
8484
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
8585
import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
86+
import org.springframework.web.servlet.mvc.method.annotation.ContextClassRequestBodyAdvice;
8687
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
8788
import org.springframework.web.servlet.mvc.method.annotation.JsonViewRequestBodyAdvice;
8889
import org.springframework.web.servlet.mvc.method.annotation.JsonViewResponseBodyAdvice;
@@ -661,6 +662,7 @@ public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
661662
List<ResponseBodyAdvice<?>> responseBodyAdvices = new ArrayList<>(2);
662663
if (JACKSON_PRESENT || JACKSON_2_PRESENT) {
663664
requestBodyAdvices.add(new JsonViewRequestBodyAdvice());
665+
requestBodyAdvices.add(new ContextClassRequestBodyAdvice());
664666
responseBodyAdvices.add(new JsonViewResponseBodyAdvice());
665667
}
666668
if (KOTLIN_REFLECT_PRESENT && KOTLIN_SERIALIZATION_PRESENT) {
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2002-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.servlet.mvc.method.annotation;
18+
19+
import java.lang.reflect.Type;
20+
import java.util.Collections;
21+
import java.util.Map;
22+
import java.util.Optional;
23+
24+
import org.jspecify.annotations.Nullable;
25+
26+
import org.springframework.core.MethodParameter;
27+
import org.springframework.http.HttpEntity;
28+
import org.springframework.http.codec.ServerSentEvent;
29+
import org.springframework.http.converter.AbstractJacksonHttpMessageConverter;
30+
import org.springframework.http.converter.HttpMessageConverter;
31+
import org.springframework.http.converter.SmartHttpMessageConverter;
32+
33+
/**
34+
* A {@link RequestBodyAdvice} implementation that adds a {@code "contextClass"} hint for {@link Optional},
35+
* {@link HttpEntity} and {@link ServerSentEvent} container types.
36+
*
37+
* @author Sebastien Deleuze
38+
* @since 7.0.3
39+
*/
40+
public class ContextClassRequestBodyAdvice extends RequestBodyAdviceAdapter {
41+
42+
@Override
43+
public boolean supports(MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
44+
if (!AbstractJacksonHttpMessageConverter.class.isAssignableFrom(converterType)) {
45+
return false;
46+
}
47+
Class<?> parameterType = parameter.getParameterType();
48+
return Optional.class.isAssignableFrom(parameterType) || HttpEntity.class.isAssignableFrom(parameterType) ||
49+
ServerSentEvent.class.isAssignableFrom(parameterType);
50+
}
51+
52+
@Override
53+
public @Nullable Map<String, Object> determineReadHints(MethodParameter parameter, Type targetType,
54+
Class<? extends SmartHttpMessageConverter<?>> converterType) {
55+
56+
return Collections.singletonMap("contextClass", parameter.getContainingClass());
57+
}
58+
}

spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
import org.springframework.web.servlet.handler.HandlerExceptionResolverComposite;
8080
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
8181
import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
82+
import org.springframework.web.servlet.mvc.method.annotation.ContextClassRequestBodyAdvice;
8283
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
8384
import org.springframework.web.servlet.mvc.method.annotation.JsonViewRequestBodyAdvice;
8485
import org.springframework.web.servlet.mvc.method.annotation.JsonViewResponseBodyAdvice;
@@ -208,11 +209,12 @@ void requestMappingHandlerAdapter() {
208209
DirectFieldAccessor fieldAccessor = new DirectFieldAccessor(adapter);
209210
@SuppressWarnings("unchecked")
210211
List<Object> bodyAdvice = (List<Object>) fieldAccessor.getPropertyValue("requestResponseBodyAdvice");
211-
assertThat(bodyAdvice).hasSize(4);
212+
assertThat(bodyAdvice).hasSize(5);
212213
assertThat(bodyAdvice.get(0).getClass()).isEqualTo(JsonViewRequestBodyAdvice.class);
213-
assertThat(bodyAdvice.get(1).getClass()).isEqualTo(KotlinRequestBodyAdvice.class);
214-
assertThat(bodyAdvice.get(2).getClass()).isEqualTo(JsonViewResponseBodyAdvice.class);
215-
assertThat(bodyAdvice.get(3).getClass()).isEqualTo(KotlinResponseBodyAdvice.class);
214+
assertThat(bodyAdvice.get(1).getClass()).isEqualTo(ContextClassRequestBodyAdvice.class);
215+
assertThat(bodyAdvice.get(2).getClass()).isEqualTo(KotlinRequestBodyAdvice.class);
216+
assertThat(bodyAdvice.get(3).getClass()).isEqualTo(JsonViewResponseBodyAdvice.class);
217+
assertThat(bodyAdvice.get(4).getClass()).isEqualTo(KotlinResponseBodyAdvice.class);
216218
}
217219

218220
@Test

spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import jakarta.servlet.http.HttpServletResponse;
3131
import org.jspecify.annotations.Nullable;
3232
import org.junit.jupiter.api.BeforeEach;
33-
import org.junit.jupiter.api.Disabled;
3433
import org.junit.jupiter.api.Test;
3534

3635
import org.springframework.core.MethodParameter;
@@ -152,7 +151,6 @@ void resolveGenericArgument() throws Exception {
152151
}
153152

154153
@Test
155-
@Disabled("Determine why this fails with JacksonJsonHttpMessageConverter but passes with MappingJackson2HttpMessageConverter")
156154
void resolveArgumentTypeVariable() throws Exception {
157155
Method method = MySimpleParameterizedController.class.getMethod("handleDto", HttpEntity.class);
158156
HandlerMethod handlerMethod = new HandlerMethod(new MySimpleParameterizedController(), method);
@@ -164,7 +162,9 @@ void resolveArgumentTypeVariable() throws Exception {
164162

165163
List<HttpMessageConverter<?>> converters = new ArrayList<>();
166164
converters.add(new JacksonJsonHttpMessageConverter());
167-
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(converters);
165+
List<Object> requestResponseBodyAdvice = new ArrayList<>();
166+
requestResponseBodyAdvice.add(new ContextClassRequestBodyAdvice());
167+
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(converters, requestResponseBodyAdvice);
168168

169169
@SuppressWarnings("unchecked")
170170
HttpEntity<SimpleBean> result = (HttpEntity<SimpleBean>)

0 commit comments

Comments
 (0)