From 4236321ef7bc9da05a2d30fc0669c1b7e8137247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=B7=E5=AD=90=20Yang?= Date: Wed, 7 Jan 2026 15:01:03 +0800 Subject: [PATCH 1/5] Fix an issue where ResolvableType.getGenerics typeProvider was missing, causing JacksonJsonHttpMessageConverter's contextClass to resolve to null MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 海子 Yang --- .../springframework/core/ResolvableType.java | 2 +- .../core/ResolvableTypeTests.java | 85 +++++++++++++++++++ .../HttpEntityMethodProcessorTests.java | 2 - 3 files changed, 86 insertions(+), 3 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/ResolvableType.java b/spring-core/src/main/java/org/springframework/core/ResolvableType.java index cc4566002c53..db93981183f4 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -802,7 +802,7 @@ else if (this.type instanceof ParameterizedType parameterizedType) { if (actualTypeArguments.length > 0) { generics = new ResolvableType[actualTypeArguments.length]; for (int i = 0; i < actualTypeArguments.length; i++) { - generics[i] = forType(actualTypeArguments[i], this.variableResolver); + generics[i] = forType(actualTypeArguments[i], this.typeProvider, this.variableResolver); } } else { diff --git a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java index 104e059ceecd..d9f7e384e004 100644 --- a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java +++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java @@ -50,6 +50,8 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.core.ResolvableType.VariableResolver; +import org.springframework.core.annotation.AnnotatedMethod; +import org.springframework.util.ClassUtils; import org.springframework.util.MultiValueMap; import static org.assertj.core.api.Assertions.assertThat; @@ -501,6 +503,18 @@ void nestedWithArray() throws Exception { assertThat(type.resolveGeneric()).isEqualTo(String.class); } + @Test + void getNested() throws Exception { + Method method = MySimpleParameterizedController.class.getMethod("handleDto", List.class); + HandlerMethod handlerMethod = new HandlerMethod(new MySimpleParameterizedController(), method); + MethodParameter methodParam = handlerMethod.getMethodParameters()[0]; + + ResolvableType resolvable = ResolvableType.forMethodParameter(methodParam).getNested(2); + + Class contextClass = methodParam.getContainingClass(); + assertThat(GenericTypeResolver.resolveType(resolvable.getType(), contextClass)).isEqualTo(SimpleBean.class); + } + @Test void getGeneric() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("stringList")); @@ -1981,4 +1995,75 @@ private String describe(ResolvableType type) { } } + + private static class HandlerMethod extends AnnotatedMethod { + + private final Class beanType; + + /** + * Create an instance from a bean instance and a method. + */ + public HandlerMethod(Object bean, Method method) { + super(method); + this.beanType = ClassUtils.getUserClass(bean); + } + + @Override + protected Class getContainingClass() { + return beanType; + } + + } + + + @SuppressWarnings("unused") + private abstract static class MyParameterizedController { + + public void handleDto(List dto) { + } + } + + + @SuppressWarnings("unused") + private static class MySimpleParameterizedController extends MyParameterizedController { + } + + + private interface Identifiable extends Serializable { + + Long getId(); + + void setId(Long id); + } + + + @SuppressWarnings({ "serial" }) + private static class SimpleBean implements Identifiable { + + private Long id; + + private String name; + + @Override + public Long getId() { + return id; + } + + @Override + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + @SuppressWarnings("unused") + public void setName(String name) { + this.name = name; + } + } + + + } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorTests.java index 24129f821c68..f8db0789b9f0 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorTests.java @@ -30,7 +30,6 @@ import jakarta.servlet.http.HttpServletResponse; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.core.MethodParameter; @@ -152,7 +151,6 @@ void resolveGenericArgument() throws Exception { } @Test - @Disabled("Determine why this fails with JacksonJsonHttpMessageConverter but passes with MappingJackson2HttpMessageConverter") void resolveArgumentTypeVariable() throws Exception { Method method = MySimpleParameterizedController.class.getMethod("handleDto", HttpEntity.class); HandlerMethod handlerMethod = new HandlerMethod(new MySimpleParameterizedController(), method); From 539ff0ca05fb993c99dbc90baaef8a9a2fbc8cc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Wed, 7 Jan 2026 09:51:04 +0100 Subject: [PATCH 2/5] Upgrade Antora dependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes gh-36105 Signed-off-by: 海子 Yang --- framework-docs/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/framework-docs/package.json b/framework-docs/package.json index 455c96232d9c..63574c16a9d0 100644 --- a/framework-docs/package.json +++ b/framework-docs/package.json @@ -1,11 +1,11 @@ { "dependencies": { - "antora": "3.2.0-alpha.4", - "@antora/atlas-extension": "1.0.0-alpha.2", - "@antora/collector-extension": "1.0.0-alpha.3", + "antora": "3.2.0-alpha.11", + "@antora/atlas-extension": "1.0.0-alpha.5", + "@antora/collector-extension": "1.0.2", "@asciidoctor/tabs": "1.0.0-beta.6", "@springio/antora-extensions": "1.14.7", "fast-xml-parser": "4.5.2", - "@springio/asciidoctor-extensions": "1.0.0-alpha.10" + "@springio/asciidoctor-extensions": "1.0.0-alpha.17" } } From f9d7937cb70ab5a066faaf04b66430136f6c27ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Wed, 7 Jan 2026 09:52:33 +0100 Subject: [PATCH 3/5] Fix locale interceptor code snippet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See gh-36105 See gh-36099 Signed-off-by: 海子 Yang --- .../ROOT/pages/web/webmvc/mvc-servlet/localeresolver.adoc | 2 +- .../mvcservlet/mvclocaleresolverinterceptor/WebConfiguration.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/localeresolver.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/localeresolver.adoc index fca88d447790..67ebebd5515c 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/localeresolver.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/localeresolver.adoc @@ -83,4 +83,4 @@ that contain a parameter named `siteLanguage` now changes the locale. So, for ex a request for the URL `https://domain.com/home.view?siteLanguage=nl` changes the site language to Dutch. The following example shows how to intercept the locale: -include-code::./WebConfiguration[tag=snippet,indent=0] +include-code::./WebConfiguration[tag=snippet,indent=0,chomp=-tags] diff --git a/framework-docs/src/main/kotlin/org/springframework/docs/web/webmvc/mvcservlet/mvclocaleresolverinterceptor/WebConfiguration.kt b/framework-docs/src/main/kotlin/org/springframework/docs/web/webmvc/mvcservlet/mvclocaleresolverinterceptor/WebConfiguration.kt index b3f7806e3ce4..f42488c03101 100644 --- a/framework-docs/src/main/kotlin/org/springframework/docs/web/webmvc/mvcservlet/mvclocaleresolverinterceptor/WebConfiguration.kt +++ b/framework-docs/src/main/kotlin/org/springframework/docs/web/webmvc/mvcservlet/mvclocaleresolverinterceptor/WebConfiguration.kt @@ -37,7 +37,7 @@ class WebConfiguration { setInterceptors(LocaleChangeInterceptor().apply { paramName = "siteLanguage" }) - /* @chomp:line urlMap = mapOf("/**/*.view" to "someController") */urlMap = mapOf("/**/*.view" to "someController") + urlMap = mapOf("/**/*.view" to "someController") } } // end::snippet[] From 38a5b3cb426070183e0c94c50d8ce4ba3a0131e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=B7=E5=AD=90=20Yang?= Date: Wed, 7 Jan 2026 17:43:28 +0800 Subject: [PATCH 4/5] Revert "Fix an issue where ResolvableType.getGenerics typeProvider was missing, causing JacksonJsonHttpMessageConverter's contextClass to resolve to null" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 2c5ba248117d038749be22d8fe34ccd3c5aace4d. Signed-off-by: 海子 Yang --- .../springframework/core/ResolvableType.java | 2 +- .../core/ResolvableTypeTests.java | 85 ------------------- .../HttpEntityMethodProcessorTests.java | 2 + 3 files changed, 3 insertions(+), 86 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/ResolvableType.java b/spring-core/src/main/java/org/springframework/core/ResolvableType.java index db93981183f4..cc4566002c53 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -802,7 +802,7 @@ else if (this.type instanceof ParameterizedType parameterizedType) { if (actualTypeArguments.length > 0) { generics = new ResolvableType[actualTypeArguments.length]; for (int i = 0; i < actualTypeArguments.length; i++) { - generics[i] = forType(actualTypeArguments[i], this.typeProvider, this.variableResolver); + generics[i] = forType(actualTypeArguments[i], this.variableResolver); } } else { diff --git a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java index d9f7e384e004..104e059ceecd 100644 --- a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java +++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java @@ -50,8 +50,6 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.core.ResolvableType.VariableResolver; -import org.springframework.core.annotation.AnnotatedMethod; -import org.springframework.util.ClassUtils; import org.springframework.util.MultiValueMap; import static org.assertj.core.api.Assertions.assertThat; @@ -503,18 +501,6 @@ void nestedWithArray() throws Exception { assertThat(type.resolveGeneric()).isEqualTo(String.class); } - @Test - void getNested() throws Exception { - Method method = MySimpleParameterizedController.class.getMethod("handleDto", List.class); - HandlerMethod handlerMethod = new HandlerMethod(new MySimpleParameterizedController(), method); - MethodParameter methodParam = handlerMethod.getMethodParameters()[0]; - - ResolvableType resolvable = ResolvableType.forMethodParameter(methodParam).getNested(2); - - Class contextClass = methodParam.getContainingClass(); - assertThat(GenericTypeResolver.resolveType(resolvable.getType(), contextClass)).isEqualTo(SimpleBean.class); - } - @Test void getGeneric() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("stringList")); @@ -1995,75 +1981,4 @@ private String describe(ResolvableType type) { } } - - private static class HandlerMethod extends AnnotatedMethod { - - private final Class beanType; - - /** - * Create an instance from a bean instance and a method. - */ - public HandlerMethod(Object bean, Method method) { - super(method); - this.beanType = ClassUtils.getUserClass(bean); - } - - @Override - protected Class getContainingClass() { - return beanType; - } - - } - - - @SuppressWarnings("unused") - private abstract static class MyParameterizedController { - - public void handleDto(List dto) { - } - } - - - @SuppressWarnings("unused") - private static class MySimpleParameterizedController extends MyParameterizedController { - } - - - private interface Identifiable extends Serializable { - - Long getId(); - - void setId(Long id); - } - - - @SuppressWarnings({ "serial" }) - private static class SimpleBean implements Identifiable { - - private Long id; - - private String name; - - @Override - public Long getId() { - return id; - } - - @Override - public void setId(Long id) { - this.id = id; - } - - public String getName() { - return name; - } - - @SuppressWarnings("unused") - public void setName(String name) { - this.name = name; - } - } - - - } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorTests.java index f8db0789b9f0..24129f821c68 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorTests.java @@ -30,6 +30,7 @@ import jakarta.servlet.http.HttpServletResponse; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.core.MethodParameter; @@ -151,6 +152,7 @@ void resolveGenericArgument() throws Exception { } @Test + @Disabled("Determine why this fails with JacksonJsonHttpMessageConverter but passes with MappingJackson2HttpMessageConverter") void resolveArgumentTypeVariable() throws Exception { Method method = MySimpleParameterizedController.class.getMethod("handleDto", HttpEntity.class); HandlerMethod handlerMethod = new HandlerMethod(new MySimpleParameterizedController(), method); From 4f603c8429e82ffa224037aa1dc3c9f120c9a0a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=B7=E5=AD=90=20Yang?= Date: Wed, 7 Jan 2026 18:02:29 +0800 Subject: [PATCH 5/5] ResolvableType.forType(Type, ResolvableType) records the typeProvider of the owner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a change to a core library, and it's a very cautious commit. Therefore, this commit reverses the previous getGenerics modification, as getGenerics has a significant impact scope. Signed-off-by: 海子 Yang --- .../main/java/org/springframework/core/ResolvableType.java | 7 ++----- .../java/org/springframework/core/ResolvableTypeTests.java | 1 + .../AbstractMessageConverterMethodArgumentResolver.java | 4 ++-- .../method/annotation/HttpEntityMethodProcessorTests.java | 2 -- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/ResolvableType.java b/spring-core/src/main/java/org/springframework/core/ResolvableType.java index cc4566002c53..04c628eef906 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -1479,11 +1479,8 @@ public static ResolvableType forType(@Nullable Type type) { * @see #forType(Type) */ public static ResolvableType forType(@Nullable Type type, @Nullable ResolvableType owner) { - VariableResolver variableResolver = null; - if (owner != null) { - variableResolver = owner.asVariableResolver(); - } - return forType(type, variableResolver); + return (owner == null ? forType(type, null, null) : + forType(type, owner.typeProvider, owner.asVariableResolver())); } /** diff --git a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java index 104e059ceecd..8a688fd3e17b 100644 --- a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java +++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java @@ -1346,6 +1346,7 @@ void narrow() throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField("stringList")); ResolvableType narrow = ResolvableType.forType(ArrayList.class, type); assertThat(narrow.getGeneric().resolve()).isEqualTo(String.class); + assertThat(type.getSource()).isSameAs(narrow.getSource()); } @Test diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java index 68c129a5dbde..d684c1a6d557 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java @@ -252,10 +252,10 @@ else if (targetClass != null && converter.canRead(targetClass, contentType)) { protected ResolvableType getNestedTypeIfNeeded(ResolvableType type) { ResolvableType genericType = type; if (Optional.class.isAssignableFrom(genericType.toClass())) { - genericType = genericType.getNested(2); + genericType = ResolvableType.forType(genericType.getNested(2).getType(), type); } if (HttpEntity.class.isAssignableFrom(genericType.toClass())) { - genericType = genericType.getNested(2); + genericType = ResolvableType.forType(genericType.getNested(2).getType(), type); } return genericType; } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorTests.java index 24129f821c68..f8db0789b9f0 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorTests.java @@ -30,7 +30,6 @@ import jakarta.servlet.http.HttpServletResponse; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.core.MethodParameter; @@ -152,7 +151,6 @@ void resolveGenericArgument() throws Exception { } @Test - @Disabled("Determine why this fails with JacksonJsonHttpMessageConverter but passes with MappingJackson2HttpMessageConverter") void resolveArgumentTypeVariable() throws Exception { Method method = MySimpleParameterizedController.class.getMethod("handleDto", HttpEntity.class); HandlerMethod handlerMethod = new HandlerMethod(new MySimpleParameterizedController(), method);