Browse Source

Fix detection of @DomainEvents and @AfterDomainEventPublication on native.

We now unconditionally process the aggregate root types declared on repositories for @Reflective annotations, which @DE and @ADEP got meta-annotated with.

Fixes #2939.
pull/2944/head
Oliver Drotbohm 2 years ago
parent
commit
8328517cc9
No known key found for this signature in database
GPG Key ID: C25FBFA0DA493A1D
  1. 7
      src/main/java/org/springframework/data/domain/AfterDomainEventPublication.java
  2. 6
      src/main/java/org/springframework/data/domain/DomainEvents.java
  3. 38
      src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotProcessor.java
  4. 11
      src/test/java/org/springframework/data/aot/CodeContributionAssert.java
  5. 7
      src/test/java/org/springframework/data/repository/aot/RepositoryRegistrationAotContributionAssert.java
  6. 70
      src/test/java/org/springframework/data/repository/aot/RepositoryRegistrationAotProcessorIntegrationTests.java

7
src/main/java/org/springframework/data/domain/AfterDomainEventPublication.java

@ -20,6 +20,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.springframework.aot.hint.annotation.Reflective;
/** /**
* Annotation to be used on a method of a Spring Data managed aggregate to get invoked after the events of an aggregate * Annotation to be used on a method of a Spring Data managed aggregate to get invoked after the events of an aggregate
* have been published. * have been published.
@ -29,8 +31,7 @@ import java.lang.annotation.Target;
* @since 1.13 * @since 1.13
* @soundtrack Benny Greb - September (Moving Parts Live) * @soundtrack Benny Greb - September (Moving Parts Live)
*/ */
@Reflective
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
public @interface AfterDomainEventPublication { public @interface AfterDomainEventPublication {}
}

6
src/main/java/org/springframework/data/domain/DomainEvents.java

@ -20,6 +20,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.springframework.aot.hint.annotation.Reflective;
/** /**
* {@link DomainEvents} can be used on methods of aggregate roots managed by Spring Data repositories to publish the * {@link DomainEvents} can be used on methods of aggregate roots managed by Spring Data repositories to publish the
* events returned by that method as Spring application events. * events returned by that method as Spring application events.
@ -30,7 +32,7 @@ import java.lang.annotation.Target;
* @since 1.13 * @since 1.13
* @soundtrack Benny Greb - Soulfood (Moving Parts Live) * @soundtrack Benny Greb - Soulfood (Moving Parts Live)
*/ */
@Reflective
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
public @interface DomainEvents { public @interface DomainEvents {}
}

38
src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotProcessor.java

@ -16,15 +16,20 @@
package org.springframework.data.repository.config; package org.springframework.data.repository.config;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Stream;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.aot.generate.GenerationContext; import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.annotation.Reflective;
import org.springframework.aot.hint.annotation.ReflectiveRuntimeHintsRegistrar;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanFactoryAware;
@ -35,8 +40,9 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean; import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.data.util.TypeContributor; import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
import org.springframework.data.util.TypeContributor;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -62,7 +68,6 @@ import org.springframework.util.StringUtils;
* @author John Blum * @author John Blum
* @since 3.0 * @since 3.0
*/ */
@SuppressWarnings("unused")
public class RepositoryRegistrationAotProcessor implements BeanRegistrationAotProcessor, BeanFactoryAware { public class RepositoryRegistrationAotProcessor implements BeanRegistrationAotProcessor, BeanFactoryAware {
private ConfigurableListableBeanFactory beanFactory; private ConfigurableListableBeanFactory beanFactory;
@ -74,7 +79,6 @@ public class RepositoryRegistrationAotProcessor implements BeanRegistrationAotPr
@Nullable @Nullable
@Override @Override
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean bean) { public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean bean) {
return isRepositoryBean(bean) ? newRepositoryRegistrationAotContribution(bean) : null; return isRepositoryBean(bean) ? newRepositoryRegistrationAotContribution(bean) : null;
} }
@ -89,6 +93,28 @@ public class RepositoryRegistrationAotProcessor implements BeanRegistrationAotPr
.forEach(it -> contributeType(it, generationContext)); .forEach(it -> contributeType(it, generationContext));
} }
/**
* Processes the repository's domain and alternative domain types to consider {@link Reflective} annotations used on
* it.
*
* @param repositoryContext must not be {@literal null}.
* @param generationContext must not be {@literal null}.
*/
private void registerReflectiveForAggregateRoot(AotRepositoryContext repositoryContext,
GenerationContext generationContext) {
RepositoryInformation information = repositoryContext.getRepositoryInformation();
ReflectiveRuntimeHintsRegistrar registrar = new ReflectiveRuntimeHintsRegistrar();
RuntimeHints hints = generationContext.getRuntimeHints();
List<Class<?>> aggregateRootTypes = new ArrayList<>();
aggregateRootTypes.add(information.getDomainType());
aggregateRootTypes.addAll(information.getAlternativeDomainTypes());
Stream.concat(Stream.of(information.getDomainType()), information.getAlternativeDomainTypes().stream())
.forEach(it -> registrar.registerRuntimeHints(hints, it));
}
private boolean isRepositoryBean(RegisteredBean bean) { private boolean isRepositoryBean(RegisteredBean bean) {
return getConfigMap().containsKey(bean.getBeanName()); return getConfigMap().containsKey(bean.getBeanName());
} }
@ -99,7 +125,9 @@ public class RepositoryRegistrationAotProcessor implements BeanRegistrationAotPr
RepositoryRegistrationAotContribution contribution = RepositoryRegistrationAotContribution.fromProcessor(this) RepositoryRegistrationAotContribution contribution = RepositoryRegistrationAotContribution.fromProcessor(this)
.forBean(repositoryBean); .forBean(repositoryBean);
return contribution.withModuleContribution(this::contribute); BiConsumer<AotRepositoryContext, GenerationContext> moduleContribution = this::registerReflectiveForAggregateRoot;
return contribution.withModuleContribution(moduleContribution.andThen(this::contribute));
} }
@Override @Override

11
src/test/java/org/springframework/data/aot/CodeContributionAssert.java

@ -17,6 +17,7 @@ package org.springframework.data.aot;
import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.Assertions.*;
import java.lang.reflect.Method;
import java.util.Arrays; import java.util.Arrays;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -50,6 +51,16 @@ public class CodeContributionAssert extends AbstractAssert<CodeContributionAsser
return this; return this;
} }
public CodeContributionAssert contributesReflectionFor(Method... methods) {
for (Method method : methods) {
assertThat(this.actual.getRuntimeHints()).describedAs("No reflection entry found for [%s]", method)
.matches(RuntimeHintsPredicates.reflection().onMethod(method));
}
return this;
}
public CodeContributionAssert doesNotContributeReflectionFor(Class<?>... types) { public CodeContributionAssert doesNotContributeReflectionFor(Class<?>... types) {
for (Class<?> type : types) { for (Class<?> type : types) {

7
src/test/java/org/springframework/data/repository/aot/RepositoryRegistrationAotContributionAssert.java

@ -23,6 +23,7 @@ import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.assertj.core.api.AbstractAssert; import org.assertj.core.api.AbstractAssert;
import org.junit.jupiter.api.function.ThrowingConsumer;
import org.springframework.aot.generate.GenerationContext; import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.test.generate.TestGenerationContext; import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.factory.aot.BeanRegistrationCode; import org.springframework.beans.factory.aot.BeanRegistrationCode;
@ -106,7 +107,7 @@ public class RepositoryRegistrationAotContributionAssert
} }
public RepositoryRegistrationAotContributionAssert codeContributionSatisfies( public RepositoryRegistrationAotContributionAssert codeContributionSatisfies(
Consumer<CodeContributionAssert> assertWith) { ThrowingConsumer<CodeContributionAssert> assertWith) {
BeanRegistrationCode mockBeanRegistrationCode = mock(BeanRegistrationCode.class); BeanRegistrationCode mockBeanRegistrationCode = mock(BeanRegistrationCode.class);
@ -114,7 +115,11 @@ public class RepositoryRegistrationAotContributionAssert
this.actual.applyTo(generationContext, mockBeanRegistrationCode); this.actual.applyTo(generationContext, mockBeanRegistrationCode);
try {
assertWith.accept(new CodeContributionAssert(generationContext)); assertWith.accept(new CodeContributionAssert(generationContext));
} catch (Throwable o_O) {
fail(o_O.getMessage(), o_O);
}
return this; return this;
} }

70
src/test/java/org/springframework/data/repository/aot/RepositoryRegistrationAotProcessorIntegrationTests.java

@ -28,22 +28,21 @@ import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean; import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.core.DecoratingProxy; import org.springframework.core.DecoratingProxy;
import org.springframework.data.aot.sample.ConfigWithCustomImplementation; import org.springframework.data.aot.sample.*;
import org.springframework.data.aot.sample.ConfigWithCustomRepositoryBaseClass;
import org.springframework.data.aot.sample.ConfigWithFragments;
import org.springframework.data.aot.sample.ConfigWithQueryMethods;
import org.springframework.data.aot.sample.ConfigWithQueryMethods.ProjectionInterface; import org.springframework.data.aot.sample.ConfigWithQueryMethods.ProjectionInterface;
import org.springframework.data.aot.sample.ConfigWithQuerydslPredicateExecutor;
import org.springframework.data.aot.sample.ConfigWithQuerydslPredicateExecutor.Person; import org.springframework.data.aot.sample.ConfigWithQuerydslPredicateExecutor.Person;
import org.springframework.data.aot.sample.ConfigWithSimpleCrudRepository; import org.springframework.data.domain.AbstractAggregateRoot;
import org.springframework.data.aot.sample.ConfigWithTransactionManagerPresent; import org.springframework.data.domain.AfterDomainEventPublication;
import org.springframework.data.aot.sample.ConfigWithTransactionManagerPresentAndAtComponentAnnotatedRepository; import org.springframework.data.domain.DomainEvents;
import org.springframework.data.aot.sample.QConfigWithQuerydslPredicateExecutor_Person;
import org.springframework.data.aot.sample.ReactiveConfig;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.Repository; import org.springframework.data.repository.Repository;
import org.springframework.data.repository.aot.RepositoryRegistrationAotProcessorIntegrationTests.EventPublicationConfiguration.Sample;
import org.springframework.data.repository.aot.RepositoryRegistrationAotProcessorIntegrationTests.EventPublicationConfiguration.SampleRepository;
import org.springframework.data.repository.config.EnableRepositories;
import org.springframework.data.repository.config.RepositoryRegistrationAotContribution; import org.springframework.data.repository.config.RepositoryRegistrationAotContribution;
import org.springframework.data.repository.config.RepositoryRegistrationAotProcessor; import org.springframework.data.repository.config.RepositoryRegistrationAotProcessor;
import org.springframework.data.repository.reactive.ReactiveSortingRepository; import org.springframework.data.repository.reactive.ReactiveSortingRepository;
@ -292,6 +291,30 @@ public class RepositoryRegistrationAotProcessorIntegrationTests {
}); });
} }
@Test // GH-2939
void registersReflectionForDomainPublicationAnnotations() {
RepositoryRegistrationAotContribution contribution = computeAotConfiguration(EventPublicationConfiguration.class)
.forRepository(SampleRepository.class);
assertThatContribution(contribution).codeContributionSatisfies(it -> {
it.contributesReflectionFor(Sample.class.getDeclaredMethod("publication"));
it.contributesReflectionFor(Sample.class.getDeclaredMethod("cleanup"));
});
}
@Test // GH-2939
void registersReflectionForInheritedDomainPublicationAnnotations() {
RepositoryRegistrationAotContribution contribution = computeAotConfiguration(
InheritedEventPublicationConfiguration.class)
.forRepository(InheritedEventPublicationConfiguration.SampleRepository.class);
assertThatContribution(contribution).codeContributionSatisfies(it -> {
it.contributesReflectionFor(AbstractAggregateRoot.class);
});
}
RepositoryRegistrationAotContributionBuilder computeAotConfiguration(Class<?> configuration) { RepositoryRegistrationAotContributionBuilder computeAotConfiguration(Class<?> configuration) {
return computeAotConfiguration(configuration, new AnnotationConfigApplicationContext()); return computeAotConfiguration(configuration, new AnnotationConfigApplicationContext());
} }
@ -333,4 +356,31 @@ public class RepositoryRegistrationAotProcessorIntegrationTests {
interface RepositoryRegistrationAotContributionBuilder { interface RepositoryRegistrationAotContributionBuilder {
RepositoryRegistrationAotContribution forRepository(Class<?> repositoryInterface); RepositoryRegistrationAotContribution forRepository(Class<?> repositoryInterface);
} }
@EnableRepositories(includeFilters = { @Filter(type = FilterType.ASSIGNABLE_TYPE, value = SampleRepository.class) },
considerNestedRepositories = true)
public class EventPublicationConfiguration {
static class Sample {
@DomainEvents
void publication() {}
@AfterDomainEventPublication
void cleanup() {}
}
interface SampleRepository extends Repository<Sample, Object> {}
}
@EnableRepositories(
includeFilters = { @Filter(type = FilterType.ASSIGNABLE_TYPE,
value = InheritedEventPublicationConfiguration.SampleRepository.class) },
considerNestedRepositories = true)
public class InheritedEventPublicationConfiguration {
static class Sample extends AbstractAggregateRoot<Sample> {}
interface SampleRepository extends Repository<Sample, Object> {}
}
} }

Loading…
Cancel
Save