Java文件的相对路径规则

  • Post author:
  • Post category:java


前言

最近做项目,又涉及到Linux Java文件的相对路径,但是相对路径在不同的服务器或者docker上居然不一样,这个就很难受,只能用绝对路径解决,因为绝对路径是固定的路径,但是相对路径为什么会在不同的服务器不一样呢?

Java源码分析与Demo

因为文件夹或者文件的创建是native方式C++实现的,笔者本地是MacOS系统,Linux类似

创建目录如上,创建文件如下:

功能大同小异,毕竟Linux一切皆文件,注意默认情况下Linux Java创建文件夹是777的权限,跟umask也相关。

创建Demo 代码

public class Main {
    public static void main(String[] args) throws InterruptedException {
        File file = new File("hello");
        boolean ok = file.mkdir();

        System.out.println(file.getAbsoluteFile());

        if (ok) {
            Thread.sleep(60*1000);
            file.delete();
        }
    }
}

运行后结果

为什么是项目的根目录??? 如果加上-Duser.dir=xxx的JVM参数,那么Java绝对路径是不正确的

C++原理与Demo

为了验证这个是为什么,构建JNI代码,注意JNI非常关键的包名

package org.example;

public class Demo {

    public native String sayHello(File file);
}

在java目录下,执行javah org.example.Demo

生成C++头文件,原因是idea把java目录作为classpath;也可以使用-cp指定classpath,javah 默认支持-jni可以不写

创建C++ lib文件

但是头文件在放在C++项目中执行cmake时,报错

Could NOT find JNI (missing: JAVA_INCLUDE_PATH JAVA_INCLUDE_PATH2 AWT)

明明已经设置了JAVA_HOME,但是还是不行,查询资料,cmake官方资料,解决问题准确

FindJNI — CMake 3.27.0 Documentation

只需在CMakeLists文件中设置,根据自己的实际情况而定

# JAVA_INCLUDE_PATH为jni.h所在路径,一般在jdk目录下的include中

set(JAVA_INCLUDE_PATH /Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home/include)

# JAVA_INCLUDE_PATH2为jni_md.h所在路径,一般在jdk目录下的include/xxx系统类别目录中

set(JAVA_INCLUDE_PATH2 /Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home/include/darwin)

set(JAVA_AWT_INCLUDE_PATH /Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home/include)

注意cmakelists文件的lib命名,可自定义

add_library(

org_example_Demo

SHARED org_example_Demo.cpp)

这个名称就是Java代码加载的名称,编译代码构建可执行文件就可以得到lib文件,linux是so文件

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

    public static void main(String[] args) {
        File file = new File("demo");
        String path = new Demo().sayHello(file);
        System.out.println(path);
    }

加入JVM参数-Djava.library.path=xxx目录,启动即可执行

C++可执行代码

#include "org_example_Demo.h"
#include <jni.h>
#include <iostream>
#include <sys/stat.h>

using namespace std;

//实现sayHello方法
JNIEXPORT jstring JNICALL Java_org_example_Demo_sayHello(JNIEnv* env, jobject obj, jobject file) {
    jclass fileClass = env->FindClass("java/io/File");
    if (!fileClass) return NULL;
    jfieldID path = env->GetFieldID(fileClass,"path", "Ljava/lang/String;");
    jstring jstr = static_cast<jstring>(env->GetObjectField(file, path));

    int success = mkdir("demo", 0777);
    cout << success << endl;
    return jstr;
}

在拿到文件后使用path创建文件,这里仿造JDK的实现,先通过类读取fieldid,然后读取field,这里直接强转jstring了

读取field,JDK使用宏定义

参考Java jni.h C++数据类型

java jni定义的类型与C++类 C++ 字节数
boolean jboolean unsigned char 1
byte jbyte signed char 1
char jchar unsigned short 2
short jshort short 2
int jint/jsize long 4
long jlong __int64 8
float jfloat float 4
double jdouble double 8
String jstring string(char*)

执行Java的main方法

说明相对路径是C代码函数执行的结果;但是当mkdir使用相对路径时,如果在C++的环境执行,直接就失败

可以看到结果是-1。JNI执行和C++原生执行的结果是不一样的😳,如果使用绝对路径可以成功

得出结论,JNI中间的一些设置数据跟C、C++底层函数执行结果相关性很大,比如创建目录或者文件,在相对路径下,C++不能成功;Java却可以成功。

总结

linux、unix环境下Java创建相对路径,表现为C、C++的函数执行,但是跟C++等原生执行的结果不一样,JNI在C类代码执行时,使用的堆外空间,里面是有一些默认设定的,比如创建相对路径的目录,JNI会默认使用项目的根路径,作为堆外空间,在linux的服务器上,可能与user.dir有关联

笔者在配置user.dir的linux docker上出现过不一致的情况,相同JVM参数(-Duser.dir)不同的docker容器创建的相对路径不一致,猜测JVM的native空间,可能跟user.dir和docker的work.dir都有关系,需要进一步查看JNI的执行原理和C++函数的源码进一步分析。



版权声明:本文为fenglllle原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。