Reputation: 18939
tl;dr: The classes in our Spring Boot jar seem to see classes within the bundled jars, but their contents don't seem to be able to. Why?
Our main product is a web app, but all the business logic is centralized in a core mac-guffin-api.jar
. mac-guffin-api.jar
is not a Spring Boot project, but has a Spring Java config file called net.initech.api.Configuration
that initializes all the services and repositories etc. We use MS SQL Server as our backend with the sqljdbc42:jar
driver.
We needed to write an ETL that needed to reuse the same business logic from API project so we created a Spring Boot Spring Batch project that imports mac-guffin-api.jar
as a Maven dependency. The ETL's configuration (net.initech.etl.Configuration
)import's APIs configuration without problem (I can see it from the console logging) but when the API configuration goes to create the database connection it cannot find the driver.
Caused by: java.lang.ClassNotFoundException: 'com.microsoft.sqlserver.jdbc.SQLServerDriver'
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at org.springframework.boot.loader.LaunchedURLClassLoader.loadClass(LaunchedURLClassLoader.java:94)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Unknown Source)
at org.apache.tomcat.jdbc.pool.PooledConnection.connectUsingDriver(PooledConnection.java:246)
... 113 more
However, I can clearly see that the JAR containing the driver is present. The contents of the ETL jar are (Nb: mac-guffin-api.jar
and sqljdbc42-4.2.jar
are not unpacked, they are jars in the ETL jar ) :
mac-guffin-etl.jar
|
+- org.springframework.boot.loader...
|
+- BOOT-INF
|
+- classes
| |
| +- com.initech.etl.Main.class
| |
| +- com.initech.etl.Configuration.class
|
+- lib
|
+- mac-guffin-api.jar
| |
| +- com.initech.api.Configuration.class
|
+- sqljdbc42-4.2.jar
|
+- com.microsoft.sqlserver.jdbc.SQLServerDriver.class
So apparently the class ETL's configuration class can see the content's of the included JARs (or at least the contents of API jar), but they API jar does not seem to be able to see the com.microsoft.sqlserver.jdbc.SQLServerDriver.class
in the fellow SQL Server JDBC jar.
I'm even able to do a Class.forName( "com.microsoft.sqlserver.jdbc.SQLServerDriver.class" )
from before the instantiation of the Spring context and it doesn't have a problem.
Is this is a limitation of the class loader? Is this because the API project is not Spring Boot? Is it because of a missing configuration parameter? What is going on here?
Upvotes: 7
Views: 533
Reputation: 2778
Somewhere in your configuration, you have ended up with the classname that is being used as the value:
'com.microsoft.sqlserver.jdbc.SQLServerDriver'
with single quotes around it. Normally the class name being loaded is printed without quotes, double or single.
This would explain why you are able to load the class but the API jar is not. Check you configuration/build files for where the driver name is set.
The only way I can get a message like yours:
Caused by: java.lang.ClassNotFoundException: 'com.microsoft.sqlserver.jdbc.SQLServerDriver'
and not:
Caused by: java.lang.ClassNotFoundException: com.microsoft.sqlserver.jdbc.SQLServerDriver
Is to deliberately ask to load a class with single quotes in the name. For example:
import java.lang.*;
public class myclass {
public static void test(String thename) {
System.out.println("trying " + thename);
try {
myclass test = (myclass) myclass.class
.getClassLoader()
.loadClass(thename)
.newInstance();
System.out.println(test.toString());
} catch (Exception e){
System.out.println("failed to load " + thename);
e.printStackTrace();
}
}
public static void main(String[] args) {
test("my.package.itwontexist");
test("'my.package.itwontexist'");
}
}
outputs:
trying my.package.itwontexist
failed to load my.package.itwontexist
java.lang.ClassNotFoundException: my.package.itwontexist
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at myclass.test(myclass.java:10)
at myclass.main(myclass.java:20)
trying 'my.package.itwontexist'
failed to load 'my.package.itwontexist'
java.lang.ClassNotFoundException: 'my.package.itwontexist'
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at myclass.test(myclass.java:10)
at myclass.main(myclass.java:21)
Upvotes: 2
Reputation: 535
It looks like you're missing the MANFIEST.MF file that directs Spring on how to load the nested jars. Here's an example hierarchy from Spring's documentation. You can read up on how to configure it by going here.
The MANIFEST.MF should contain this (for the structure below):
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.mycompany.project.MyApplication
Start-Class
is your entry point into your application
Main-Class
is the Loader you need to load the nested jars.
Example structure:
example.war
|
+-META-INF
| +-MANIFEST.MF
+-org
| +-springframework
| +-boot
| +-loader
| +-<spring boot loader classes>
+-WEB-INF
+-classes
| +-com
| +-mycompany
| +-project
| +-YourClasses.class
+-lib
| +-dependency1.jar
| +-dependency2.jar
+-lib-provided
+-servlet-api.jar
+-dependency3.jar
Upvotes: 0
Reputation: 306
Its possible that you are getting driver value from configuration, e.g.
my.driver = 'com.microsoft.sqlserver.jdbc.SQLServerDriver'
And that configuration is returning value with single quotes. Please check your configuration files.
Upvotes: 2