我在 Java 11 上使用 JavaFX 来创建桌面应用程序。该应用程序被捆绑到一个自定义运行时映像中,其所有模块及其依赖项使用jlink
. 对于依赖注入,应用程序依赖于 Spring 框架。
我知道 Spring 模块目前不包含 a module-info.class
,而是作为“自动模块”发货。由于无法与自动模块捆绑,我使用 Moditect Maven 插件jlink
手动添加到每个 Spring 依赖项 JAR。module-info.class
以下是 的各个部分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>
...
使用上述模块定义,jlink
成功没有问题,我的应用程序被捆绑到包含所有依赖项的自定义运行时映像中。
但是,如果我使用捆绑的 JRE 运行应用程序,就会出现问题:
java -m com.my.app/com.my.app.Main
导致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
根据这个线程,可以使用以下方法修复错误--add-reads
:
java --add-reads spring.core=com.my.app -m com.my.app/com.my.app.Main
IllegalAccessException
相反,抛出了一种不同的类型:
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
根据错误消息的建议,我随后添加了另一个--add-reads
参数:
java --add-reads spring.core=com.my.app --add-reads com.my.app=spring.core -m com.my.app/com.my.app.Main
现在应用程序启动并读取它的AppConfig.class
,它带有注释@ComponentScan
:
@Configuration
@ComponentScan
class AppConfig {
}
但是,Spring 不会检测到使用构造型 ( @Component
, @Service
) 注释的项目的单个类,如输出Main.java
所示:
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
为什么会@ComponentScan
错过所有带注释的课程?AppConfig.java
位于我的应用程序的根包中,所有类都位于子包中,因此 Spring 应该能够根据给定的配置检测它们。
为了完整起见,这module-info.java
是我的应用程序:
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;
}