diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContribution.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContribution.java index 5960d80952d..4c91aa49442 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContribution.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContribution.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -33,7 +33,6 @@ import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.javapoet.ClassName; import org.springframework.javapoet.CodeBlock; import org.springframework.javapoet.MethodSpec; -import org.springframework.util.ClassUtils; /** * AOT contribution from a {@link BeanRegistrationsAotProcessor} used to @@ -115,23 +114,10 @@ class BeanRegistrationsAotContribution ReflectionHints hints = runtimeHints.reflection(); Class beanClass = beanRegistrationKey.beanClass(); hints.registerType(beanClass, MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INTROSPECT_DECLARED_METHODS); - introspectPublicMethodsOnAllInterfaces(hints, beanClass); + hints.registerForInterfaces(beanClass, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS)); }); } - private void introspectPublicMethodsOnAllInterfaces(ReflectionHints hints, Class type) { - Class currentClass = type; - while (currentClass != null && currentClass != Object.class) { - for (Class interfaceType : currentClass.getInterfaces()) { - if (!ClassUtils.isJavaLanguageInterface(interfaceType)) { - hints.registerType(interfaceType, MemberCategory.INTROSPECT_PUBLIC_METHODS); - introspectPublicMethodsOnAllInterfaces(hints, interfaceType); - } - } - currentClass = currentClass.getSuperclass(); - } - } - /** * Gather the necessary information to register a particular bean. * @param methodGenerator the {@link BeanDefinitionMethodGenerator} to use diff --git a/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java b/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java index c819ae35519..80159d6a8b4 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java @@ -175,6 +175,28 @@ public class ReflectionHints { return this; } + /** + * Register or customize reflection hints for all the interfaces implemented by + * the given type and its parent classes, ignoring the common Java language interfaces. + * The specified {@code typeHint} consumer is invoked for each type. + * @param type the type to consider + * @param typeHint a builder to further customize hints for each type + * @return {@code this}, to facilitate method chaining + */ + public ReflectionHints registerForInterfaces(Class type, Consumer typeHint) { + Class currentClass = type; + while (currentClass != null && currentClass != Object.class) { + for (Class interfaceType : currentClass.getInterfaces()) { + if (!ClassUtils.isJavaLanguageInterface(interfaceType)) { + this.registerType(interfaceType, typeHint); + registerForInterfaces(interfaceType, typeHint); + } + } + currentClass = currentClass.getSuperclass(); + } + return this; + } + /** * Register the need for reflection on the specified {@link Field}. * @param field the field that requires reflection diff --git a/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java b/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java index 2c3351c752c..5e4c3cc14f8 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java @@ -16,6 +16,7 @@ package org.springframework.aot.hint; +import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -209,6 +210,18 @@ class ReflectionHintsTests { }); } + @Test + void registerOnInterfaces() { + this.reflectionHints.registerForInterfaces(ChildType.class, + typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS)); + assertThat(this.reflectionHints.typeHints()).hasSize(2) + .noneMatch(typeHint -> typeHint.getType().getCanonicalName().equals(Serializable.class.getCanonicalName())) + .anyMatch(typeHint -> typeHint.getType().getCanonicalName().equals(SecondInterface.class.getCanonicalName()) + && typeHint.getMemberCategories().contains(MemberCategory.INTROSPECT_PUBLIC_METHODS)) + .anyMatch(typeHint -> typeHint.getType().getCanonicalName().equals(FirstInterface.class.getCanonicalName()) + && typeHint.getMemberCategories().contains(MemberCategory.INTROSPECT_PUBLIC_METHODS)); + } + private void assertTestTypeMethodHints(Consumer methodHint) { assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(typeHint -> { assertThat(typeHint.getType().getCanonicalName()).isEqualTo(TestType.class.getCanonicalName()); @@ -241,4 +254,28 @@ class ReflectionHintsTests { } + interface FirstInterface { + void first(); + } + + interface SecondInterface { + void second(); + } + + @SuppressWarnings("serial") + static class ParentType implements Serializable, FirstInterface { + @Override + public void first() { + + } + } + + @SuppressWarnings("serial") + static class ChildType extends ParentType implements SecondInterface { + @Override + public void second() { + + } + } + }