问题
I'm using JavaFX on Java 11 to create a desktop app. The app is bundled into a custom runtime image with all its modules and their dependencies using jlink
. For dependency injection, the app relies on the Spring Framework.
I'm aware that Spring modules currently do not include a module-info.class
, instead they are shipped as "automatic modules". Since automatic modules cannot be bundled with jlink
, I manually added module-info.class
to each Spring dependency JAR using the Moditect Maven Plugin. Here are the respective parts of pom.xml
:
...
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${dependency.spring.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.moditect</groupId>
<artifactId>moditect-maven-plugin</artifactId>
<version>1.0.0.Beta2</version>
<executions>
<execution>
<id>add-module-infos</id>
<phase>package</phase>
<goals>
<goal>add-module-info</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/dependencies</outputDirectory>
<modules>
<module>
<artifact>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</artifact>
<moduleInfoSource>
module spring.context {
opens org.springframework.context.support to spring.core;
requires java.naming;
exports org.springframework.validation.annotation;
exports org.springframework.ui.context.support;
exports org.springframework.validation;
exports org.springframework.jndi;
exports org.springframework.context.annotation;
exports org.springframework.context.event;
exports org.springframework.context.support;
exports org.springframework.context;
exports org.springframework.format.support;
exports org.springframework.format;
exports org.springframework.jmx.export.naming;
exports org.springframework.stereotype;
exports org.springframework.ui.context;
opens org.springframework.context.annotation to spring.core, spring.beans, com.my.app;
opens org.springframework.instrument.classloading to spring.data.jpa;
requires java.desktop;
requires spring.aop;
requires spring.beans;
requires spring.core;
requires spring.expression;
requires spring.jcl;
//requires java.annotation;
}
</moduleInfoSource>
</module>
<module>
<artifact>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</artifact>
<moduleInfoSource>
module spring.aop {
exports org.springframework.aop.config;
exports org.springframework.aop.framework;
exports org.springframework.aop.framework.autoproxy;
exports org.springframework.aop.support;
exports org.springframework.aop.aspectj.annotation;
opens org.aopalliance.intercept to spring.tx;
requires spring.beans;
requires spring.core;
requires spring.jcl;
//requires aspectjrt;
exports org.springframework.aop.scope;
}
</moduleInfoSource>
</module>
<module>
<artifact>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</artifact>
<moduleInfoSource>
module spring.beans {
exports org.springframework.beans.factory.wiring;
exports org.springframework.beans;
exports org.springframework.beans.factory;
exports org.springframework.beans.factory.annotation;
exports org.springframework.beans.factory.config;
exports org.springframework.beans.factory.support;
exports org.springframework.beans.factory.parsing;
exports org.springframework.beans.factory.xml;
exports org.springframework.beans.propertyeditors;
exports org.springframework.beans.support;
requires java.management.rmi;
requires java.desktop;
requires spring.core;
requires spring.jcl;
}
</moduleInfoSource>
</module>
<module>
<artifact>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</artifact>
<moduleInfoSource>
module spring.core {
exports org.springframework.cglib.reflect;
exports org.springframework.util.comparator;
exports org.springframework.asm;
exports org.springframework.cglib.core;
exports org.springframework.cglib.proxy;
exports org.springframework.cglib.transform;
exports org.springframework.core;
exports org.springframework.core.annotation;
exports org.springframework.core.convert;
exports org.springframework.core.convert.converter;
exports org.springframework.core.convert.support;
exports org.springframework.core.env;
exports org.springframework.core.io;
exports org.springframework.core.io.support;
exports org.springframework.core.type;
exports org.springframework.core.type.filter;
exports org.springframework.core.type.classreading;
exports org.springframework.objenesis;
exports org.springframework.util;
exports org.springframework.util.xml;
//requires aspectjrt;
//requires javassist;
requires jdk.jconsole;
requires java.desktop;
requires java.management;
requires java.xml;
requires spring.jcl;
}
</moduleInfoSource>
</module>
<module>
<artifact>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
</artifact>
<moduleInfoSource>
module spring.expression {
exports org.springframework.expression;
exports org.springframework.expression.spel;
exports org.springframework.expression.spel.standard;
exports org.springframework.expression.spel.support;
requires spring.core;
}
</moduleInfoSource>
</module>
<module>
<artifact>
<groupId>org.springframework</groupId>
<artifactId>spring-jcl</artifactId>
</artifact>
<moduleInfoSource>
module spring.jcl {
requires java.logging;
exports org.apache.commons.logging;
}
</moduleInfoSource>
</module>
</modules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
With the above-mentioned module definitions, jlink
succeeds without problems and my app is bundled into a custom runtime image that contains all its dependencies.
However, if I run the app using the bundled JRE, problems arise:
java -m com.my.app/com.my.app.Main
causes an IllegalAccessException
:
Caused by: java.lang.IllegalAccessException: module spring.core does not read module com.my.app
at java.base/java.lang.invoke.MethodHandles.privateLookupIn(MethodHandles.java:197)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at spring.core@5.1.8.RELEASE/org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:500)
... 25 more
According to this thread, the error can be fixed using --add-reads
:
java --add-reads spring.core=com.my.app -m com.my.app/com.my.app.Main
Instead, a different kind of IllegalAccessException
is thrown:
Caused by: java.lang.IllegalAccessError: class com.my.app.AppConfig$$EnhancerBySpringCGLIB$$e9ee1f7f (in module com.my.app) cannot access class org.springframework.cglib.core.ReflectUtils (in module spring.core) because module com.my.app does not read module spring.core
at com.my.app/com.my.app.AppConfig$$EnhancerBySpringCGLIB$$e9ee1f7f.CGLIB$STATICHOOK1(<generated>)
at com.my.app/com.my.app.AppConfig$$EnhancerBySpringCGLIB$$e9ee1f7f.<clinit>(<generated>)
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:398)
at spring.core@5.1.8.RELEASE/org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:563)
at spring.core@5.1.8.RELEASE/org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363)
at spring.core@5.1.8.RELEASE/org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:582)
at spring.core@5.1.8.RELEASE/org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:110)
at spring.core@5.1.8.RELEASE/org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:108)
at spring.core@5.1.8.RELEASE/org.springframework.cglib.core.internal.LoadingCache$2.call(LoadingCache.java:54)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at spring.core@5.1.8.RELEASE/org.springframework.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:61)
... 18 more
As suggested by the error message, I then added another --add-reads
parameter:
java --add-reads spring.core=com.my.app --add-reads com.my.app=spring.core -m com.my.app/com.my.app.Main
Now the app starts and reads its AppConfig.class
, which is annotated with @ComponentScan
:
@Configuration
@ComponentScan
class AppConfig {
}
However, Spring does not detect a single class of the project that is annotated with a stereotype (@Component
, @Service
), as the output of Main.java
indicates:
public class Main extends Application {
private ConfigurableApplicationContext applicationContext;
public static void main(String[] args) {
launch(args);
}
@Override
public void init() {
applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
}
@Override
public void start(Stage mainWindow) throws IOException {
System.out.println("Printing beans: " + applicationContext.getBeanDefinitionNames().length);
for (String beanDefinitionName : applicationContext.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
}
@Override
public void stop() {
applicationContext.stop();
}
}
Printing beans: 5
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
appConfig
Why does @ComponentScan
miss all my annotated classes? AppConfig.java
is in the root package of my app and all the classes reside in subpackages, therefore Spring should be able to detect them based on the given configuration.
For completeness, here's the module-info.java
of my app:
module com.my.app {
requires java.logging;
requires transitive javafx.graphics;
requires transitive javafx.controls;
requires transitive javafx.fxml;
requires spring.context;
requires spring.beans;
requires java.sql;
exports com.my.app;
exports com.my.app.controllers;
exports com.my.app.controllers.modals;
exports com.my.app.models;
exports com.my.app.exceptions;
exports com.my.app.services;
exports com.my.app.services.impl;
opens com.my.app to spring.aop, spring.beans, spring.context, spring.core, spring.expression, spring.jcl;
opens com.my.app.controllers to spring.aop, spring.beans, spring.context, spring.core, spring.expression, spring.jcl;
opens com.my.app.controllers.modals to spring.aop, spring.beans, spring.context, spring.core, spring.expression, spring.jcl;
opens com.my.app.services to spring.aop, spring.beans, spring.context, spring.core, spring.expression, spring.jcl;
opens com.my.app.services.impl to spring.aop, spring.beans, spring.context, spring.core, spring.expression, spring.jcl;
}
来源:https://stackoverflow.com/questions/57183100/componentscan-does-not-detect-beans-in-a-jlinked-java-runtime-image