51 votes

findResource("") retournant null lorsque module-info.java est présent, pourquoi ?

Je suis en train de déboguer pourquoi en présence de module-info.java dans mon application Spring Boot, spring-orm lance une exception pendant le démarrage. Voici l'exception :

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is java.lang.NoClassDefFoundError: javax/transaction/UserTransaction
    at spring.beans@5.0.8.RELEASE/org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1699) ~[spring-beans-5.0.8.RELEASE.jar:na]
    at spring.beans@5.0.8.RELEASE/org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:573) ~[spring-beans-5.0.8.RELEASE.jar:na]
    at spring.beans@5.0.8.RELEASE/org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495) ~[spring-beans-5.0.8.RELEASE.jar:na]
    at spring.beans@5.0.8.RELEASE/org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317) ~[spring-beans-5.0.8.RELEASE.jar:na]
    at spring.beans@5.0.8.RELEASE/org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.0.8.RELEASE.jar:na]
    at spring.beans@5.0.8.RELEASE/org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315) ~[spring-beans-5.0.8.RELEASE.jar:na]
    at spring.beans@5.0.8.RELEASE/org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.0.8.RELEASE.jar:na]
    at spring.context@5.0.8.RELEASE/org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1089) ~[spring-context-5.0.8.RELEASE.jar:na]
    at spring.context@5.0.8.RELEASE/org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:859) ~[spring-context-5.0.8.RELEASE.jar:na]
    at spring.context@5.0.8.RELEASE/org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550) ~[spring-context-5.0.8.RELEASE.jar:na]
    at spring.boot@2.0.4.RELEASE/org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.0.4.RELEASE.jar:na]
    at spring.boot@2.0.4.RELEASE/org.springframework.boot.SpringApplication.refresh(SpringApplication.java:762) [spring-boot-2.0.4.RELEASE.jar:na]
    at spring.boot@2.0.4.RELEASE/org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:398) [spring-boot-2.0.4.RELEASE.jar:na]
    at spring.boot@2.0.4.RELEASE/org.springframework.boot.SpringApplication.run(SpringApplication.java:330) [spring-boot-2.0.4.RELEASE.jar:na]
    at spring.boot@2.0.4.RELEASE/org.springframework.boot.SpringApplication.run(SpringApplication.java:1258) [spring-boot-2.0.4.RELEASE.jar:na]
    at spring.boot@2.0.4.RELEASE/org.springframework.boot.SpringApplication.run(SpringApplication.java:1246) [spring-boot-2.0.4.RELEASE.jar:na]
    at tech.flexpoint.dashmanserver/tech.flexpoint.dashmanserver.DashmanServerApplication.main(DashmanServerApplication.java:13) [classes/:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[na:na]
    at spring.boot.devtools@2.0.4.RELEASE/org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) [spring-boot-devtools-2.0.4.RELEASE.jar:na]
Caused by: java.lang.NoClassDefFoundError: javax/transaction/UserTransaction
    at java.base/java.lang.Class.getDeclaredMethods0(Native Method) ~[na:na]
    at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3119) ~[na:na]
    at java.base/java.lang.Class.privateGetPublicMethods(Class.java:3144) ~[na:na]
    at java.base/java.lang.Class.getMethods(Class.java:1863) ~[na:na]
    at hibernate.core@5.2.17.Final/org.hibernate.service.internal.AbstractServiceRegistryImpl.applyInjections(AbstractServiceRegistryImpl.java:288) ~[hibernate-core-5.2.17.Final.jar:na]
    at hibernate.core@5.2.17.Final/org.hibernate.service.internal.AbstractServiceRegistryImpl.injectDependencies(AbstractServiceRegistryImpl.java:279) ~[hibernate-core-5.2.17.Final.jar:na]
    at hibernate.core@5.2.17.Final/org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:239) ~[hibernate-core-5.2.17.Final.jar:na]
    at hibernate.core@5.2.17.Final/org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:210) ~[hibernate-core-5.2.17.Final.jar:na]
    at hibernate.core@5.2.17.Final/org.hibernate.service.internal.SessionFactoryServiceRegistryImpl.getService(SessionFactoryServiceRegistryImpl.java:80) ~[hibernate-core-5.2.17.Final.jar:na]
    at hibernate.core@5.2.17.Final/org.hibernate.internal.SessionFactoryImpl.canAccessTransactionManager(SessionFactoryImpl.java:942) ~[hibernate-core-5.2.17.Final.jar:na]
    at hibernate.core@5.2.17.Final/org.hibernate.internal.SessionFactoryImpl.buildCurrentSessionContext(SessionFactoryImpl.java:953) ~[hibernate-core-5.2.17.Final.jar:na]
    at hibernate.core@5.2.17.Final/org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:319) ~[hibernate-core-5.2.17.Final.jar:na]
    at hibernate.core@5.2.17.Final/org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:462) ~[hibernate-core-5.2.17.Final.jar:na]
    at hibernate.core@5.2.17.Final/org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:892) ~[hibernate-core-5.2.17.Final.jar:na]
    at spring.orm@5.0.8.RELEASE/org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:57) ~[spring-orm-5.0.8.RELEASE.jar:na]
    at spring.orm@5.0.8.RELEASE/org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:365) ~[spring-orm-5.0.8.RELEASE.jar:na]
    at spring.orm@5.0.8.RELEASE/org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:390) ~[spring-orm-5.0.8.RELEASE.jar:na]
    at spring.orm@5.0.8.RELEASE/org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:377) ~[spring-orm-5.0.8.RELEASE.jar:na]
    at spring.orm@5.0.8.RELEASE/org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:341) ~[spring-orm-5.0.8.RELEASE.jar:na]
    at spring.beans@5.0.8.RELEASE/org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1758) ~[spring-beans-5.0.8.RELEASE.jar:na]
    at spring.beans@5.0.8.RELEASE/org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1695) ~[spring-beans-5.0.8.RELEASE.jar:na]
    ... 21 common frames omitted
Caused by: java.lang.ClassNotFoundException: javax.transaction.UserTransaction
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582) ~[na:na]
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:190) ~[na:na]
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:499) ~[na:na]
    ... 42 common frames omitted

J'ai trouvé le problème à URLClassLoader.findResource("") en retournant sur null si module-info.java est présent mais "file:/C:/Users/pupeno/Documents/Dashman/code/dashmanserver/target/classes/" si ce n'est pas le cas.

J'ai créé l'exemple le plus simple possible qui génère la même exception. Pour l'exécuter, vous devez :

  1. Clonez et installez une copie récente de Moditect à partir d'ici : https://github.com/moditect/moditect parce que la correction de ce bug n'est pas encore sortie : https://github.com/moditect/moditect/issues/51
  2. Clonez le dépôt de démonstration à partir de : https://github.com/dashmantech/demo
  3. Configurer une base de données PostgreSQL locale avec les informations d'identification demo/confi/application.properties
  4. Exécuter mvn clean package d'abord, afin que ModiTec crée tous les modules
  5. Ouvrez le projet dans une copie récente d'IntelliJ
  6. Cliquez sur lecture pour le profil "Run Demo" (le .idea est inclus avec le profil d'exécution approprié, avec des arguments, etc).

J'ai besoin findResource("") pour revenir "file:/C:/Users/pupeno/Documents/Dashman/code/dashmanserver/target/classes/" de sorte que spring-orm peut fonctionner.

findResource("") ressemble à ça :

public URL findResource(final String name) {
    /*
     * The same restriction to finding classes applies to resources
     */
    URL url = AccessController.doPrivileged(
        new PrivilegedAction<>() {
            public URL run() {
                return ucp.findResource(name, true);
            }
        }, acc);

    return url != null ? URLClassPath.checkURL(url) : null;
}

Ainsi, je peux voir qu'il y a un accès qui est correct sans utiliser le système de modules mais qui est empêché par le système de modules de Java lorsqu'un fichier module-infe.java est présent. Mon problème est que je ne vois pas comment le faire fonctionner, que faut-il exporter ou ouvrir pour que cela fonctionne ?

La façon dont Spring Boot provoque l'appel de cette méthode est par le biais de RestartClassLoader une sous-classe de URLClassLoader en particulier, la ligne 124 qui appelle super.findResource(name) dans :

@Override
public URL findResource(String name) {
    final ClassLoaderFile file = this.updatedFiles.getFile(name);
    if (file == null) {
        return super.findResource(name);
    }
    if (file.getKind() == Kind.DELETED) {
        return null;
    }
    return AccessController
            .doPrivileged((PrivilegedAction<URL>) () -> createFileUrl(name, file));
}

La spécificité RestartClassLoader utilisée est un membre de ClassPathResource et c'est défini de cette façon :

this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());

dans le constructeur, ligne 85 .

Enfin, getDefaultClassLoader() ressemble à ça :

/**
 * Return the default ClassLoader to use: typically the thread context
 * ClassLoader, if available; the ClassLoader that loaded the ClassUtils
 * class will be used as fallback.
 * <p>Call this method if you intend to use the thread context ClassLoader
 * in a scenario where you clearly prefer a non-null ClassLoader reference:
 * for example, for class path resource loading (but not necessarily for
 * {@code Class.forName}, which accepts a {@code null} ClassLoader
 * reference as well).
 * @return the default ClassLoader (only {@code null} if even the system
 * ClassLoader isn't accessible)
 * @see Thread#getContextClassLoader()
 * @see ClassLoader#getSystemClassLoader()
 */
@Nullable
public static ClassLoader getDefaultClassLoader() {
    ClassLoader cl = null;
    try {
        cl = Thread.currentThread().getContextClassLoader();
    }
    catch (Throwable ex) {
        // Cannot access thread context ClassLoader - falling back...
    }
    if (cl == null) {
        // No thread context class loader -> use class loader of this class.
        cl = ClassUtils.class.getClassLoader();
        if (cl == null) {
            // getClassLoader() returning null indicates the bootstrap ClassLoader
            try {
                cl = ClassLoader.getSystemClassLoader();
            }
            catch (Throwable ex) {
                // Cannot access system ClassLoader - oh well, maybe the caller can live with null...
            }
        }
    }
    return cl;
}

Mon module-info.java contient :

module tech.flexpoint.dashman {
    exports tech.flexpoint.dashman to com.fasterxml.jackson.databind;
    exports tech.flexpoint.dashman.controllers.configurator to javafx.fxml;

    opens tech.flexpoint.dashman to javafx.graphics, jna;
    opens tech.flexpoint.dashman.controllers.common to javafx.fxml;
    opens tech.flexpoint.dashman.controllers.configurator to javafx.fxml;
    opens tech.flexpoint.dashman.models to org.hibernate.validator, tech.flexpoint.dashmancommon, javafx.base;

    opens common;
    opens configurator;
    opens displayer;
    opens winscreensaver;

    requires appdirs;
    requires org.bouncycastle.provider;
    requires com.fasterxml.jackson.core;
    requires com.fasterxml.jackson.databind;
    requires com.fasterxml.jackson.datatype.jdk8;
    requires io.sentry;
    requires jackson.annotations;
    requires java.desktop;
    requires java.sql;
    requires java.validation;
    requires javafx.controls;
    requires javafx.fxml;
    requires javafx.graphics;
    requires javafx.media;
    requires javafx.web;
    requires jna;
    requires jna.platform;
    requires org.apache.commons.lang3;
    requires org.kordamp.ikonli.javafx;
    requires org.kordamp.ikonli.fontawesome5;
    requires spring.core;
    requires spring.retry;
    requires spring.web;
    requires tech.flexpoint.dashmancommon;
}

Dans IntelliJ, j'ai activé ces plugins :

  • Plugin Lombok
  • .ginore
  • PowerShell
  • Lanceur VisualVM
  • Surligneur ANSI
  • Prise en charge des scripts par lots
  • Visualisateur de bytecode
  • Support CMD
  • Copyright
  • Couverture
  • Support CSS
  • Outils de base de données et SQL
  • Intégration de Git
  • GitHub
  • Gradle
  • Groovy
  • Intégration de Heroku
  • Outils HTML
  • Client HTTP
  • l18n pour Java
  • Synchronisation des paramètres de l'IDE
  • Décompilateur de bytecode Java
  • Java EE : EJB, JPA, Servlets
  • Débogueur Java Stream
  • JavaFX
  • JUnit
  • Trieuse de lignes
  • Support du format Markdown
  • Intégration de Maven
  • Extension d'intégration Maven
  • Prise en charge des cadres de persistance
  • Soutien aux propriétés
  • Soutien de Smali
  • Spring AOP/@AspectJ
  • Lot de printemps
  • Spring Boot
  • Données de printemps
  • Modèles d'intégration de Spring
  • Spring OSGi
  • Sécurité du printemps
  • Soutien de printemps
  • Services Web de Spring
  • Spring WebSocket
  • Terminal
  • YAML

12voto

Henrik Points 3489

Une chose que je remarque, c'est que votre application (en supposant qu'elle est empaquetée en tech.flexpoint.dashman ) ne semble pas être ouvert à Spring de quelque manière que ce soit, ce qui entraînera sûrement l'échec du chargement des classes/un accès illégal.

Je m'attendrais à voir quelque chose comme ça dans module-info.java (en fonction de vos dépendances Spring) :

opens tech.flexpoint.dashman to spring.core, spring.beans, spring.context;

L'exception est un NoClassDefFoundError qui est lancé au moment de l'exécution lorsque la définition de classe d'une classe qui était connu au moment de la compilation ne peut être résolu, dans ce cas l'interface javax.transaction.UserTransaction qui fait partie de la Java Transaction API (JTA) .

Comme d'autres l'ont souligné, JTA n'est pas fourni avec le JDK et doit être ajouté en tant que dépendance de compilation. Cependant, la classe qui doit charger le fichier UserTransaction provient de la définition de la classe spring-boot-autoconfigure qui est responsable de ses propres dépendances ( spring-boot-autoconfigure@2.4.0.RELEASE jboss-transaction-spi@7.6.0.Final jboss-transaction-api_1.2_spec@1.1.1.Final ), vous devez donc no devez ajouter JTA comme dépendance.

Cependant, comme vous voulez empaqueter votre propre application comme un module Java 9, elle doit indiquer explicitement ses dépendances . spring-boot-autoconfigure n'est pas encore une bibliothèque modulaire Java 9, et ne le fait pas pour vous (c'est-à-dire de manière transitive). Le nom de module automatique pour JTA est java.transaction Vous devez donc ajouter l'exigence dans module-info.java :

requires java.transaction;

J'ai fait fonctionner votre exemple et j'ai effectivement obtenu NoClassDefFoundError lors de l'exécution depuis IntelliJ IDEA. La trace de la pile pointait vers un ClassNotFoundException ce qui indique des problèmes de classpath. Étant donné qu'IDEA calcule le classpath lorsqu'il lance l'application à partir de là, j'ai voulu voir si je pouvais reproduire l'erreur lorsque j'utilise la commande spring-boot-maven-plugin pour exécuter l'application.

J'ai copié la configuration d'exécution d'IDEA dans le fichier spring-boot-maven-plugin comme indiqué ci-dessous :

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <configuration>
      <mainClass>tech.flexpoint.demo.DemoApplication</mainClass>
      <jvmArguments>--show-module-resolution --add-opens=java.base/java.lang=spring.core --add-opens=java.base/java.io=tomcat.embed.core --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED</jvmArguments>
      <workingDirectory>${project.basedir}</workingDirectory>
  </configuration>
</plugin>

Puis j'ai invoqué mvn spring-boot:run et voilà, l'application a démarré avec succès, sans erreur. Je ne peux que conclure qu'il s'agit d'un problème avec le classpath calculé par IntelliJ.

3voto

Francesco Menzani Points 4100

En supposant que vous avez déclaré la dépendance :

<dependency>
    <groupId>javax.transaction</groupId>
    <artifactId>javax.transaction-api</artifactId>
    <version>1.3</version>
</dependency>

Inclure les éléments suivants dans module-info.java :

requires java.transaction;

La version 1.3 déclare le nom du module automatique, alors que la version 1.2 ne le fait pas.
Ce dernier requires javax.transaction.api; . Source :

1voto

Rinsad Ahmed Points 1757

Comme vous l'avez mentionné dans votre problème original, le code fonctionne sans module-info.java mais pas avec le module-info.java. Je peux voir que vous avez fait tout ce travail difficile en expliquant le problème, en créant un projet minimal et ainsi de suite pour aller au fond du problème.

En examinant votre problème, il est évident que l'un des modules est à l'origine du problème. URLClassLoader.findResource("") en retournant sur null . Il se peut qu'un des modules en bas de la liste surcharge la méthode de cette classe ou que son implémentation soit ambiguë.

Pourquoi ne pas commencer par un module-info.java vide pour l'exemple minimal et continuer à ajouter un module à la fois jusqu'à ce que nous voyions l'erreur ? Je pense que cela nous aidera à trouver le coupable.

0voto

syslogic Points 749

Cette question (ou une question similaire) avait déjà été déposée pour le spring-boot sur GitHub (mais avec Java 9).

J'aurais moditect sous suspicion, alors qu'il y a aussi des questions déposées pour moditect en GitHub et j'ai également trouvé votre numéro là ; mise à jour de l'ASM pour 6.2.1 corrige au moins un autre changement de rupture :

<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm</artifactId>
    <version>6.2.1</version>
</dependency>

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X