Skip to content

Commit f4607da

Browse files
committed
Introduce RegisterReflection
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
1 parent b0807d8 commit f4607da

8 files changed

+460
-44
lines changed

Diff for: spring-core/src/main/java/org/springframework/aot/hint/annotation/Reflective.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@
3535
* @author Stephane Nicoll
3636
* @author Sam Brannen
3737
* @since 6.0
38-
* @see SimpleReflectiveProcessor
3938
* @see ReflectiveRuntimeHintsRegistrar
39+
* @see RegisterReflection @RegisterReflection
4040
* @see RegisterReflectionForBinding @RegisterReflectionForBinding
4141
*/
4242
@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.aot.hint.annotation;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
import org.springframework.aot.hint.MemberCategory;
26+
27+
/**
28+
* Register reflection hints against an arbitrary number of target classes.
29+
*
30+
* <p>When using this annotation directly, only the defined
31+
* {@linkplain #memberCategories() member categories} are registered for each
32+
* target class. The target classes can be specified by class or class names.
33+
* When both are specified, they are all considered. If no target class is
34+
* specified, the current class is used.
35+
*
36+
* <p>This annotation can be used as a meta-annotation to customize how hints
37+
* are registered against each target class.
38+
*
39+
* <p>The annotated element can be any bean:
40+
* <pre><code class="java">
41+
* &#064;Configuration
42+
* &#064;RegisterReflection(classes = CustomerEntry.class, memberCategories = PUBLIC_FIELDS)
43+
* public class MyConfig {
44+
* // ...
45+
* }</code></pre>
46+
*
47+
* <p>To register reflection hints for the type itself, only member categories
48+
* should be specified:<pre><code class="java">
49+
* &#064;Component
50+
* &#064;RegisterReflection(memberCategories = INVOKE_PUBLIC_METHODS)
51+
* public class MyComponent {
52+
* // ...
53+
* }</code></pre>
54+
*
55+
* <p>Reflection hints can be registered from a method. In this case, at least
56+
* one target class should be specified:<pre><code class="java">
57+
* &#064;Component
58+
* public class MyComponent {
59+
*
60+
* &#064;RegisterReflection(classes = CustomerEntry.class, memberCategories = PUBLIC_FIELDS)
61+
* CustomerEntry process() { ... }
62+
* // ...
63+
* }</code></pre>
64+
*
65+
* <p>If the class is not available, {@link #classNames()} allows to specify the
66+
* fully qualified name, rather than the {@link Class} reference.
67+
*
68+
* @author Stephane Nicoll
69+
* @since 6.2
70+
*/
71+
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
72+
@Retention(RetentionPolicy.RUNTIME)
73+
@Documented
74+
@Reflective(RegisterReflectionReflectiveProcessor.class)
75+
public @interface RegisterReflection {
76+
77+
/**
78+
* Classes for which reflection hints should be registered. Consider using
79+
* {@link #classNames()} for classes that are not public in the current
80+
* scope. If both {@code classes} and {@code classNames} are specified, they
81+
* are merged in a single set.
82+
* <p>
83+
* By default, the annotated type is the target of the registration. When
84+
* placed on a method, at least one class must be specified.
85+
* @see #classNames()
86+
*/
87+
Class<?>[] classes() default {};
88+
89+
/**
90+
* Alternative to {@link #classes()} to specify the classes as class names.
91+
* @see #classes()
92+
*/
93+
String[] classNames() default {};
94+
95+
/**
96+
* Specify the {@linkplain MemberCategory member categories} to enable.
97+
*/
98+
MemberCategory[] memberCategories() default {};
99+
100+
}

Diff for: spring-core/src/main/java/org/springframework/aot/hint/annotation/RegisterReflectionForBinding.java

+32-17
Original file line numberDiff line numberDiff line change
@@ -25,61 +25,76 @@
2525
import org.springframework.core.annotation.AliasFor;
2626

2727
/**
28-
* Indicates that the classes specified in the annotation attributes require some
29-
* reflection hints for binding or reflection-based serialization purposes. For each
30-
* class specified, hints on constructors, fields, properties, record components,
31-
* including types transitively used on properties and record components are registered.
32-
* At least one class must be specified in the {@code value} or {@code classes} annotation
33-
* attributes.
28+
* Register reflection hints for data binding or reflection-based serialization
29+
* against an arbitrary number of target classes.
3430
*
35-
* <p>The annotated element can be a configuration class &mdash; for example:
31+
* <p>For each class hints are registered for constructors, fields, properties,
32+
* and record components. Hints are also registered for types transitively used
33+
* on properties and record components.
3634
*
37-
* <pre class="code">
35+
* <p>The annotated element can be a configuration class &mdash; for example:
36+
* <pre><code class="java">
3837
* &#064;Configuration
3938
* &#064;RegisterReflectionForBinding({Foo.class, Bar.class})
4039
* public class MyConfig {
4140
* // ...
42-
* }</pre>
41+
* }</code></pre>
4342
*
44-
* <p>The annotated element can be any Spring bean class or method &mdash; for example:
43+
* <p>When the annotated element is a type, the type itself is registered if no
44+
* candidates are provided:<pre><code class="java">
45+
* &#064;Component
46+
* &#064;RegisterReflectionForBinding
47+
* public class MyBean {
48+
* // ...
49+
* }</code></pre>
4550
*
46-
* <pre class="code">
47-
* &#064;Service
51+
* The annotation can also be specified on a method. In that case, at least one
52+
* target class must be specified:<pre><code class="java">
53+
* &#064;Component
4854
* public class MyService {
4955
*
5056
* &#064;RegisterReflectionForBinding(Baz.class)
51-
* public void process() {
57+
* public Baz process() {
5258
* // ...
5359
* }
5460
*
55-
* }</pre>
61+
* }</code></pre>
5662
*
5763
* <p>The annotated element can also be any test class that uses the <em>Spring
5864
* TestContext Framework</em> to load an {@code ApplicationContext}.
5965
*
6066
* @author Sebastien Deleuze
67+
* @author Stephane Nicoll
6168
* @since 6.0
6269
* @see org.springframework.aot.hint.BindingReflectionHintsRegistrar
63-
* @see Reflective @Reflective
70+
* @see RegisterReflection @RegisterReflection
6471
*/
6572
@Target({ElementType.TYPE, ElementType.METHOD})
6673
@Retention(RetentionPolicy.RUNTIME)
6774
@Documented
75+
@RegisterReflection
6876
@Reflective(RegisterReflectionForBindingProcessor.class)
6977
public @interface RegisterReflectionForBinding {
7078

7179
/**
7280
* Alias for {@link #classes()}.
7381
*/
74-
@AliasFor("classes")
82+
@AliasFor(annotation = RegisterReflection.class, attribute = "classes")
7583
Class<?>[] value() default {};
7684

7785
/**
7886
* Classes for which reflection hints should be registered.
7987
* <p>At least one class must be specified either via {@link #value} or {@code classes}.
8088
* @see #value()
8189
*/
82-
@AliasFor("value")
90+
@AliasFor(annotation = RegisterReflection.class, attribute = "classes")
8391
Class<?>[] classes() default {};
8492

93+
/**
94+
* Alternative to {@link #classes()} to specify the classes as class names.
95+
* @see #classes()
96+
*/
97+
@AliasFor(annotation = RegisterReflection.class, attribute = "classNames")
98+
String[] classNames() default {};
99+
85100
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,39 +16,28 @@
1616

1717
package org.springframework.aot.hint.annotation;
1818

19-
import java.lang.reflect.AnnotatedElement;
20-
2119
import org.springframework.aot.hint.BindingReflectionHintsRegistrar;
20+
import org.springframework.aot.hint.MemberCategory;
2221
import org.springframework.aot.hint.ReflectionHints;
23-
import org.springframework.core.annotation.AnnotationUtils;
24-
import org.springframework.util.Assert;
2522

2623
/**
2724
* A {@link ReflectiveProcessor} implementation that registers reflection hints
28-
* for data binding purpose (class, constructors, fields, properties, record
29-
* components, including types transitively used on properties and record components).
25+
* for data binding purpose, that is class, constructors, fields, properties,
26+
* record components, including types transitively used on properties and record
27+
* components.
3028
*
3129
* @author Sebastien Deleuze
30+
* @author Stephane Nicoll
3231
* @since 6.0
3332
* @see RegisterReflectionForBinding @RegisterReflectionForBinding
3433
*/
35-
public class RegisterReflectionForBindingProcessor implements ReflectiveProcessor {
34+
class RegisterReflectionForBindingProcessor extends RegisterReflectionReflectiveProcessor {
3635

3736
private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar();
3837

39-
4038
@Override
41-
public void registerReflectionHints(ReflectionHints hints, AnnotatedElement element) {
42-
RegisterReflectionForBinding registerReflection =
43-
AnnotationUtils.getAnnotation(element, RegisterReflectionForBinding.class);
44-
if (registerReflection != null) {
45-
Class<?>[] classes = registerReflection.classes();
46-
Assert.state(classes.length != 0, () -> "A least one class should be specified in " +
47-
"@RegisterReflectionForBinding attributes, and none was provided on " + element);
48-
for (Class<?> type : classes) {
49-
this.bindingRegistrar.registerReflectionHints(hints, type);
50-
}
51-
}
39+
protected void registerReflectionHints(ReflectionHints hints, Class<?> target, MemberCategory[] memberCategories) {
40+
this.bindingRegistrar.registerReflectionHints(hints, target);
5241
}
5342

5443
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.aot.hint.annotation;
18+
19+
import java.lang.reflect.AnnotatedElement;
20+
import java.util.ArrayList;
21+
import java.util.Arrays;
22+
import java.util.List;
23+
import java.util.Objects;
24+
25+
import org.apache.commons.logging.Log;
26+
import org.apache.commons.logging.LogFactory;
27+
28+
import org.springframework.aot.hint.MemberCategory;
29+
import org.springframework.aot.hint.ReflectionHints;
30+
import org.springframework.core.annotation.AnnotatedElementUtils;
31+
import org.springframework.lang.Nullable;
32+
import org.springframework.util.Assert;
33+
import org.springframework.util.ClassUtils;
34+
35+
/**
36+
* A {@link ReflectiveProcessor} implementation that pairs with
37+
* {@link RegisterReflection @RegisterReflection}. Can be used as a base
38+
* implementation for composed annotations that are meta-annotated with
39+
* {@link RegisterReflection}.
40+
*
41+
* @author Stephane Nicoll
42+
* @since 6.2
43+
*/
44+
public class RegisterReflectionReflectiveProcessor implements ReflectiveProcessor {
45+
46+
private static final Log logger = LogFactory.getLog(RegisterReflectionReflectiveProcessor.class);
47+
48+
@Override
49+
public final void registerReflectionHints(ReflectionHints hints, AnnotatedElement element) {
50+
RegisterReflection annotation = AnnotatedElementUtils.getMergedAnnotation(
51+
element, RegisterReflection.class);
52+
Assert.notNull(annotation, "Element must be annotated with @" + RegisterReflection.class.getSimpleName()
53+
+ ": " + element);
54+
ReflectionRegistration registration = parse(element, annotation);
55+
registerReflectionHints(hints, registration);
56+
}
57+
58+
protected ReflectionRegistration parse(AnnotatedElement element, RegisterReflection annotation) {
59+
List<Class<?>> allClassNames = new ArrayList<>();
60+
allClassNames.addAll(Arrays.asList(annotation.classes()));
61+
allClassNames.addAll(Arrays.stream(annotation.classNames())
62+
.map(this::loadClass).filter(Objects::nonNull).toList());
63+
if (allClassNames.isEmpty()) {
64+
if (element instanceof Class<?> clazz) {
65+
allClassNames.add(clazz);
66+
}
67+
else {
68+
throw new IllegalStateException("At least one class must be specified, "
69+
+ "could not detect target from '" + element + "'");
70+
}
71+
}
72+
return new ReflectionRegistration(allClassNames.toArray(new Class<?>[0]),
73+
annotation.memberCategories());
74+
}
75+
76+
protected void registerReflectionHints(ReflectionHints hints, ReflectionRegistration registration) {
77+
for (Class<?> target : registration.classes) {
78+
registerReflectionHints(hints, target, registration.memberCategories);
79+
}
80+
}
81+
82+
protected void registerReflectionHints(ReflectionHints hints, Class<?> target, MemberCategory[] memberCategories) {
83+
hints.registerType(target, type -> type.withMembers(memberCategories));
84+
}
85+
86+
@Nullable
87+
private Class<?> loadClass(String className) {
88+
try {
89+
return ClassUtils.forName(className, getClass().getClassLoader());
90+
}
91+
catch (Exception ex) {
92+
logger.warn("Ignoring '" + className + "': " + ex.getMessage());
93+
return null;
94+
}
95+
}
96+
97+
protected record ReflectionRegistration(Class<?>[] classes, MemberCategory[] memberCategories) {}
98+
99+
}

0 commit comments

Comments
 (0)