Repository navigation
fix(graalvm): walk superclass hierarchy for unnamed @Inject fields in NativeComponentInjector - #158
Conversation
… NativeComponentInjector NativeComponentInjector.newInstance() was calling getDeclaredFields() which only scanned the immediate class, silently skipping unnamed @Inject fields declared in superclasses. Replace the inline stream with collectUnnamedInjectFields() which walks the full hierarchy up to (but not including) SystemComponent, mirroring the existing collectNamedInjectFields() pattern in EnkanFeature. Add NativeComponentRegistry.unregister() to support test teardown, and add @AfterEach cleanup in EnkanFeatureTest to prevent registry state leaking between tests. Closes #152 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Fixes GraalVM native injection for unnamed @Inject fields declared in superclasses by making NativeComponentInjector scan the full class hierarchy, and adds test scaffolding to prevent binder registry state leakage between tests.
Changes:
- Update
NativeComponentInjector.newInstance()to collect unnamed@Injectfields across superclasses (up toSystemComponent). - Add
NativeComponentRegistry.unregister()to support test teardown. - Add regression tests covering inherited unnamed injection and ensuring named-field collection still ignores unnamed inherited fields.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| kotowari-graalvm/src/test/java/enkan/graalvm/EnkanFeatureTest.java | Adds superclass-hierarchy regression tests and cleans registry state after each test. |
| kotowari-graalvm/src/main/java/enkan/graalvm/NativeComponentRegistry.java | Adds unregister() to remove binders (used by tests to prevent cross-test leakage). |
| kotowari-graalvm/src/main/java/enkan/graalvm/NativeComponentInjector.java | Walks superclass hierarchy to inject unnamed @Inject fields and updates reflection-requirement wording. |
Comments suppressed due to low confidence (1)
kotowari-graalvm/src/main/java/enkan/graalvm/NativeComponentInjector.java:30
- The Javadoc here says
ComponentInjector#injectFieldcallsField.setAccessible(true), but the current implementation usesField.trySetAccessible()(seeenkan-system/src/main/java/enkan/system/inject/ComponentInjector.java:42-45). Also, now thatcollectUnnamedInjectFields()walks superclasses, the reflection-registration requirement applies to the declaring classes of those unnamed@Injectfields as well (not just the concrete component class). Please update this comment to reflect both points so native-image users don’t get misled.
* <p><b>Reflection requirement:</b> unnamed {@code @Inject} fields (those annotated with
* {@code @Inject} but <em>not</em> with {@code @Named}) are resolved by
* {@link ComponentInjector#injectField} which calls {@code Field.setAccessible(true)}.
* Any component class that has such fields must therefore have
* {@code "allDeclaredFields": true} in its {@code reflect-config.json} entry,
* or be registered via {@code EnkanFeature} with
* {@code RuntimeReflection.registerAllFields(componentClass)}.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| while (current != null && current != Object.class | ||
| && !current.getName().equals("enkan.component.SystemComponent")) { | ||
| for (Field f : current.getDeclaredFields()) { |
There was a problem hiding this comment.
collectUnnamedInjectFields uses a hard-coded fully-qualified class name string to stop at SystemComponent. Since this class already imports enkan.component.SystemComponent, consider using SystemComponent.class.getName() (or a private static constant shared with EnkanFeature) to avoid a fragile magic string/typo risk.
…ing-class requirement - Correct Field.setAccessible(true) to Field.trySetAccessible() per actual impl - Note that reflect-config.json registration is required for every declaring class in the superclass hierarchy, not only the concrete component class Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
NativeComponentInjector.newInstance()was usinggetDeclaredFields()which only scanned the immediate class, silently skipping unnamed@Injectfields declared in superclassescollectUnnamedInjectFields()helper that walks the full class hierarchy up to (but not including)SystemComponent, mirroring the existingcollectNamedInjectFields()pattern inEnkanFeatureNativeComponentRegistry.unregister()to support test teardown and prevent registry state from leaking between testsCloses #152
Test plan
newInstanceInjectsUnnamedFieldFromSuperclass— regression test that fails on old code, passes after fixcollectNamedInjectFieldsIgnoresUnnamedFieldInSuperclass— confirms named-field collector still ignores inherited unnamed fieldsmvn test -pl kotowari-graalvm -am -DexcludedGroups=integration🤖 Generated with Claude Code