Skip to content

Commit

Permalink
Allow ReflectionHints to register hints on interface hierarchies
Browse files Browse the repository at this point in the history
This commit promotes a previously private method in
`BeanRegistrationsAotContribution` to a top-level method in
`ReflectionHints`.

This helps to register hints on all interfaces implemented in the class
hierarchy of the given type.

Closes gh-32824
  • Loading branch information
bclozel committed May 14, 2024
1 parent a86612a commit f7a6a7b
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -33,7 +33,6 @@
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
Expand Down Expand Up @@ -115,23 +114,10 @@ private void generateRegisterHints(RuntimeHints runtimeHints, Map<BeanRegistrati
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,28 @@ public ReflectionHints registerTypes(Iterable<TypeReference> types, Consumer<Typ
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.Builder> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -209,6 +210,18 @@ void registerMethodTwiceUpdatesExistingEntry() {
});
}

@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<ExecutableHint> methodHint) {
assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(typeHint -> {
assertThat(typeHint.getType().getCanonicalName()).isEqualTo(TestType.class.getCanonicalName());
Expand Down Expand Up @@ -241,4 +254,28 @@ void setName(String name) {

}

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() {

}
}

}

0 comments on commit f7a6a7b

Please sign in to comment.