将 JNI 库添加到 spring 引导 (maven) jar

Adding a JNI library to a spring boot (maven) jar

我在 Java-Spring-Boot 应用程序、Windows 10 OS 和 Intellij IDE 上使用 Google Or-Tools 库. 为了让它在 intellij 上工作,我做了以下事情:

  1. 为 Visual Studio 2019 安装 Microsoft Visual C++ Redistributable(根据 installation instructions 要求)。

  2. 下载并提取了 Java 的 OR-Tools 库(包括 2 个 jar 和 1 个 dll 文件)。

  3. 在 Intellij 中,我将这些 jar 添加为模块依赖项(在名为 lib 的文件夹下)。

  4. 在 Intellij 运行 配置中将 lib 库路径添加到 VM 选项。

  5. 在我的代码中静态加载库:

    static {System.loadLibrary("jniortools");}

现在我可以运行 项目成功形成 Intellij。 接下来,我想将所有内容打包到一个 spring 引导 jar 中,它可以 运行 在任何 windows 机器上。

我的文件夹结构是:

我的 pom 文件非常基础,一些依赖标准 spring-boot-maven-plugin:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

当我尝试使用 mvn install 打包代码时,我得到了包 com.google.ortools.sat does not exist

我如何确保 maven 将那些第 3 方 jar 打包到可执行文件 spring-boot jar?

更新

我添加到我的 pom 文件中:

<dependency>
    <groupId>com.google.ortools</groupId>
    <artifactId>ortools</artifactId>
    <version>LATEST</version>
    <type>jar</type>
    <scope>system</scope>
    <systemPath>${project.basedir}/lib/com.google.ortools.jar</systemPath>
</dependency>
<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>LATEST</version>
    <type>jar</type>
    <scope>system</scope>
    <systemPath>${project.basedir}/lib/protobuf.jar</systemPath>
</dependency>


<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-antrun-plugin</artifactId>
            <version>1.7</version>
            <executions>
                <execution>
                    <phase>generate-test-sources</phase>
                    <configuration>
                        <tasks>
                            <mkdir dir="${project.basedir}/target/lib"/>
                            <echo message="Creating lib folder..."/>
                            <copy todir="${project.basedir}/target/lib">
                                <fileset dir="${project.basedir}/lib">
                                    <include name="**/**"/>
                                </fileset>
                            </copy>
                        </tasks>
                    </configuration>
                    <goals>
                        <goal>run</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

另外添加到库路径:

static {
    try {
        String orToolsDllLibrary = System.getProperty("user.dir") + "\lib";
        addLibraryPath(orToolsDllLibrary);
        System.loadLibrary("jniortools");
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public static void addLibraryPath(String pathToAdd) throws Exception {
    final Field usrPathsField = ClassLoader.class.getDeclaredField("usr_paths");
    usrPathsField.setAccessible(true);

    //get array of paths
    final String[] paths = (String[]) usrPathsField.get(null);

    //check if the path to add is already present
    for (String path : paths) {
        if (path.equals(pathToAdd)) {
            return;
        }
    }

    //add the new path
    final String[] newPaths = Arrays.copyOf(paths, paths.length + 1);
    newPaths[newPaths.length - 1] = pathToAdd;
    usrPathsField.set(null, newPaths);
}

现在 运行ning 命令 java -jar myApp-0.0.1-SNAPSHOT.jar 出现异常:

Caused by: java.lang.NoClassDefFoundError: com/google/ortools/sat/CpSolver
        at java.base/java.lang.Class.getDeclaredMethods0(Native Method) ~[na:na]
        at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3167) ~[na:na]
        at java.base/java.lang.Class.getDeclaredMethods(Class.java:2310) ~[na:na]
        at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:463) ~[spring-core-5.2.6.RELEASE.jar!/:5.2.6.RELEASE]
        ... 29 common frames omitted
Caused by: java.lang.ClassNotFoundException: com.google.ortools.sat.CpSolver
        at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:471) ~[na:na]
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:588) ~[na:na]
        at org.springframework.boot.loader.LaunchedURLClassLoader.loadClass(LaunchedURLClassLoader.java:129) ~[solver-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521) ~[na:na]
        ... 33 common frames omitted

我不确定您是如何将库添加到您的项目中的?你好像没有通过Maven搞定吧?

过去我采用了通过在 Maven 中使用 system 范围来添加它的方法(参见 here。这会在你的 pom 中提供类似这样的内容:

<dependency>
    <groupId>com.google.ortools</groupId>
    <artifactId>ortools</artifactId>
    <version>1.0</version>
    <scope>system</scope>
    <systemPath>${basedir}/src/main/resources/lib/com.google.ortools.jar</systemPath>
</dependency>

但是,这种方法也可能很痛苦,尤其是当您必须在多平台上工作时。最近,我发现了 this repo,这让我使用 OR 工具变得更加轻松。希望这有帮助。

更新: 我强烈建议使用下面更新的方法,因为它不那么令人头疼:

<repositories>
    ...
    <repository>
        <id>bintray</id>
        <url>https://dl.bintray.com/magneticflux/maven</url>
    </repository>
    ....
</repositories>
<dependencies>
  <dependency>
       <groupId>com.skaggsm.ortools</groupId>
       <artifactId>ortools-natives-all</artifactId>
       <version>7.7.7810</version>
  </dependency>
  <!-- OR-tools needs protobuf       -->
   <dependency>
       <groupId>com.google.protobuf</groupId>
       <artifactId>protobuf-java</artifactId>
       <version>3.12.2</version>
   </dependency>
</dependencies>

然后就可以静态加载库了:

static {
    OrToolsHelper.loadLibrary()
}

确保使用 JDK >= 11 作为详细说明 here

我试过了:

首先实用地将jniortools库添加到java.library.path

static {
    String orToolsDllLibrary = System.getProperty("user.dir") + "\lib";
    addLibraryPath(orToolsDllLibrary);
    System.loadLibrary("jniortools");
}


public static void addLibraryPath(String pathToAdd) throws Exception {
    final Field usrPathsField = ClassLoader.class.getDeclaredField("usr_paths");
    usrPathsField.setAccessible(true);

    //get array of paths
    final String[] paths = (String[]) usrPathsField.get(null);

    //check if the path to add is already present
    for (String path : paths) {
        if (path.equals(pathToAdd)) {
            return;
        }
    }

    //add the new path
    final String[] newPaths = Arrays.copyOf(paths, paths.length + 1);
    newPaths[newPaths.length - 1] = pathToAdd;
    usrPathsField.set(null, newPaths);
}

在 pom 文件中:

<dependencies>
....

    <dependency>
        <groupId>com.google.protobuf</groupId>
        <artifactId>protobuf-java</artifactId>
        <version>3.12.2</version>
    </dependency>
    <dependency>
        <groupId>com.google</groupId>
        <artifactId>ortools</artifactId>
        <version>0.0.2</version>
    </dependency>
    
    ....
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-install-plugin</artifactId>
            <version>2.5.2</version>
            <executions>
                <execution>
                    <id>install-external</id>
                    <phase>process-resources</phase>
                    <configuration>
                        <file>${basedir}/lib/com.google.ortools.jar</file>
                        <repositoryLayout>default</repositoryLayout>
                        <groupId>com.google</groupId>
                        <artifactId>ortools</artifactId>
                        <version>0.0.2</version>
                        <packaging>jar</packaging>
                        <generatePom>true</generatePom>
                    </configuration>
                    <goals>
                        <goal>install-file</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-antrun-plugin</artifactId>
            <version>1.7</version>
            <executions>
                <execution>
                    <phase>generate-sources</phase>
                    <configuration>
                        <tasks>
                            <mkdir dir="${project.basedir}/target/lib"/>
                            <echo message="Creating lib folder..."/>
                            <copy todir="${project.basedir}/target/lib">
                                <fileset dir="${project.basedir}/lib">
                                    <include name="**/**"/>
                                </fileset>
                            </copy>
                        </tasks>
                    </configuration>
                    <goals>
                        <goal>run</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>