问题描述
我们有一个使用 OR-Tools 的应用程序,它由几个模块(maven,都是 Spring Boot 模块)组成,特别是:
- 优化器模块,它通过以下代码加载 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;
}
}
- 服务模块,它使用:
@Service
public class PlanningService{
public void executePlan() {
OptimizerData optimizerData = Optimizer.getoptimizerData();
...
}
}
- web 模块(spring-boot-starter-web 依赖项),通过控制器提供服务
我们在以下情况下成功运行了代码:
- 在 Optimizer 模块上运行测试方法(在 netbeans 上),该方法调用
getoptimizerData
方法 - 生成一个包含 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
一些重要的观察:
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);
}
}
}
这种方法的结果是:
- 优化器模块中的测试文件(运行良好):
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
- 通过编译 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
- 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 加载的类之间略有不同,特别是:
- Netbeans 运行:
Loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader : C:\Users\my-user\AppData\Local\Temp\temp_ortools\jniortools.dll
- 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 库一起工作的见解。