spring boot - ejemplo - ¿Por qué los Jars en tarros no pueden ver el contenido de otros tarros en tarros si están en el mismo tarro?
classloader java ejemplo (3)
tl; dr: Las clases en nuestro jar de Spring Boot parecen ver las clases dentro de los frascos empaquetados, pero sus contenidos no parecen ser capaces de hacerlo. ¿Por qué?
Nuestro producto principal es una aplicación web, pero toda la lógica empresarial está centralizada en un núcleo mac-guffin-api.jar
. mac-guffin-api.jar
no es un proyecto de Spring Boot, pero tiene un archivo de configuración Spring Java llamado net.initech.api.Configuration
que inicializa todos los servicios y repositorios, etc. Usamos MS SQL Server como nuestro back-end con el sqljdbc42:jar
controlador de sqljdbc42:jar
Necesitábamos escribir un ETL que necesitara reutilizar la misma lógica comercial del proyecto API, por lo que creamos un proyecto Spring Boot Spring Batch que importa mac-guffin-api.jar
como una dependencia Maven. La configuración de la configuración de ETL ( net.initech.etl.Configuration
) de las API de importación sin problemas (puedo verlo desde el registro de la consola) pero cuando la configuración de la API va a crear la conexión de la base de datos, no puede encontrar el controlador.
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
Sin embargo, puedo ver claramente que el JAR que contiene el controlador está presente. Los contenidos del mac-guffin-api.jar
ETL son ( Nb: mac-guffin-api.jar
y sqljdbc42-4.2.jar
no desempaquetados, son jar en el contenedor ETL):
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
Aparentemente, la clase de configuración de la clase ETL puede ver el contenido de los archivos JAR incluidos (o al menos los contenidos del contenedor API), pero el contenedor API no parece poder ver el com.microsoft.sqlserver.jdbc.SQLServerDriver.class
en el jar servidor JDBC de SQL Server.
Incluso puedo hacer un Class.forName( "com.microsoft.sqlserver.jdbc.SQLServerDriver.class" )
antes de la instanciación del contexto Spring y no tiene ningún problema.
¿Es esto una limitación del cargador de clases? ¿Esto es porque el proyecto API no es Spring Boot? ¿Es por un parámetro de configuración faltante? ¿Que esta pasando aqui?
En algún lugar de su configuración, ha terminado con el nombre de clase que se está utilizando como valor:
''com.microsoft.sqlserver.jdbc.SQLServerDriver''
con comillas simples a su alrededor. Normalmente, el nombre de la clase que se está cargando se imprime sin comillas, doble o simple.
Esto explicaría por qué puede cargar la clase, pero el jar API no. Verifique los archivos de configuración / compilación donde se establece el nombre del controlador.
MANIFESTACIÓN
La única forma en que puedo recibir un mensaje como el tuyo:
Caused by: java.lang.ClassNotFoundException: ''com.microsoft.sqlserver.jdbc.SQLServerDriver''
y no:
Caused by: java.lang.ClassNotFoundException: com.microsoft.sqlserver.jdbc.SQLServerDriver
Es pedir deliberadamente cargar una clase con comillas simples en el nombre. Por ejemplo:
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''");
}
}
productos:
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)
Es posible que obtenga el valor del controlador de la configuración, por ejemplo
my.driver = ''com.microsoft.sqlserver.jdbc.SQLServerDriver''
Y esa configuración devuelve valor con comillas simples. Por favor revisa tus archivos de configuración.
Parece que le falta el archivo MANFIEST.MF que dirige Spring sobre cómo cargar los archivos jar anidados. Aquí hay una jerarquía de ejemplo de la documentación de Spring. Puede leer sobre cómo configurarlo yendo aquí .
MANIFEST.MF debería contener esto (para la estructura siguiente):
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.mycompany.project.MyApplication
Start-Class
es su punto de entrada a su aplicación Main-Class
es el Loader que necesita para cargar los contenedores anidados.
Estructura de ejemplo:
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