Skip to content

Commit

Permalink
Introduce RegisterReflection
Browse files Browse the repository at this point in the history
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
snicoll committed Jul 4, 2024
1 parent 206d81e commit 4f6e75c
Show file tree
Hide file tree
Showing 9 changed files with 494 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
* @author Stephane Nicoll
* @author Sam Brannen
* @since 6.0
* @see SimpleReflectiveProcessor
* @see ReflectiveRuntimeHintsRegistrar
* @see RegisterReflection @RegisterReflection
* @see RegisterReflectionForBinding @RegisterReflectionForBinding
*/
@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD})
Expand Down
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">
* &#064;Configuration
* &#064;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 {};

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,61 +25,76 @@
import org.springframework.core.annotation.AliasFor;

/**
* Indicates that the classes specified in the annotation attributes require some
* reflection hints for binding or reflection-based serialization purposes. For each
* class specified, hints on constructors, fields, properties, record components,
* including types transitively used on properties and record components are registered.
* At least one class must be specified in the {@code value} or {@code classes} annotation
* attributes.
* Register reflection hints for data binding or reflection-based serialization
* against an arbitrary number of target classes.
*
* <p>The annotated element can be a configuration class &mdash; for example:
* <p>For each class hints are registered for constructors, fields, properties,
* and record components. Hints are also registered for types transitively used
* on properties and record components.
*
* <pre class="code">
* <p>The annotated element can be a configuration class &mdash; for example:
* <pre><code class="java">
* &#064;Configuration
* &#064;RegisterReflectionForBinding({Foo.class, Bar.class})
* public class MyConfig {
* // ...
* }</pre>
* }</code></pre>
*
* <p>The annotated element can be any Spring bean class or method &mdash; for example:
* <p>When the annotated element is a type, the type itself is registered if no
* candidates are provided:<pre><code class="java">
* &#064;Component
* &#064;RegisterReflectionForBinding
* public class MyBean {
* // ...
* }</code></pre>
*
* <pre class="code">
* &#064;Service
* The annotation can also be specified on a method. In that case, at least one
* target class must be specified:<pre><code class="java">
* &#064;Component
* public class MyService {
*
* &#064;RegisterReflectionForBinding(Baz.class)
* public void process() {
* public Baz process() {
* // ...
* }
*
* }</pre>
* }</code></pre>
*
* <p>The annotated element can also be any test class that uses the <em>Spring
* TestContext Framework</em> to load an {@code ApplicationContext}.
*
* @author Sebastien Deleuze
* @author Stephane Nicoll
* @since 6.0
* @see org.springframework.aot.hint.BindingReflectionHintsRegistrar
* @see Reflective @Reflective
* @see RegisterReflection @RegisterReflection
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RegisterReflection
@Reflective(RegisterReflectionForBindingProcessor.class)
public @interface RegisterReflectionForBinding {

/**
* Alias for {@link #classes()}.
*/
@AliasFor("classes")
@AliasFor(annotation = RegisterReflection.class, attribute = "classes")
Class<?>[] value() default {};

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

/**
* Alternative to {@link #classes()} to specify the classes as class names.
* @see #classes()
*/
@AliasFor(annotation = RegisterReflection.class, attribute = "classNames")
String[] classNames() default {};

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,39 +16,28 @@

package org.springframework.aot.hint.annotation;

import java.lang.reflect.AnnotatedElement;

import org.springframework.aot.hint.BindingReflectionHintsRegistrar;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.ReflectionHints;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;

/**
* A {@link ReflectiveProcessor} implementation that registers reflection hints
* for data binding purpose (class, constructors, fields, properties, record
* components, including types transitively used on properties and record components).
* for data binding purpose, that is class, constructors, fields, properties,
* record components, including types transitively used on properties and record
* components.
*
* @author Sebastien Deleuze
* @author Stephane Nicoll
* @since 6.0
* @see RegisterReflectionForBinding @RegisterReflectionForBinding
*/
public class RegisterReflectionForBindingProcessor implements ReflectiveProcessor {
class RegisterReflectionForBindingProcessor extends RegisterReflectionReflectiveProcessor {

private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar();


@Override
public void registerReflectionHints(ReflectionHints hints, AnnotatedElement element) {
RegisterReflectionForBinding registerReflection =
AnnotationUtils.getAnnotation(element, RegisterReflectionForBinding.class);
if (registerReflection != null) {
Class<?>[] classes = registerReflection.classes();
Assert.state(classes.length != 0, () -> "A least one class should be specified in " +
"@RegisterReflectionForBinding attributes, and none was provided on " + element);
for (Class<?> type : classes) {
this.bindingRegistrar.registerReflectionHints(hints, type);
}
}
protected void registerReflectionHints(ReflectionHints hints, Class<?> target, MemberCategory[] memberCategories) {
this.bindingRegistrar.registerReflectionHints(hints, target);
}

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

}
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();

}
Loading

0 comments on commit 4f6e75c

Please sign in to comment.