前言
刚接触 java
的时候很困惑一个事情 File
相对路径,以哪个目录为参照物。
随着 io 模型的发展,java 1.7 的 nio
,使用 Path
、Paths
和 Files
等来方便 io 的操作。
ClassLoader
用于获取class 文件
的 io,我们也可以用于获取文件的 io,以便于我们读取文件内容。
本文设计内容
- File ,ZipFile,JarFile 读取相对路径和绝对路径文件内容。
- System.getProperty("user.dir”) 是怎么来的。
- Paths、Path、Files 读取文件内容。
- 类加载器获取文件内容,Class.getResourceAsStream 和 ClassLoader.getResourceAsStream。
- 介绍类加载器的双亲委派模型,及在代码中找到对应的加载逻辑。
代码基于 Mac 10.15.4
,JDK 1.8。
基于 File 获取文件内容
绝对路径的内容获取比较简单,直接获取文件 io ,然后利用工具类读取文件内容。
获取绝对路径文件内容
final File file = new File("/Users/zhangpanqin/github/fly-java/demo.txt");
final byte[] bytes = cn.hutool.core.io.FileUtil.readBytes(file);
System.out.println(new String(bytes, StandardCharsets.UTF_8));
获取 JarFile 中的内容
JarFile
继承 ZipFile
用于获取 jar 包中的内容。比如我想获取 jar 中的某个文件的的内容。
final File file = new File("/Users/zhangpanqin/github/fly-java/src/main/resources/fastjson-1.2.68.jar");
final JarFile jarFile = new JarFile(file);
final JarEntry jarEntry = jarFile.getJarEntry("META-INF/LICENSE.txt");
final InputStream inputStream = jarFile.getInputStream(jarEntry);
// 工具类是 hutool
System.out.println(IoUtil.read(inputStream, StandardCharsets.UTF_8));
IoUtil.close(inputStream);
获取相对路径的内容
File.getAbsolutePath
查看源码可以发现,相对路径其实就是在前面拼接了 System.getProperty("user.dir")
。
class UnixFileSystem extends FileSystem {
public String resolve(File f) {
if (isAbsolute(f)) return f.getPath();
return resolve(System.getProperty("user.dir"), f.getPath());
}
}
只要我们弄清楚 System.getProperty("user.dir")
,问题就迎刃而解。Java System Properties 中介绍 user.dir
是用户的工作目录。
什么是用户工作目录呢?就是执行 java 命令的目录。在那个目录下执行命令,usr.dir 就会被 java 虚拟机赋值为执行命令的路径。我在 /Users/zhangpanqin/github/fly-java/test
目录下运行编译的 class
文件。-cp
指定 classpath 路径。
nio 读取文件内容
Path
可以类比 File
理解使用。然后工具类 Paths
可以获得 Path
,Files
更是提供了丰富的 api 用于crud 操作文件 Path
。
获取绝对路径内容
@Test
public void run33() throws IOException {
final Path path = Paths.get("/Users/zhangpanqin/github/fly-java/demo.txt");
final byte[] bytes = Files.readAllBytes(path);
System.out.println(new String(bytes,StandardCharsets.UTF_8));
}
获取相对路径内容
Paths
获取相对路径时,路径不以 /
开头。也可以理解成相对于 System.getProperty("user.dir")
路径。
public static void main(String[] args) {
System.out.println(System.getProperty("user.dir"));
System.out.println(Paths.get("").toAbsolutePath());
}
基于 ClassLoader 获取文件内容
ClassLoader.getResourceAsStream
// ClassLoader.getResourceAsStream java 1.8 源码
public InputStream getResourceAsStream(String name) {
URL url = getResource(name);
try {
return url != null ? url.openStream() : null;
} catch (IOException e) {
return null;
}
}
从代码可以看到主要逻辑还是集中在 getResource
。
public URL getResource(String name) {
URL url;
if (parent != null) {
url = parent.getResource(name);
} else {
url = getBootstrapResource(name);
}
if (url == null) {
url = findResource(name);
}
return url;
}
以上代码的逻辑也即是我们常听到的 双亲委派机制
。先让 父类加载
去加载资源,找不到再有自己找。类加载器单独讲
。
类加载器读取读取资源,先从自己负责的路径查找。比如应用类加载器 sun.misc.Launcher.AppClassLoader#AppClassLoader
负责 classpath
查找资源。类加载器读取资源相对于 File
和 Path
优势在哪里呢?比如当我想获取一个 jar
中的资源,你用路径就比较麻烦了,ClassLoader 可以从负责的路径下寻找,还可以去 jar 包中寻找。
final URL resource = Test2.class.getClassLoader().getResource("com/alibaba/fastjson/JSONArray.class");
System.out.println(resource);
上述打印结果
jar:file:/Users/zhangpanqin/.m2/repository/com/alibaba/fastjson/1.2.62/fastjson-1.2.62.jar!/com/alibaba/fastjson/JSONArray.class
我们还可以获取一个路径的 inputstream
@Test
public void run222(){
final InputStream resourceAsStream = Test2.class.getClassLoader().getResourceAsStream("META-INF/maven/com.alibaba/fastjson/pom.properties");
System.out.println(IoUtil.read(resourceAsStream, StandardCharsets.UTF_8));
}
上述结果为:
#Generated by Maven
#Mon Oct 07 22:09:36 CST 2019
version=1.2.62
groupId=com.alibaba
artifactId=fastjson
Class.getResourceAsStream
Class.getResourceAsStream
实际也调用的 ClassLoader
加载资源,但是它会将路径补充到相对于当前类所在包的路径。
比如
// com.fly.study.java.classloader.Test2
@Test
public void run555(){
System.out.println(Test2.class.getResource("name"));
}
实际查找的资源为 com.fly.study.java.classloader.name
。相对于当前类所在包的资源。
类加载器
我们经常会听到类加载器的 双亲委派模型
。
去哪里可以看到这些类加载呢。
启动类加载器
不是 java 代码实现的我们看不到源码。
sun.misc.Launcher 类中有我们知道的 扩展类加载器 sun.misc.Launcher.ExtClassLoader
和 应用类加载器 sun.misc.Launcher.AppClassLoader
。
java.lang.ClassLoader#getSystemClassLoader
代码看的话,实际返回的应用类加载器。
public static ClassLoader getSystemClassLoader() {
initSystemClassLoader();
if (scl == null) {
return null;
}
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkClassLoaderPermission(scl, Reflection.getCallerClass());
}
return scl;
}
private static synchronized void initSystemClassLoader() {
if (!sclSet) {
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if (l != null) {
Throwable oops = null;
scl = l.getClassLoader();
}
}
}
运行代码测试,返回的是应用类加载器。
// sun.misc.Launcher$AppClassLoader@18b4aac2
@Test
public void run66(){
System.out.println(ClassLoader.getSystemClassLoader());
}
这三个类加载器负责不同路径下的类加载。
启动类加载器介绍
启动类加载器 BootClassLoader
负责System.getProperty("sun.boot.class.path")
的类加载。也即是以下类。
${JAVA_HOME}/jre/lib/*.jar
和 ${JAVA_HOME}/jre/classes
类的加载。
/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/resources.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/rt.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/sunrsasign.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jsse.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jce.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/charsets.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jfr.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/classes
@Test
public void run77() {
final URLClassPath bootstrapClassPath = Launcher.getBootstrapClassPath();
final URL[] urLs = bootstrapClassPath.getURLs();
Stream.of(urLs).forEach(item->{
System.out.println(item.getFile());
});
}
扩展类加载器
扩展类加载器 sun.misc.Launcher.ExtClassLoader
为加载 System.getProperty("java.ext.dirs")
中的类。
@Test
public void run99() {
final String property = System.getProperty("java.ext.dirs");
final String[] split = property.split(":");
Stream.of(split).forEach(item -> {
System.out.println(item);
});
}
/Users/zhangpanqin/Library/Java/Extensions
/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext
/Library/Java/Extensions
/Network/Library/Java/Extensions
/System/Library/Java/Extensions
/usr/lib/java
应用类加载器
应用类加载器 sun.misc.Launcher.AppClassLoader
加载 System.getProperty("java.class.path");
# -cp 指定 classpath 路径,多个路径可以使用 : 分开(linux 下为 :,window 下为 ;),
java -cp /Users/zhangpanqin/github/fly-java/target/classes com.fly.study.java.classloader.Test2
本文由 张攀钦的博客 http://www.mflyyou.cn/ 创作。 可自由转载、引用,但需署名作者且注明文章出处。