diff --git a/spring-core/src/main/java/org/springframework/aot/hint/annotation/Reflective.java b/spring-core/src/main/java/org/springframework/aot/hint/annotation/Reflective.java index 02dcdcef6ba..b53af8a82c8 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/annotation/Reflective.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/annotation/Reflective.java @@ -35,8 +35,8 @@ import org.springframework.core.annotation.AliasFor; * @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}) diff --git a/spring-core/src/main/java/org/springframework/aot/hint/annotation/RegisterReflection.java b/spring-core/src/main/java/org/springframework/aot/hint/annotation/RegisterReflection.java new file mode 100644 index 00000000000..fb72fca8a13 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/aot/hint/annotation/RegisterReflection.java @@ -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. + * + *

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. + * + *

This annotation can be used as a meta-annotation to customize how hints + * are registered against each target class. + * + *

The annotated element can be any bean: + *


+ * @Configuration
+ * @RegisterReflection(classes = CustomerEntry.class, memberCategories = PUBLIC_FIELDS)
+ * public class MyConfig {
+ *     // ...
+ * }
+ * + *

To register reflection hints for the type itself, only member categories + * should be specified:


+ * @Component
+ * @RegisterReflection(memberCategories = INVOKE_PUBLIC_METHODS)
+ * public class MyComponent {
+ *     // ...
+ * }
+ * + *

Reflection hints can be registered from a method. In this case, at least + * one target class should be specified:


+ * @Component
+ * public class MyComponent {
+ *
+ *     @RegisterReflection(classes = CustomerEntry.class, memberCategories = PUBLIC_FIELDS)
+ *     CustomerEntry process() { ... }
+ *     // ...
+ * }
+ * + *

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. + *

+ * 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 {}; + +} diff --git a/spring-core/src/main/java/org/springframework/aot/hint/annotation/RegisterReflectionForBinding.java b/spring-core/src/main/java/org/springframework/aot/hint/annotation/RegisterReflectionForBinding.java index ea0077c9eb6..c91ab40d4d7 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/annotation/RegisterReflectionForBinding.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/annotation/RegisterReflectionForBinding.java @@ -25,53 +25,61 @@ import java.lang.annotation.Target; 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. * - *

The annotated element can be a configuration class — for example: + *

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. * - *

+ * 

The annotated element can be a configuration class — for example: + *


  * @Configuration
  * @RegisterReflectionForBinding({Foo.class, Bar.class})
  * public class MyConfig {
  *     // ...
- * }
+ * }
* - *

The annotated element can be any Spring bean class or method — for example: + *

When the annotated element is a type, the type itself is registered if no + * candidates are provided:


+ * @Component
+ * @RegisterReflectionForBinding
+ * public class MyBean {
+ *     // ...
+ * }
* - *
- * @Service
+ * The annotation can also be specified on a method. In that case, at least one
+ * target class must be specified:

+ * @Component
  * public class MyService {
  *
  *     @RegisterReflectionForBinding(Baz.class)
- *     public void process() {
+ *     public Baz process() {
  *         // ...
  *     }
  *
- * }
+ * }
* *

The annotated element can also be any test class that uses the Spring * TestContext Framework 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 {}; /** @@ -79,7 +87,14 @@ public @interface RegisterReflectionForBinding { *

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 {}; + } diff --git a/spring-core/src/main/java/org/springframework/aot/hint/annotation/RegisterReflectionForBindingProcessor.java b/spring-core/src/main/java/org/springframework/aot/hint/annotation/RegisterReflectionForBindingProcessor.java index 06e79ee53af..c87f6a4e409 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/annotation/RegisterReflectionForBindingProcessor.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/annotation/RegisterReflectionForBindingProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 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. @@ -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); } } diff --git a/spring-core/src/main/java/org/springframework/aot/hint/annotation/RegisterReflectionReflectiveProcessor.java b/spring-core/src/main/java/org/springframework/aot/hint/annotation/RegisterReflectionReflectiveProcessor.java new file mode 100644 index 00000000000..2308f0a42a8 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/aot/hint/annotation/RegisterReflectionReflectiveProcessor.java @@ -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> 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) {} + +} diff --git a/spring-core/src/test/java/org/springframework/aot/hint/annotation/ReflectiveRuntimeHintsRegistrarTests.java b/spring-core/src/test/java/org/springframework/aot/hint/annotation/ReflectiveRuntimeHintsRegistrarTests.java index f4b92183a1a..e2d860d74a1 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/annotation/ReflectiveRuntimeHintsRegistrarTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/annotation/ReflectiveRuntimeHintsRegistrarTests.java @@ -25,6 +25,7 @@ import java.lang.reflect.Method; import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.FieldHint; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.ReflectionHints; import org.springframework.aot.hint.RuntimeHints; @@ -118,6 +119,18 @@ class ReflectiveRuntimeHintsRegistrarTests { .satisfies(methodHint -> assertThat(methodHint.getName()).isEqualTo("managed"))); } + @Test + void shouldProcessDifferentAnnotationsOnTypeAndField() { + process(SampleTypeAndFieldAnnotatedBean.class); + assertThat(this.runtimeHints.reflection().getTypeHint(SampleTypeAndFieldAnnotatedBean.class)) + .satisfies(typeHint -> { + assertThat(typeHint.fields().map(FieldHint::getName)).containsOnly("MESSAGE"); + assertThat(typeHint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); + assertThat(typeHint.methods()).isEmpty(); + assertThat(typeHint.constructors()).isEmpty(); + }); + } + @Test void shouldInvokeCustomProcessor() { process(SampleCustomProcessor.class); @@ -175,6 +188,14 @@ class ReflectiveRuntimeHintsRegistrarTests { } } + @RegisterReflection(memberCategories = MemberCategory.INVOKE_DECLARED_CONSTRUCTORS) + static class SampleTypeAndFieldAnnotatedBean { + + @Reflective + private static final String MESSAGE = "Hello"; + + } + @SuppressWarnings("unused") static class SampleMethodMetaAnnotatedBean { diff --git a/spring-core/src/test/java/org/springframework/aot/hint/annotation/RegisterReflectionForBindingProcessorTests.java b/spring-core/src/test/java/org/springframework/aot/hint/annotation/RegisterReflectionForBindingProcessorTests.java index 38744bac692..f19fdb5227d 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/annotation/RegisterReflectionForBindingProcessorTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/annotation/RegisterReflectionForBindingProcessorTests.java @@ -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. @@ -28,6 +28,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; * Tests for {@link RegisterReflectionForBindingProcessor}. * * @author Sebastien Deleuze + * @author Stephane Nicoll */ class RegisterReflectionForBindingProcessorTests { @@ -52,10 +53,11 @@ class RegisterReflectionForBindingProcessorTests { } @Test - void throwExceptionWithoutAnnotationAttributeOnClass() { - assertThatThrownBy(() -> processor.registerReflectionHints(hints.reflection(), - SampleClassWithoutAnnotationAttribute.class)) - .isInstanceOf(IllegalStateException.class); + void registerReflectionForBindingOnClassItself() { + processor.registerReflectionHints(hints.reflection(), SampleClassWithoutAnnotationAttribute.class); + assertThat(RuntimeHintsPredicates.reflection().onType(SampleClassWithoutAnnotationAttribute.class)).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onType(String.class)).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onMethod(SampleClassWithoutAnnotationAttribute.class, "getName")).accepts(hints); } @Test @@ -80,12 +82,16 @@ class RegisterReflectionForBindingProcessorTests { static class SampleClassWithGetter { public String getName() { - return null; + return "test"; } } @RegisterReflectionForBinding static class SampleClassWithoutAnnotationAttribute { + + public String getName() { + return "test"; + } } static class SampleClassWithoutMethodLevelAnnotationAttribute { diff --git a/spring-core/src/test/java/org/springframework/aot/hint/annotation/RegisterReflectionReflectiveProcessorTests.java b/spring-core/src/test/java/org/springframework/aot/hint/annotation/RegisterReflectionReflectiveProcessorTests.java new file mode 100644 index 00000000000..f605c3e261e --- /dev/null +++ b/spring-core/src/test/java/org/springframework/aot/hint/annotation/RegisterReflectionReflectiveProcessorTests.java @@ -0,0 +1,186 @@ +/* + * 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.lang.reflect.Method; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.ExecutableHint; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.TypeHint; +import org.springframework.aot.hint.TypeReference; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; + +/** + * Tests for {@link RegisterReflectionReflectiveProcessor}. + * + * @author Stephane Nicoll + */ +class RegisterReflectionReflectiveProcessorTests { + + private static final List NO_METHODS = Collections.emptyList(); + + private final RegisterReflectionReflectiveProcessor processor = new RegisterReflectionReflectiveProcessor(); + + private final RuntimeHints hints = new RuntimeHints(); + + @Nested + class AnnotatedTypeTests { + + @Test + void registerReflectionWithMemberCategory() { + registerReflectionHints(RegistrationSimple.class); + assertBasicTypeHint(SimplePojo.class, NO_METHODS, List.of(MemberCategory.INVOKE_PUBLIC_METHODS)); + } + + @Test + void registerReflectionForMultipleTargets() { + registerReflectionHints(RegistrationMultipleTargets.class); + assertThat(hints.reflection().typeHints()).allSatisfy( + hasOnlyMemberCategories(MemberCategory.INVOKE_PUBLIC_METHODS)); + assertThat(hints.reflection().typeHints().map(TypeHint::getType)) + .hasSameElementsAs(TypeReference.listOf(Number.class, Double.class, Integer.class, Float.class)); + } + + @Test + void registerReflectionOnTargetClass() { + registerReflectionHints(AnnotatedSimplePojo.class); + assertBasicTypeHint(AnnotatedSimplePojo.class, NO_METHODS, + List.of(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)); + } + } + + @Nested + class AnnotatedMethodTests { + + @Test + void registerReflectionForStaticField() throws NoSuchMethodException { + Method method = RegistrationMethod.class.getDeclaredMethod("doReflection"); + registerReflectionHints(method); + assertBasicTypeHint(SimplePojo.class, NO_METHODS, List.of(MemberCategory.INVOKE_DECLARED_METHODS)); + } + + @Test + void registerReflectionWithoutTarget() throws NoSuchMethodException { + Method method = RegistrationMethodWithoutTarget.class.getDeclaredMethod("doReflection"); + assertThatIllegalStateException() + .isThrownBy(() -> registerReflectionHints(method)) + .withMessageContaining("At least one class must be specified, could not detect target from '") + .withMessageContaining(method.toString()); + } + } + + private void assertBasicTypeHint(Class type, List methodNames, List memberCategories) { + TypeHint typeHint = getTypeHint(type); + assertThat(typeHint.methods()).map(ExecutableHint::getName).hasSameElementsAs(methodNames); + assertThat(typeHint.getMemberCategories()).hasSameElementsAs(memberCategories); + assertThat(typeHint.fields()).isEmpty(); + assertThat(typeHint.constructors()).isEmpty(); + } + + private Consumer hasOnlyMemberCategories(MemberCategory... categories) { + return typeHint -> { + assertThat(typeHint.fields()).isEmpty(); + assertThat(typeHint.methods()).isEmpty(); + assertThat(typeHint.constructors()).isEmpty(); + assertThat(typeHint.getMemberCategories()).containsOnly(categories); + }; + } + + private TypeHint getTypeHint(Class target) { + TypeHint typeHint = hints.reflection().getTypeHint(target); + assertThat(typeHint).isNotNull(); + return typeHint; + } + + private void registerReflectionHints(AnnotatedElement annotatedElement) { + this.processor.registerReflectionHints(this.hints.reflection(), annotatedElement); + } + + @RegisterReflection(classes = SimplePojo.class, memberCategories = MemberCategory.INVOKE_PUBLIC_METHODS) + static class RegistrationSimple {} + + @RegisterReflection(classes = { Number.class, Double.class }, + classNames = { "java.lang.Integer", "java.lang.Float" }, memberCategories = MemberCategory.INVOKE_PUBLIC_METHODS) + static class RegistrationMultipleTargets { + + } + + static class RegistrationMethod { + + @RegisterReflection(classes = SimplePojo.class, memberCategories = MemberCategory.INVOKE_DECLARED_METHODS) + private void doReflection() { + + } + + } + + static class RegistrationMethodWithoutTarget { + + @RegisterReflection(memberCategories = MemberCategory.INVOKE_DECLARED_CONSTRUCTORS) + private void doReflection() { + + } + + } + + + static class SimplePojo { + + private String name; + + private String description; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return this.description; + } + + public void setDescription(String description) { + this.description = description; + } + } + + @RegisterReflection(memberCategories = MemberCategory.INVOKE_DECLARED_CONSTRUCTORS) + static class AnnotatedSimplePojo { + + private String test; + + AnnotatedSimplePojo(String test) { + this.test = test; + } + + } + +} +