序
这里从源码学习一下rocksdb是如何加载本地类库的。
maven
org.rocksdb
rocksdbjni
5.5.1
加载
private enum LibraryState {
NOT_LOADED,
LOADING,
LOADED
}
private static AtomicReference libraryLoaded
= new AtomicReference<>(LibraryState.NOT_LOADED);
static {
RocksDB.loadLibrary();
}
/**
* Loads the necessary library files.
* Calling this method twice will have no effect.
* By default the method extracts the shared library for loading at
* java.io.tmpdir, however, you can override this temporary location by
* setting the environment variable ROCKSDB_SHAREDLIB_DIR.
*/
public static void loadLibrary() {
if (libraryLoaded.get() == LibraryState.LOADED) {
return;
}
if (libraryLoaded.compareAndSet(LibraryState.NOT_LOADED,
LibraryState.LOADING)) {
final String tmpDir = System.getenv("ROCKSDB_SHAREDLIB_DIR");
// loading possibly necessary libraries.
for (final CompressionType compressionType : CompressionType.values()) {
try {
if (compressionType.getLibraryName() != null) {
System.loadLibrary(compressionType.getLibraryName());
}
} catch (UnsatisfiedLinkError e) {
// since it may be optional, we ignore its loading failure here.
}
}
try {
NativeLibraryLoader.getInstance().loadLibrary(tmpDir);
} catch (IOException e) {
libraryLoaded.set(LibraryState.NOT_LOADED);
throw new RuntimeException("Unable to load the RocksDB shared library"
+ e);
}
libraryLoaded.set(LibraryState.LOADED);
return;
}
while (libraryLoaded.get() == LibraryState.LOADING) {
try {
Thread.sleep(10);
} catch(final InterruptedException e) {
//ignore
}
}
}
这里设计了一个AtomicReference
,用来避免重复加载
NativeLibraryLoader
/**
* This class is used to load the RocksDB shared library from within the jar.
* The shared library is extracted to a temp folder and loaded from there.
*/
public class NativeLibraryLoader {
//singleton
private static final NativeLibraryLoader instance = new NativeLibraryLoader();
private static boolean initialized = false;
private static final String sharedLibraryName = Environment.getSharedLibraryName("rocksdb");
private static final String jniLibraryName = Environment.getJniLibraryName("rocksdb");
private static final String jniLibraryFileName = Environment.getJniLibraryFileName("rocksdb");
private static final String tempFilePrefix = "librocksdbjni";
private static final String tempFileSuffix = Environment.getJniLibraryExtension();
/**
* Get a reference to the NativeLibraryLoader
*
* @return The NativeLibraryLoader
*/
public static NativeLibraryLoader getInstance() {
return instance;
}
/**
* Firstly attempts to load the library from java.library.path,
* if that fails then it falls back to extracting
* the library from the classpath
* {@link org.rocksdb.NativeLibraryLoader#loadLibraryFromJar(java.lang.String)}
*
* @param tmpDir A temporary directory to use
* to copy the native library to when loading from the classpath.
* If null, or the empty string, we rely on Java's
* {@link java.io.File#createTempFile(String, String)}
* function to provide a temporary location.
* The temporary file will be registered for deletion
* on exit.
*
* @throws java.io.IOException if a filesystem operation fails.
*/
public synchronized void loadLibrary(final String tmpDir) throws IOException {
try {
System.loadLibrary(sharedLibraryName);
} catch(final UnsatisfiedLinkError ule1) {
try {
System.loadLibrary(jniLibraryName);
} catch(final UnsatisfiedLinkError ule2) {
loadLibraryFromJar(tmpDir);
}
}
}
/**
* Attempts to extract the native RocksDB library
* from the classpath and load it
*
* @param tmpDir A temporary directory to use
* to copy the native library to. If null,
* or the empty string, we rely on Java's
* {@link java.io.File#createTempFile(String, String)}
* function to provide a temporary location.
* The temporary file will be registered for deletion
* on exit.
*
* @throws java.io.IOException if a filesystem operation fails.
*/
void loadLibraryFromJar(final String tmpDir)
throws IOException {
if (!initialized) {
System.load(loadLibraryFromJarToTemp(tmpDir).getAbsolutePath());
initialized = true;
}
}
File loadLibraryFromJarToTemp(final String tmpDir)
throws IOException {
final File temp;
if (tmpDir == null || tmpDir.isEmpty()) {
temp = File.createTempFile(tempFilePrefix, tempFileSuffix);
} else {
temp = new File(tmpDir, jniLibraryFileName);
if (temp.exists() && !temp.delete()) {
throw new RuntimeException("File: " + temp.getAbsolutePath()
+ " already exists and cannot be removed.");
}
if (!temp.createNewFile()) {
throw new RuntimeException("File: " + temp.getAbsolutePath()
+ " could not be created.");
}
}
if (!temp.exists()) {
throw new RuntimeException("File " + temp.getAbsolutePath() + " does not exist.");
} else {
temp.deleteOnExit();
}
// attempt to copy the library from the Jar file to the temp destination
try (final InputStream is = getClass().getClassLoader().
getResourceAsStream(jniLibraryFileName)) {
if (is == null) {
throw new RuntimeException(jniLibraryFileName + " was not found inside JAR.");
} else {
Files.copy(is, temp.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
}
return temp;
}
/**
* Private constructor to disallow instantiation
*/
private NativeLibraryLoader() {
}
}
这个采用了单例的模式,另外加载的代码结构比较清晰
/**
* Firstly attempts to load the library from java.library.path,
* if that fails then it falls back to extracting
* the library from the classpath
* {@link org.rocksdb.NativeLibraryLoader#loadLibraryFromJar(java.lang.String)}
*
* @param tmpDir A temporary directory to use
* to copy the native library to when loading from the classpath.
* If null, or the empty string, we rely on Java's
* {@link java.io.File#createTempFile(String, String)}
* function to provide a temporary location.
* The temporary file will be registered for deletion
* on exit.
*
* @throws java.io.IOException if a filesystem operation fails.
*/
public synchronized void loadLibrary(final String tmpDir) throws IOException {
try {
System.loadLibrary(sharedLibraryName);
} catch(final UnsatisfiedLinkError ule1) {
try {
System.loadLibrary(jniLibraryName);
} catch(final UnsatisfiedLinkError ule2) {
loadLibraryFromJar(tmpDir);
}
}
}
首先从java.library.path加载,加载失败的话,从classpath的jar包加载
Environment设计
public static String getJniLibraryName(final String name) {
if (isUnix()) {
final String arch = is64Bit() ? "64" : "32";
if(isPowerPC()) {
return String.format("%sjni-linux-%s", name, ARCH);
} else {
return String.format("%sjni-linux%s", name, arch);
}
} else if (isMac()) {
return String.format("%sjni-osx", name);
} else if (isAix() && is64Bit()) {
return String.format("%sjni-aix64", name);
} else if (isSolaris()) {
final String arch = is64Bit() ? "64" : "32";
return String.format("%sjni-solaris%s", name, arch);
} else if (isWindows() && is64Bit()) {
return String.format("%sjni-win64", name);
}
throw new UnsupportedOperationException(String.format("Cannot determine JNI library name for ARCH='%s' OS='%s' name='%s'", ARCH, OS, name));
}
单独的一个Environment来构造跨平台的类库路径