Open ennerf opened 1 year ago
Yes, it's a great idea, but not quite straightforward to implement. compiler-core
can mark fields and methods that require reflections with special annotation, and then compiler.micronaut
can skip generating introspection for controllers that does not require reflections. The only show stopper now is controller construction logic. If you do not have custom controller factory, controllers are created using Micronaut's introspector. To work-around this, new factory method that constructs controller or throws if there is no accessible no-args constructor can be added to CompiledFXMLLoader
.
Reflective implementation is also possible, but I haven't implemented it because it's not clear to me how to deal with Jigsaw modules. Should this implementation be in automatic module, or should there be compiled module-info patcher? To implement MLFXLoaderDelegate
there should be some kind of cache of available loaders at runtime. This cache can be implemented using ServiceLoader
, but that would require tonns of provides io.github.paullo612.mlfx.api.CompiledFXMLLoader with ...
in your module-info.
For the reference: there is reflection based accessor implementation on Groovy for tests.
About $Definition
files: Looks like you're using some sort of DI in your app. I found no way to stop Micronaut's annotation processor from generating bean definitions for now. Looks like I should ask Micronaut guys to provide one. For now, you can just delete those $Definition
files with maven-clean-plugin
on package
phase. The only required files are ...$Introspection
and ...$IntrospectionRef
.
micronaut-inject-java
cannot be removed from annotation processors classpath, because it provides AST abstraction implementation for Java.
Right, I didn't consider the module-info. I rarely use reflection and usually create a shaded/obfuscated/shrunk jar that gets rid of the individual jigsaw modules, so I'm not familiar with how it works across modules. I think some amount of one-time user setup would be reasonable as long as it gets rid of the grunt work of running the agent on updates.
After looking at the setup a bit more, my understanding is that this project has 3 different annotation processors:
@CompileFXML
and generates $CompiledFXMLLoader
classes@FXML
and marks classes as @Introspected
for Micronaut@GeneratedByMLFX
and generates META-INF/services/io.github.paullo612.mlfx.api.CompiledFXMLLoader
All of them get called by the annotation processor service in micronaut-inject-java
, which also calls the micronaut processors for generating $Introspection
and $Definition
. I tried using only the compiler-core processor, but that still generates the definition classes. Unless there is some micronaut flag to disable it this looks harder to get rid of than I initially thought.
The ProGuard issue I ran into seems to come from the $CompiledFXMLLoader
classes, but I can't say anything specific because the error is absolutely useless... I'll create an issue if I manage to reproduce it in a smaller example.
[proguard] ProGuard, version 7.2.0
[proguard] Unexpected error
[proguard] java.lang.StringIndexOutOfBoundsException: String index out of range: 93
[proguard] at java.lang.StringLatin1.charAt(StringLatin1.java:48) ~[?:?]
[proguard] at java.lang.String.charAt(String.java:1512) ~[?:?]
[proguard] at proguard.classfile.util.DescriptorClassEnumeration.nextFluff(DescriptorClassEnumeration.java:171) ~[proguard-core-9.0.8.jar:9.0.8]
[proguard] at proguard.classfile.util.DescriptorClassEnumeration.classCount(DescriptorClassEnumeration.java:66) ~[proguard-core-9.0.8.jar:9.0.8]
[proguard] at proguard.classfile.util.ClassReferenceInitializer.findReferencedClasses(ClassReferenceInitializer.java:1320) ~[proguard-core-9.0.8.jar:9.0.8]
[proguard] at proguard.classfile.util.ClassReferenceInitializer.visitLocalVariableTypeInfo(ClassReferenceInitializer.java:718) ~[proguard-core-9.0.8.jar:9.0.8]
[proguard] at proguard.classfile.attribute.LocalVariableTypeTableAttribute.localVariablesAccept(LocalVariableTypeTableAttribute.java:73) ~[proguard-core-9.0.8.jar:9.0.8]
[proguard] at proguard.classfile.util.ClassReferenceInitializer.visitLocalVariableTypeTableAttribute(ClassReferenceInitializer.java:609) ~[proguard-core-9.0.8.jar:9.0.8]
[proguard] at proguard.classfile.attribute.LocalVariableTypeTableAttribute.accept(LocalVariableTypeTableAttribute.java:60) ~[proguard-core-9.0.8.jar:9.0.8]
[proguard] at proguard.classfile.attribute.CodeAttribute.attributesAccept(CodeAttribute.java:236) ~[proguard-core-9.0.8.jar:9.0.8]
[proguard] at proguard.classfile.util.ClassReferenceInitializer.visitCodeAttribute(ClassReferenceInitializer.java:595) ~[proguard-core-9.0.8.jar:9.0.8]
[proguard] at proguard.classfile.attribute.CodeAttribute.accept(CodeAttribute.java:138) ~[proguard-core-9.0.8.jar:9.0.8]
[proguard] at proguard.classfile.ProgramMethod.attributesAccept(ProgramMethod.java:148) ~[proguard-core-9.0.8.jar:9.0.8]
[proguard] at proguard.classfile.util.ClassReferenceInitializer.visitProgramMethod(ClassReferenceInitializer.java:309) ~[proguard-core-9.0.8.jar:9.0.8]
[proguard] at proguard.classfile.ProgramMethod.accept(ProgramMethod.java:140) ~[proguard-core-9.0.8.jar:9.0.8]
[proguard] at proguard.classfile.ProgramClass.methodsAccept(ProgramClass.java:695) ~[proguard-core-9.0.8.jar:9.0.8]
[proguard] at proguard.classfile.util.ClassReferenceInitializer.visitProgramClass(ClassReferenceInitializer.java:266) ~[proguard-core-9.0.8.jar:9.0.8]
[proguard] at proguard.classfile.ProgramClass.accept(ProgramClass.java:544) ~[proguard-core-9.0.8.jar:9.0.8]
[proguard] at proguard.classfile.ClassPool.classesAccept(ClassPool.java:291) ~[proguard-core-9.0.8.jar:9.0.8]
[proguard] at proguard.Initializer.execute(Initializer.java:187) ~[proguard-base-7.2.0.jar:7.2.0]
[proguard] at proguard.ProGuard.initialize(ProGuard.java:324) ~[proguard-base-7.2.0.jar:7.2.0]
[proguard] at proguard.ProGuard.execute(ProGuard.java:138) ~[proguard-base-7.2.0.jar:7.2.0]
[proguard] at proguard.ProGuard.main(ProGuard.java:675) [proguard-base-7.2.0.jar:7.2.0]
Thanks for looking into it. My answer took too long to write, so I didn't see your comment until after.
I'm currently using Afterburner.fx for DI. It's very minimal and works quite well with JavaFX.
Micronaut-inject generates a ton extra
$Introspection
and$Definition
classes that bloat the binary and seem to produce some problems with ProGuard obfuscation.Making the fxml fields/methods public generates byte code that doesn't depend on Micronaut introspection, but the classes still get generated. Is there a way to run only the fxml annotation processor? I tried removing the
micronaut-inject-java
annotation processor, but that results in the fxml files not being generated either.Fwiw, I did a quick test yesterday to automatically generate GraalVM reflect-config files for annotated fields (see ConfigGenerator) and it turned out to be pretty straight forward. It might make sense to have a low-dependency mode that uses public access and falls back to reflection w/ generated config files.