forked from spring-projects/spring-framework
-
Notifications
You must be signed in to change notification settings - Fork 2
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 spring-projectsgh-29194
- Loading branch information
Showing
9 changed files
with
494 additions
and
42 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
82 changes: 82 additions & 0 deletions
82
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,82 @@ | ||
/* | ||
* 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.Repeatable; | ||
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 type that is target for registration: | ||
* <pre class="code"> | ||
* @Configuration | ||
* @RegisterReflection(classes = CustomerEntry.class, memberCategories = PUBLIC_FIELDS) | ||
* public class MyConfig { | ||
* // ... | ||
* }</pre> | ||
* | ||
* TODO: complete | ||
* @author Stephane Nicoll | ||
* @since 6.2 | ||
*/ | ||
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE }) | ||
@Retention(RetentionPolicy.RUNTIME) | ||
@Documented | ||
@Repeatable(RegisterReflections.class) | ||
@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
102 changes: 102 additions & 0 deletions
102
...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,102 @@ | ||
/* | ||
* 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 java.util.Set; | ||
|
||
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) { | ||
Set<RegisterReflection> annotations = AnnotatedElementUtils.getMergedRepeatableAnnotations( | ||
element, RegisterReflection.class, RegisterReflections.class); | ||
Assert.notEmpty(annotations, "Element must be annotated with @" + RegisterReflection.class.getSimpleName() | ||
+ ": " + element); | ||
for (RegisterReflection annotation : annotations) { | ||
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) {} | ||
|
||
} |
38 changes: 38 additions & 0 deletions
38
spring-core/src/main/java/org/springframework/aot/hint/annotation/RegisterReflections.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,38 @@ | ||
/* | ||
* 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; | ||
|
||
/** | ||
* Container annotation that aggregates several {@link RegisterReflection} annotations. | ||
* | ||
* @author Stephane Nicoll | ||
* @since 6.2 | ||
*/ | ||
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE }) | ||
@Retention(RetentionPolicy.RUNTIME) | ||
@Documented | ||
public @interface RegisterReflections { | ||
|
||
RegisterReflection[] value(); | ||
|
||
} |
Oops, something went wrong.