8000 feat(graalvm): auto-register all META-INF/services SPI implementations for reflection by kawasima · Pull Request #159 · enkan/enkan · GitHub
[go: up one dir, main page]

Skip to content

feat(graalvm): auto-register all META-INF/services SPI implementations for reflection - #159

Merged
kawasima merged 3 commits into
developfrom
feature/auto-register-spi-impls
Apr 4, 2026
Merged

kawasima merged 3 commits into
developfrom
feature/auto-register-spi-impls

Conversation

@kawasima
@kawasima kawasima commented Apr 4, 2026
Copy link
Copy Markdown
Collaborator

Summary

Design decisions

  • Uses access.getApplicationClassPath() (List<Path>) for reliable scanning — avoids ClassLoader.getResources("META-INF/services/") which silently fails for JARs without directory entries
  • Handles FileSystemAlreadyExistsException: reuses the existing FileSystem rather than opening a duplicate (GraalVM may have already opened the JAR)
  • Scans only root-level META-INF/services/ files; skips META-INF/versions/N/ subdirs in multi-release JARs
  • Framework/JDK type filtering (shouldSkipType()) is applied in registerServiceLoaderImpls(), keeping discoverServiceLoaderClasses() a pure, independently testable helper

Test plan

  • discoverServiceLoaderClasses_findsImplFromDirectoryEntry — discovers an impl registered in a temp directory services file
  • discoverServiceLoaderClasses_ignoresMissingClass — silently skips unresolvable class names without throwing
  • All 21 existing tests continue to pass: mvn test -pl kotowari-graalvm -am -DexcludedGroups=integration

Closes #151

🤖 Generated with Claude Code

kawasima and others added 2 commits April 4, 2026 18:31
…r reflection

KotowariFeature now scans every META-INF/services/<name> file on the
application classpath at native-image build time and registers each
discovered implementation class for reflection automatically.

Previously, any MessageBodyReader/Writer added via ServiceLoader required
manual reflect-config.json entries to avoid silent runtime failures.
The fix is generic: all SPI implementations on the classpath are covered,
not just SerDes types.

Key design decisions:
- Uses access.getApplicationClassPath() (List<Path>) for reliable classpath
  scanning — avoids ClassLoader.getResources() which silently fails for JARs
  without directory entries
- Handles FileSystemAlreadyExistsException: reuses the existing FileSystem
  rather than opening a duplicate
- Scans only root-level META-INF/services/ files; skips multi-release JAR
  subdirs (META-INF/versions/N/...)
- Filtering of framework/JDK types is done in registerServiceLoaderImpls
  via shouldSkipType(), keeping discoverServiceLoaderClasses() a pure
  testable helper that returns all resolved classes

Closes #151

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Reorder imports to alphabetical (java.io before java.lang)
- Add comment explaining why openedHere guards the fs.close() call

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

< 8000 div class="comment-body markdown-body js-comment-body soft-wrap user-select-contain d-block css-overflow-wrap-anywhere">

Pull request overview

Adds GraalVM native-image build-time auto-registration of Service Provider Interface (SPI) implementations found under META-INF/services/, so ServiceLoader-discovered providers (e.g., JAX-RS MessageBodyReader/Writer) don’t require manual reflect-config.json entries.

Changes:

  • Scan every application classpath entry for META-INF/services/* files and resolve listed provider classes.
  • Register discovered provider implementation classes for reflection during beforeAnalysis.
  • Add unit tests for service-file discovery (directory entry + missing-class handling).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
kotowari-graalvm/src/main/java/kotowari/graalvm/KotowariFeature.java Implements classpath scanning of META-INF/services/* and registers discovered provider implementations for reflection.
kotowari-graalvm/src/test/java/kotowari/graalvm/KotowariFeatureTest.java Adds tests validating service provider discovery from a directory and skipping unresolvable providers.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +305 to +306
line = line.strip();
if (line.isEmpty() || line.startsWith("#")) continue;
Copilot AI Apr 4, 2026
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseServiceFile only treats lines starting with # as comments, but ServiceLoader provider-configuration syntax allows inline comments (e.g. com.Foo # optional). Without stripping the trailing #... portion, Class.forName will fail and valid providers won’t be registered. Consider truncating each non-empty line at the first # before stripping/processing.

Suggested change
line = line.strip();
if (line.isEmpty() || line.startsWith("#")) continue;
int commentStart = line.indexOf('#');
if (commentStart >= 0) {
line = line.substring(0, commentStart);
}
line = line.strip();
if (line.isEmpty()) continue;

Copilot uses AI. Check for mistakes.
Comment on lines +244 to +245
8000
RuntimeReflection.registerAllDeclaredFields(cls);
RuntimeReflection.registerAllDeclaredMethods(cls);
Copilot AI Apr 4, 2026
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

registerServiceLoaderImpls registers all declared fields and methods for every discovered SPI implementation. This can significantly increase native-image reflection metadata (startup/memory/image size) and broadens reflective access beyond what ServiceLoader typically needs (constructors). Consider narrowing registration to constructors only (or constructors + public members actually used reflectively) unless there’s a concrete need for full member registration.

Suggested change
RuntimeReflection.registerAllDeclaredFields(cls);
RuntimeReflection.registerAllDeclaredMethods(cls);

Copilot uses AI. Check for mistakes.
Comment on lines +343 to +346
URL classesUrl = SimpleForm.class.getProtectionDomain().getCodeSource().getLocation();
URLClassLoader cl = new URLClassLoader(new URL[]{classesUrl}, null);

Path tmpDir = Files.createTempDirectory("spi-dir-test");
Copilot AI Apr 4, 2026
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test manually closes URLClassLoader cl at the end; if an assertion fails earlier, the classloader won’t be closed. Prefer try-with-resources for URLClassLoader (consistent with shouldSkipType_doesNotSkipUnnamedModuleAppClass) and consider using JUnit’s @TempDir or cleanup to avoid leaving temp directories behind.

Copilot uses AI. Check for mistakes.
Comment on lines +370 to +375
URLClassLoader cl = new URLClassLoader(
new URL[]{tmpDir.toUri().toURL()}, getClass().getClassLoader());

assertThatCode(() -> feature.discoverServiceLoaderClasses(List.of(tmpDir), cl))
.doesNotThrowAnyException();
cl.close();
Copilot AI Apr 4, 2026
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test manually calls cl.close(); if discoverServiceLoaderClasses(...) throws unexpectedly (or an assertion is added later), the classloader may leak. Use try-with-resources for the URLClassLoader and consider @TempDir for the temp directory to ensure cleanup.

Copilot uses AI. Check for mistakes.
- parseServiceFile: strip inline comments per ServiceLoader spec
  (e.g. "com.Foo # note" is valid and was previously causing ClassNotFoundException)
- Tests: use try-with-resources for URLClassLoader to ensure close on assertion failure

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@kawasima
kawasima merged commit a2018a8 into develop Apr 4, 2026
1 check passed
@kawasima
kawasima deleted the feature/auto-register-spi-impls branch April 4, 2026 10:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

0