为什么Jars中的Jars看到Jars中其他Jars的内容,如果它们在同一个Jar中?

时间:2017-04-17 20:21:17

标签: spring-boot jar classloader

tl; dr:我们的Spring Boot jar中的类似乎看到了捆绑罐中的类,但它们的内容似乎无法实现。为什么呢?

我们的主要产品是网络应用,但所有业务逻辑都集中在核心mac-guffin-api.jar中。 mac-guffin-api.jar 不是一个Spring Boot项目,但有一个名为net.initech.api.Configuration的Spring Java配置文件初始化所有服务和存储库等。我们使用MS SQL Server作为我们的后端sqljdbc42:jar驱动程序。

我们需要编写一个需要从API项目中重用相同业务逻辑的ETL,因此我们创建了一个Spring {4}导入mac-guffin-api.jar作为Maven依赖项的Spring Boot Spring Batch项目。 ETL的配置(net.initech.etl.Configuration)导入的API配置没有问题(我可以从控制台日志中看到它),但是当API配置创建数据库连接时它无法找到司机。

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

但是,我可以清楚地看到包含驱动程序的JAR存在。 ETL jar的内容是( Nb: mac-guffin-api.jarsqljdbc42-4.2.jar 解压缩,它们是ETL jar中的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

显然,类ETL的配置类可以看到包含的JAR的内容(或者至少是API jar的内容),但是他们的API jar似乎无法看到SQL Server JDBC jar中的com.microsoft.sqlserver.jdbc.SQLServerDriver.class

我甚至可以在Spring上下文实例化之前做Class.forName( "com.microsoft.sqlserver.jdbc.SQLServerDriver.class" )并且它没有问题。

这是类加载器的限制吗?这是因为API项目不是Spring Boot吗?是因为缺少配置参数?这里发生了什么?

3 个答案:

答案 0 :(得分:1)

在您的配置中的某个地方,您最终得到了用作值的类名:

'com.microsoft.sqlserver.jdbc.SQLServerDriver'

用单引号括起来。通常,正在加载的类名称不带引号,双引号或单引号。

这可以解释为什么你能够加载类,但API jar不能。检查配置/构建文件以确定驱动程序名称的设置位置。

样本

我能得到像你这样的信息的唯一方法:

Caused by: java.lang.ClassNotFoundException: 'com.microsoft.sqlserver.jdbc.SQLServerDriver'

而不是:

Caused by: java.lang.ClassNotFoundException: com.microsoft.sqlserver.jdbc.SQLServerDriver

是故意要求在名称中加载带单引号的类。例如:

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'");
        }
}

输出:

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)

答案 1 :(得分:1)

您可能从配置中获取驱动程序值,例如

  

my.driver ='com.microsoft.sqlserver.jdbc.SQLServerDriver'

该配置使用单引号返回值。请检查配置文件。

答案 2 :(得分:0)

看起来你错过了MANFIEST.MF文件,它指示Spring如何加载嵌套的jar。这是Spring文档的一个示例层次结构。您可以了解如何配置它by going here

MANIFEST.MF应该包含这个(对于下面的结构):

Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.mycompany.project.MyApplication

Start-Class是您进入申请的入口点 Main-Class是加载嵌套jar所需的Loader。

示例结构:

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