findResource("") devuelve null cuando module-info.java está presente, ¿por qué?
java 9 modular (5)
Como mencionó en su problema original, el código funciona sin module-info.java pero no con module-info.java. Veo que ha hecho todo este trabajo duro para explicar el problema, crear un proyecto mínimo y demás para profundizar en el problema.
En cuanto a su problema, es obvio que uno de los módulos está causando que el URLClassLoader.findResource("")
devuelva null
. Podría ser uno de los módulos de la lista que está anulando este método de clase o tiene una implementación ambigua.
¿Por qué no comienzas con un módulo vacío -info.java para el ejemplo mínimo y sigues agregando 1 módulo a la vez hasta que veamos el error? Creo que esto nos ayudará a encontrar al culpable.
Estoy depurando por qué en presencia de module-info.java
en mi aplicación Spring Boot, spring-orm
lanza una excepción durante el tiempo de arranque. Esta es la excepción:
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("")
el problema en URLClassLoader.findResource("")
devolviendo null
si module-info.java
está presente pero "file:/C:/Users/pupeno/Documents/Dashman/code/dashmanserver/target/classes/"
if no es.
mvn clean package
ejemplo mínimo posible que arroja la misma excepción: https://github.com/dashmantech/demo Para ejecutarlo, primero debe ejecutar mvn clean package
, para que ModiTec cree todos los módulos y luego pueda ejecutar el .idea
Spring de IntelliJ (el directorio .idea
se incluye con el perfil de ejecución apropiado, con argumentos, etc.).
Necesito findResource("")
para devolver "file:/C:/Users/pupeno/Documents/Dashman/code/dashmanserver/target/classes/"
para que spring-orm
pueda funcionar.
findResource("")
ve así:
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;
}
Así que puedo ver que hay algún acceso que está funcionando bien sin usar el sistema de módulos, pero el sistema de módulos de Java lo previene cuando hay un module-infe.java
presente. Mi problema es que no veo cómo hacer que funcione, ¿qué debería exportar o abrir para que funcione?
La forma en que Spring Boot está causando la llamada de ese método es a través de RestartClassLoader
, una subclase de URLClassLoader
, específicamente, la línea 124 que llama a super.findResource(name)
en:
@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 instancia específica de RestartClassLoader
se utiliza es miembro de ClassPathResource
y está definida de esta manera:
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
en el constructor, línea 85 .
Por último, getDefaultClassLoader()
ve así:
/**
* 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;
}
Mi module-info.java
contiene:
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;
}
No veo ninguna dependencia relacionada con javax.transaction
en su configuración module-info.java
. ¿Puede estar escondido en algún lugar de tu submódulo?
Una cosa más que vale la pena comprobar es que java.transaction
no se resuelve por defecto
Es posible que desee incluir el módulo javax.transaction
lugar:
requires javax.transaction.api;
Sin la excepción de la pila, no hay mucho que hacer aquí.
Una cosa que noto es que su aplicación (suponiendo que esté empaquetada en tech.flexpoint.dashman
) no parece estar abierta para Spring de ninguna manera, lo que seguramente resultará en una carga de clase / acceso ilegal fallida.
Esperaría ver algo como esto en module-info.java
(dependiendo de las dependencias de Spring):
opens tech.flexpoint.dashman to spring.core, spring.beans, spring.context;
No sé si esto es relevante, como se mencionó anteriormente, debe incluir el seguimiento de la pila.
EDIT 1
Gracias por el seguimiento de la pila.
La excepción es un NoClassDefFoundError
, que se lanza en tiempo de ejecución cuando no se puede resolver la definición de clase de una clase conocida en tiempo de compilación , en este caso la interfaz javax.transaction.UserTransaction
, que es parte de Java Transaction API (JTA) .
Como han señalado otros, JTA no está incluido con el JDK, y debe agregarse como una dependencia de compilación. Sin embargo, la clase que necesita cargar la definición de la clase UserTransaction
proviene del artefacto de spring-boot-autoconfigure
, que es responsable de sus propias dependencias ( [email protected]
-> [email protected]
-> [email protected]
), por lo que no debería necesitar agregar JTA como una dependencia.
Sin embargo, debido a que desea empaquetar su propia aplicación como un módulo de Java 9, necesita indicar explícitamente sus dependencias . spring-boot-autoconfigure
aún no es una biblioteca modularizada de Java 9, y no hace esto por usted (es decir, transitivamente). El nombre del módulo automático para JTA es java.transaction
, por lo que debe agregar el requisito en module-info.java
:
requires java.transaction;
EDIT 2 - Solución
Después de tocar un poco (compilar una versión instantánea de moditect, crear la demo
base de datos postgresql, agregar META-INF/orm.xml
como raíz de la unidad de persistencia), obtuve su ejemplo en ejecución y de hecho NoClassDefFoundException
al ejecutar desde IntelliJ IDEA. La stacktrace apuntaba a una ClassNotFoundException
, que indica problemas de classpath. Como IDEA calcula el classpath al iniciar la aplicación desde allí, quería ver si podía reproducir el error al usar el spring-boot-maven-plugin
para ejecutar la aplicación.
Copié la configuración de ejecución de IDEA a la configuración de spring-boot-maven-plugin
, como se muestra a continuación:
<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>
Luego mvn spring-boot:run
y voila, la aplicación se inició correctamente sin errores.
Solo puedo concluir que este es un problema con el classpath calculado por IntelliJ. Espero que esto te ayude a dar un paso más.
Suponiendo que haya declarado la dependencia:
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>javax.transaction-api</artifactId>
<version>1.3</version>
</dependency>
Incluye lo siguiente en module-info.java
:
requires java.transaction;
La versión 1.3 declara el nombre del módulo automático, mientras que la versión 1.2 no.
El último requires javax.transaction.api;
. Source
este problema (o similar) ya se había archivado para Spring-boot en GitHub (pero con Java 9).
Tendría moditect bajo sospecha, aunque también hay problemas para moditect
en GitHub y también he encontrado su issue allí; la actualización de ASM a 6.2.1
corrige al menos otro cambio de interrupción:
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>6.2.1</version>
</dependency>