@ComponentScan does not detect beans in a jlink'ed Java runtime image

不想你离开。 提交于 2019-12-12 20:00:14

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!