Browse Source

Merge branch 'gh-28967'

Closes gh-28967
pull/28961/head
Stephane Nicoll 4 years ago
parent
commit
b03d0479e4
  1. 42
      spring-beans/src/main/java/org/springframework/beans/factory/annotation/JakartaAnnotationsRuntimeHints.java
  2. 3
      spring-beans/src/main/resources/META-INF/spring/aot.factories
  3. 62
      spring-beans/src/test/java/org/springframework/beans/factory/annotation/JakartaAnnotationsRuntimeHintsTests.java
  4. 4
      spring-context/spring-context.gradle
  5. 8
      spring-context/src/main/java/org/springframework/context/aot/ReflectiveProcessorBeanRegistrationAotProcessor.java
  6. 15
      spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessor.java
  7. 16
      spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java
  8. 14
      spring-context/src/test/java/org/springframework/context/aot/ReflectiveProcessorBeanRegistrationAotProcessorTests.java
  9. 3
      spring-core/src/main/java/org/springframework/aot/hint/annotation/Reflective.java
  10. 41
      spring-core/src/main/java/org/springframework/aot/hint/support/CoreAnnotationsRuntimeHints.java
  11. 90
      spring-core/src/main/java/org/springframework/aot/hint/support/RuntimeHintsUtils.java
  12. 9
      spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotation.java
  13. 7
      spring-core/src/main/java/org/springframework/core/annotation/MissingMergedAnnotation.java
  14. 22
      spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java
  15. 1
      spring-core/src/main/resources/META-INF/spring/aot.factories
  16. 61
      spring-core/src/test/java/org/springframework/aot/hint/support/CoreAnnotationsRuntimeHintsTests.java
  17. 108
      spring-core/src/test/java/org/springframework/aot/hint/support/RuntimeHintsUtilsTests.java
  18. 27
      spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java
  19. 5
      spring-core/src/test/java/org/springframework/core/annotation/MissingMergedAnnotationTests.java
  20. 5
      spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessagingAnnotationsRuntimeHints.java
  21. 5
      spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/SimpAnnotationsRuntimeHints.java
  22. 2
      spring-tx/src/main/java/org/springframework/transaction/annotation/TransactionRuntimeHints.java
  23. 15
      spring-web/src/main/java/org/springframework/web/bind/annotation/WebAnnotationsRuntimeHintsRegistrar.java

42
spring-beans/src/main/java/org/springframework/beans/factory/annotation/JakartaAnnotationsRuntimeHints.java

@ -1,42 +0,0 @@ @@ -1,42 +0,0 @@
/*
* Copyright 2002-2022 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.beans.factory.annotation;
import java.util.stream.Stream;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.support.RuntimeHintsUtils;
import org.springframework.util.ClassUtils;
/**
* {@link RuntimeHintsRegistrar} for Jakarta annotations.
* <p>Hints are only registered if Jakarta inject is on the classpath.
*
* @author Brian Clozel
*/
class JakartaAnnotationsRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
if (ClassUtils.isPresent("jakarta.inject.Inject", classLoader)) {
Stream.of("jakarta.inject.Inject", "jakarta.inject.Qualifier").forEach(annotationType ->
RuntimeHintsUtils.registerAnnotation(hints, ClassUtils.resolveClassName(annotationType, classLoader)));
}
}
}

3
spring-beans/src/main/resources/META-INF/spring/aot.factories

@ -1,5 +1,2 @@ @@ -1,5 +1,2 @@
org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\
org.springframework.beans.factory.aot.BeanRegistrationsAotProcessor
org.springframework.aot.hint.RuntimeHintsRegistrar=\
org.springframework.beans.factory.annotation.JakartaAnnotationsRuntimeHints

62
spring-beans/src/test/java/org/springframework/beans/factory/annotation/JakartaAnnotationsRuntimeHintsTests.java

@ -1,62 +0,0 @@ @@ -1,62 +0,0 @@
/*
* Copyright 2002-2022 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.beans.factory.annotation;
import jakarta.inject.Inject;
import jakarta.inject.Qualifier;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.beans.factory.aot.AotServices;
import org.springframework.util.ClassUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link JakartaAnnotationsRuntimeHints}.
*
* @author Brian Clozel
*/
class JakartaAnnotationsRuntimeHintsTests {
private final RuntimeHints hints = new RuntimeHints();
@BeforeEach
void setup() {
AotServices.factories().load(RuntimeHintsRegistrar.class)
.forEach(registrar -> registrar.registerHints(this.hints,
ClassUtils.getDefaultClassLoader()));
}
@Test
void jakartaInjectAnnotationHasHints() {
assertThat(RuntimeHintsPredicates.reflection().onType(Inject.class)
.withMemberCategory(MemberCategory.INVOKE_DECLARED_METHODS)).accepts(this.hints);
}
@Test
void jakartaQualifierAnnotationHasHints() {
assertThat(RuntimeHintsPredicates.reflection().onType(Qualifier.class)
.withMemberCategory(MemberCategory.INVOKE_DECLARED_METHODS)).accepts(this.hints);
}
}

4
spring-context/spring-context.gradle

@ -1,7 +1,3 @@ @@ -1,7 +1,3 @@
plugins {
id 'org.springframework.build.runtimehints-agent'
}
description = "Spring Context"
apply plugin: "kotlin"

8
spring-context/src/main/java/org/springframework/context/aot/ReflectiveProcessorBeanRegistrationAotProcessor.java

@ -142,9 +142,11 @@ class ReflectiveProcessorBeanRegistrationAotProcessor implements BeanRegistratio @@ -142,9 +142,11 @@ class ReflectiveProcessorBeanRegistrationAotProcessor implements BeanRegistratio
}
private void registerAnnotationIfNecessary(RuntimeHints hints, AnnotatedElement element) {
MergedAnnotation<Reflective> reflectiveAnnotation = MergedAnnotations.from(element).get(Reflective.class);
if (reflectiveAnnotation.getDistance() > 0) {
RuntimeHintsUtils.registerAnnotation(hints, reflectiveAnnotation.getRoot().getType());
MergedAnnotation<Reflective> reflectiveAnnotation = MergedAnnotations.from(element)
.get(Reflective.class);
MergedAnnotation<?> metaSource = reflectiveAnnotation.getMetaSource();
if (metaSource != null) {
RuntimeHintsUtils.registerAnnotationIfNecessary(hints, metaSource);
}
}

15
spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessor.java

@ -25,14 +25,9 @@ import org.apache.commons.logging.LogFactory; @@ -25,14 +25,9 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.autoproxy.AbstractBeanFactoryAwareAdvisingPostProcessor;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.support.RuntimeHintsUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.core.task.TaskExecutor;
import org.springframework.lang.Nullable;
import org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor.AsyncAnnotationRuntimeHints;
import org.springframework.util.Assert;
import org.springframework.util.function.SingletonSupplier;
@ -67,7 +62,6 @@ import org.springframework.util.function.SingletonSupplier; @@ -67,7 +62,6 @@ import org.springframework.util.function.SingletonSupplier;
* @see ScheduledAnnotationBeanPostProcessor
*/
@SuppressWarnings("serial")
@ImportRuntimeHints(AsyncAnnotationRuntimeHints.class)
public class AsyncAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor {
/**
@ -160,13 +154,4 @@ public class AsyncAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAd @@ -160,13 +154,4 @@ public class AsyncAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAd
this.advisor = advisor;
}
static class AsyncAnnotationRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
RuntimeHintsUtils.registerAnnotation(hints, Async.class);
}
}
}

16
spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java

@ -37,9 +37,6 @@ import org.apache.commons.logging.LogFactory; @@ -37,9 +37,6 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.support.RuntimeHintsUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
@ -58,7 +55,6 @@ import org.springframework.context.ApplicationContext; @@ -58,7 +55,6 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.Ordered;
@ -68,7 +64,6 @@ import org.springframework.core.annotation.AnnotationUtils; @@ -68,7 +64,6 @@ import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.ScheduledAnnotationsRuntimeHints;
import org.springframework.scheduling.config.CronTask;
import org.springframework.scheduling.config.FixedDelayTask;
import org.springframework.scheduling.config.FixedRateTask;
@ -111,7 +106,6 @@ import org.springframework.util.StringValueResolver; @@ -111,7 +106,6 @@ import org.springframework.util.StringValueResolver;
* @see org.springframework.scheduling.config.ScheduledTaskRegistrar
* @see AsyncAnnotationBeanPostProcessor
*/
@ImportRuntimeHints(ScheduledAnnotationsRuntimeHints.class)
public class ScheduledAnnotationBeanPostProcessor
implements ScheduledTaskHolder, MergedBeanDefinitionPostProcessor, DestructionAwareBeanPostProcessor,
Ordered, EmbeddedValueResolverAware, BeanNameAware, BeanFactoryAware, ApplicationContextAware,
@ -611,14 +605,4 @@ public class ScheduledAnnotationBeanPostProcessor @@ -611,14 +605,4 @@ public class ScheduledAnnotationBeanPostProcessor
this.registrar.destroy();
}
static class ScheduledAnnotationsRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
RuntimeHintsUtils.registerAnnotation(hints, Scheduled.class);
RuntimeHintsUtils.registerAnnotation(hints, Schedules.class);
}
}
}

14
spring-context/src/test/java/org/springframework/context/aot/ReflectiveProcessorBeanRegistrationAotProcessorTests.java

@ -25,7 +25,6 @@ import java.lang.annotation.Target; @@ -25,7 +25,6 @@ import java.lang.annotation.Target;
import org.junit.jupiter.api.Test;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeReference;
import org.springframework.aot.hint.annotation.Reflective;
@ -93,19 +92,18 @@ class ReflectiveProcessorBeanRegistrationAotProcessorTests { @@ -93,19 +92,18 @@ class ReflectiveProcessorBeanRegistrationAotProcessorTests {
}
@Test
void shouldRegisterAnnotation() {
void shouldNotRegisterAnnotationProxyIfNotNeeded() {
process(SampleMethodMetaAnnotatedBean.class);
RuntimeHints runtimeHints = this.generationContext.getRuntimeHints();
assertThat(RuntimeHintsPredicates.reflection().onType(SampleInvoker.class).withMemberCategory(MemberCategory.INVOKE_DECLARED_METHODS)).accepts(runtimeHints);
assertThat(runtimeHints.proxies().jdkProxies()).isEmpty();
}
@Test
void shouldRegisterAnnotationAndProxyWithAliasFor() {
void shouldRegisterAnnotationProxy() {
process(SampleMethodMetaAnnotatedBeanWithAlias.class);
RuntimeHints runtimeHints = this.generationContext.getRuntimeHints();
assertThat(RuntimeHintsPredicates.reflection().onType(RetryInvoker.class).withMemberCategory(MemberCategory.INVOKE_DECLARED_METHODS)).accepts(runtimeHints);
assertThat(RuntimeHintsPredicates.proxies().forInterfaces(RetryInvoker.class, SynthesizedAnnotation.class)).accepts(runtimeHints);
assertThat(RuntimeHintsPredicates.proxies().forInterfaces(
SampleInvoker.class, SynthesizedAnnotation.class)).accepts(runtimeHints);
}
@Test
@ -236,7 +234,7 @@ class ReflectiveProcessorBeanRegistrationAotProcessorTests { @@ -236,7 +234,7 @@ class ReflectiveProcessorBeanRegistrationAotProcessorTests {
}
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Reflective
@ -246,7 +244,7 @@ class ReflectiveProcessorBeanRegistrationAotProcessorTests { @@ -246,7 +244,7 @@ class ReflectiveProcessorBeanRegistrationAotProcessorTests {
}
@Target({ElementType.METHOD})
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@SampleInvoker

3
spring-core/src/main/java/org/springframework/aot/hint/annotation/Reflective.java

@ -32,6 +32,9 @@ import org.springframework.core.annotation.AliasFor; @@ -32,6 +32,9 @@ import org.springframework.core.annotation.AliasFor;
* the annotated element. By default, a reflection hint is added on the
* annotated element so that it can be discovered and invoked if necessary.
*
* <p>A reflection hint is also added if necessary on the annotation that
* <em>directly</em> uses this annotation.
*
* @author Stephane Nicoll
* @author Sam Brannen
* @since 6.0

41
spring-core/src/main/java/org/springframework/aot/hint/support/CoreAnnotationsRuntimeHints.java

@ -1,41 +0,0 @@ @@ -1,41 +0,0 @@
/*
* Copyright 2002-2022 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.support;
import java.util.stream.Stream;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.Order;
import org.springframework.lang.Nullable;
/**
* {@link RuntimeHintsRegistrar} for core annotations.
*
* @author Phillip Webb
* @since 6.0
*/
class CoreAnnotationsRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
Stream.of(AliasFor.class, Order.class).forEach(annotationType ->
RuntimeHintsUtils.registerAnnotation(hints, annotationType));
}
}

90
spring-core/src/main/java/org/springframework/aot/hint/support/RuntimeHintsUtils.java

@ -16,19 +16,9 @@ @@ -16,19 +16,9 @@
package org.springframework.aot.hint.support;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.Consumer;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeHint;
import org.springframework.aot.hint.TypeHint.Builder;
import org.springframework.aot.hint.annotation.Reflective;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.SynthesizedAnnotation;
/**
@ -40,72 +30,48 @@ import org.springframework.core.annotation.SynthesizedAnnotation; @@ -40,72 +30,48 @@ import org.springframework.core.annotation.SynthesizedAnnotation;
*/
public abstract class RuntimeHintsUtils {
/**
* A {@link TypeHint} customizer suitable for an annotation. Make sure
* that its attributes are visible.
*/
public static final Consumer<Builder> ANNOTATION_HINT = hint ->
hint.withMembers(MemberCategory.INVOKE_DECLARED_METHODS);
/**
* Register the necessary hints so that the specified annotation is visible
* at runtime.
* <p>If an annotation attribute aliases an attribute of another annotation,
* the other annotation is registered as well and a JDK proxy hint is defined
* so that the synthesized annotation can be resolved.
* @param hints the {@link RuntimeHints} instance to use
* @param annotationType the annotation type
* @see SynthesizedAnnotation
* @deprecated as annotation attributes are visible without additional hints
*/
@Deprecated
public static void registerAnnotation(RuntimeHints hints, Class<?> annotationType) {
registerAnnotation(hints, annotationType, false);
registerSynthesizedAnnotation(hints, annotationType);
}
/**
* Register the necessary hints so that the specified <em>composable</em>
* annotation is visible at runtime. Use this method rather than the regular
* {@link #registerAnnotation(RuntimeHints, Class)} when the specified
* annotation is meta-annotated, but the meta-annotated annotations do not
* need to be visible.
* Register the necessary hints so that the specified annotation can be
* synthesized at runtime if necessary. Such hints are usually required
* if any of the following apply:
* <ul>
* <li>Use {@link AliasFor} for local aliases</li>
* <li>Has a meta-annotation that uses {@link AliasFor} for attribute overrides</li>
* <li>Has nested annotations or arrays of annotations that are synthesizable</li>
* </ul>
* Consider using {@link #registerAnnotationIfNecessary(RuntimeHints, MergedAnnotation)}
* that determines if the hints are required.
* @param hints the {@link RuntimeHints} instance to use
* @param annotationType the composable annotation type
* @see #registerAnnotation(RuntimeHints, Class)
* @param annotationType the annotation type
* @see SynthesizedAnnotation
*/
public static void registerComposableAnnotation(RuntimeHints hints, Class<?> annotationType) {
registerAnnotation(hints, annotationType, true);
public static void registerSynthesizedAnnotation(RuntimeHints hints, Class<?> annotationType) {
hints.proxies().registerJdkProxy(annotationType, SynthesizedAnnotation.class);
}
private static void registerAnnotation(RuntimeHints hints, Class<?> annotationType, boolean withProxy) {
hints.reflection().registerType(annotationType, ANNOTATION_HINT);
Set<Class<?>> allAnnotations = new LinkedHashSet<>();
collectAliasedAnnotations(new HashSet<>(), allAnnotations, annotationType);
allAnnotations.forEach(annotation -> {
hints.reflection().registerType(annotation, ANNOTATION_HINT);
hints.proxies().registerJdkProxy(annotation, SynthesizedAnnotation.class);
});
if (!allAnnotations.isEmpty() || withProxy) {
hints.proxies().registerJdkProxy(annotationType, SynthesizedAnnotation.class);
}
}
private static void collectAliasedAnnotations(Set<Class<?>> seen, Set<Class<?>> types, Class<?> annotationType) {
if (seen.contains(annotationType) || AliasFor.class.equals(annotationType) ||
Reflective.class.equals(annotationType)) {
return;
}
seen.add(annotationType);
for (Method method : annotationType.getDeclaredMethods()) {
AliasFor aliasFor = method.getAnnotation(AliasFor.class);
if (aliasFor != null) {
Class<?> annotationAttribute = aliasFor.annotation();
Class<?> targetAnnotation = (annotationAttribute != Annotation.class
? annotationAttribute : annotationType);
if (types.add(targetAnnotation)) {
if (!targetAnnotation.equals(annotationType)) {
collectAliasedAnnotations(seen, types, targetAnnotation);
}
}
}
/**
* Determine if the specified annotation can be synthesized at runtime, and
* register the necessary hints accordingly.
* @param hints the {@link RuntimeHints} instance to use
* @param annotation the annotation
* @see #registerSynthesizedAnnotation(RuntimeHints, Class)
*/
public static void registerAnnotationIfNecessary(RuntimeHints hints, MergedAnnotation<?> annotation) {
if (annotation.isSynthesizable()) {
registerSynthesizedAnnotation(hints, annotation.getType());
}
}

9
spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotation.java

@ -477,6 +477,15 @@ public interface MergedAnnotation<A extends Annotation> { @@ -477,6 +477,15 @@ public interface MergedAnnotation<A extends Annotation> {
*/
<T extends Map<String, Object>> T asMap(Function<MergedAnnotation<?>, T> factory, Adapt... adaptations);
/**
* Determine if this merged annotation is <em>synthesizable</em>.
* <p>Consult the documentation for {@link #synthesize()} for an explanation
* of what is considered synthesizable.
* @return {@code true} if the mapped annotation is synthesizable
* @since 6.0
*/
boolean isSynthesizable();
/**
* Create a type-safe synthesized version of this merged annotation that can
* be used directly in code.

7
spring-core/src/main/java/org/springframework/core/annotation/MissingMergedAnnotation.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@ -134,6 +134,11 @@ final class MissingMergedAnnotation<A extends Annotation> extends AbstractMerged @@ -134,6 +134,11 @@ final class MissingMergedAnnotation<A extends Annotation> extends AbstractMerged
return factory.apply(this);
}
@Override
public boolean isSynthesizable() {
return false;
}
@Override
public String toString() {
return "(missing)";

22
spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java

@ -319,6 +319,17 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn @@ -319,6 +319,17 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
return value;
}
@Override
public boolean isSynthesizable() {
// Is this a mapped annotation for a composed annotation, and are there
// annotation attributes (mirrors) that need to be merged?
if (getDistance() > 0 && this.resolvedMirrors.length > 0) {
return true;
}
// Is the mapped annotation itself synthesizable?
return this.mapping.isSynthesizable();
}
@Override
@SuppressWarnings("unchecked")
protected A createSynthesizedAnnotation() {
@ -347,22 +358,15 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn @@ -347,22 +358,15 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
* Determine if the supplied annotation has not already been synthesized
* <strong>and</strong> whether the mapped annotation is a composed annotation
* that needs to have its attributes merged or the mapped annotation is
* {@linkplain AnnotationTypeMapping#isSynthesizable() synthesizable} in general.
* {@linkplain #isSynthesizable() synthesizable} in general.
* @param annotation the annotation to check
* @since 5.3.22
*/
private boolean isSynthesizable(Annotation annotation) {
// Already synthesized?
if (annotation instanceof SynthesizedAnnotation) {
return false;
}
// Is this a mapped annotation for a composed annotation, and are there
// annotation attributes (mirrors) that need to be merged?
if (getDistance() > 0 && this.resolvedMirrors.length > 0) {
return true;
}
// Is the mapped annotation itself synthesizable?
return this.mapping.isSynthesizable();
return isSynthesizable();
}
@Override

1
spring-core/src/main/resources/META-INF/spring/aot.factories

@ -1,3 +1,2 @@ @@ -1,3 +1,2 @@
org.springframework.aot.hint.RuntimeHintsRegistrar=\
org.springframework.aot.hint.support.CoreAnnotationsRuntimeHints,\
org.springframework.aot.hint.support.SpringFactoriesLoaderRuntimeHints

61
spring-core/src/test/java/org/springframework/aot/hint/support/CoreAnnotationsRuntimeHintsTests.java

@ -1,61 +0,0 @@ @@ -1,61 +0,0 @@
/*
* Copyright 2002-2022 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.support;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.ClassUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link CoreAnnotationsRuntimeHints}.
*
* @author Phillip Webb
*/
class CoreAnnotationsRuntimeHintsTests {
private final RuntimeHints hints = new RuntimeHints();
@BeforeEach
void setup() {
SpringFactoriesLoader.forResourceLocation("META-INF/spring/aot.factories")
.load(RuntimeHintsRegistrar.class).forEach(registrar -> registrar
.registerHints(this.hints, ClassUtils.getDefaultClassLoader()));
}
@Test
void aliasForHasHints() {
assertThat(RuntimeHintsPredicates.reflection().onType(AliasFor.class)
.withMemberCategory(MemberCategory.INVOKE_DECLARED_METHODS)).accepts(this.hints);
}
@Test
void orderAnnotationHasHints() {
assertThat(RuntimeHintsPredicates.reflection().onType(Order.class)
.withMemberCategory(MemberCategory.INVOKE_DECLARED_METHODS)).accepts(this.hints);
}
}

108
spring-core/src/test/java/org/springframework/aot/hint/support/RuntimeHintsUtilsTests.java

@ -23,11 +23,11 @@ import java.util.function.Consumer; @@ -23,11 +23,11 @@ import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.JdkProxyHint;
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 org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.SynthesizedAnnotation;
import static org.assertj.core.api.Assertions.assertThat;
@ -43,75 +43,45 @@ class RuntimeHintsUtilsTests { @@ -43,75 +43,45 @@ class RuntimeHintsUtilsTests {
private final RuntimeHints hints = new RuntimeHints();
@Test
void registerAnnotationType() {
RuntimeHintsUtils.registerAnnotation(this.hints, SampleInvoker.class);
assertThat(this.hints.reflection().typeHints()).singleElement()
.satisfies(annotationHint(SampleInvoker.class));
assertThat(this.hints.proxies().jdkProxies()).isEmpty();
void registerSynthesizedAnnotation() {
RuntimeHintsUtils.registerSynthesizedAnnotation(this.hints, SampleInvoker.class);
assertThat(this.hints.proxies().jdkProxies()).singleElement()
.satisfies(annotationProxy(SampleInvoker.class));
}
@Test
void registerComposableAnnotationType() {
RuntimeHintsUtils.registerComposableAnnotation(this.hints, SampleInvoker.class);
assertThat(this.hints.reflection().typeHints()).singleElement()
.satisfies(annotationHint(SampleInvoker.class));
assertThat(this.hints.proxies().jdkProxies()).singleElement()
.satisfies(annotationProxy(SampleInvoker.class));
void registerAnnotationIfNecessaryWithNonSynthesizedAnnotation() throws NoSuchFieldException {
MergedAnnotation<SampleInvoker> annotation = MergedAnnotations
.from(TestBean.class.getField("sampleInvoker")).get(SampleInvoker.class);
RuntimeHintsUtils.registerAnnotationIfNecessary(this.hints, annotation);
assertThat(this.hints.proxies().jdkProxies()).isEmpty();
}
@Test
void registerAnnotationTypeWithLocalUseOfAliasForRegistersProxy() {
RuntimeHintsUtils.registerAnnotation(this.hints, LocalMapping.class);
assertThat(this.hints.reflection().typeHints()).singleElement()
.satisfies(annotationHint(LocalMapping.class));
void registerAnnotationIfNecessaryWithLocalAliases() throws NoSuchFieldException {
MergedAnnotation<LocalMapping> annotation = MergedAnnotations
.from(TestBean.class.getField("localMapping")).get(LocalMapping.class);
RuntimeHintsUtils.registerAnnotationIfNecessary(this.hints, annotation);
assertThat(this.hints.proxies().jdkProxies()).singleElement()
.satisfies(annotationProxy(LocalMapping.class));
}
@Test
void registerAnnotationTypeProxyRegistersJdkProxies() {
RuntimeHintsUtils.registerAnnotation(this.hints, RetryInvoker.class);
assertThat(this.hints.reflection().typeHints())
.anySatisfy(annotationHint(RetryInvoker.class))
.anySatisfy(annotationHint(SampleInvoker.class))
.hasSize(2);
assertThat(this.hints.proxies().jdkProxies())
.anySatisfy(annotationProxy(RetryInvoker.class))
.anySatisfy(annotationProxy(SampleInvoker.class))
.hasSize(2);
}
@Test // gh-28953
void registerAnnotationForAliasForShouldNotRegisterSynthesizedAnnotationProxy() {
RuntimeHintsUtils.registerAnnotation(this.hints, AliasFor.class);
assertThat(this.hints.reflection().typeHints()).singleElement()
.satisfies(annotationHint(AliasFor.class));
assertThat(this.hints.proxies().jdkProxies()).isEmpty();
void registerAnnotationIfNecessaryWithMetaAttributeOverride() throws NoSuchFieldException {
MergedAnnotation<SampleInvoker> annotation = MergedAnnotations
.from(TestBean.class.getField("retryInvoker")).get(SampleInvoker.class);
RuntimeHintsUtils.registerAnnotationIfNecessary(this.hints, annotation);
assertThat(this.hints.proxies().jdkProxies()).singleElement()
.satisfies(annotationProxy(SampleInvoker.class));
}
@Test
void registerAnnotationTypeWhereUsedAsAMetaAnnotationRegistersHierarchy() {
RuntimeHintsUtils.registerAnnotation(this.hints, RetryWithEnabledFlagInvoker.class);
assertThat(this.hints.reflection().typeHints())
.anySatisfy(annotationHint(RetryWithEnabledFlagInvoker.class))
.anySatisfy(annotationHint(RetryInvoker.class))
.anySatisfy(annotationHint(SampleInvoker.class))
.hasSize(3);
assertThat(this.hints.proxies().jdkProxies())
.anySatisfy(annotationProxy(RetryWithEnabledFlagInvoker.class))
.anySatisfy(annotationProxy(RetryInvoker.class))
.anySatisfy(annotationProxy(SampleInvoker.class))
.hasSize(3);
}
private Consumer<TypeHint> annotationHint(Class<?> type) {
return typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(type));
assertThat(typeHint.constructors()).isEmpty();
assertThat(typeHint.fields()).isEmpty();
assertThat(typeHint.methods()).isEmpty();
assertThat(typeHint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_DECLARED_METHODS);
};
void registerAnnotationIfNecessaryWithSynthesizedAttribute() throws NoSuchFieldException {
MergedAnnotation<RetryContainer> annotation = MergedAnnotations
.from(TestBean.class.getField("retryContainer")).get(RetryContainer.class);
RuntimeHintsUtils.registerAnnotationIfNecessary(this.hints, annotation);
assertThat(this.hints.proxies().jdkProxies()).singleElement()
.satisfies(annotationProxy(RetryContainer.class));
}
private Consumer<JdkProxyHint> annotationProxy(Class<?> type) {
@ -120,6 +90,22 @@ class RuntimeHintsUtilsTests { @@ -120,6 +90,22 @@ class RuntimeHintsUtilsTests {
}
static class TestBean {
@SampleInvoker
public String sampleInvoker;
@LocalMapping
public String localMapping;
@RetryInvoker
public String retryInvoker;
@RetryContainer(retry = @RetryInvoker(3))
public String retryContainer;
}
@Retention(RetentionPolicy.RUNTIME)
@interface LocalMapping {
@ -149,13 +135,9 @@ class RuntimeHintsUtilsTests { @@ -149,13 +135,9 @@ class RuntimeHintsUtilsTests {
}
@Retention(RetentionPolicy.RUNTIME)
@RetryInvoker
@interface RetryWithEnabledFlagInvoker {
@AliasFor(annotation = RetryInvoker.class)
int value() default 5;
@interface RetryContainer {
boolean enabled() default true;
RetryInvoker retry();
}

27
spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java

@ -1504,6 +1504,13 @@ class MergedAnnotationsTests { @@ -1504,6 +1504,13 @@ class MergedAnnotationsTests {
assertThat(synthesizedComponent.value()).isEqualTo("webController");
}
@Test
void isSynthesizableWithoutAttributeAliases() throws Exception {
Component component = WebController.class.getAnnotation(Component.class);
assertThat(component).isNotNull();
assertThat(MergedAnnotation.from(component).isSynthesizable()).isFalse();
}
@Test
void synthesizeAlreadySynthesized() throws Exception {
Method method = WebController.class.getMethod("handleMappedWithValueAttribute");
@ -1567,10 +1574,16 @@ class MergedAnnotationsTests { @@ -1567,10 +1574,16 @@ class MergedAnnotationsTests {
void synthesizeShouldNotSynthesizeNonsynthesizableAnnotationsWhenUsingMergedAnnotationsFromApi() {
MergedAnnotations mergedAnnotations = MergedAnnotations.from(SecurityConfig.class);
EnableWebSecurity enableWebSecurity = mergedAnnotations.get(EnableWebSecurity.class).synthesize();
MergedAnnotation<EnableWebSecurity> enableWebSecurityAnnotation =
mergedAnnotations.get(EnableWebSecurity.class);
assertThat(enableWebSecurityAnnotation.isSynthesizable()).isFalse();
EnableWebSecurity enableWebSecurity = enableWebSecurityAnnotation.synthesize();
assertThat(enableWebSecurity).isNotInstanceOf(SynthesizedAnnotation.class);
EnableGlobalAuthentication enableGlobalAuthentication = mergedAnnotations.get(EnableGlobalAuthentication.class).synthesize();
MergedAnnotation<EnableGlobalAuthentication> enableGlobalAuthenticationMergedAnnotation =
mergedAnnotations.get(EnableGlobalAuthentication.class);
assertThat(enableGlobalAuthenticationMergedAnnotation.isSynthesizable()).isFalse();
EnableGlobalAuthentication enableGlobalAuthentication = enableGlobalAuthenticationMergedAnnotation.synthesize();
assertThat(enableGlobalAuthentication).isNotInstanceOf(SynthesizedAnnotation.class);
}
@ -1718,8 +1731,9 @@ class MergedAnnotationsTests { @@ -1718,8 +1731,9 @@ class MergedAnnotationsTests {
ImplicitAliasesTestConfiguration config = clazz.getAnnotation(
ImplicitAliasesTestConfiguration.class);
assertThat(config).isNotNull();
ImplicitAliasesTestConfiguration synthesized = MergedAnnotation.from(
config).synthesize();
MergedAnnotation<ImplicitAliasesTestConfiguration> mergedAnnotation = MergedAnnotation.from(config);
assertThat(mergedAnnotation.isSynthesizable()).isTrue();
ImplicitAliasesTestConfiguration synthesized = mergedAnnotation.synthesize();
assertThat(synthesized).isInstanceOf(SynthesizedAnnotation.class);
assertThat(synthesized.value()).isEqualTo(expected);
assertThat(synthesized.location1()).isEqualTo(expected);
@ -1746,8 +1760,11 @@ class MergedAnnotationsTests { @@ -1746,8 +1760,11 @@ class MergedAnnotationsTests {
ImplicitAliasesWithImpliedAliasNamesOmittedTestConfiguration config = clazz.getAnnotation(
ImplicitAliasesWithImpliedAliasNamesOmittedTestConfiguration.class);
assertThat(config).isNotNull();
MergedAnnotation<ImplicitAliasesWithImpliedAliasNamesOmittedTestConfiguration> mergedAnnotation =
MergedAnnotation.from(config);
assertThat(mergedAnnotation.isSynthesizable()).isTrue();
ImplicitAliasesWithImpliedAliasNamesOmittedTestConfiguration synthesized =
MergedAnnotation.from(config).synthesize();
mergedAnnotation.synthesize();
assertThat(synthesized).isInstanceOf(SynthesizedAnnotation.class);
assertThat(synthesized.value()).isEqualTo(expected);
assertThat(synthesized.location()).isEqualTo(expected);

5
spring-core/src/test/java/org/springframework/core/annotation/MissingMergedAnnotationTests.java

@ -255,6 +255,11 @@ class MissingMergedAnnotationTests { @@ -255,6 +255,11 @@ class MissingMergedAnnotationTests {
assertThat(this.missing.getDefaultValue("value", Integer.class)).isEmpty();
}
@Test
void isSynthesizableReturnsFalse() {
assertThat(this.missing.isSynthesizable()).isFalse();
}
@Test
void synthesizeThrowsNoSuchElementException() {
assertThatNoSuchElementException().isThrownBy(this.missing::synthesize);

5
spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessagingAnnotationsRuntimeHints.java

@ -35,8 +35,7 @@ public class MessagingAnnotationsRuntimeHints implements RuntimeHintsRegistrar { @@ -35,8 +35,7 @@ public class MessagingAnnotationsRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
Stream.of(Controller.class, DestinationVariable.class, Header.class, Headers.class,
MessageExceptionHandler.class, MessageMapping.class, Payload.class, SendTo.class).forEach(
annotationType -> RuntimeHintsUtils.registerAnnotation(hints, annotationType));
Stream.of(Controller.class, Header.class, Headers.class, Payload.class).forEach(annotationType ->
RuntimeHintsUtils.registerSynthesizedAnnotation(hints, annotationType));
}
}

5
spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/SimpAnnotationsRuntimeHints.java

@ -16,8 +16,6 @@ @@ -16,8 +16,6 @@
package org.springframework.messaging.simp.annotation;
import java.util.stream.Stream;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.support.RuntimeHintsUtils;
@ -33,7 +31,6 @@ public class SimpAnnotationsRuntimeHints implements RuntimeHintsRegistrar { @@ -33,7 +31,6 @@ public class SimpAnnotationsRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
Stream.of(SendToUser.class, SubscribeMapping.class).forEach(
annotationType -> RuntimeHintsUtils.registerAnnotation(hints, annotationType));
RuntimeHintsUtils.registerSynthesizedAnnotation(hints, SendToUser.class);
}
}

2
spring-tx/src/main/java/org/springframework/transaction/annotation/TransactionRuntimeHints.java

@ -37,7 +37,7 @@ class TransactionRuntimeHints implements RuntimeHintsRegistrar { @@ -37,7 +37,7 @@ class TransactionRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
RuntimeHintsUtils.registerAnnotation(hints, Transactional.class);
RuntimeHintsUtils.registerSynthesizedAnnotation(hints, Transactional.class);
hints.reflection()
.registerTypes(List.of(
TypeReference.of(Isolation.class),

15
spring-web/src/main/java/org/springframework/web/bind/annotation/WebAnnotationsRuntimeHintsRegistrar.java

@ -36,15 +36,12 @@ public final class WebAnnotationsRuntimeHintsRegistrar implements RuntimeHintsRe @@ -36,15 +36,12 @@ public final class WebAnnotationsRuntimeHintsRegistrar implements RuntimeHintsRe
@Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
Stream.of(Controller.class, ControllerAdvice.class, CookieValue.class,
CrossOrigin.class, DeleteMapping.class, ExceptionHandler.class,
GetMapping.class, InitBinder.class, Mapping.class, MatrixVariable.class,
ModelAttribute.class, PatchMapping.class, PathVariable.class,
PostMapping.class, PutMapping.class, RequestAttribute.class,
RequestBody.class, RequestHeader.class, RequestMapping.class,
RequestParam.class, RequestPart.class, ResponseBody.class,
ResponseStatus.class, RestController.class, RestControllerAdvice.class,
SessionAttribute.class, SessionAttributes.class).forEach(
annotationType -> RuntimeHintsUtils.registerAnnotation(hints, annotationType));
CrossOrigin.class, MatrixVariable.class, ModelAttribute.class,
PathVariable.class, RequestAttribute.class, RequestHeader.class,
RequestMapping.class, RequestParam.class, RequestPart.class,
ResponseStatus.class, SessionAttribute.class, SessionAttributes.class)
.forEach(annotationType ->
RuntimeHintsUtils.registerSynthesizedAnnotation(hints, annotationType));
}
}

Loading…
Cancel
Save