springboot项目使用ffmpeg和opencv运行时如何加载so依赖?

版本

  • java21
  • springboot3
  • ffmpeg 7.1-1.5.11
  • opencv 4.10.0-1.5.11

pom.xml

  <properties>
    <bytedeco.version>1.5.11</bytedeco.version>
    <opencv-platform.version>4.10.0-1.5.11</opencv-platform.version>
    <ffmpeg-platform.version>7.1-1.5.11</ffmpeg-platform.version>
    <javacpp.platform.linux-x86_64>linux-x86_64</javacpp.platform.linux-x86_64>
    <javacpp.platform.windows-x86_64>windows-x86_64</javacpp.platform.windows-x86_64>
    <javacpp.platform>${javacpp.platform.linux-x86_64}</javacpp.platform>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.bytedeco</groupId>
      <artifactId>javacpp</artifactId>
      <version>${bytedeco.version}</version>
    </dependency>
    <dependency>
      <groupId>org.bytedeco</groupId>
      <artifactId>javacv</artifactId>
      <version>${bytedeco.version}</version>
    </dependency>
    <dependency>
      <groupId>org.bytedeco</groupId>
      <artifactId>opencv</artifactId>
      <version>${opencv-platform.version}</version>
      <classifier>${javacpp.platform}</classifier>
    </dependency>
    <dependency>
      <groupId>org.bytedeco</groupId>
      <artifactId>ffmpeg</artifactId>
      <version>${ffmpeg-platform.version}</version>
      <classifier>${javacpp.platform}</classifier>
    </dependency>
  </dependencies>

springboot打包后jar包结构(简化后)

image.png

可以看到在spring的jar包中的BOOT-INF/lib目录下ffmpeg-7.1-1.5.11-linux-x86_64.jaropencv-4.10.0-1.5.11-linux-x86_64.jar中,已经存在运行所需的so依赖。正常来说在运行时就会加载这些库。
  • 但是实际运行时它并没有到ffmpeg-7.1-1.5.11-linux-x86_64.jaropencv-4.10.0-1.5.11-linux-x86_64.jar中查找所需的库,出现了以下错误:

    Caused by: java.lang.ExceptionInInitializerError: Exception java.lang.UnsatisfiedLinkError: no jniavutil in java.library.path: /usr/java/packages/lib:/usr/lib64:/lib64:/lib:/usr/lib [in thread "http-nio-8100-exec-1"]
      at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2458) ~[na:na]
      at java.base/java.lang.Runtime.loadLibrary0(Runtime.java:916) ~[na:na]
      at java.base/java.lang.System.loadLibrary(System.java:2059) ~[na:na]
      at org.bytedeco.javacpp.Loader.loadLibrary(Loader.java:1832) ~[javacpp-1.5.11.jar!/:1.5.11]
      at org.bytedeco.javacpp.Loader.load(Loader.java:1423) ~[javacpp-1.5.11.jar!/:1.5.11]
      at org.bytedeco.javacpp.Loader.load(Loader.java:1234) ~[javacpp-1.5.11.jar!/:1.5.11]
      at org.bytedeco.javacpp.Loader.load(Loader.java:1210) ~[javacpp-1.5.11.jar!/:1.5.11]
      at org.bytedeco.ffmpeg.global.avutil.<clinit>(avutil.java:14) ~[ffmpeg-7.1-1.5.11.jar!/:7.1-1.5.11]
      at java.base/java.lang.Class.forName0(Native Method) ~[na:na]
      at java.base/java.lang.Class.forName(Class.java:534) ~[na:na]
      at java.base/java.lang.Class.forName(Class.java:513) ~[na:na]
      at org.bytedeco.javacpp.Loader.load(Loader.java:1289) ~[javacpp-1.5.11.jar!/:1.5.11]
      at org.bytedeco.javacpp.Loader.load(Loader.java:1234) ~[javacpp-1.5.11.jar!/:1.5.11]
      at org.bytedeco.javacpp.Loader.load(Loader.java:1226) ~[javacpp-1.5.11.jar!/:1.5.11]
      at org.bytedeco.javacv.FFmpegFrameGrabber.tryLoad(FFmpegFrameGrabber.java:111) ~[javacv-1.5.11.jar!/:1.5.11]
      at org.bytedeco.javacv.FFmpegFrameGrabber.<clinit>(FFmpegFrameGrabber.java:137) ~[javacv-1.5.11.jar!/:1.5.11]
      ... 179 common frames omitted
在Windows中上开发环境中没有问题他可以找到,但是打包成jar运行就出错了,我觉得可能是因为在开发工具中运行时工具自动设置了classpath,在调用ffmpeg的时候,因为ffmpeg-7.1-1.5.11-linux-x86_64.jaropencv-4.10.0-1.5.11-linux-x86_64.jar在classpath中,所以能找到相关库。
但是在打成jar后,运行的命令是java -jar xxx.jar 此时ffmpegopencv就不在classpath中,所以找不到,不知道这样理解对不对。
  • idea 开发环境运行时的命令

    C:\app\java\java-21\bin\java.exe
     -XX:TieredStopAtLevel=1
     -Dspring.output.ansi.enabled=always
     -Dcom.sun.management.jmxremote
     -Dspring.jmx.enabled=true
     -Dspring.liveBeansView.mbeanDomain
     -Dspring.application.admin.enabled=true "-Dmanagement.endpoints.jmx.exposure.include=*"
     -classpath "D:\app_data\maven_repo\org\bytedeco\javacpp\1.5.11\javacpp-1.5.11.jar;
               D:\app_data\maven_repo\org\bytedeco\javacv\1.5.11\javacv-1.5.11.jar;
               D:\app_data\maven_repo\org\bytedeco\opencv\4.10.0-1.5.11\opencv-4.10.0-1.5.11.jar;
               D:\app_data\maven_repo\org\bytedeco\ffmpeg\7.1-1.5.11\ffmpeg-7.1-1.5.11.jar;
               D:\app_data\maven_repo\org\bytedeco\opencv\4.10.0-1.5.11\opencv-4.10.0-1.5.11-windows-x86_64.jar;
               D:\app_data\maven_repo\org\bytedeco\ffmpeg\7.1-1.5.11\ffmpeg-7.1-1.5.11-windows-x86_64.jar;
     com.demo.CommonOssApplication

问题

  • 在打包后如何加载jar包lib目录中的so文件?
  • 因为最终需要构建成docker镜像,也尝试过将这些so文件添加到docker基础镜像中,但是在开发环境可以正常运行,那打包后应该也有办法让他正常运行,而不是吧so文件添加到基础镜像中或jar包外部。
阅读 435
avatarAI BotBETA

在Spring Boot打包为可执行JAR的情况下,加载嵌套JAR中的.so文件需要特殊处理。以下是解决方案:

核心问题:JavaCPP的Loader默认无法直接访问嵌套在Spring Boot JAR内的依赖JAR中的.so文件,需要显式引导加载过程。

解决方案

  1. 添加系统属性启动参数
    在启动命令中加入以下参数:

    java -Dorg.bytedeco.javacpp.pathsfirst=true -jar your-app.jar
  2. 添加初始化代码
    在Spring Boot启动类(如@SpringBootApplication类)的静态块中强制初始化FFmpeg/OpenCV:

    static {
     // 强制加载关键类
     try {
         Class.forName("org.bytedeco.ffmpeg.global.avutil");
         Class.forName("org.bytedeco.opencv.global.opencv_core");
     } catch (ClassNotFoundException e) {
         throw new RuntimeException(e);
     }
    }
  3. 验证Maven打包配置
    确保spring-boot-maven-plugin正确包含依赖:

    <plugin>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-maven-plugin</artifactId>
     <configuration>
         <excludes>
             <!-- 不要排除任何需要的依赖 -->
             <exclude>
                 <groupId>none</groupId>
                 <artifactId>none</artifactId>
             </exclude>
         </excludes>
     </configuration>
    </plugin>
  4. 完整依赖配置示例

    <dependency>
     <groupId>org.bytedeco</groupId>
     <artifactId>opencv-platform</artifactId>
     <version>${opencv-platform.version}</version>
    </dependency>
    <dependency>
     <groupId>org.bytedeco</groupId>
     <artifactId>ffmpeg-platform</artifactId>
     <version>${ffmpeg-platform.version}</version>
    </dependency>

原理说明

  • pathsfirst=true强制优先从classpath查找库
  • 静态初始化块确保在Spring上下文加载前触发JavaCPP的加载机制
  • 使用-platform依赖会自动包含所有平台库,但需要结合javacpp.platform属性过滤

Docker部署注意事项
即使正确加载.so文件,仍需确保基础镜像包含FFmpeg/OpenCV的运行时依赖(如libavcodec等)。推荐使用包含多媒体支持的base image:

FROM eclipse-temurin:21-jre-jammy
RUN apt-get update && apt-get install -y \
    libopencv-dev \
    ffmpeg \
    && rm -rf /var/lib/apt/lists/*
2 个回答

问题的核心在于JavaCPP的Loader类需要能够找到并解压这些本地库文件。当你在开发环境运行时,IDE添加了所有的依赖到classpath中,所以能正常加载。但打包成jar后,这些嵌套在jar包中的本地库无法被直接访问。

1. 在应用启动时编程方式配置JavaCPP

创建一个配置类,在应用启动时执行:

package com.demo.config;

import org.bytedeco.javacpp.Loader;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;

@Configuration
public class JavaCppConfig {

    @PostConstruct
    public void init() {
        // 设置JavaCPP提取本地库的目录
        System.setProperty("org.bytedeco.javacpp.extract", "true");
        System.setProperty("org.bytedeco.javacpp.extractDir", "/tmp/javacpp-native-libs");
        
        // 预加载需要的库,这会触发JavaCPP从jar中提取库文件
        try {
            // 预加载FFmpeg相关库
            Loader.load(org.bytedeco.ffmpeg.global.avutil.class);
            Loader.load(org.bytedeco.ffmpeg.global.avcodec.class);
            Loader.load(org.bytedeco.ffmpeg.global.avformat.class);
            Loader.load(org.bytedeco.ffmpeg.global.swscale.class);
            
            // 预加载OpenCV库
            Loader.load(org.bytedeco.opencv.global.opencv_core.class);
            Loader.load(org.bytedeco.opencv.global.opencv_imgproc.class);
            
            System.out.println("Native libraries loaded successfully");
        } catch (Exception e) {
            System.err.println("Failed to load native libraries: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

2. 自定义Docker镜像构建

可以创建一个自定义的Dockerfile:

FROM openjdk:21-slim

# 安装程序运行所需的库
RUN apt-get update && apt-get install -y \
    libgomp1 \
    && rm -rf /var/lib/apt/lists/*

# 创建目录用于提取本地库
RUN mkdir -p /opt/javacpp-native-libs
ENV JAVACPP_EXTRACT_DIR=/opt/javacpp-native-libs

# 设置工作目录
WORKDIR /app

# 复制应用jar包
COPY target/*.jar app.jar

# 预先提取所有本地库到指定目录
RUN mkdir -p /tmp/extract && \
    cd /tmp/extract && \
    java -Dorg.bytedeco.javacpp.extract=true \
         -Dorg.bytedeco.javacpp.extractDir=/opt/javacpp-native-libs \
         -jar /app/app.jar --extract-native-libraries && \
    rm -rf /tmp/extract

# 设置系统属性,指向提取的本地库位置
ENV JAVA_OPTS="-Dorg.bytedeco.javacpp.extractDir=/opt/javacpp-native-libs -Dorg.bytedeco.javacpp.extract=false"

# 启动应用
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
  1. 在应用中添加JavaCppConfig配置类,确保应用启动时正确加载本地库
  2. 使用自定义Dockerfile构建镜像,预先提取并配置好本地库路径

这样的组合解决方案既能处理开发环境的问题,也能确保在Docker容器中正常运行,而不必修改基础镜像。

值得注意的问题:

  • JavaCPP需要能够写入临时目录来提取本地库
  • 在Docker环境中,可能需要添加-Dorg.bytedeco.javacpp.maxphysicalbytes=0来防止内存限制问题
  • 如果使用多个平台(如windows和linux),注意配置正确的platform属性

或者可以尝试

通过Java系统属性配置JavaCPP

在启动应用时添加以下系统属性:

java -Djava.library.path=path/to/extracted/libs -Djavacpp.platform=linux-x86_64 -jar your-app.jar

使用 Maven 插件解压本地库

在打包时,使用 Maven 插件(如 maven-dependency-plugin)将 BOOT-INF/lib 中的 .so 文件解压到文件系统的一个特定目录(例如 /tmp/native-libs),然后在运行时设置 java.library.path 指向该目录。以下是 pom.xml 配置示例:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-dependency-plugin</artifactId>
            <executions>
                <execution>
                    <id>unpack-native-libs</id>
                    <phase>prepare-package</phase>
                    <goals>
                        <goal>unpack</goal>
                    </goals>
                    <configuration>
                        <artifactItems>
                            <artifactItem>
                                <groupId>org.bytedeco</groupId>
                                <artifactId>ffmpeg</artifactId>
                                <version>${ffmpeg-platform.version}</version>
                                <type>jar</type>
                                <overWrite>false</overWrite>
                                <outputDirectory>${project.build.directory}/native-libs</outputDirectory>
                                <includes>**/*.so</includes>
                            </artifactItem>
                            <artifactItem>
                                <groupId>org.bytedeco</groupId>
                                <artifactId>opencv</artifactId>
                                <version>${opencv-platform.version}</version>
                                <type>jar</type>
                                <overWrite>false</overWrite>
                                <outputDirectory>${project.build.directory}/native-libs</outputDirectory>
                                <includes>**/*.so</includes>
                            </artifactItem>
                        </artifactItems>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
  • 打包后,.so 文件会解压到 target/native-libs 目录。
  • 运行时通过 -Djava.library.path=target/native-libs 加载。

Docker 环境优化

如果最终需要构建 Docker 镜像,可以在 Dockerfile 中复制 .so 文件到一个固定的目录(例如 /app/native-libs),并在启动时设置 java.library.path:

FROM openjdk:21-jdk-slim

WORKDIR /app

COPY target/your-app.jar /app/
COPY target/native-libs/ /app/native-libs/

ENTRYPOINT ["java", "-Djava.library.path=/app/native-libs", "-jar", "your-app.jar"]
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题