FindResource ("") возвращает null, когда присутствует module-info.java, почему это так?

Я отлаживаю, почему при наличии module-info.java в моем приложении Spring Boot spring-orm выдает исключение во время запуска. Это исключение:

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 [email protected]/org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1699) ~[spring-beans-5.0.8.RELEASE.jar:na]
    at [email protected]/org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:573) ~[spring-beans-5.0.8.RELEASE.jar:na]
    at [email protected]/org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495) ~[spring-beans-5.0.8.RELEASE.jar:na]
    at [email protected]/org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317) ~[spring-beans-5.0.8.RELEASE.jar:na]
    at [email protected]/org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.0.8.RELEASE.jar:na]
    at [email protected]/org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315) ~[spring-beans-5.0.8.RELEASE.jar:na]
    at [email protected]/org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.0.8.RELEASE.jar:na]
    at [email protected]/org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1089) ~[spring-context-5.0.8.RELEASE.jar:na]
    at [email protected]/org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:859) ~[spring-context-5.0.8.RELEASE.jar:na]
    at [email protected]/org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550) ~[spring-context-5.0.8.RELEASE.jar:na]
    at [email protected]/org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.0.4.RELEASE.jar:na]
    at [email protected]/org.springframework.boot.SpringApplication.refresh(SpringApplication.java:762) [spring-boot-2.0.4.RELEASE.jar:na]
    at [email protected]/org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:398) [spring-boot-2.0.4.RELEASE.jar:na]
    at [email protected]/org.springframework.boot.SpringApplication.run(SpringApplication.java:330) [spring-boot-2.0.4.RELEASE.jar:na]
    at [email protected]/org.springframework.boot.SpringApplication.run(SpringApplication.java:1258) [spring-boot-2.0.4.RELEASE.jar:na]
    at [email protected]/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 [email protected]/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 [email protected]/org.hibernate.service.internal.AbstractServiceRegistryImpl.applyInjections(AbstractServiceRegistryImpl.java:288) ~[hibernate-core-5.2.17.Final.jar:na]
    at [email protected]/org.hibernate.service.internal.AbstractServiceRegistryImpl.injectDependencies(AbstractServiceRegistryImpl.java:279) ~[hibernate-core-5.2.17.Final.jar:na]
    at [email protected]/org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:239) ~[hibernate-core-5.2.17.Final.jar:na]
    at [email protected]/org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:210) ~[hibernate-core-5.2.17.Final.jar:na]
    at [email protected]/org.hibernate.service.internal.SessionFactoryServiceRegistryImpl.getService(SessionFactoryServiceRegistryImpl.java:80) ~[hibernate-core-5.2.17.Final.jar:na]
    at [email protected]/org.hibernate.internal.SessionFactoryImpl.canAccessTransactionManager(SessionFactoryImpl.java:942) ~[hibernate-core-5.2.17.Final.jar:na]
    at [email protected]/org.hibernate.internal.SessionFactoryImpl.buildCurrentSessionContext(SessionFactoryImpl.java:953) ~[hibernate-core-5.2.17.Final.jar:na]
    at [email protected]/org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:319) ~[hibernate-core-5.2.17.Final.jar:na]
    at [email protected]/org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:462) ~[hibernate-core-5.2.17.Final.jar:na]
    at [email protected]/org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:892) ~[hibernate-core-5.2.17.Final.jar:na]
    at [email protected]/org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:57) ~[spring-orm-5.0.8.RELEASE.jar:na]
    at [email protected]/org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:365) ~[spring-orm-5.0.8.RELEASE.jar:na]
    at [email protected]/org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:390) ~[spring-orm-5.0.8.RELEASE.jar:na]
    at [email protected]/org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:377) ~[spring-orm-5.0.8.RELEASE.jar:na]
    at [email protected]/org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:341) ~[spring-orm-5.0.8.RELEASE.jar:na]
    at [email protected]/org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1758) ~[spring-beans-5.0.8.RELEASE.jar:na]
    at [email protected]/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

Я отследил проблему, чтобы URLClassLoader.findResource("") возвращал null, если module-info.java присутствует, но "file:/C:/Users/pupeno/Documents/Dashman/code/dashmanserver/target/classes/", если его нет.

Я создал минимально возможный пример, который генерирует то же исключение. Для его запуска вам необходимо:

  1. Клонируйте и установите последнюю копию Moditect отсюда: https://github.com/moditect/moditect из-за того, что эта ошибка еще не исправлена: https://github.com/moditect/moditect/issues/51
  2. Клонируйте демонстрационное репо из: https://github.com/dashmantech/demo
  3. Настройте локальную базу данных PostgreSQL с учетными данными demo / confi / application.properties
  4. Сначала запустите mvn clean package, чтобы ModiTec создал все модули
  5. Откройте проект в последней копии IntelliJ
  6. Нажмите кнопку воспроизведения для профиля «Запустить демонстрацию» (каталог .idea включен в соответствующий профиль запуска, с аргументами и т. д.).

Мне нужен findResource(""), чтобы вернуть "file:/C:/Users/pupeno/Documents/Dashman/code/dashmanserver/target/classes/", чтобы spring-orm мог работать.

findResource("") выглядит так:

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;
}

Итак, я вижу, что происходит некоторый доступ, который возможен без использования модульной системы, но ему препятствует модульная система Java, когда присутствует module-infe.java. Моя проблема в том, что я не вижу, как заставить его работать, что нужно экспортировать или открыть, чтобы он работал?

Способ Spring Boot вызывает вызов этого метода через RestartClassLoader, подкласс URLClassLoader, в частности, строку 124, которая вызывает super.findResource(name) в:

@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));
}

Конкретный используемый экземпляр RestartClassLoader является членом ClassPathResource и определяется следующим образом:

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

в конструкторе строка 85.

Наконец, getDefaultClassLoader() выглядит так:

/**
 * 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;
}

Мой module-info.java содержит:

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;
}

В IntelliJ у меня включены следующие плагины:

  • Плагин Lombok
  • .ginore
  • PowerShell
  • VisualVM Launcher
  • ANSI Highlighter
  • Поддержка пакетных сценариев
  • Наблюдатель байт-кода
  • Поддержка CMD
  • авторское право
  • Покрытие
  • Поддержка CSS
  • Инструменты баз данных и SQL
  • Интеграция с Git
  • GitHub
  • Gradle
  • Groovy
  • Интеграция с Heroku
  • HTML инструменты
  • HTTP-клиент
  • l18n для Java
  • Синхронизация настроек IDE
  • Декомпилятор байт-кода Java
  • Java EE: EJB, JPA, сервлеты
  • Отладчик Java Stream
  • JavaFX
  • JUnit
  • Сортировщик строк
  • Поддержка Markdown
  • Интеграция с Maven
  • Расширение интеграции Maven
  • Поддержка фреймворков сохраняемости
  • Поддержка свойств
  • Смали Поддержка
  • Spring AOP / @ AspectJ
  • Весенняя партия
  • Весенний ботинок
  • Весенние данные
  • Шаблоны интеграции Spring
  • Весенний OSGi
  • Весенняя безопасность
  • Весенняя поддержка
  • Весенние веб-службы
  • Spring WebSocket
  • Терминал
  • YAML
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
51
0
1 622
4

Ответы 4

Я заметил одну вещь: ваше приложение (при условии, что оно упаковано в tech.flexpoint.dashman) никоим образом не открывается для Spring, что, несомненно, приведет к неудачной загрузке класса / незаконному доступу.

Я ожидал увидеть что-то подобное в module-info.java (в зависимости от ваших зависимостей Spring):

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

Исключением является NoClassDefFoundError, который генерируется во время выполнения, когда определение класса класса был известен во время компиляции не может быть разрешено, в данном случае интерфейс javax.transaction.UserTransaction, который является частью API транзакций Java (JTA).

Как указывали другие, JTA не связан с JDK и должен быть добавлен в качестве зависимости компиляции. Однако класс, которому необходимо загрузить определение класса UserTransaction, происходит от артефакта spring-boot-autoconfigure, который отвечает за свои собственные зависимости ([email protected] ? [email protected] ? [email protected]), поэтому вам следует нет добавить JTA в качестве зависимости.

Однако, поскольку вы хотите упаковать собственное приложение как модуль Java 9, для него требуется явно указать его зависимости. spring-boot-autoconfigure еще не является модульной библиотекой Java 9 и не делает этого за вас (т.е. транзитивно). Автоматическое имя модуля для JTA - java.transaction, поэтому вам нужно добавить требование в module-info.java:

requires java.transaction;

Я запустил ваш пример и действительно получил NoClassDefFoundError при запуске из IntelliJ IDEA. Трассировка стека указала на ClassNotFoundException, что указывает на проблемы с путями к классам. Поскольку IDEA вычисляет путь к классам при запуске приложения оттуда, я хотел посмотреть, смогу ли я воспроизвести ошибку при использовании spring-boot-maven-plugin для запуска приложения.

Я скопировал конфигурацию запуска IDEA в конфигурацию spring-boot-maven-plugin, как показано ниже:

<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>

Затем я вызвал mvn spring-boot:run и вуаля, приложение успешно загрузилось без ошибок. Я могу только сделать вывод, что это проблема с путем к классам, вычисленным IntelliJ.

Это приложение tech.flexpoint.dashmanserver, и оно уже было открыто для spring.core, spring.beans, но не для spring.context. Его добавление, к сожалению, ничего не изменило. Я добавил исключение, которое я получаю, в начало вопроса.

pupeno 26.08.2018 10:00

Я использую moditect, чтобы превратить каждую из моих зависимостей в модули.

pupeno 27.08.2018 10:20

@pupeno Тогда вполне может быть проблема с ModiTect. Он все еще находится в стадии бета-тестирования.

Henrik 27.08.2018 10:23

@pupeno Я не могу придумать другого объяснения, кроме того, что это связано с ошибкой в ​​одном из определений информации модуля, возможно, наиболее вероятно, для одной из зависимостей. Вы пробовали без moditect, просто явно указав все требования в своем собственном module-info.java?

Henrik 29.08.2018 15:32

@pupeno Кажется, проблема пути к классам IntelliJ - см. обновленный ответ.

Henrik 30.08.2018 12:15

Как это работает без module-info.java. Это определенно будет конфликтующий модуль в списке, а не проблема с Intellij.

Rinsad Ahmed 31.08.2018 14:20

@RinsadAhmed IntelliJ, вероятно, использует информацию из module-info.java при создании пути к классам. spring-boot-maven-plugin также генерирует путь к классам для выполнения приложения и явно работает лучше. Пожалуйста, дайте мне знать, если вы получите ошибку при использовании плагина spring -boot, но если вы не можете, вам следует пересмотреть свой голос против.

Henrik 31.08.2018 14:49

@pupeno Есть ли удача заставить его работать таким образом?

Henrik 01.09.2018 09:55

Приношу свои извинения за задержку, жизнь забилась. Действительно, запуск из командной строки, как и вы, работал. Это хорошая новость, потому что это означает, что модульность моего проекта более завершена, чем я ожидал, но плохая новость, потому что я не могу отлаживать приложение при его запуске таким образом.

pupeno 01.09.2018 16:11

@pupeno Вы можете настроить плагин на включить отладку, а затем создать Конфигурация удаленного запуска отладки в IDEA.

Henrik 04.09.2018 20:47

Предполагая, что вы объявили зависимость:

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

Включите в module-info.java следующее:

requires java.transaction;

Версия 1.3 объявляет автоматическое имя модуля, а версия 1.2 - нет. Последний requires javax.transaction.api;. Source

Когда я добавляю requires java.transaction;, я получаю сообщение: java.transaction is deprecated and marked for removal.

pupeno 27.08.2018 10:19

@pupeno Вы можете игнорировать такое сообщение. Здесь два модуля имеют одно и то же имя: один в JDK, помеченный для удаления, и другой, предоставленный во время выполнения, от которого вы хотите зависеть.

spongebob 27.08.2018 12:26

Хорошо. Я добавил requires java.transaction;. К сожалению, это не повлияло на проблему.

pupeno 27.08.2018 12:45

Как вы упомянули в своей исходной проблеме, код работает без module-info.java, но не с module-info.java. Я вижу, что вы проделали всю эту тяжелую работу по объяснению проблемы, созданию минимального проекта и так далее, чтобы углубиться в проблему.

Глядя на вашу проблему, очевидно, что один из модулей заставляет URLClassLoader.findResource("") возвращать null. Возможно, один из модулей в списке переопределяет этот метод класса или имеет неоднозначную реализацию.

Почему бы вам не начать с пустого module-info.java для минимального примера и продолжать добавлять по одному модулю за раз, пока мы не увидим ошибку? Я считаю, что это поможет нам найти виновника.

эта (или подобная) проблема уже была зарегистрирована для весенней загрузки на GitHub (но с Java 9).

Под подозрением у меня был бы модитект, в то время как есть также проблемы, зарегистрированные для moditect на GitHub, и я также нашел там ваш проблема; обновление ASM до 6.2.1 исправляет как минимум одно критическое изменение:

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

Другие вопросы по теме