背景
因为执行需要,需要把jar内templates文件夹下的的文件夹及文件加压到宿主机器的某个路径下, 以便执行对应的脚本文件
PS: 通过类加载器等方式,直接getFile遍历文件,在idea中运行是没问题的,但是当打包成jar运行就会出现问题,因为jar内文件的路径不是真实路径,会出现异常
java.io.FileNotFoundException: class path resource [xxx/xxx/] cannot be resolved to absolute file path because it does not reside in the file system: jar:file:xxx.jar!/BOOT-INF/classes!/xxx/xxx
方式一
知道文件名的情况下,无需一层一层的遍历,将文件路径都指定好,然后根据流文件拷贝
package com.aimsphm.practice;import lombok.extern.slf4j.Slf4j;
import com.google.common.collect.Lists;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.util.ObjectUtils;import javax.annotation.PostConstruct;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.List;@Component
public class App {public static void main(String[] args) {SpringApplication.run(App.class, args);}@Value("${customer.config.data-root:/usr/data/}")private String dataRoot;@PostConstructpublic void initDatabase() {dataRoot = dataRoot.endsWith("/") ? dataRoot : dataRoot + "/";List<String> fileList = getFiles();fileList.stream().filter(x -> !ObjectUtils.isEmpty(x)).forEach(x -> {try {URL resource = App.class.getClassLoader().getResource(x);InputStream inputStream = resource.openStream();if (ObjectUtils.isEmpty(inputStream)) {return;}File file = new File(dataRoot + x);if (!file.exists()) {FileUtils.copyInputStreamToFile(inputStream, file);}} catch (IOException e) {log.error("失败:",e)}});}private List<String> getFiles() {return Lists.newArrayList("db/practice.db","local-data/0/p-1.jpg","local-data/0/p-2.jpg","local-data/0/p-3.jpg","local-data/0/p-4.jpg","local-data/1/meter-1.png","local-data/-1/yw-1.png","local-data/sound/test.txt","local-data/multi/1/meter-multi.jpg","local-data/multi/-1/yewei.png","");}
}
方式二
通过resource的方式,获取文件的描述信息,然后根据描述信息,获取文件的路径信息,然后通过拷贝流文件,将文件最终拷贝到指定的路径下
package com.example.demo.test;import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.stereotype.Component;import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;/*** 复制resource文件、文件夹** @author MILLA*/
@Component
@Slf4j
public class JarFileUtil {public void copyFolderFromJar() throws IOException {this.copyFolderFromJar("templates", "/usr/data/files");}/*** 复制path目录下所有文件到指定的文件系统中** @param path 文件目录 不能以/开头* @param newPath 新文件目录*/public void copyFolderFromJar(String path, String newPath) throws IOException {path = preOperation(path, newPath);ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();//获取所有匹配的文件(包含根目录文件、子目录、子目录下的文件)Resource[] resources = resolver.getResources("classpath:" + path + "/**");//打印有多少文件for (Resource resource : resources) {//文件名//以jar包运行时,不能使用resource.getFile()获取文件路径、判断是否为文件等,会报错://java.io.FileNotFoundException: class path resource [xxx/xxx/] cannot be resolved to absolute file path because it does not reside in the file system: jar:file:xxx.jar!/BOOT-INF/classes!/xxx/xxx//文件路径//file [/xxx/xxx]String description = resource.getDescription();description = description.replace("\\", "/");description = description.replace(path, "/");//保留 /xxx/xxxdescription = description.replaceAll("(.*\\[)|(]$)", "").trim();//以“文件目录”进行分割,获取文件相对路径//获取文件相对路径,/xxx/xxx//新文件路径String newFilePath = newPath + "/" + description;if (newFilePath.endsWith("/")) {File file = new File(newFilePath);//文件夹if (file.exists()) {boolean mkdir = file.mkdir();log.debug("路径[{}]创建是否成功状态:{} ", newFilePath, mkdir);}} else {//文件InputStream stream = resource.getInputStream();write2File(stream, newFilePath);}}}/*** 文件预处理** @param path 原文件路径* @param newPath 目标路径* @return 新的路径字符串*/private static String preOperation(String path, String newPath) {if (!new File(newPath).exists()) {boolean mkdir = new File(newPath).mkdir();log.debug("路径[{}]创建是否成功状态:{} ", newPath, mkdir);}if (path.contains("\\")) {path = path.replace("\\", "/");}//保证没有重复的/出现path = path.replaceAll("(?<!\\G/|[^/])/+", "");if (path.startsWith("/")) {//以/开头,去掉/path = path.substring(1);}if (path.endsWith("/")) {//以/结尾,去掉/path = path.substring(0, path.length() - 1);}return path;}/*** 输入流写入文件** @param is 输入流* @param filePath 文件保存目录路径* @throws IOException IOException*/public static void write2File(InputStream is, String filePath) throws IOException {File destFile = new File(filePath);File parentFile = destFile.getParentFile();boolean mkdirs = parentFile.mkdirs();log.debug("路径[{}]创建是否成功状态:{} ", filePath, mkdirs);if (!destFile.exists()) {boolean newFile = destFile.createNewFile();log.debug("路径[{}]创建是否成功状态:{} ", destFile.getPath(), newFile);}OutputStream os = new FileOutputStream(destFile);int len = 8192;byte[] buffer = new byte[len];while ((len = is.read(buffer, 0, len)) != -1) {os.write(buffer, 0, len);}os.close();is.close();}public static void main(String[] args) throws IOException {//文件夹复制String path = "templates";String newPath = "D:/tmp";new JarFileUtil().copyFolderFromJar(path, newPath);}}
如果开发中使用一些文件操作依赖,可简化代码如下
<!--文件依赖 -->
<dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.3.3</version></dependency>
package com.example.demo.test;import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import java.io.File;/*** <p>* 功能描述:* </p>** @author MILLA* @version 1.0* @since 2024/05/31 16:30*/
@Slf4j
@Component
public class JarFileUtil{public static void main(String[] args) {JarFileUtilinit = new JarFileUtil();init.copyFile2Temp("//templates//shell//", "/usr/data/shell/files");}@PostConstructpublic void copyFile2Temp() {}public void copyFile2Temp(String source, String target) {try {source = preOperation(source, target);ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();Resource[] resources = resolver.getResources(source + "/**");for (int i = 0; i < resources.length; i++) {Resource resource = resources[i];
// resource.getFile() jar运行时候不能用该方法获取文件,因为jar的路径不对String description = resource.getDescription();description = description.replace("\\", "/");description = description.replace(source, "/");//保留 /xxx/xxxdescription = description.replaceAll("(.*\\[)|(]$)", "").trim();//以“文件目录”进行分割,获取文件相对路径//获取文件相对路径,/xxx/xxx//新文件路径String newFilePath = target + File.separator + description;File file = new File(newFilePath);if (newFilePath.endsWith("/")) {boolean mkdirs = file.mkdirs();log.debug("路径[{}]创建是否成功状态:{} ", newFilePath, mkdirs);} else {FileUtils.copyInputStreamToFile(resource.getInputStream(), file);}}} catch (Exception e) {log.error("文件拷贝异常:", e);}}private static String preOperation(String source, String target) {if (!new File(target).exists()) {boolean mkdir = new File(target).mkdir();log.debug("路径[{}]创建是否成功状态:{} ", target, mkdir);}if (source.contains("\\")) {source = source.replace("\\", "/");}//保证没有重复的/出现source = source.replaceAll("(?<!\\G/|[^/])/+", "");if (source.startsWith("/")) {//以/开头,去掉/source = source.substring(1);}if (source.endsWith("/")) {//以/结尾,去掉/source = source.substring(0, source.length() - 1);}return source;}
}
通过这种方式,就能将正在运行的jar中的文件,拷贝到指定的路径下,记录备查