Netbeans 上的 Spring 应用程序 java.lang.UnsatisfiedLinkError 运行

问题描述

我们有一个使用 OR-Tools 的应用程序,它由几个模块(maven,都是 Spring Boot 模块)组成,特别是:

  1. 优化器模块,它通过以下代码加载 or-tools DLL:
public class Optimizer {

    static {
        try {
            // extracts .dll from or-tools-windows jar and copies it into C:\users\my-user\AppData\Local\Temp\temp_ortools
            // calls System.load("C:\users\my-user\AppData\Local\Temp\temp_ortools\jniortools.dll")
            carregaBibliotecasORTools(); 
        } catch (Exception e) {
            log.info(e.getMessage());
        } catch (Error e) {
            log.info(e.getMessage());
        }
    }

    public static OptimizerData getoptimizerData() {
       
        OptimizerData optimizerData = new OptimizerData();
        System.out.println("Instantiating Solver");
        optimizerData.solver = MPSolver.createSolver("SCIP");
        System.out.println("Solver Instantiated");
        
        return optimizerData;
        
    }

}
  1. 服务模块,它使用:
@Service
public class PlanningService{

    public void executePlan() {
       
        OptimizerData optimizerData = Optimizer.getoptimizerData();

        ...
        
    }

}
  1. web 模块(spring-boot-starter-web 依赖项),通过控制器提供服务

我们在以下情况下成功运行了代码

  1. 在 Optimizer 模块上运行测试方法(在 netbeans 上),该方法调用 getoptimizerData 方法
  2. 生成一个包含 3 个模块的 fat jar(通过在 netbeans 上构建 web 模块)并运行 fat jar。端点调用工作正常

但是,当我们在 Netbeans 上运行 web 模块时,调用端点时会显示以下错误消息: 2021-05-09 22:59:38.587 ERROR 19300 --- [nio-8020-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch Failed; nested exception is java.lang.UnsatisfiedLinkError: com.google.ortools.linearsolver.main_research_linear_solverJNI.MPSolver_createSolver(Ljava/lang/String;)J] with root cause

一些重要的观察:

  1. 鉴于 fat jar 工作正常,这似乎是 netbeans 特定的问题
  2. 我们执行了以下方法 (System.load) 以验证库是否确实已加载:
    public static void listAllLoadednativeLibrariesFromJVM() {
        
        ClassLoader appLoader = ClassLoader.getSystemClassLoader();
        ClassLoader currentLoader = LibraryLoader.class.getClassLoader();

        ClassLoader[] loaders = new ClassLoader[] { appLoader,currentLoader };
        for (int i=0; i<loaders.length; i++) {
            final String[] libraries = ClassScope.getLoadedLibraries(loaders[i]);
            for (String library : libraries) {
                System.out.println("Loader " + loaders[i].getClass().getName() + " : " + library);
            }
        }
        
    }

这种方法的结果是:

  1. 优化器模块中的测试文件(运行良好):
Loader sun.misc.Launcher$AppClassLoader : C:\Program Files\Java\jdk1.8.0_281\jre\bin\zip.dll
Loader sun.misc.Launcher$AppClassLoader : C:\Users\my-user\AppData\Local\Temp\temp_ortools\jniortools.dll
Loader sun.misc.Launcher$AppClassLoader : C:\Program Files\Java\jdk1.8.0_281\jre\bin\zip.dll
Loader sun.misc.Launcher$AppClassLoader : C:\Users\my-user\AppData\Local\Temp\temp_ortools\jniortools.dll
  1. 通过编译 web 模块生成的 Fat .jar(工作正常):
Loader sun.misc.Launcher$AppClassLoader : C:\Program Files\Java\jre1.8.0_281\bin\zip.dll
Loader sun.misc.Launcher$AppClassLoader : C:\Program Files\Java\jre1.8.0_281\bin\management.dll
Loader sun.misc.Launcher$AppClassLoader : C:\Program Files\Java\jre1.8.0_281\bin\net.dll
Loader sun.misc.Launcher$AppClassLoader : C:\Program Files\Java\jre1.8.0_281\bin\nio.dll
Loader sun.misc.Launcher$AppClassLoader : C:\Users\my-user\AppData\Local\Temp\temp_ortools\jniortools.dll
Loader org.springframework.boot.loader.Launchedurlclassloader : C:\Program Files\Java\jre1.8.0_281\bin\zip.dll
Loader org.springframework.boot.loader.Launchedurlclassloader : C:\Program Files\Java\jre1.8.0_281\bin\management.dll
Loader org.springframework.boot.loader.Launchedurlclassloader : C:\Program Files\Java\jre1.8.0_281\bin\net.dll
Loader org.springframework.boot.loader.Launchedurlclassloader : C:\Program Files\Java\jre1.8.0_281\bin\nio.dll
Loader org.springframework.boot.loader.Launchedurlclassloader : C:\Users\my-user\AppData\Local\Temp\temp_ortools\jniortools.dll
  1. Netbeans 在 Web 模块上“运行”命令
Loader sun.misc.Launcher$AppClassLoader : C:\Program Files\Java\jdk1.8.0_281\jre\bin\zip.dll
Loader sun.misc.Launcher$AppClassLoader : C:\Program Files\Java\jdk1.8.0_281\jre\bin\management.dll
Loader sun.misc.Launcher$AppClassLoader : C:\Program Files\Java\jdk1.8.0_281\jre\bin\net.dll
Loader sun.misc.Launcher$AppClassLoader : C:\Program Files\Java\jdk1.8.0_281\jre\bin\nio.dll
Loader sun.misc.Launcher$AppClassLoader : C:\Users\my-user\AppData\Local\Temp\temp_ortools\jniortools.dll
Loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader : C:\Program Files\Java\jdk1.8.0_281\jre\bin\zip.dll
Loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader : C:\Program Files\Java\jdk1.8.0_281\jre\bin\management.dll
Loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader : C:\Program Files\Java\jdk1.8.0_281\jre\bin\net.dll
Loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader : C:\Program Files\Java\jdk1.8.0_281\jre\bin\nio.dll
Loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader : C:\Users\my-user\AppData\Local\Temp\temp_ortools\jniortools.dll

解决方法

通过比较 listAllLoadedNativeLibrariesFromJVM 调用的最后 2 个输出,fat jar 和 netbeans 加载的类之间略有不同,特别是:

  1. Netbeans 运行:Loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader : C:\Users\my-user\AppData\Local\Temp\temp_ortools\jniortools.dll
  2. fat jar 运行:Loader org.springframework.boot.loader.LaunchedURLClassLoader : C:\Users\my-user\AppData\Local\Temp\temp_ortools\jniortools.dll

根据这些日志,我们确定以下依赖性是问题的原因:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>

这种依赖允许在代码更改的情况下更快地重新启动 Spring 应用程序。通过删除此依赖项,应用程序可以在 Netbeans 上运行而不会出现任何问题。

欢迎任何有关如何使 Spring Boot Devtools 与静态加载的 .dll 库一起工作的见解。