我试图在Spring中使用OpenCV。OpenCV jar及其本机库是从源代码构建的,并放置在我的引导jar中。我已经将它们从jar中提取出来并成功加载,但是在使用时会引发错误。以下是我尝试的总结
System.loadLibrary(Core.NATIVE_LIBRARY_NAME)
加载库时,它会抛出UnsatisfiedLinkError: Native library is already loaded in another classloader
。删除第二个负载是有效的,但是由于它们没有我需要的版本,所以我必须手动构建OpenCV才能使用。System.loadLibrary(Core.NATIVE_LIBRARY_NAME)
加载,没有错误。尝试使用任何OpenCV方法,都会引发错误。这是我的密码
OpenCVLoader.java
import org.opencv.core.Core;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.nio.file.AccessDeniedException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class OpenCVLoader {
private static final Logger LOGGER = LoggerFactory.getLogger(OpenCVLoader.class);
private static final String NATIVE_LIB_NAME = "libopencv_java3411.dylib";
private static Path extractNativeBinary() {
Path nativeLibTemporaryPath = null;
try (InputStream nativeLibInputStream = new ClassPathResource(NATIVE_LIB_NAME).getInputStream()) {
nativeLibTemporaryPath = new TemporaryDirectory().markDeleteOnExit().getPath().resolve("./" + NATIVE_LIB_NAME).normalize();
Files.createDirectories(nativeLibTemporaryPath.getParent());
Files.copy(nativeLibInputStream, nativeLibTemporaryPath);
return nativeLibTemporaryPath;
} catch (IOException ex) {
LOGGER.error(ex.getMessage());
}
return nativeLibTemporaryPath;
}
private static class TemporaryDirectory {
static final String OPENCV_PREFIX = "opencv";
final Path path;
public TemporaryDirectory() {
try {
path = Files.createTempDirectory(OPENCV_PREFIX);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public Path getPath() {
return path;
}
public TemporaryDirectory markDeleteOnExit() {
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
delete();
}
});
return this;
}
private void delete(Path path) {
if (!Files.exists(path)) {
return;
}
try {
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult postVisitDirectory(final Path dir, final IOException e) throws IOException {
Files.deleteIfExists(dir);
return super.postVisitDirectory(dir, e);
}
@Override
public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs)
throws IOException {
Files.deleteIfExists(file);
return super.visitFile(file, attrs);
}
});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void delete() {
delete(path);
}
}
/**
* Exactly once per {@link ClassLoader}, attempt to load the native library (via {@link System#loadLibrary(String)} with {@link Core#NATIVE_LIBRARY_NAME}). If the first attempt fails, the native binary will be extracted from the classpath to a temporary location (which gets cleaned up on shutdown), that location is added to the {@code java.library.path} system property and {@link ClassLoader#usr_paths}, and then another call to load the library is made. Note this method uses reflection to gain access to private memory in {@link ClassLoader} as there's no documented method to augment the library path at runtime. Spurious calls are safe.
*/
public static void loadShared() {
SharedLoader.getInstance();
}
/**
* @see <a href="http://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom">Initialization-on-demand holder idiom</a>
*/
private static class SharedLoader {
// Class loader error messages indicating OpenCV is not found on java.library.path
private static final List<String> errorMessages = Arrays.asList(
String.format("no %s in java.library.path", Core.NATIVE_LIBRARY_NAME),
String.format("%s (Not found in java.library.path)", Core.NATIVE_LIBRARY_NAME)
);
private Path libraryPath;
private SharedLoader() {
try {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
} catch (final UnsatisfiedLinkError ule) {
/* Only update the library path and load if the original error indicates it's missing from the library path. */
if (ule == null || !openCVNotFoundInJavaLibraryPath(ule.getMessage())) {
throw ule;
}
/* Retain this path for cleaning up the library path later. */
this.libraryPath = extractNativeBinary();
addLibraryPath(libraryPath.getParent());
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
}
}
/**
* Check if any error fragment is contained in the errorMessage
* @param errorMessage the message to check
* @return true if any error fragment matches, false otherwise
*/
private boolean openCVNotFoundInJavaLibraryPath(String errorMessage) {
for (String errorFragment : errorMessages) {
if (errorMessage.contains(errorFragment)) {
return true;
}
}
return false;
}
/**
* Cleans up patches done to the environment.
*/
@Override
protected void finalize() throws Throwable {
super.finalize();
if (null == libraryPath) {
return;
}
removeLibraryPath(libraryPath.getParent());
}
private static class Holder {
private static final SharedLoader INSTANCE = new SharedLoader();
}
public static SharedLoader getInstance() {
return Holder.INSTANCE;
}
/**
* Adds the provided {@link Path}, normalized, to the {@link ClassLoader#usr_paths} array, as well as to the {@code java.library.path} system property. Uses the reflection API to make the field accessible, and maybe unsafe in environments with a security policy.
*
* @see <a href="http://stackoverflow.com/q/15409223">Adding new paths for native libraries at runtime in Java</a>
*/
private static void addLibraryPath(final Path path) {
final String normalizedPath = path.normalize().toString();
try {
final Field field = ClassLoader.class.getDeclaredField("usr_paths");
field.setAccessible(true);
final Set<String> userPaths = new HashSet<>(Arrays.asList((String[]) field.get(null)));
userPaths.add(normalizedPath);
field.set(null, userPaths.toArray(new String[userPaths.size()]));
System.setProperty("java.library.path", System.getProperty("java.library.path") + File.pathSeparator + normalizedPath);
} catch (IllegalAccessException e) {
throw new RuntimeException("Failed to get permissions to set library path");
} catch (NoSuchFieldException e) {
throw new RuntimeException("Failed to get field handle to set library path");
}
}
}
}
ObjectDetectionService.java
public class ObjectDetectionService {
static {
OpenCVLoader.loadShared(); // loaded fine,
System.loadLibrary(Core.NATIVE_LIBRARY_NAME); //try to load 1 more time, no exception is thrown
Mat testMat = new Mat(); // UnsastifiedLinkError;
}
}
堆栈跟踪
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2020-07-06 10:02:18,639 ERROR [restartedMain] --- [org.springframework.boot.SpringApplication][SpringApplication.java:837] Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'objectDetectionService' defined in file [/Users/minhtus/personal/ppr/build/classes/java/main/com/fptu/swp/ppr/detection/service/ObjectDetectionService.class]: Instantiation of bean failed; nested exception is java.lang.UnsatisfiedLinkError: org.opencv.core.Mat.n_Mat()J
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1320)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1214)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:557)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:895)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:878)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:143)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1237)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)
at com.fptu.swp.ppr.Application.main(Application.java:30)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49)
Caused by: java.lang.UnsatisfiedLinkError: org.opencv.core.Mat.n_Mat()J
at org.opencv.core.Mat.n_Mat(Native Method)
at org.opencv.core.Mat.<init>(Mat.java:23)
at com.fptu.swp.ppr.detection.service.ObjectDetectionService.<clinit>(ObjectDetectionService.java:32)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:204)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:87)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1312)
... 23 common frames omitted
发布于 2020-11-09 12:18:48
我的解决方案是禁用devtools,正如评论中所建议的那样。
发布于 2020-07-06 09:50:11
您必须将适当的OpenCV dlls
包含到项目中。最简单的方法是将代码打包到jar文件中,并将OpenCV dlls
放在jar文件所在的目录中。这样,它们就会自动包含在您的类路径中。
https://stackoverflow.com/questions/62748932
复制相似问题