-
Notifications
You must be signed in to change notification settings - Fork 38.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This commit introduces a declarative way of registering reflection information for arbitrary types. Types can be specified as a class, a class name, or by annotating the type itself. This existing RegisterReflectionForBinding becomes a specialized version of the new annotation, registering the necessary hints for data binding. Closes gh-29194
- Loading branch information
Showing
8 changed files
with
460 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
100 changes: 100 additions & 0 deletions
100
spring-core/src/main/java/org/springframework/aot/hint/annotation/RegisterReflection.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
/* | ||
* 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. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.springframework.aot.hint.annotation; | ||
|
||
import java.lang.annotation.Documented; | ||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
import org.springframework.aot.hint.MemberCategory; | ||
|
||
/** | ||
* Register reflection hints against an arbitrary number of target classes. | ||
* | ||
* <p>When using this annotation directly, only the defined | ||
* {@linkplain #memberCategories() member categories} are registered for each | ||
* target class. The target classes can be specified by class or class names. | ||
* When both are specified, they are all considered. If no target class is | ||
* specified, the current class is used. | ||
* | ||
* <p>This annotation can be used as a meta-annotation to customize how hints | ||
* are registered against each target class. | ||
* | ||
* <p>The annotated element can be any bean: | ||
* <pre><code class="java"> | ||
* @Configuration | ||
* @RegisterReflection(classes = CustomerEntry.class, memberCategories = PUBLIC_FIELDS) | ||
* public class MyConfig { | ||
* // ... | ||
* }</code></pre> | ||
* | ||
* <p>To register reflection hints for the type itself, only member categories | ||
* should be specified:<pre><code class="java"> | ||
* @Component | ||
* @RegisterReflection(memberCategories = INVOKE_PUBLIC_METHODS) | ||
* public class MyComponent { | ||
* // ... | ||
* }</code></pre> | ||
* | ||
* <p>Reflection hints can be registered from a method. In this case, at least | ||
* one target class should be specified:<pre><code class="java"> | ||
* @Component | ||
* public class MyComponent { | ||
* | ||
* @RegisterReflection(classes = CustomerEntry.class, memberCategories = PUBLIC_FIELDS) | ||
* CustomerEntry process() { ... } | ||
* // ... | ||
* }</code></pre> | ||
* | ||
* <p>If the class is not available, {@link #classNames()} allows to specify the | ||
* fully qualified name, rather than the {@link Class} reference. | ||
* | ||
* @author Stephane Nicoll | ||
* @since 6.2 | ||
*/ | ||
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE }) | ||
@Retention(RetentionPolicy.RUNTIME) | ||
@Documented | ||
@Reflective(RegisterReflectionReflectiveProcessor.class) | ||
public @interface RegisterReflection { | ||
|
||
/** | ||
* Classes for which reflection hints should be registered. Consider using | ||
* {@link #classNames()} for classes that are not public in the current | ||
* scope. If both {@code classes} and {@code classNames} are specified, they | ||
* are merged in a single set. | ||
* <p> | ||
* By default, the annotated type is the target of the registration. When | ||
* placed on a method, at least one class must be specified. | ||
* @see #classNames() | ||
*/ | ||
Class<?>[] classes() default {}; | ||
|
||
/** | ||
* Alternative to {@link #classes()} to specify the classes as class names. | ||
* @see #classes() | ||
*/ | ||
String[] classNames() default {}; | ||
|
||
/** | ||
* Specify the {@linkplain MemberCategory member categories} to enable. | ||
*/ | ||
MemberCategory[] memberCategories() default {}; | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
99 changes: 99 additions & 0 deletions
99
...n/java/org/springframework/aot/hint/annotation/RegisterReflectionReflectiveProcessor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
/* | ||
* 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. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.springframework.aot.hint.annotation; | ||
|
||
import java.lang.reflect.AnnotatedElement; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.Objects; | ||
|
||
import org.apache.commons.logging.Log; | ||
import org.apache.commons.logging.LogFactory; | ||
|
||
import org.springframework.aot.hint.MemberCategory; | ||
import org.springframework.aot.hint.ReflectionHints; | ||
import org.springframework.core.annotation.AnnotatedElementUtils; | ||
import org.springframework.lang.Nullable; | ||
import org.springframework.util.Assert; | ||
import org.springframework.util.ClassUtils; | ||
|
||
/** | ||
* A {@link ReflectiveProcessor} implementation that pairs with | ||
* {@link RegisterReflection @RegisterReflection}. Can be used as a base | ||
* implementation for composed annotations that are meta-annotated with | ||
* {@link RegisterReflection}. | ||
* | ||
* @author Stephane Nicoll | ||
* @since 6.2 | ||
*/ | ||
public class RegisterReflectionReflectiveProcessor implements ReflectiveProcessor { | ||
|
||
private static final Log logger = LogFactory.getLog(RegisterReflectionReflectiveProcessor.class); | ||
|
||
@Override | ||
public final void registerReflectionHints(ReflectionHints hints, AnnotatedElement element) { | ||
RegisterReflection annotation = AnnotatedElementUtils.getMergedAnnotation( | ||
element, RegisterReflection.class); | ||
Assert.notNull(annotation, "Element must be annotated with @" + RegisterReflection.class.getSimpleName() | ||
+ ": " + element); | ||
ReflectionRegistration registration = parse(element, annotation); | ||
registerReflectionHints(hints, registration); | ||
} | ||
|
||
protected ReflectionRegistration parse(AnnotatedElement element, RegisterReflection annotation) { | ||
List<Class<?>> allClassNames = new ArrayList<>(); | ||
allClassNames.addAll(Arrays.asList(annotation.classes())); | ||
allClassNames.addAll(Arrays.stream(annotation.classNames()) | ||
.map(this::loadClass).filter(Objects::nonNull).toList()); | ||
if (allClassNames.isEmpty()) { | ||
if (element instanceof Class<?> clazz) { | ||
allClassNames.add(clazz); | ||
} | ||
else { | ||
throw new IllegalStateException("At least one class must be specified, " | ||
+ "could not detect target from '" + element + "'"); | ||
} | ||
} | ||
return new ReflectionRegistration(allClassNames.toArray(new Class<?>[0]), | ||
annotation.memberCategories()); | ||
} | ||
|
||
protected void registerReflectionHints(ReflectionHints hints, ReflectionRegistration registration) { | ||
for (Class<?> target : registration.classes) { | ||
registerReflectionHints(hints, target, registration.memberCategories); | ||
} | ||
} | ||
|
||
protected void registerReflectionHints(ReflectionHints hints, Class<?> target, MemberCategory[] memberCategories) { | ||
hints.registerType(target, type -> type.withMembers(memberCategories)); | ||
} | ||
|
||
@Nullable | ||
private Class<?> loadClass(String className) { | ||
try { | ||
return ClassUtils.forName(className, getClass().getClassLoader()); | ||
} | ||
catch (Exception ex) { | ||
logger.warn("Ignoring '" + className + "': " + ex.getMessage()); | ||
return null; | ||
} | ||
} | ||
|
||
protected record ReflectionRegistration(Class<?>[] classes, MemberCategory[] memberCategories) {} | ||
|
||
} |
Oops, something went wrong.