From ef671b4491f1622ead1654a1a37c31cfee580955 Mon Sep 17 00:00:00 2001 From: Yanming Zhou Date: Tue, 30 Jun 2026 09:11:24 +0800 Subject: [PATCH] Introduce `ResolvableType::resolveRequiredGeneric` to return casted non-null value Use case: ```java abstract class AbstractRepository { final Class entityClass; AbstractRepository() { entityClass = ResolvableType.forClass(getClass()).as(AbstractRepository.class).resolveRequiredGeneric(); } } ``` Signed-off-by: Yanming Zhou --- .../springframework/core/ResolvableType.java | 18 ++++++++++++++++++ .../core/ResolvableTypeTests.java | 17 +++++++++++++++++ 2 files changed, 35 insertions(+) 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 3d69e9fd92c2..999fc480cc0b 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -866,6 +866,24 @@ public Class[] resolveGenerics(Class fallback) { return getGeneric(indexes).resolve(); } + /** + * Convenience method that will {@link #getGeneric(int...) get} and + * {@link #resolve() resolve} a specific generic parameter. + * @param indexes the indexes that refer to the generic parameter + * (can be omitted to return the first generic) + * @return a resolved {@link Class} + * @throws IllegalStateException if no generic was available + * @see #resolveGeneric(int...) + * @see #getGeneric(int...) + * @see #resolve() + */ + @SuppressWarnings("unchecked") + public Class resolveRequiredGeneric(int... indexes) { + Class generic = getGeneric(indexes).resolve(); + Assert.state(generic != null, "The generic must not be null"); + return (Class) generic; + } + /** * Resolve this type to a {@link java.lang.Class}, returning {@code null} * if the type cannot be resolved. This method will consider bounds of 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 f6e66a2c0b57..fc0aa63ac80c 100644 --- a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java +++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java @@ -55,6 +55,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.atLeastOnce; @@ -1601,6 +1602,14 @@ void gh34541() throws Exception { assertThat(typeWithGenerics.isAssignableFrom(PaymentCreator.class)).isTrue(); } + @Test + void resolveRequiredGeneric() { + assertThat(new AbstractRepository() {}.entityClass).isEqualTo(String.class); + assertThat(new AbstractRepository() {}.entityClass).isEqualTo(Long.class); + assertThat(new AbstractRepository<>() {}.entityClass).isEqualTo(Object.class); + assertThatIllegalStateException().isThrownBy(() -> new AbstractRepository() {}); + } + private ResolvableType testSerialization(ResolvableType type) throws Exception { ByteArrayOutputStream bos = new ByteArrayOutputStream(); @@ -2013,6 +2022,14 @@ static class PaymentCreatorParameter { abstract static class Payment { } + abstract class AbstractRepository { + + final Class entityClass; + + AbstractRepository() { + entityClass = ResolvableType.forClass(getClass()).as(AbstractRepository.class).resolveRequiredGeneric(); + } + } private static class ResolvableTypeAssert extends AbstractAssert{