Skip to content

Commit 75f18d3

Browse files
mcollovaticlaudeZheSun88
authored
fix: register reflection and resource hints for error handlers in native builds (#23054) (#23068)
Extract and register exception types from HasErrorParameter<E> generic parameter for reflection. Register resource hints for ClassPathScanner to discover error handler classes at runtime in GraalVM native images. Fixes #23041 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: Zhe Sun <31067185+ZheSun88@users.noreply.github.com>
1 parent 91031b5 commit 75f18d3

File tree

2 files changed

+110
-4
lines changed

2 files changed

+110
-4
lines changed

vaadin-spring/src/main/java/com/vaadin/flow/spring/springnative/VaadinBeanFactoryInitializationAotProcessor.java

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
import java.util.List;
2424
import java.util.Set;
2525
import java.util.stream.Collectors;
26+
import java.util.function.Function;
2627

28+
import com.vaadin.flow.internal.ReflectTools;
2729
import org.reflections.Reflections;
2830
import org.slf4j.Logger;
2931
import org.slf4j.LoggerFactory;
@@ -113,7 +115,8 @@ public BeanFactoryInitializationAotContribution processAheadOfTime(
113115

114116
registerSubTypes(hints, reflections, Component.class);
115117
registerSubTypes(hints, reflections, RouterLayout.class);
116-
registerSubTypes(hints, reflections, HasErrorParameter.class);
118+
registerSubTypes(hints, reflections, HasErrorParameter.class,
119+
VaadinBeanFactoryInitializationAotProcessor::getExceptionTypeFromHasErrorParameter);
117120
registerSubTypes(hints, reflections, ComponentEvent.class);
118121
registerSubTypes(hints, reflections, HasUrlParameter.class);
119122
registerSubTypes(hints, reflections,
@@ -124,9 +127,7 @@ public BeanFactoryInitializationAotContribution processAheadOfTime(
124127

125128
private void registerSubTypes(RuntimeHints hints, Reflections reflections,
126129
Class<?> cls) {
127-
for (var c : getSubtypesOf(reflections, cls)) {
128-
registerType(hints, c);
129-
}
130+
registerSubTypes(hints, reflections, cls, null);
130131
}
131132

132133
private void registerSubTypes(RuntimeHints hints, Reflections reflections,
@@ -143,6 +144,29 @@ private void registerSubTypes(RuntimeHints hints, Reflections reflections,
143144
}
144145
}
145146

147+
private void registerSubTypes(RuntimeHints hints, Reflections reflections,
148+
Class<?> cls,
149+
Function<Class<?>, Set<Class<?>>> relatedTypesExtractor) {
150+
for (var c : getSubtypesOf(reflections, cls)) {
151+
registerType(hints, c);
152+
if (relatedTypesExtractor != null) {
153+
for (var related : relatedTypesExtractor.apply(c)) {
154+
registerType(hints, related);
155+
}
156+
}
157+
}
158+
}
159+
160+
// Visible for testing
161+
static Set<Class<?>> getExceptionTypeFromHasErrorParameter(Class<?> clazz) {
162+
Class<?> exceptionType = ReflectTools.getGenericInterfaceType(clazz,
163+
HasErrorParameter.class);
164+
if (exceptionType != null) {
165+
return Set.of(exceptionType);
166+
}
167+
return Set.of();
168+
}
169+
146170
private static List<String> getPackagesWithRoutes(BeanFactory beanFactory) {
147171
List<String> packages = new ArrayList<String>();
148172
packages.add("com.vaadin");
@@ -255,6 +279,10 @@ private void registerType(RuntimeHints hints, Class<?> c) {
255279
}
256280
MemberCategory[] memberCategories = MemberCategory.values();
257281
hints.reflection().registerType(c, memberCategories);
282+
// Resource hints are needed for ClassPathScanner in
283+
// VaadinServletContextInitializer to discover classes at runtime
284+
// in native builds (GraalVM)
285+
registerResources(hints, c);
258286
}
259287

260288
private static List<String> getPackages(BeanFactory beanFactory) {

vaadin-spring/src/test/java/com/vaadin/flow/spring/springnative/VaadinBeanFactoryInitializationAotProcessorTest.java

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.Collection;
2121
import java.util.Collections;
2222
import java.util.List;
23+
import java.util.Set;
2324
import java.util.stream.Collectors;
2425

2526
import org.junit.jupiter.api.Test;
@@ -316,6 +317,43 @@ void processAheadOfTime_hasErrorParameterSubtype_reflectionHintRegistered() {
316317
.accepts(hints);
317318
}
318319

320+
@Test
321+
void processAheadOfTime_hasErrorParameterSubtype_exceptionTypeReflectionHintRegistered() {
322+
RuntimeHints hints = processAotForHintsWithSubtypes(
323+
TestCustomExceptionView.class, HasErrorParameter.class);
324+
325+
assertThat(RuntimeHintsPredicates.reflection()
326+
.onType(TestCustomException.class))
327+
.as("Exception type from HasErrorParameter generic parameter should be registered for reflection")
328+
.accepts(hints);
329+
}
330+
331+
@Test
332+
void processAheadOfTime_hasErrorParameterSubtype_resourceHintRegistered() {
333+
RuntimeHints hints = processAotForHintsWithSubtypes(
334+
TestErrorParameterView.class, HasErrorParameter.class);
335+
336+
assertThat(RuntimeHintsPredicates.resource()
337+
.forResource(toClassResourcePath(TestErrorParameterView.class)))
338+
.as("HasErrorParameter subtype should be registered as resource")
339+
.accepts(hints);
340+
}
341+
342+
@Test
343+
void processAheadOfTime_hasErrorParameterSubtype_exceptionTypeResourceHintRegistered() {
344+
RuntimeHints hints = processAotForHintsWithSubtypes(
345+
TestCustomExceptionView.class, HasErrorParameter.class);
346+
347+
assertThat(RuntimeHintsPredicates.resource()
348+
.forResource(toClassResourcePath(TestCustomException.class)))
349+
.as("Exception type from HasErrorParameter generic parameter should be registered as resource")
350+
.accepts(hints);
351+
}
352+
353+
private static String toClassResourcePath(Class<?> clazz) {
354+
return clazz.getName().replace('.', '/') + ".class";
355+
}
356+
319357
@Test
320358
void processAheadOfTime_componentEventSubtype_reflectionHintRegistered() {
321359
RuntimeHints hints = processAotForHintsWithSubtypes(
@@ -512,6 +550,27 @@ void getSubtypesOf_findsHasErrorParameterImplementations() {
512550
.contains(TestErrorParameterView.class);
513551
}
514552

553+
@Test
554+
void getExceptionTypeFromHasErrorParameter_extractsGenericType() {
555+
Set<Class<?>> exceptionTypes = VaadinBeanFactoryInitializationAotProcessor
556+
.getExceptionTypeFromHasErrorParameter(
557+
TestCustomExceptionView.class);
558+
559+
assertThat(exceptionTypes).as(
560+
"Should extract exception type from HasErrorParameter implementation")
561+
.containsExactly(TestCustomException.class);
562+
}
563+
564+
@Test
565+
void getExceptionTypeFromHasErrorParameter_returnsEmptyForNonHasErrorParameter() {
566+
Set<Class<?>> exceptionTypes = VaadinBeanFactoryInitializationAotProcessor
567+
.getExceptionTypeFromHasErrorParameter(TestRouteView.class);
568+
569+
assertThat(exceptionTypes)
570+
.as("Should return empty set for non-HasErrorParameter class")
571+
.isEmpty();
572+
}
573+
515574
@Test
516575
void getSubtypesOf_findsHasUrlParameterImplementations() {
517576
VaadinBeanFactoryInitializationAotProcessor processor = new VaadinBeanFactoryInitializationAotProcessor();
@@ -788,6 +847,25 @@ public int setErrorParameter(BeforeEnterEvent event,
788847
}
789848
}
790849

850+
public static class TestCustomException extends Exception {
851+
public TestCustomException() {
852+
}
853+
854+
public TestCustomException(String message) {
855+
super(message);
856+
}
857+
}
858+
859+
@Tag("div")
860+
public static class TestCustomExceptionView extends Component
861+
implements HasErrorParameter<TestCustomException> {
862+
@Override
863+
public int setErrorParameter(BeforeEnterEvent event,
864+
ErrorParameter<TestCustomException> parameter) {
865+
return 400;
866+
}
867+
}
868+
791869
@Tag("div")
792870
public static class TestUrlParameterView extends Component
793871
implements HasUrlParameter<String> {

0 commit comments

Comments
 (0)