草庐IT

JNI库文件加载源码解析

程序员札记 2023-09-17 原文

下面我们结合源码详细探讨下JNI调用的库文件是如何加载的,为啥HelloWorld.so必须被命名成libHelloWorld.so,JNI_OnLoad方法是在什么时候回调的,返回的版本号有啥用?先看下总体的流程图

Java源码解析

System.loadLibrary和System.load方法

System.loadLibrary(String)方法用来加载动态链接库的,String参数是指定动态链接库的模块名的而非真实的文件名的。System还有另外一个load(String)方法,也是用来加载动态链接库的,不过String参数是库文件的绝对路径名,比如上述示例中的System.loadLibrary("HelloWorld") 可以替换成System.load("/home/openjdk/cppTest/HelloWorld.so")。两者的区别在于前者的库文件位置是配置化的,更灵活;而后者是代码写死的,适用于测试场景,生产不建议使用。两者的源码如下:

 public static void load(String filename) {
        Runtime.getRuntime().load0(Reflection.getCallerClass(), filename);
    }
 
 
public static void loadLibrary(String libname) {
        Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
}

查看两者的注释,load方法要求fileName必须是绝对路径,JVM解析时会去除路径部分和文件后缀得到文件名,利用文件名生成该动态链接库的模块名,比如文件名是HelloWorld,JVM中用JNI_OnLoad_HelloWorld来表示这个模块;loadLibrary方法则直接使用JNI_OnLoad_libname作为模块名。

从类图我们可以知道接下来会调用Runtime类的loadLibrary0方法,在这个方法里面会做两件事:

  • 通过loader.findLibrary(libraryName)找到对应库的全路径
  • 通过doLoad(filename, loader)加载库文件

Runtime.loadLibrary和Runtime.load方法

Runtime的这两个方法的源码如下:

public void load(String filename) {
        load0(Reflection.getCallerClass(), filename);
    }
 
    synchronized void load0(Class<?> fromClass, String filename) {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkLink(filename);
        }
        //检查是否是绝对路径
        if (!(new File(filename).isAbsolute())) {
            throw new UnsatisfiedLinkError(
                "Expecting an absolute path of the library: " + filename);
        }
        ClassLoader.loadLibrary(fromClass, filename, true);
    }
 
 
public void loadLibrary(String libname) {
        loadLibrary0(Reflection.getCallerClass(), libname);
    }
 
    synchronized void loadLibrary0(Class<?> fromClass, String libname) {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkLink(libname);
        }
        //检查是否包含路径分隔符
        if (libname.indexOf((int)File.separatorChar) != -1) {
            throw new UnsatisfiedLinkError(
    "Directory separator should not appear in library name: " + libname);
        }
        Class
Loader.loadLibrary(fromClass, libname, false);
    }

可以看到最终都是调用ClassLoader.loadLibrary方法完成动态链接库文件的加载。

ClassLoader.loadLibrary方法

static void loadLibrary(Class<?> fromClass, String name,
                            boolean isAbsolute) {
      //fromClass是一开始调用System的类,getClassLoader一般返回AppClassLoader实例,
      //只有通过启动类加载器加载的类如String,getClassLoader会返回null
       ClassLoader loader =
            (fromClass == null) ? null : fromClass.getClassLoader();
        //usr_paths和sys_paths是ClassLoader的两个静态属性,如果为空则初始化
        if (sys_paths == null) {
            usr_paths = initializePath("java.library.path");
            sys_paths = initializePath("sun.boot.library.path");
        }
        //如果是绝对路径
        if (isAbsolute) {
            if (loadLibrary0(fromClass, new File(name))) {
                return;
            }
            throw new UnsatisfiedLinkError("Can't load library: " + name);
        }
        //如果loader不为空,尝试通过findLibrary查找指定模块的绝对路径
        if (loader != null) {
            //ClassLoader的实现默认返回空,只有ExtClassLoader改写了该方法
            String libfilename = loader.findLibrary(name);
            if (libfilename != null) {
                File libfile = new File(libfilename);
                //校验是否是绝对路径,如果不为null则必须是绝对路径
                if (!libfile.isAbsolute()) {
                    throw new UnsatisfiedLinkError(
    "ClassLoader.findLibrary failed to return an absolute path: " + libfilename);
                }
                if (loadLibrary0(fromClass, libfile)) {
                    return;
                }
                throw new UnsatisfiedLinkError("Can't load " + libfilename);
            }
        }
        //无论loader是否为空
        //遍历sys_paths下的子路径,查找是否存在目标文件
        for (int i = 0 ; i < sys_paths.length ; i++) {
            //获取映射后的子路径下的文件名,mapLibraryName是本地方法
            File libfile = new File(sys_paths[i], System.mapLibraryName(name));
            //尝试加载目标文件,如果存在则返回
            if (loadLibrary0(fromClass, libfile)) {
                return;
            }
            //mapAlternativeName默认返回null
            libfile = ClassLoaderHelper.mapAlternativeName(libfile);
            if (libfile != null && loadLibrary0(fromClass, libfile)) {
                return;
            }
        }
        //如果loader不为空,通常会走到此逻辑
        if (loader != null) {
            //遍历usr_paths下的所有子路径
            for (int i = 0 ; i < usr_paths.length ; i++) {
                //获取子路径下映射过的文件名
                File libfile = new File(usr_paths[i],
                                        System.mapLibraryName(name));
                if (loadLibrary0(fromClass, libfile)) {
                    return;
                }
                libfile = ClassLoaderHelper.mapAlternativeName(libfile);
                if (libfile != null && loadLibrary0(fromClass, libfile)) {
                    return;
                }
            }
        }
        //没有找到,加载失败,抛出异常
        throw new UnsatisfiedLinkError("no " + name + " in java.library.path");
    }
 
protected String findLibrary(String libname) {
        return null;
    }

findLibrary被改写的子类如下:

image.png

System.mapLibraryName是一个本地方法,表示将库名映射成特定平台下的库文件名,定义如下:

image.png

ClassLoader.loadLibrary0方法

private static boolean loadLibrary0(Class<?> fromClass, final File file) {
        //检查是否是内置的动态链接库,findBuiltinLib是本地方法
        String name = findBuiltinLib(file.getName());
        boolean isBuiltin = (name != null);
        //如果不是
        if (!isBuiltin) {
            boolean exists = AccessController.doPrivileged(
                new PrivilegedAction<Object>() {
                    public Object run() {
                        //检查文件是否存在
                        return file.exists() ? Boolean.TRUE : null;
                    }})
                != null;
            //文件不存在返回false
            if (!exists) {
                return false;
            }
            try {
            //获取简洁形式的路径
                name = file.getCanonicalPath();
            } catch (IOException e) {
                return false;
            }
        }
        //获取调用类的类加载器已加载的库文件缓存,如果为空则使用系统类加载对应的缓存
        ClassLoader loader =
            (fromClass == null) ? null : fromClass.getClassLoader();
        //nativeLibraries是私有实例属性,表示该ClassLoader已经加载过的共享库缓存
        Vector<NativeLibrary> libs =
            loader != null ? loader.nativeLibraries : systemNativeLibraries;
        synchronized (libs) {
            int size = libs.size();
            //遍历缓存
            for (int i = 0; i < size; i++) {
                NativeLibrary lib = libs.elementAt(i);
                //如果找到同名的说明已经加载过了,则返回true
                if (name.equals(lib.name)) {
                    return true;
                }
            }
            //loadedLibraryNames是ClassLoader的静态属性,Vector<String>类型,表示全局的所有Class
            Loader实例已加载的共享库的文件名的缓存
            //两层的synchronized控制保证极端并发情况下只有一个线程加载共享库
            synchronized (loadedLibraryNames) {
                //如果存在说明该库文件已经被其他的某个ClassLoader实例加载过了
                if (loadedLibraryNames.contains(name)) {
                    throw new UnsatisfiedLinkError
                        ("Native Library " +
                         name +
                         " already loaded in another classloader");
                }
               
                //nativeLibraryContext是ClassLoader的静态属性,Stack<NativeLibrary>类型的,表示正在加载或者卸载的共享库缓存
                int n = nativeLibraryContext.size();
                for (int i = 0; i < n; i++) {
                    NativeLibrary lib = nativeLibraryContext.elementAt(i);
                    if (name.equals(lib.name)) {
                        //如果存在同名的且是同一个ClassLoader实例则返回true,不同的则返回false
                        if (loader == lib.fromClass.getClassLoader()) {
                            return true;
                        } else {
                            throw new UnsatisfiedLinkError
                                ("Native Library " +
                                 name +
                                 " is being loaded in another classloader");
                        }
                    }
                }
                NativeLibrary lib = new NativeLibrary(fromClass, name, isBuiltin);
                //入栈
                nativeLibraryContext.push(lib);
                try {
                   //执行实际的加载任务,load是ClassLoader的静态内部类NativeLibrary的本地方法
                    lib.load(name, isBuiltin);
                } finally {
                    //出栈
                    nativeLibraryContext.pop();
                }
                //如果加载成功
                if (lib.loaded) {
                    //将该实例加入到缓存中
                    loadedLibraryNames.addElement(name);
                    libs.addElement(lib);
                    return true;
                }
                return false;
            }
        }
    }

其中NativeLibrary是ClassLoader的静态内部类,包级访问,该类就是用来表示一个已经加载过的库文件,每个ClassLoader实例加载的库文件对应NativeLibrary实例都保存在ClassLoader的nativeLibraries属性中,所有ClassLoader实例加载过NativeLibrary实例都保存在ClassLoader的全局systemNativeLibraries属性中,每个库文件都有依赖的JDK版本,NativeLibrary的属性jniVersion就是用来记录该库文件依赖的JDK版本,JVM加载库文件时会校验是否支持库文件要求的JDK版本,属性loaded表示该库文件是否已加载。

上述源码解析表明同一个共享库文件只能被一个ClassLoader实例加载,因为对JVM而言同名的库文件加载一次就行了,包含本地方法的类可以被不同ClassLoader实例加载生成不同的Klass,但是他们的本地方法执行时可以调用相同的本地方法实现。那么不同名的库文件如果包含同名的方法实现会如何呢?

可以把上述示例中的HelloWorld.cpp的打印调整下,重新编译生成一个新的so文件,然后在HelloWorld.java中同时加载两个so文件,新的so文件第一个加载,结果如下:

image.png

执行多次,从结果看使用的是第一个加载的共享库的方法实现,即多个共享库如果存在同名方法实现则JVM只绑定第一个加载的对应的方法实现,本地方法的这种特殊性是JVM调用操作系统加载动态链接库的实现决定的,也跟C语言不支持方法同名有关系,需要在生产应用中重点注意,为了避免方法同名所以生成头文件时方法名会包含全限定类名,但是这无法解决同一个类不同版本的同名方法的问题。

C++源码解析

上节Java源码分析中发现库文件加载涉及三个本地方法,String mapLibraryName(String libname),String findBuiltinLib(String name)和load(String name, boolean isBuiltin)方法,第一个方法的实现参考OpenJDK jdk/src/share/native/java/lang/System.c,第二个参考同一目录下ClassLoader.c。

mapLibraryName

JNIEXPORT jstring JNICALL
Java_java_lang_System_mapLibraryName(JNIEnv *env, jclass ign, jstring libname)
{
    int len;
    //前缀的长度
    int prefix_len = (int) strlen(JNI_LIB_PREFIX);
    //后缀的长度
    int suffix_len = (int) strlen(JNI_LIB_SUFFIX);
 
    jchar chars[256];
    //非空校验
    if (libname == NULL) {
        JNU_ThrowNullPointerException(env, 0);
        return NULL;
    }
    //长度校验
    len = (*env)->GetStringLength(env, libname);
    if (len > 240) {
        JNU_ThrowIllegalArgumentException(env, "name too long");
        return NULL;
    }
    //将前缀复制到数组中
    cpchars(chars, JNI_LIB_PREFIX, prefix_len);
    //将libname复制到数组中前缀的后面
    (*env)->GetStringRegion(env, libname, 0, len, chars + prefix_len);
    len += prefix_len;
    //将后缀复制到数组中libname的后面
    cpchars(chars + len, JNI_LIB_SUFFIX, suffix_len);
    len += suffix_len;
    //拼成一个新的字符串
    return (*env)->NewString(env, chars, len);
}

Linux下前缀后缀的定义在hotspot/src/os/linux/vm/jvm_linux.h中,如下:


image.png

这就解释了为啥把HelloWorld.so 改成libHelloWorld.so就可以正常加载该库文件了。

findBuiltinLib

JNIEXPORT jstring JNICALL
Java_java_lang_ClassLoader_findBuiltinLib
  (JNIEnv *env, jclass cls, jstring name)
{
    const char *cname;
    char *libName;
    int prefixLen = (int) strlen(JNI_LIB_PREFIX);
    int suffixLen = (int) strlen(JNI_LIB_SUFFIX);
    int len;
    jstring lib;
    void *ret;
    const char *onLoadSymbols[] = JNI_ONLOAD_SYMBOLS;
    //非空校验
    if (name == NULL) {
        JNU_ThrowInternalError(env, "NULL filename for native library");
        return NULL;
    }
    //初始化procHandle,实际是一个dlopen函数的额指针
    procHandle = getProcessHandle();
    //将name中的字符串拷贝到cname中
    cname = JNU_GetStringPlatformChars(env, name, 0);
    if (cname == NULL) {
        return NULL;
    }
    //校验cname的长度是否大于前缀长度加上后缀长度
    len = strlen(cname);
    if (len <= (prefixLen+suffixLen)) {
        JNU_ReleaseStringPlatformChars(env, name, cname);
        return NULL;
    }
    //libName初始化
    libName = malloc(len + 1); //+1 for null if prefix+suffix == 0
    if (libName == NULL) {
        JNU_ReleaseStringPlatformChars(env, name, cname);
        JNU_ThrowOutOfMemoryError(env, NULL);
        return NULL;
    }
    //跳过前缀将cname复制到libName中
    if (len > prefixLen) {
        strcpy(libName, cname+prefixLen);
    }
    //释放cname对应的内存
    JNU_ReleaseStringPlatformChars(env, name, cname);
    //将后缀起始字符置为\0,标记字符串结束,即相当于去掉了后缀
    libName[strlen(libName)-suffixLen] = '\0';
 
    //查找该libname是否存在
    ret = findJniFunction(env, procHandle, libName, JNI_TRUE);
    if (ret != NULL) {
        //如果存在返回,用libname构造一个java String并返回
        lib = JNU_NewStringPlatform(env, libName);
        //释放libName的内存
        free(libName);
        return lib;
    }
    //如果不存在,释放libName的内存,返回NULL
    free(libName);
    return NULL;
}
 
static void *findJniFunction(JNIEnv *env, void *handle,
                                    const char *cname, jboolean isLoad) {
    //字符串指针数组,实际是{"JNI_OnLoad"}                                
    const char *onLoadSymbols[] = JNI_ONLOAD_SYMBOLS;
    //实际是{"JNI_OnUnload"}
    const char *onUnloadSymbols[] = JNI_ONUNLOAD_SYMBOLS;
    const char **syms;
    int symsLen;
    void *entryName = NULL;
    char *jniFunctionName;
    int i;
    int len;
 
    // 根据isLoad判断 JNI_On(Un)Load<_libname>
    if (isLoad) {
        syms = onLoadSymbols;
        symsLen = sizeof(onLoadSymbols) / sizeof(char *);
    } else {
        syms = onUnloadSymbols;
        symsLen = sizeof(onUnloadSymbols) / sizeof(char *);
    }
    for (i = 0; i < symsLen; i++) {
        //检查拼起来的JNI_On(Un)Load<_libname>的长度是否大于最大值FILENAME_MAX
        if ((len = (cname != NULL ? strlen(cname) : 0) + strlen(syms[i]) + 2) >
            FILENAME_MAX) {
            goto done;
        }
        //给jniFunctionName分配内存
        jniFunctionName = malloc(len);
        if (jniFunctionName == NULL) {
            JNU_ThrowOutOfMemoryError(env, NULL);
            goto done;
        }
        //拼成JNI_On(Un)Load<_libname>,拼成的字符串作为底层ddl查找的参数,libname就是要查找的库文件,JNI_OnLoad就是在库文件中查找的目标函数名
        buildJniFunctionName(syms[i], cname, jniFunctionName);
        //查找该方法是否已加载
        entryName = JVM_FindLibraryEntry(handle, jniFunctionName);
        //释放内存
        free(jniFunctionName);
        //如果不为空则终止循环,返回entryName
        if(entryName) {
            break;
        }
    }
 
 done:
    return entryName;
}
 
void buildJniFunctionName(const char *sym, const char *cname,
                          char *jniEntryName) {
    //将sym复制到数组jniEntryName中                      
    strcpy(jniEntryName, sym);
    if (cname != NULL) {
        //字符串连接
        strcat(jniEntryName, "_");
        strcat(jniEntryName, cname);
    }
}

load

JNIEXPORT void JNICALL
Java_java_lang_ClassLoader_00024NativeLibrary_load
  (JNIEnv *env, jobject this, jstring name, jboolean isBuiltin)
{
    const char *cname;
    jint jniVersion;
    jthrowable cause;
    void * handle;
 
   //jniVersionID等初始化
    if (!initIDs(env))
        return;
    
    //将java String对象的字符串复制到cname 字符数组中
    cname = JNU_GetStringPlatformChars(env, name, 0);
    if (cname == 0)
        return;
    //如果未加载则加载该库文件
    handle = isBuiltin ? procHandle : JVM_LoadLibrary(cname);
    //如果加载完成
    if (handle) {
        JNI_OnLoad_t JNI_OnLoad;
        //获取该库文件中的JNI_OnLoad函数
        JNI_OnLoad = (JNI_OnLoad_t)findJniFunction(env, handle,
                                               isBuiltin ? cname : NULL,
                                               JNI_TRUE);
        //如果库文件包含了JNI_OnLoad函数                                     
        if (JNI_OnLoad) {
            JavaVM *jvm;
            //通过JNIEnv获取对应的JavaVM
            (*env)->GetJavaVM(env, &jvm);
            //获取库文件要求的jniVersion
            jniVersion = (*JNI_OnLoad)(jvm, NULL);
        } else {
            jniVersion = 0x00010001;
        }
 
        //如果出现异常
        cause = (*env)->ExceptionOccurred(env);
        if (cause) {
            (*env)->ExceptionClear(env);
            (*env)->Throw(env, cause);
            if (!isBuiltin) {
                JVM_UnloadLibrary(handle);
            }
            goto done;
        }
 
        //如果库文件要求的jniVersion不支持则抛出异常
        if (!JVM_IsSupportedJNIVersion(jniVersion) ||
            (isBuiltin && jniVersion < JNI_VERSION_1_8)) {
            char msg[256];
            jio_snprintf(msg, sizeof(msg),
                         "unsupported JNI version 0x%08X required by %s",
                         jniVersion, cname);
            JNU_ThrowByName(env, "java/lang/UnsatisfiedLinkError", msg);
            if (!isBuiltin) {
                JVM_UnloadLibrary(handle);
            }
            goto done;
        }
        //如果库文件要求的jniVersion支持则设置NativeLibrary实例的jniVersion属性
        (*env)->SetIntField(env, this, jniVersionID, jniVersion);
    } else {
        cause = (*env)->ExceptionOccurred(env);
        if (cause) {
            (*env)->ExceptionClear(env);
            (*env)->SetLongField(env, this, handleID, (jlong)0);
            (*env)->Throw(env, cause);
        }
        goto done;
    }
    //如果加载成功,则设置NativeLibrary实例的handle属性和loaded属性
    (*env)->SetLongField(env, this, handleID, ptr_to_jlong(handle));
    (*env)->SetBooleanField(env, this, loadedID, JNI_TRUE);
 
 done:
    //释放cname对应的内存
    JNU_ReleaseStringPlatformChars(env, name, cname);
}
 
static jboolean initIDs(JNIEnv *env)
{
    //handleID,jniVersionID,loadedID都是静态全局变量,表示NativeLibrary类的handle,jniVersion,loaded三个属性的属性ID
    //可通过属性ID设置实例的属性
    //当第一次加载库文件的时候会根据NativeLibrary类初始化这些静态属性
    if (handleID == 0) {
        jclass this =
            (*env)->FindClass(env, "java/lang/ClassLoader$NativeLibrary");
        if (this == 0)
            return JNI_FALSE;
        handleID = (*env)->GetFieldID(env, this, "handle", "J");
        if (handleID == 0)
            return JNI_FALSE;
        jniVersionID = (*env)->GetFieldID(env, this, "jniVersion", "I");
        if (jniVersionID == 0)
            return JNI_FALSE;
        loadedID = (*env)->GetFieldID(env, this, "loaded", "Z");
        if (loadedID == 0)
             return JNI_FALSE;
        procHandle = getProcessHandle();
    }
    return JNI_TRUE;
}

上述分析解释了执行System.loadLibrary方法的时候具体是在哪回调库文件中定义的JNI_OnLoad方法以及JNI_OnLoad方法返回的JDK版本的用途。

JVM_FindLibraryEntry和JVM_LoadLibrary

 JVM_LoadLibrary的方法定义在jvm.h中,实现在hotspot/src/share/vm/prims/jvm.cpp中,如下图:
image.png

JVM_FindLibraryEntry的实现如下:


image.png

不同操作系统实现不一样,这里涉及较多的操作系统相关的知识,这里不做讨论。

总结

将上述Java源码和C++源码的分析串联起来,结论就是,当调用System.loadLibrary方法加载库文件的时候,会首先尝试从已加载的库文件中查找是否存在目标库文件的JNI_OnLoad方法,如果没有,则可能是库文件不存在,库文件未加载或者库文件已加载但是没有定义该方法三种原因。所以首先判断该库文件是否存在,如果不存在则返回加载失败。如果存在则在已加载的NativeLibrary实例集合中查找是否存在库文件名一致的NativeLibrary实例,如果存在则返回该实例表示已加载完成。如果不存在则说明该文件未加载,然后调用JVM_LoadLibrary加载该文件,加载完成重新查找该文件中的JNI_OnLoad方法,如果定义了则调用该方法获取该库文件要求的JDK版本,如果没有定义则默认为JDK1.1,判断当前JVM是否支持该版本,如果支持则设置NativeLibrary实例的相关属性,并将其添加到已加载的NativeLibrary实例集合中,整体流程图如下:
image.png

结合上述分析也可得出System.loadLibrary方法实际只是完成了库文件加载而已,如果未执行RegisterNatives显示绑定本地方法和Java方法,则两者之间的绑定只有到该方法被首次调用时才能完成,因此对于需要频繁调用的方法建议使用RegisterNatives方法在库文件加载时即完成绑定,避免方法调用时再去查找时的性能损耗。

JDK库文件加载

JDK中的标准类如java.lang.Object中并没有调用System.loadLibrary方法加载库文件,那么JDK依赖的库文件是谁加载的?什么时候加载的?可以查找os::dll_load方法的调用链,如下图:
image.png

其中os::native_java_library() 方法就是答案,该方法在JVM初始化的时候调用的,该方法加载ddl的实现如下:


image.png

有关JNI库文件加载源码解析的更多相关文章

  1. Ruby 解析字符串 - 2

    我有一个字符串input="maybe(thisis|thatwas)some((nice|ugly)(day|night)|(strange(weather|time)))"Ruby中解析该字符串的最佳方法是什么?我的意思是脚本应该能够像这样构建句子:maybethisissomeuglynightmaybethatwassomenicenightmaybethiswassomestrangetime等等,你明白了......我应该一个字符一个字符地读取字符串并构建一个带有堆栈的状态机来存储括号值以供以后计算,还是有更好的方法?也许为此目的准备了一个开箱即用的库?

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  4. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

    我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

  5. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

  6. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  7. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  8. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  9. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  10. ruby - 用逗号、双引号和编码解析 csv - 2

    我正在使用ruby​​1.9解析以下带有MacRoman字符的csv文件#encoding:ISO-8859-1#csv_parse.csvName,main-dialogue"Marceu","Giveittohimóhe,hiswife."我做了以下解析。require'csv'input_string=File.read("../csv_parse.rb").force_encoding("ISO-8859-1").encode("UTF-8")#=>"Name,main-dialogue\r\n\"Marceu\",\"Giveittohim\x97he,hiswife.\"\

随机推荐