Skip to content

Commit 9bd0abb

Browse files
committed
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.
1 parent 880b141 commit 9bd0abb

File tree

6 files changed

+119
-22
lines changed

6 files changed

+119
-22
lines changed

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

+4-3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import java.lang.annotation.RetentionPolicy;
2121
import java.lang.annotation.Target;
2222

23+
import org.springframework.aot.hint.annotation.Reflective;
24+
2325
/**
2426
* Annotation to be used on a method of a Spring Data managed aggregate to get invoked after the events of an aggregate
2527
* have been published.
@@ -29,8 +31,7 @@
2931
* @since 1.13
3032
* @soundtrack Benny Greb - September (Moving Parts Live)
3133
*/
34+
@Reflective
3235
@Retention(RetentionPolicy.RUNTIME)
3336
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
34-
public @interface AfterDomainEventPublication {
35-
36-
}
37+
public @interface AfterDomainEventPublication {}

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import java.lang.annotation.RetentionPolicy;
2121
import java.lang.annotation.Target;
2222

23+
import org.springframework.aot.hint.annotation.Reflective;
24+
2325
/**
2426
* {@link DomainEvents} can be used on methods of aggregate roots managed by Spring Data repositories to publish the
2527
* events returned by that method as Spring application events.
@@ -30,7 +32,7 @@
3032
* @since 1.13
3133
* @soundtrack Benny Greb - Soulfood (Moving Parts Live)
3234
*/
35+
@Reflective
3336
@Retention(RetentionPolicy.RUNTIME)
3437
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
35-
public @interface DomainEvents {
36-
}
38+
public @interface DomainEvents {}

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

+33-5
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,20 @@
1616
package org.springframework.data.repository.config;
1717

1818
import java.lang.annotation.Annotation;
19+
import java.util.ArrayList;
1920
import java.util.Collections;
21+
import java.util.List;
2022
import java.util.Map;
2123
import java.util.function.BiConsumer;
2224
import java.util.function.Predicate;
25+
import java.util.stream.Stream;
2326

2427
import org.apache.commons.logging.Log;
2528
import org.apache.commons.logging.LogFactory;
26-
2729
import org.springframework.aot.generate.GenerationContext;
30+
import org.springframework.aot.hint.RuntimeHints;
31+
import org.springframework.aot.hint.annotation.Reflective;
32+
import org.springframework.aot.hint.annotation.ReflectiveRuntimeHintsRegistrar;
2833
import org.springframework.beans.BeansException;
2934
import org.springframework.beans.factory.BeanFactory;
3035
import org.springframework.beans.factory.BeanFactoryAware;
@@ -35,8 +40,9 @@
3540
import org.springframework.beans.factory.support.RegisteredBean;
3641
import org.springframework.beans.factory.support.RootBeanDefinition;
3742
import org.springframework.core.annotation.MergedAnnotation;
38-
import org.springframework.data.util.TypeContributor;
43+
import org.springframework.data.repository.core.RepositoryInformation;
3944
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
45+
import org.springframework.data.util.TypeContributor;
4046
import org.springframework.lang.Nullable;
4147
import org.springframework.util.Assert;
4248
import org.springframework.util.StringUtils;
@@ -62,7 +68,6 @@
6268
* @author John Blum
6369
* @since 3.0
6470
*/
65-
@SuppressWarnings("unused")
6671
public class RepositoryRegistrationAotProcessor implements BeanRegistrationAotProcessor, BeanFactoryAware {
6772

6873
private ConfigurableListableBeanFactory beanFactory;
@@ -74,7 +79,6 @@ public class RepositoryRegistrationAotProcessor implements BeanRegistrationAotPr
7479
@Nullable
7580
@Override
7681
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean bean) {
77-
7882
return isRepositoryBean(bean) ? newRepositoryRegistrationAotContribution(bean) : null;
7983
}
8084

@@ -89,6 +93,28 @@ protected void contribute(AotRepositoryContext repositoryContext, GenerationCont
8993
.forEach(it -> contributeType(it, generationContext));
9094
}
9195

96+
/**
97+
* Processes the repository's domain and alternative domain types to consider {@link Reflective} annotations used on
98+
* it.
99+
*
100+
* @param repositoryContext must not be {@literal null}.
101+
* @param generationContext must not be {@literal null}.
102+
*/
103+
private void registerReflectiveForAggregateRoot(AotRepositoryContext repositoryContext,
104+
GenerationContext generationContext) {
105+
106+
RepositoryInformation information = repositoryContext.getRepositoryInformation();
107+
ReflectiveRuntimeHintsRegistrar registrar = new ReflectiveRuntimeHintsRegistrar();
108+
RuntimeHints hints = generationContext.getRuntimeHints();
109+
110+
List<Class<?>> aggregateRootTypes = new ArrayList<>();
111+
aggregateRootTypes.add(information.getDomainType());
112+
aggregateRootTypes.addAll(information.getAlternativeDomainTypes());
113+
114+
Stream.concat(Stream.of(information.getDomainType()), information.getAlternativeDomainTypes().stream())
115+
.forEach(it -> registrar.registerRuntimeHints(hints, it));
116+
}
117+
92118
private boolean isRepositoryBean(RegisteredBean bean) {
93119
return getConfigMap().containsKey(bean.getBeanName());
94120
}
@@ -99,7 +125,9 @@ protected RepositoryRegistrationAotContribution newRepositoryRegistrationAotCont
99125
RepositoryRegistrationAotContribution contribution = RepositoryRegistrationAotContribution.fromProcessor(this)
100126
.forBean(repositoryBean);
101127

102-
return contribution.withModuleContribution(this::contribute);
128+
BiConsumer<AotRepositoryContext, GenerationContext> moduleContribution = this::registerReflectiveForAggregateRoot;
129+
130+
return contribution.withModuleContribution(moduleContribution.andThen(this::contribute));
103131
}
104132

105133
@Override

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

+11
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import static org.assertj.core.api.Assertions.*;
1919

20+
import java.lang.reflect.Method;
2021
import java.util.Arrays;
2122
import java.util.stream.Stream;
2223

@@ -51,6 +52,16 @@ public CodeContributionAssert contributesReflectionFor(Class<?>... types) {
5152
return this;
5253
}
5354

55+
public CodeContributionAssert contributesReflectionFor(Method... methods) {
56+
57+
for (Method method : methods) {
58+
assertThat(this.actual.getRuntimeHints()).describedAs("No reflection entry found for [%s]", method)
59+
.matches(RuntimeHintsPredicates.reflection().onMethod(method));
60+
}
61+
62+
return this;
63+
}
64+
5465
public CodeContributionAssert doesNotContributeReflectionFor(Class<?>... types) {
5566

5667
for (Class<?> type : types) {

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

+7-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.function.Consumer;
2424

2525
import org.assertj.core.api.AbstractAssert;
26+
import org.junit.jupiter.api.function.ThrowingConsumer;
2627
import org.springframework.aot.generate.GenerationContext;
2728
import org.springframework.aot.test.generate.TestGenerationContext;
2829
import org.springframework.beans.factory.aot.BeanRegistrationCode;
@@ -106,15 +107,19 @@ public RepositoryRegistrationAotContributionAssert verifyFragments(Consumer<Set<
106107
}
107108

108109
public RepositoryRegistrationAotContributionAssert codeContributionSatisfies(
109-
Consumer<CodeContributionAssert> assertWith) {
110+
ThrowingConsumer<CodeContributionAssert> assertWith) {
110111

111112
BeanRegistrationCode mockBeanRegistrationCode = mock(BeanRegistrationCode.class);
112113

113114
GenerationContext generationContext = new TestGenerationContext(Object.class);
114115

115116
this.actual.applyTo(generationContext, mockBeanRegistrationCode);
116117

117-
assertWith.accept(new CodeContributionAssert(generationContext));
118+
try {
119+
assertWith.accept(new CodeContributionAssert(generationContext));
120+
} catch (Throwable o_O) {
121+
fail(o_O.getMessage(), o_O);
122+
}
118123

119124
return this;
120125
}

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

+60-10
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,21 @@
2828
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
2929
import org.springframework.beans.factory.support.RegisteredBean;
3030
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
31+
import org.springframework.context.annotation.ComponentScan.Filter;
32+
import org.springframework.context.annotation.FilterType;
3133
import org.springframework.core.DecoratingProxy;
32-
import org.springframework.data.aot.sample.ConfigWithCustomImplementation;
33-
import org.springframework.data.aot.sample.ConfigWithCustomRepositoryBaseClass;
34-
import org.springframework.data.aot.sample.ConfigWithFragments;
35-
import org.springframework.data.aot.sample.ConfigWithQueryMethods;
34+
import org.springframework.data.aot.sample.*;
3635
import org.springframework.data.aot.sample.ConfigWithQueryMethods.ProjectionInterface;
37-
import org.springframework.data.aot.sample.ConfigWithQuerydslPredicateExecutor;
3836
import org.springframework.data.aot.sample.ConfigWithQuerydslPredicateExecutor.Person;
39-
import org.springframework.data.aot.sample.ConfigWithSimpleCrudRepository;
40-
import org.springframework.data.aot.sample.ConfigWithTransactionManagerPresent;
41-
import org.springframework.data.aot.sample.ConfigWithTransactionManagerPresentAndAtComponentAnnotatedRepository;
42-
import org.springframework.data.aot.sample.QConfigWithQuerydslPredicateExecutor_Person;
43-
import org.springframework.data.aot.sample.ReactiveConfig;
37+
import org.springframework.data.domain.AbstractAggregateRoot;
38+
import org.springframework.data.domain.AfterDomainEventPublication;
39+
import org.springframework.data.domain.DomainEvents;
4440
import org.springframework.data.domain.Page;
4541
import org.springframework.data.repository.PagingAndSortingRepository;
4642
import org.springframework.data.repository.Repository;
43+
import org.springframework.data.repository.aot.RepositoryRegistrationAotProcessorIntegrationTests.EventPublicationConfiguration.Sample;
44+
import org.springframework.data.repository.aot.RepositoryRegistrationAotProcessorIntegrationTests.EventPublicationConfiguration.SampleRepository;
45+
import org.springframework.data.repository.config.EnableRepositories;
4746
import org.springframework.data.repository.config.RepositoryRegistrationAotContribution;
4847
import org.springframework.data.repository.config.RepositoryRegistrationAotProcessor;
4948
import org.springframework.data.repository.reactive.ReactiveSortingRepository;
@@ -292,6 +291,30 @@ void registersQTypeIfPresent() {
292291
});
293292
}
294293

294+
@Test // GH-2939
295+
void registersReflectionForDomainPublicationAnnotations() {
296+
297+
RepositoryRegistrationAotContribution contribution = computeAotConfiguration(EventPublicationConfiguration.class)
298+
.forRepository(SampleRepository.class);
299+
300+
assertThatContribution(contribution).codeContributionSatisfies(it -> {
301+
it.contributesReflectionFor(Sample.class.getDeclaredMethod("publication"));
302+
it.contributesReflectionFor(Sample.class.getDeclaredMethod("cleanup"));
303+
});
304+
}
305+
306+
@Test // GH-2939
307+
void registersReflectionForInheritedDomainPublicationAnnotations() {
308+
309+
RepositoryRegistrationAotContribution contribution = computeAotConfiguration(
310+
InheritedEventPublicationConfiguration.class)
311+
.forRepository(InheritedEventPublicationConfiguration.SampleRepository.class);
312+
313+
assertThatContribution(contribution).codeContributionSatisfies(it -> {
314+
it.contributesReflectionFor(AbstractAggregateRoot.class);
315+
});
316+
}
317+
295318
RepositoryRegistrationAotContributionBuilder computeAotConfiguration(Class<?> configuration) {
296319
return computeAotConfiguration(configuration, new AnnotationConfigApplicationContext());
297320
}
@@ -333,4 +356,31 @@ RepositoryRegistrationAotContributionBuilder computeAotConfiguration(Class<?> co
333356
interface RepositoryRegistrationAotContributionBuilder {
334357
RepositoryRegistrationAotContribution forRepository(Class<?> repositoryInterface);
335358
}
359+
360+
@EnableRepositories(includeFilters = { @Filter(type = FilterType.ASSIGNABLE_TYPE, value = SampleRepository.class) },
361+
considerNestedRepositories = true)
362+
public class EventPublicationConfiguration {
363+
364+
static class Sample {
365+
366+
@DomainEvents
367+
void publication() {}
368+
369+
@AfterDomainEventPublication
370+
void cleanup() {}
371+
}
372+
373+
interface SampleRepository extends Repository<Sample, Object> {}
374+
}
375+
376+
@EnableRepositories(
377+
includeFilters = { @Filter(type = FilterType.ASSIGNABLE_TYPE,
378+
value = InheritedEventPublicationConfiguration.SampleRepository.class) },
379+
considerNestedRepositories = true)
380+
public class InheritedEventPublicationConfiguration {
381+
382+
static class Sample extends AbstractAggregateRoot<Sample> {}
383+
384+
interface SampleRepository extends Repository<Sample, Object> {}
385+
}
336386
}

0 commit comments

Comments
 (0)