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.
This commit is contained in:
@@ -20,6 +20,8 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
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
|
||||
* have been published.
|
||||
@@ -29,8 +31,7 @@ import java.lang.annotation.Target;
|
||||
* @since 1.13
|
||||
* @soundtrack Benny Greb - September (Moving Parts Live)
|
||||
*/
|
||||
@Reflective
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
||||
public @interface AfterDomainEventPublication {
|
||||
|
||||
}
|
||||
public @interface AfterDomainEventPublication {}
|
||||
|
||||
@@ -20,6 +20,8 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
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
|
||||
* events returned by that method as Spring application events.
|
||||
@@ -30,7 +32,7 @@ import java.lang.annotation.Target;
|
||||
* @since 1.13
|
||||
* @soundtrack Benny Greb - Soulfood (Moving Parts Live)
|
||||
*/
|
||||
@Reflective
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
||||
public @interface DomainEvents {
|
||||
}
|
||||
public @interface DomainEvents {}
|
||||
|
||||
@@ -16,15 +16,20 @@
|
||||
package org.springframework.data.repository.config;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
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.factory.BeanFactory;
|
||||
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.RootBeanDefinition;
|
||||
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.util.TypeContributor;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -62,7 +68,6 @@ import org.springframework.util.StringUtils;
|
||||
* @author John Blum
|
||||
* @since 3.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class RepositoryRegistrationAotProcessor implements BeanRegistrationAotProcessor, BeanFactoryAware {
|
||||
|
||||
private ConfigurableListableBeanFactory beanFactory;
|
||||
@@ -74,7 +79,6 @@ public class RepositoryRegistrationAotProcessor implements BeanRegistrationAotPr
|
||||
@Nullable
|
||||
@Override
|
||||
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean bean) {
|
||||
|
||||
return isRepositoryBean(bean) ? newRepositoryRegistrationAotContribution(bean) : null;
|
||||
}
|
||||
|
||||
@@ -89,6 +93,28 @@ public class RepositoryRegistrationAotProcessor implements BeanRegistrationAotPr
|
||||
.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) {
|
||||
return getConfigMap().containsKey(bean.getBeanName());
|
||||
}
|
||||
@@ -99,7 +125,9 @@ public class RepositoryRegistrationAotProcessor implements BeanRegistrationAotPr
|
||||
RepositoryRegistrationAotContribution contribution = RepositoryRegistrationAotContribution.fromProcessor(this)
|
||||
.forBean(repositoryBean);
|
||||
|
||||
return contribution.withModuleContribution(this::contribute);
|
||||
BiConsumer<AotRepositoryContext, GenerationContext> moduleContribution = this::registerReflectiveForAggregateRoot;
|
||||
|
||||
return contribution.withModuleContribution(moduleContribution.andThen(this::contribute));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.data.aot;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -51,6 +52,16 @@ public class CodeContributionAssert extends AbstractAssert<CodeContributionAsser
|
||||
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) {
|
||||
|
||||
for (Class<?> type : types) {
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.assertj.core.api.AbstractAssert;
|
||||
import org.junit.jupiter.api.function.ThrowingConsumer;
|
||||
import org.springframework.aot.generate.GenerationContext;
|
||||
import org.springframework.aot.test.generate.TestGenerationContext;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationCode;
|
||||
@@ -106,7 +107,7 @@ public class RepositoryRegistrationAotContributionAssert
|
||||
}
|
||||
|
||||
public RepositoryRegistrationAotContributionAssert codeContributionSatisfies(
|
||||
Consumer<CodeContributionAssert> assertWith) {
|
||||
ThrowingConsumer<CodeContributionAssert> assertWith) {
|
||||
|
||||
BeanRegistrationCode mockBeanRegistrationCode = mock(BeanRegistrationCode.class);
|
||||
|
||||
@@ -114,7 +115,11 @@ public class RepositoryRegistrationAotContributionAssert
|
||||
|
||||
this.actual.applyTo(generationContext, mockBeanRegistrationCode);
|
||||
|
||||
assertWith.accept(new CodeContributionAssert(generationContext));
|
||||
try {
|
||||
assertWith.accept(new CodeContributionAssert(generationContext));
|
||||
} catch (Throwable o_O) {
|
||||
fail(o_O.getMessage(), o_O);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -28,22 +28,21 @@ import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
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.data.aot.sample.ConfigWithCustomImplementation;
|
||||
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.*;
|
||||
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.ConfigWithSimpleCrudRepository;
|
||||
import org.springframework.data.aot.sample.ConfigWithTransactionManagerPresent;
|
||||
import org.springframework.data.aot.sample.ConfigWithTransactionManagerPresentAndAtComponentAnnotatedRepository;
|
||||
import org.springframework.data.aot.sample.QConfigWithQuerydslPredicateExecutor_Person;
|
||||
import org.springframework.data.aot.sample.ReactiveConfig;
|
||||
import org.springframework.data.domain.AbstractAggregateRoot;
|
||||
import org.springframework.data.domain.AfterDomainEventPublication;
|
||||
import org.springframework.data.domain.DomainEvents;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
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.RepositoryRegistrationAotProcessor;
|
||||
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) {
|
||||
return computeAotConfiguration(configuration, new AnnotationConfigApplicationContext());
|
||||
}
|
||||
@@ -333,4 +356,31 @@ public class RepositoryRegistrationAotProcessorIntegrationTests {
|
||||
interface RepositoryRegistrationAotContributionBuilder {
|
||||
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> {}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user