关于什么是文件描述符,见文末的链接⑥
先说下为什么写这篇文章。
在开发中,有这么个需求:想通过InputStream获取到打开文件的文件描述符的int值。
但是目前只能通过流的getFD()方法获取到FileDescriptor对象,然后FileDescriptor对象并没有提供返回文件描述符整数值的方法:
String path = "/xxx/xxx/xxx.txt";
RandomAccessFile raf = new RandomAccessFile(path,"r");
FileDescriptor fd = raf.getFD();
因此就调研了一些获取文件描述符整型值的方案。
有两种方案:
① 通过native方法去做。
② 通过SharedSecrets去做。
一、通过native方法获取FileDescriptor对象代表的整数值
Tips:Hadoop中就是采用了这种方案。
1.1 JNI预备知识
涉及到的JNI函数有:GetFieldID()、GetIntField()。
读者可参考JNI文档对函数的功能、参数意义等进行详细查阅,这里我用简短的话描述一下这两个函数。
https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#GetFieldID
① GetFieldID()函数原型:
jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
用于返回一个成员变量(也就是非静态的)的成员ID,这个ID用于Get这个成员变量的值,或者Set这个成员变量的值。
② GetIntField()函数原型:
NativeType GetIntField(JNIEnv *env, jobject obj, jfieldID fieldID);
很好理解,就是获取obj对象里jfieldID的成员变量的值。
1.2 具体实现
我们在java代码中定义一个native方法:
public native int getIntFileDescriptor(FileDescriptor fd);
然后通过javah生成.h头文件,进行具体实现。在具体的实现中可利用如下代码:
// 整数类型fd的fieldID
static jfieldID fd_descriptor;
void fd_init(JNIEnv* env) {
// 通过GetFieldID获得整型成员变量fd的fieldID,赋值给上面声明的fd_descriptor。
fd_descriptor = (*env)->GetFieldID(env, fd_class, "fd", "I");
}
/*
* Given an instance 'obj' of java.io.FileDescriptor, return the
* underlying fd, or throw if unavailable
*/
int fd_get(JNIEnv* env, jobject obj) {
if (obj == NULL) {
THROW(env, "java/lang/NullPointerException",
"FileDescriptor object is null");
return -1;
}
// 通过GetIntField获取到整型值,参数中的fd_descriptor在上面已经被赋值过了。
return (*env)->GetIntField(env, obj, fd_descriptor);
}
二、通过SharedSecrets获取FileDescriptor对象代表的整数值
这种方案是我在阅读FileDescriptor源码时发现的线索。开始时在FileDescriptor类中想寻找get方法。
get方法没找到,不过找到了一些获取文件描述符方法的蛛丝马迹。
于是我用如下代码成功获取到了整型的文件描述符(并通过输出结果 + strace跟踪系统调用验证结果是正确的):
String path = "/home/xxxx/xxxx.java";
RandomAccessFile randomAccessFile = new RandomAccessFile(path,"r");
int fdint = SharedSecrets.getJavaIOFileDescriptorAccess().get(randomAccessFile.getFD());
System.out.println("file des: " + fdint);
三、理解SharedSecrets
SharedSecrets这个类是sun.misc包下的,供内部使用,不建议Java开发者使用。我们找到openJDK的源码(https://github.com/frohoff/jdk8u-jdk/blob/master/src/share/classes/sun/misc/SharedSecrets.java),看其中的相关注释。英文注释总结起来就是一句话:SharedSecrets提供了不使用反射的情况下在其他包中调用私有实现方法的一种机制,。
/* A repository of “shared secrets”, which are a mechanism for
calling implementation-private methods in another package without
using reflection. A package-private class implements a public
interface and provides the ability to call package-private methods
within that package; the object implementing that interface is
provided through a third package to which access is restricted.
This framework avoids the primary disadvantage of using reflection
for this purpose, namely the loss of compile-time checking.
*/
回顾一下Java的访问权限修饰词:
类成员访问权限修饰词有四类:private(只能在当前类中被访问),无(在同一个包下可以访问),protected(同一个包下和子类可以访问) 和 public(所有类都可以访问)。
所以SharedSecrets要解决的问题就是在包B中能够访问到包A中的private类型的实现方法。这样的实现方式不会将A中的private类型的方法暴露给所有用户。
更多关于SharedSecrets使用方式在文章最后的链接里。
参考
①JDK源码
②Hadoop-3.1.0源码
③https://stackoverflow.com/questions/46722452/how-does-the-sharedsecrets-mechanism-work
④https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#GetFieldID
⑤https://stackoverflow.com/questions/46722452/how-does-the-sharedsecrets-mechanism-work
⑥http://c.biancheng.net/view/3066.html
网友评论