Skip to content

Commit 97906d4

Browse files
authored
Merge pull request #197 from AxonFramework/fix-native-reachability-hints
Added native-compilation hints for reflection
2 parents 8e356c2 + 571298a commit 97906d4

File tree

8 files changed

+167
-27
lines changed

8 files changed

+167
-27
lines changed

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@
211211
<configuration>
212212
<source>${maven.compiler.source}</source>
213213
<target>${maven.compiler.target}</target>
214+
<encoding>UTF-8</encoding>
214215
</configuration>
215216
</plugin>
216217
<!-- test -->

src/main/java/org/axonframework/springboot/aot/MessageHandlerRuntimeHintsRegistrar.java

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,17 @@
1616

1717
package org.axonframework.springboot.aot;
1818

19+
import org.axonframework.common.Priority;
20+
import org.axonframework.common.ReflectionUtils;
21+
import org.axonframework.common.annotation.AnnotationUtils;
1922
import org.axonframework.messaging.Message;
2023
import org.axonframework.messaging.annotation.AnnotatedHandlerInspector;
24+
import org.axonframework.messaging.annotation.ClasspathParameterResolverFactory;
2125
import org.axonframework.messaging.annotation.MessageHandlingMember;
26+
import org.axonframework.messaging.annotation.MultiParameterResolverFactory;
27+
import org.axonframework.messaging.annotation.ParameterResolver;
28+
import org.axonframework.messaging.annotation.ParameterResolverFactory;
29+
import org.axonframework.modelling.command.AggregateMember;
2230
import org.axonframework.queryhandling.annotation.QueryHandlingMember;
2331
import org.axonframework.spring.config.MessageHandlerLookup;
2432
import org.springframework.aot.generate.GenerationContext;
@@ -31,10 +39,17 @@
3139
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
3240

3341
import java.lang.reflect.Constructor;
42+
import java.lang.reflect.Executable;
3443
import java.lang.reflect.Method;
44+
import java.lang.reflect.Parameter;
3545
import java.util.Collection;
46+
import java.util.HashSet;
3647
import java.util.List;
48+
import java.util.Map;
49+
import java.util.Optional;
50+
import java.util.Set;
3751
import java.util.stream.Collectors;
52+
import java.util.stream.Stream;
3853

3954
/**
4055
* BeanFactoryInitializationAotProcessor that registers message handler methods declared on beans for reflection. This
@@ -50,19 +65,59 @@ public class MessageHandlerRuntimeHintsRegistrar implements BeanFactoryInitializ
5065

5166
@Override
5267
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
53-
List<Class<?>> messageHandlingClasses =
68+
Set<Class<?>> messageHandlingClasses =
5469
MessageHandlerLookup.messageHandlerBeans(messageType(), beanFactory, true)
5570
.stream()
5671
.map(beanFactory::getType)
57-
.distinct()
58-
.collect(Collectors.toList());
59-
List<MessageHandlingMember<?>> messageHandlingMembers = messageHandlingClasses
72+
.collect(Collectors.toSet());
73+
74+
Set<Class<?>> detectedClasses = new HashSet<>();
75+
messageHandlingClasses.forEach(c -> registerAggregateMembers(c, detectedClasses));
76+
77+
List<MessageHandlingMember<?>> messageHandlingMembers = detectedClasses
6078
.stream()
61-
.flatMap(beanType -> AnnotatedHandlerInspector.inspectType(beanType).getAllHandlers().values()
62-
.stream())
79+
.flatMap(beanType ->
80+
{
81+
AnnotatedHandlerInspector<?> inspector = AnnotatedHandlerInspector.inspectType(
82+
beanType,
83+
MultiParameterResolverFactory.ordered(
84+
ClasspathParameterResolverFactory.forClass(beanType),
85+
new LenientParameterResolver()
86+
));
87+
return Stream.concat(inspector.getAllHandlers().values().stream(),
88+
inspector.getAllInterceptors().values().stream());
89+
})
6390
.flatMap(Collection::stream)
6491
.collect(Collectors.toList());
65-
return new MessageHandlerContribution(messageHandlingClasses, messageHandlingMembers);
92+
return new MessageHandlerContribution(detectedClasses, messageHandlingMembers);
93+
}
94+
95+
private void registerAggregateMembers(Class<?> entityType, Set<Class<?>> reflectiveClasses) {
96+
if (!reflectiveClasses.add(entityType)) {
97+
return;
98+
}
99+
100+
ReflectionUtils.fieldsOf(entityType).forEach(field -> {
101+
Optional<Map<String, Object>> annotationAttributes = AnnotationUtils.findAnnotationAttributes(field,
102+
AggregateMember.class);
103+
if (annotationAttributes.isPresent()) {
104+
Class<?> declaredType = (Class<?>) annotationAttributes.get().get("type");
105+
Class<?> forwardingMode = (Class<?>) annotationAttributes.get().get("eventForwardingMode");
106+
reflectiveClasses.add(forwardingMode);
107+
108+
if (declaredType != Void.class) {
109+
registerAggregateMembers(declaredType, reflectiveClasses);
110+
} else if (Map.class.isAssignableFrom(field.getType())) {
111+
Optional<Class<?>> type = ReflectionUtils.resolveMemberGenericType(field, 1);
112+
type.ifPresent(t -> registerAggregateMembers(t, reflectiveClasses));
113+
} else if (Collection.class.isAssignableFrom(field.getType())) {
114+
Optional<Class<?>> type = ReflectionUtils.resolveMemberGenericType(field, 0);
115+
type.ifPresent(t -> registerAggregateMembers(t, reflectiveClasses));
116+
} else {
117+
registerAggregateMembers(field.getType(), reflectiveClasses);
118+
}
119+
}
120+
});
66121
}
67122

68123
/**
@@ -80,14 +135,13 @@ private static class MessageHandlerContribution implements BeanFactoryInitializa
80135

81136
private final BindingReflectionHintsRegistrar registrar = new BindingReflectionHintsRegistrar();
82137

83-
private final List<Class<?>> messageHandlingClasses;
138+
private final Set<Class<?>> messageHandlingClasses;
84139

85140
private final List<MessageHandlingMember<?>> messageHandlingMembers;
86141

87142
public MessageHandlerContribution(
88-
List<Class<?>> messageHandlingClasses,
89-
List<MessageHandlingMember<?>> messageHandlingMembers
90-
) {
143+
Set<Class<?>> messageHandlingClasses,
144+
List<MessageHandlingMember<?>> messageHandlingMembers) {
91145
this.messageHandlingClasses = messageHandlingClasses;
92146
this.messageHandlingMembers = messageHandlingMembers;
93147
}
@@ -108,4 +162,26 @@ public void applyTo(GenerationContext generationContext,
108162
});
109163
}
110164
}
165+
166+
@Priority(Priority.LAST)
167+
private static class LenientParameterResolver implements ParameterResolverFactory, ParameterResolver<Object> {
168+
169+
@Override
170+
public ParameterResolver<Object> createInstance(Executable executable,
171+
Parameter[] parameters,
172+
int parameterIndex) {
173+
return this;
174+
}
175+
176+
@Override
177+
public Object resolveParameterValue(Message message) {
178+
throw new UnsupportedOperationException(
179+
"This parameter resolver is not mean for production use. Only for detecting handler methods.");
180+
}
181+
182+
@Override
183+
public boolean matches(Message message) {
184+
return true;
185+
}
186+
}
111187
}

src/test/java/com/axoniq/someproject/App.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,14 @@
1717
package com.axoniq.someproject;
1818

1919
import org.springframework.boot.autoconfigure.SpringBootApplication;
20+
import org.springframework.context.annotation.Bean;
2021

2122
@SpringBootApplication
2223
public class App {
2324

25+
@Bean
26+
public SomeBean springBean() {
27+
return new SomeBean();
28+
}
29+
2430
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright (c) 2010-2024. Axon Framework
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.axoniq.someproject;
18+
19+
public class SomeBean {
20+
21+
}

src/test/java/com/axoniq/someproject/something/SingleAggregateChild.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,25 @@
1616

1717
package com.axoniq.someproject.something;
1818

19+
import com.axoniq.someproject.SomeBean;
1920
import com.axoniq.someproject.api.SingleChildCommand;
20-
import com.axoniq.someproject.api.SomeChildCommand;
2121
import org.axonframework.commandhandling.CommandHandler;
22+
import org.axonframework.messaging.InterceptorChain;
23+
import org.axonframework.modelling.command.CommandHandlerInterceptor;
2224
import org.axonframework.modelling.command.EntityId;
2325

2426
public record SingleAggregateChild(
2527
@EntityId String id,
2628
String property
2729
) {
2830

31+
@CommandHandlerInterceptor
32+
public Object intercept(InterceptorChain chain) throws Exception {
33+
return chain.proceed();
34+
}
35+
2936
@CommandHandler
30-
public void handle(SingleChildCommand command) {
37+
public void handle(SingleChildCommand command, SomeBean someBean) {
3138
//left empty to not overcomplicate things
3239
}
3340
}

src/test/java/com/axoniq/someproject/something/SomeAggregate.java

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,14 @@
2626
import com.axoniq.someproject.api.StatusChangedEvent;
2727
import org.axonframework.commandhandling.CommandHandler;
2828
import org.axonframework.eventsourcing.EventSourcingHandler;
29+
import org.axonframework.messaging.InterceptorChain;
30+
import org.axonframework.messaging.Message;
31+
import org.axonframework.messaging.interceptors.ExceptionHandler;
2932
import org.axonframework.modelling.command.AggregateIdentifier;
3033
import org.axonframework.modelling.command.AggregateMember;
3134
import org.axonframework.modelling.command.AggregateRoot;
35+
import org.axonframework.modelling.command.CommandHandlerInterceptor;
36+
import org.axonframework.modelling.command.ForwardMatchingInstances;
3237

3338
import java.util.ArrayList;
3439
import java.util.HashMap;
@@ -41,24 +46,35 @@
4146
@AggregateRoot(type = "some_aggregate")
4247
public class SomeAggregate {
4348

49+
@AggregateMember(eventForwardingMode = ForwardMatchingInstances.class)
50+
private final List<SomeAggregateChild> childList = new ArrayList<>();
51+
@AggregateMember
52+
private final Map<String, SomeAggregateChild> childMap = new HashMap<>();
4453
@AggregateIdentifier
4554
private String id;
4655
private String status;
47-
4856
@AggregateMember
4957
private SingleAggregateChild child;
5058

51-
@AggregateMember
52-
private final List<SomeAggregateChild> childList = new ArrayList<>();
53-
54-
@AggregateMember
55-
private final Map<String, SomeAggregateChild> childMap = new HashMap<>();
56-
5759
@CommandHandler
5860
public SomeAggregate(SomeCommand command) {
5961
apply(new SomeEvent(command.id()));
6062
}
6163

64+
public SomeAggregate() {
65+
// Required by Axon to construct an empty instance to initiate Event Sourcing.
66+
}
67+
68+
@ExceptionHandler
69+
public void exceptionHandler(Exception error) throws Exception {
70+
throw error;
71+
}
72+
73+
@CommandHandlerInterceptor
74+
public Object intercept(Message<?> message, InterceptorChain chain) throws Exception {
75+
return chain.proceed();
76+
}
77+
6278
@CommandHandler
6379
public void handle(ChangeStatusCommand command) {
6480
if (Objects.equals(status, command.newStatus())) {
@@ -96,8 +112,4 @@ protected void onAddedToList(ChildAddedToListEvent event) {
96112
protected void onAddedToMap(ChildAddedToMapEvent event) {
97113
this.childMap.put(event.key(), new SomeAggregateChild(event.id(), event.property()));
98114
}
99-
100-
public SomeAggregate() {
101-
// Required by Axon to construct an empty instance to initiate Event Sourcing.
102-
}
103115
}

src/test/java/com/axoniq/someproject/something/SomeAggregateChild.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.axoniq.someproject.something;
1818

19+
import com.axoniq.someproject.SomeBean;
1920
import com.axoniq.someproject.api.SomeChildCommand;
2021
import org.axonframework.commandhandling.CommandHandler;
2122
import org.axonframework.modelling.command.EntityId;
@@ -26,7 +27,7 @@ public record SomeAggregateChild(
2627
) {
2728

2829
@CommandHandler
29-
public void handle(SomeChildCommand command) {
30+
public void handle(SomeChildCommand command, SomeBean someBean) {
3031
//left empty to not overcomplicate things
3132
}
3233
}

src/test/java/org/axonframework/springboot/aot/MessageHandlerRuntimeHintsRegistrarTest.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import com.axoniq.someproject.something.SomeAggregateChild;
3131
import com.axoniq.someproject.something.SomeProjectionWithGroupAnnotation;
3232
import com.axoniq.someproject.something.SomeProjectionWithoutGroupAnnotation;
33+
import org.axonframework.modelling.command.ForwardMatchingInstances;
34+
import org.axonframework.modelling.command.ForwardToAll;
3335
import org.junit.jupiter.api.*;
3436
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
3537
import org.springframework.aot.test.generate.TestGenerationContext;
@@ -51,8 +53,6 @@ class MessageHandlerRuntimeHintsRegistrarTest {
5153
@BeforeEach
5254
void processAheadOfTime() {
5355
addClassToBeanFactory(SomeAggregate.class);
54-
addClassToBeanFactory(SingleAggregateChild.class);
55-
addClassToBeanFactory(SomeAggregateChild.class);
5656
addClassToBeanFactory(SomeProjectionWithGroupAnnotation.class);
5757
addClassToBeanFactory(SomeProjectionWithoutGroupAnnotation.class);
5858
new ApplicationContextAotGenerator().processAheadOfTime(this.applicationContext, this.generationContext);
@@ -87,6 +87,22 @@ void handlerMethodsHaveReflectiveHints() {
8787
testReflectionMethod(SomeAggregateChild.class, "handle");
8888
}
8989

90+
@Test
91+
void handlerInterceptorsHaveReflectiveHints() {
92+
testReflectionMethod(SomeAggregate.class, "intercept");
93+
testReflectionMethod(SomeAggregate.class, "exceptionHandler");
94+
testReflectionMethod(SingleAggregateChild.class, "intercept");
95+
}
96+
97+
@Test
98+
void childEntitiesHaveReflectiveHints() {
99+
testReflectionMethod(SomeAggregateChild.class, "handle");
100+
testReflectionMethod(SingleAggregateChild.class, "intercept");
101+
testReflectionMethod(SingleAggregateChild.class, "handle");
102+
testForConstructor(ForwardMatchingInstances.class);
103+
testForConstructor(ForwardToAll.class);
104+
}
105+
90106
private void addClassToBeanFactory(Class<?> clazz) {
91107
BeanDefinition definition = new RootBeanDefinition(clazz);
92108
beanFactory.registerBeanDefinition(clazz.getName(), definition);

0 commit comments

Comments
 (0)