问题
I'm debugging why in the presence of module-info.java
in my Spring Boot application, spring-orm
throws an exception during start up time. This is the 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
I tracked down the problem to to URLClassLoader.findResource("")
returning null
if module-info.java
is present but "file:/C:/Users/pupeno/Documents/Dashman/code/dashmanserver/target/classes/"
if it's not.
I created the minimum possible example that throws the same exception. To run it, you need to:
- Clone and install a recent copy of Moditect from here: https://github.com/moditect/moditect due to this bug fix not being out yet: https://github.com/moditect/moditect/issues/51
- Clone the demo repo from: https://github.com/dashmantech/demo
- Set up a local PostgreSQL database with credentials demo/confi/application.properties
- Run
mvn clean package
first, so that ModiTec creates all the modules - Open the project in a recent copy of IntelliJ
- Click play for the "Run Demo" profile (the
.idea
directory is included with the appropriate run profile, with arguments, etc).
I need findResource("")
to return "file:/C:/Users/pupeno/Documents/Dashman/code/dashmanserver/target/classes/"
so that spring-orm
can work.
findResource("")
looks like this:
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;
}
So I can see there's some access going on that's ok without using the module system but it's prevented by Java's module system when a module-infe.java
is present. My problem is that I don't see how to make it work, what should export or open for it to work?
The way Spring Boot is causing the call of that method is through RestartClassLoader
, a subclass of URLClassLoader
, specifically, line 124 which calls super.findResource(name)
in:
@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));
}
The specific RestartClassLoader
instance being used is a member of ClassPathResource
and it's defined this way:
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
in the constructor, line 85.
Lastly, getDefaultClassLoader()
looks like this:
/**
* 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;
}
My module-info.java
contains:
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;
}
In IntelliJ I have these plugins enabled:
- Lombok Plugin
- .ginore
- PowerShell
- VisualVM Launcher
- ANSI Highlighter
- Batch Scripts Support
- Bytecode Viewer
- CMD Support
- Copyright
- Coverage
- CSS Support
- Database Tools and SQL
- Git Integration
- GitHub
- Gradle
- Groovy
- Heroku integration
- HTML Tools
- HTTP Client
- l18n for Java
- IDE Settings Sync
- Java Bytecode Decompiler
- Java EE: EJB, JPA, Servlets
- Java Stream debugger
- JavaFX
- JUnit
- Lines Sorter
- Markdown support
- Maven Integration
- Maven Integration Extension
- Persistence Frameworks Support
- Properties Support
- Smali Support
- Spring AOP/@AspectJ
- Spring Batch
- Spring Boot
- Spring Data
- Spring Integration Patterns
- Spring OSGi
- Spring Security
- Spring Support
- Spring Web Services
- Spring WebSocket
- Terminal
- YAML
回答1:
One thing I notice is that your application (assuming that it's packaged in tech.flexpoint.dashman
) does not seem to be opened up to Spring in any way, which will surely result in failed class loading/illegal access.
I would expect to see something like this in module-info.java
(depending on your Spring dependencies):
opens tech.flexpoint.dashman to spring.core, spring.beans, spring.context;
The exception is a NoClassDefFoundError
, which is thrown at runtime when the class definition of a class that was known at compile time cannot be resolved, in this case the interface javax.transaction.UserTransaction
, which is part of the Java Transaction API (JTA).
As others have pointed out, JTA is not bundled with the JDK, and needs to be added as a compile dependency. However, the class that needs to load the UserTransaction
class definition comes from the spring-boot-autoconfigure
artifact, which is responsible for its own dependencies (spring-boot-autoconfigure@2.4.0.RELEASE 🡒 jboss-transaction-spi@7.6.0.Final 🡒 jboss-transaction-api_1.2_spec@1.1.1.Final), so you should not need to add JTA as a dependency.
However, because you want to package your own app as a Java 9 module, it needs to explicitly state its dependencies. spring-boot-autoconfigure
is not yet a modularized Java 9 library, and does not do this for you (i.e. transitively). The automatic module name for JTA is java.transaction
, so you need to add the requirement in module-info.java
:
requires java.transaction;
I got your example running and did indeed get NoClassDefFoundError
s when running from IntelliJ IDEA. The stacktrace pointed back to a ClassNotFoundException
, which indicates classpath problems. Since IDEA calculates the classpath when launching the application from there, I wanted to see if I could reproduce the error when using the spring-boot-maven-plugin
to run the application.
I copied the IDEA run configuration to the spring-boot-maven-plugin
configuration, as shown below:
<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>
Then I invoked mvn spring-boot:run
and voila, the application booted successfully without errors.
I can only conclude that this is an issue with the classpath calculated by IntelliJ.
回答2:
Assuming you have declared the dependency:
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>javax.transaction-api</artifactId>
<version>1.3</version>
</dependency>
Include the following in module-info.java
:
requires java.transaction;
Version 1.3 declares the automatic module name, while version 1.2 doesn't.
The latter requires javax.transaction.api;
. Source
回答3:
As you have mentioned in your original problem, the code works without module-info.java but not with the module-info.java. I can see that you have done all this hard work in explaining the problem, creating a minimal project and so on to drill down to the problem.
Looking at your problem it is obvious that one of the modules is causing the URLClassLoader.findResource("")
returning null
. It might be one of the modules down the list is overriding this class method or have an ambiguous implementation.
Why don't you start with an empty module-info.java for the minimal example and keep adding 1 module at a time till we see the error? I believe this will help us find the culprit.
回答4:
this (or similar) issue already had been filed for spring-boot on GitHub (but with Java 9).
I'd have moditect under suspicion, while there are also issues filed for moditect
on GitHub and I've also found your issue there; updating ASM to 6.2.1
fixes at least one other breaking change:
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>6.2.1</version>
</dependency>
来源:https://stackoverflow.com/questions/51944963/findresource-returning-null-when-module-info-java-is-present-why-is-that