刚接触 java
的时候很困惑一个事情 File
相对路径,以哪个目录为参照物。
随着 io 模型的发展,java 1.7 的 nio
,使用 Path
、 Paths
和 Files
等来方便 io 的操作。
ClassLoader
用于获取 class 文件
的 io,我们也可以用于获取文件的 io,以便于我们读取文件内容。
<font color=red>代码基于 Mac 10.15.4
,JDK 1.8。</font>
绝对路径的内容获取比较简单,直接获取文件 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
继承 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
是用户的工作目录。
<font color=red>什么是用户工作目录呢?就是执行 java 命令的目录。</font>在那个目录下执行命令,usr.dir 就会被 java 虚拟机赋值为执行命令的路径。我在 /Users/zhangpanqin/github/fly-java/test
目录下运行编译的 class
文件。 -cp
指定 classpath 路径。
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.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
实际也调用的 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
创作。 可自由转载、引用,但需署名作者且注明文章出处。
如转载至微信公众号,请在文末添加作者公众号二维码。微信公众号名称:Mflyyou