---
title: Copy file or folder from classpath or jar in Java
tags:
- java
date: 2019-05-20 01:00:18
---
> 若感觉过程太冗长的话,可以直接看“[`#解决方案`](#解决方案)”快速找到答案。
# 需求
从 `jar` 包中复制`文件`或者`文件夹`到外部系统(这种需求可能不是特别多,但是还是有一些需要注意的点)。
# 问题
这里可能会面临两个问题,那就是开发的时候和打包成 `jar` 所使用的文件系统是不一样的。所以,在开发环境的时候,会呈现预期的效果,但一旦构建成 `jar` 包之后,就会出现文件找不到等恼人的问题。
# 分析
这里分析一下为什么会出现在 `jar` 包中找不到文件的问题。
对于 `jar` 中的`文件`复制来讲,大家第一时间肯定会想到用 `getClass().getClassLoader().getResourceAsStream("classpath:file_name")`,但这仅仅对于`文件`有效。
那么对于`文件夹`的复制呢?
我们假定需要复制的文件夹的路径为:`classpath:static`。
1. 首先,我们通过我们各自熟悉的方式获取到该`文件夹`的 `URI`。
- 在`开发环境`中的结果如下:
```txt
file:/path/to/project/resources/static
```
- 在 `jar` 包中的结果如下:
```txt
jar:file:/path/to/jar_file/xxx.jar!/BOOT-INF/classes!/static
```
1. 接着,我们需要读取文件信息。
这里用 `Java 8` 的 `Path` 来获取文件信息。
```java
public static Path get(URI uri);
```
在`开发环境`中测试是没有任何问题的。但是只要生成为 `jar` 包测试后,就会出现文件找不到异常。
查看 Path#get 源码可以发现首先会判断 `uri` 的 `schema`,很显然,`jar` 包中的文件的 `schema` 都为 `jar:file`。
也就意味着,两种环境中获取的 `FileSystem` 的方式是不相同的。
```java
public static Path get(URI uri) {
String scheme = uri.getScheme();
if (scheme == null)
throw new IllegalArgumentException("Missing scheme");
// check for default provider to avoid loading of installed providers
if (scheme.equalsIgnoreCase("file"))
return FileSystems.getDefault().provider().getPath(uri);
// try to find provider
for (FileSystemProvider provider: FileSystemProvider.installedProviders()) {
if (provider.getScheme().equalsIgnoreCase(scheme)) {
return provider.getPath(uri);
}
}
throw new FileSystemNotFoundException("Provider \"" + scheme + "\" not installed");
}
```
通过调试可以发现,默认提供的仅仅只有两种 `FileSystemProvider`:`LinuxFileSystemProvider` 和 `ZipFileSystemProvider`。
对于 `schema` 为 `jar:file` 的 `URI` 将会获取到 `ZipFileSystemProvider`。
查看 `ZipFileSystemProvider#getPath` 源码将会发现,它会根据传入的 `uri` 去获取一个已经注册了的 `FileSystem`。
由于我们此前没有关于此 `uri` 的 `FileSystem`,故我们需要手动创建一个于此 `uri` 对应的 `FielSystem`,即可解决问题。
# 解决方案
手动创建一个 `FileSystem` 即可解决前面遇到的问题,且需要更换获取文件夹的主路径为 `/BOOT-INF/classes/`(这里很重要)。
```java
FileSystem fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap());
Path path = fileSystem.getPath("/BOOT-INF/classes/static");
```
但是对于 `开发环境` 可能不太友好。为了更好地兼容两者,需要提前判断 `uri` 的类型是否为 `jar`,代码如下:
```java
Path path = null;
if(uri.getScheme().equalsIgnoreCase("jar")) {
FileSystem fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap());
path = fileSystem.getPath("/BOOT-INF/classes/static");
} else {
path = Paths.get(uri);
}
```
Copy file or folder from classpath or jar in Java