类加载器网上大同小异 本地没问题一发布公司容器化部署环境中类加载器报ClassNotFoundException 已解决! 自取
import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; import org.springframework.core.io.InputStreamResource; import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.security.SecureClassLoader; import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** * 动态加载jar或者class * * @author jack * @since 2022/05/01 */ @Slf4j public final class LoadJarClassUtil1 { private static final String JAR_SUFFIX = ".jar"; private static final int JAR_SUFFIX_LENGTH = ".jar".length(); private static final String CLASS_SUFFIX = ".class"; private static final String TMP_DIR_SUFFIX = "__temp__"; private static final int CLASS_SUFFIX_LENGTH = CLASS_SUFFIX.length(); /** * 添加资源的方法 */ private static final Method ADD_URL_METHOD; private static final Method METHOD_DEFINECLASS; /** * 类加载器 */ private static final URLClassLoader CLASS_LOADER; private static final SecureClassLoader SECURE_CLASS_LOADER; static { try { ADD_URL_METHOD = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); ADD_URL_METHOD.setAccessible(true); METHOD_DEFINECLASS = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); METHOD_DEFINECLASS.setAccessible(true); CLASS_LOADER = (URLClassLoader) ClassLoader.getSystemClassLoader(); SECURE_CLASS_LOADER = (SecureClassLoader) Thread.currentThread().getContextClassLoader(); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } /** * 加载指定的jar文件中的所有class(或: 加载指定目录(含其子孙目录)下的所有jar文件中的所有class) * <p> * 注:普通的jar包与spring-boot jar包都支持。 * * @param jarOrDirFile 要加载的jar文件(或jar文件所在的目录) * <br/> * 注:如果jarOrDir是目录,那么该目录包括其子孙目录下的所有jar都会被加载。 * @param includePrefixSet 当通过前缀控制是否实例化Class对象 * <br /> * 注: 若includePrefixSet为null或者为空集合,那么默认实例化所有的class * @param excludePrefixSet 通过前缀控制是否排除实例化Class对象 * <br /> * 注: excludePrefixSet优先级高于includePrefixSet。 * @return 已加载了的class实例集合 */ public static Set<Class<?>> loadJar(File jarOrDirFile, Set<String> includePrefixSet, Set<String> excludePrefixSet) { Set<Class<?>> classInstanceSet = new HashSet<>(); if (jarOrDirFile == null || !jarOrDirFile.exists()) { log.warn("jarOrDirFile is null Or jarOrDirFile is non-exist."); return classInstanceSet; } List<File> jarFileList = IOUtil.listFileOnly(jarOrDirFile, JAR_SUFFIX); List<File> bootJarFileList = new ArrayList<>(16); List<File> normalJarFileList = new ArrayList<>(16); jarFileList.forEach(jar -> { if (isBootJar(jar)) { bootJarFileList.add(jar); } else { normalJarFileList.add(jar); } }); classInstanceSet.addAll(loadBootJar(bootJarFileList, includePrefixSet, excludePrefixSet)); classInstanceSet.addAll(loadNormalJar(normalJarFileList, true, includePrefixSet, excludePrefixSet)); return classInstanceSet; } /** * 加载 jar中 配置文件Properties * * @param jarPath * @param propertiesNames * @return * @throws IOException */ public static Map<String, Properties> LoadProperties(String jarPath, List<String> propertiesNames) throws IOException { Map<String, Properties> properties = new HashMap<>(); JarFile jar = new JarFile(jarPath); Enumeration<?> entries = jar.entries(); YamlPropertiesFactoryBean yamlPropertiesFactoryBean = new YamlPropertiesFactoryBean(); while (entries.hasMoreElements()) { JarEntry entry = (JarEntry) entries.nextElement(); if (propertiesNames.stream().anyMatch(t -> entry.getName().toLowerCase().endsWith(t.toLowerCase()))) { InputStreamResource inputStreamResource = new InputStreamResource(jar.getInputStream(entry)); yamlPropertiesFactoryBean.setResources(inputStreamResource); Map<String, Properties> map = new HashMap<>(); properties.put(entry.getName(), yamlPropertiesFactoryBean.getObject()); } } return properties; } /** * 加载 实现传入接口的 类 * * @param jarPath * @param cl * @return * @throws IOException * @throws ClassNotFoundException * @throws IllegalAccessException * @throws InstantiationException */ public static List<Class<?>> LoadClassImplementsInterface(String jarPath, Class<?> cl) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException { List<Class<?>> classes = new ArrayList<>(); JarFile jar = new JarFile(jarPath); Enumeration<?> entries = jar.entries(); while (entries.hasMoreElements()) { JarEntry entry = (JarEntry) entries.nextElement(); if (entry.getName().endsWith(".class")) { String replace = entry.getName().replace(".class", "").replace("/", "."); InputStreamResource inputStreamResource = new InputStreamResource(jar.getInputStream(entry)); InputStream inputStream = inputStreamResource.getInputStream(); byte[] bytes = toByteArray(inputStream); METHOD_DEFINECLASS.invoke(SECURE_CLASS_LOADER, bytes, 0, bytes.length); Class<?> aClass = SECURE_CLASS_LOADER.loadClass(replace); if (!aClass.isInterface()) { if (Arrays.stream(aClass.getInterfaces()).anyMatch(t -> t.getName().equals(cl.getName()))) { classes.add(aClass); } } } } return classes; } public static List<Class<?>> LoadClasss(String jarPath) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException { List<Class<?>> classes = new ArrayList<>(); JarFile jar = new JarFile(jarPath); Enumeration<?> entries = jar.entries(); while (entries.hasMoreElements()) { JarEntry entry = (JarEntry) entries.nextElement(); if (entry.getName().endsWith(".class")) { String replace = entry.getName().replace(".class", "").replace("/", "."); InputStreamResource inputStreamResource = new InputStreamResource(jar.getInputStream(entry)); InputStream inputStream = inputStreamResource.getInputStream(); byte[] bytes = toByteArray(inputStream); METHOD_DEFINECLASS.invoke(SECURE_CLASS_LOADER, bytes, 0, bytes.length); Class<?> aClass = SECURE_CLASS_LOADER.loadClass(replace); if (!aClass.isInterface()) { classes.add(aClass); } } } return classes; } /** * 加载指定路径下所有class文件 * * @param classLongNameRootDirSet classLongNameRootDir集合, * 其中classLongNameRootDir为顶级包的父目录 <br/> * 举例说明: * 假设,现有结构/dir1/dir2/com/aaa/bbb/ccc/Qwer.class, 其中Qwer的全类名为 com.aaa.bbb.ccc.Qwer * 那么,在这里面,顶级包就是com, classLongNameRootDir就应该是/dir1/dir2/ * @param includePrefixSet 通过前缀控制是否实例化Class对象 * <br /> * 注: 若includePrefixSet为null或者为空集合,那么默认实例化所有的class * @param excludePrefixSet 通过前缀控制是否排除实例化Class对象 * <br /> * 注: excludePrefixSet优先级高于includePrefixSet。 * @return 已加载了的class实例集合 */ public static Set<Class<?>> loadClass(Set<File> classLongNameRootDirSet, Set<String> includePrefixSet, Set<String> excludePrefixSet) { if (classLongNameRootDirSet == null || classLongNameRootDirSet.size() == 0) { log.warn("classLongNameRootDirSet is empty."); return new HashSet<>(); } classLongNameRootDirSet = classLongNameRootDirSet.stream() .filter(x -> x.exists() && x.isDirectory()) .collect(Collectors.toSet()); if (classLongNameRootDirSet.isEmpty()) { log.warn("Valid classLongNameRootDir is empty."); return new HashSet<>(); } // 加载 classLongNameRootDirSet.forEach(classLongNameRootDir -> { try { ADD_URL_METHOD.invoke(CLASS_LOADER, classLongNameRootDir.toURI().toURL()); } catch (IllegalAccessException | InvocationTargetException | MalformedURLException e) { throw new RuntimeException(e); } }); // (去重)采集所有类全类名 Set<String> classLongNameSet = new HashSet<>(); classLongNameRootDirSet.forEach(classLongNameRootDir -> { int classLongNameStartIndex = classLongNameRootDir.getAbsolutePath().length() + 1; List<File> classFileList = IOUtil.listFileOnly(classLongNameRootDir, CLASS_SUFFIX); classLongNameSet.addAll(classFileList.stream() .map(classFile -> { String absolutePath = classFile.getAbsolutePath(); // 形如: com/aaa/bbb/ccc/Qwer String classLongPath = absolutePath.substring(classLongNameStartIndex, absolutePath.length() - CLASS_SUFFIX_LENGTH); return classLongPath.replace('\\', '.').replace("/", "."); }).filter(classLongName -> { if (excludePrefixSet != null && excludePrefixSet.size() > 0) { if (excludePrefixSet.stream().anyMatch(classLongName::startsWith)) { return false; } } if (includePrefixSet != null && includePrefixSet.size() > 0) { return includePrefixSet.stream().anyMatch(classLongName::startsWith); } return true; }) .collect(Collectors.toSet()) ); }); // 转换为class实例 return classLongNameSet.stream() .map(LoadJarClassUtil1::createClassInstance) .filter(Objects::nonNull) .collect(Collectors.toSet()); } /** * 加载(spring-boot打包出来的)jar文件(中的所有class) * <p> * 注: jar文件中,BOOT-INF/lib目录(含其子孙目录)下的所有jar文件,会被当做normal-jar,也一并进行加载。 * 注: jar文件中其余位置的jar文件(如果有的话)不会被加载. * * @param jarFileList 要加载的jar文件集合 * @param includePrefixSet 通过前缀控制是否实例化Class对象 * <br /> * 注: 若includePrefixSet为null或者为空集合,那么默认实例化所有的class * @param excludePrefixSet 通过前缀控制是否排除实例化Class对象 * <br /> * 注: excludePrefixSet优先级高于includePrefixSet。 * @return 已加载了的class文件全类名集合 */ private static Set<Class<?>> loadBootJar(List<File> jarFileList, Set<String> includePrefixSet, Set<String> excludePrefixSet) { Set<Class<?>> classInstanceSet = new HashSet<>(); if (jarFileList == null || jarFileList.size() == 0) { return classInstanceSet; } verifyJarFile(jarFileList); Set<File> bootClassRootDirSet = new HashSet<>(); Set<File> bootLibSet = new HashSet<>(); Set<File> tmpDirSet = new HashSet<>(); for (File file : jarFileList) { String absolutePath = file.getAbsolutePath(); String tmpDir = absolutePath.substring(0, absolutePath.length() - JAR_SUFFIX_LENGTH) + TMP_DIR_SUFFIX; // 记录临时目录 tmpDirSet.add(new File(tmpDir)); JarUtil.unJarWar(absolutePath, tmpDir); // 记录bootClassRootDir bootClassRootDirSet.add(new File(tmpDir, "BOOT-INF/classes")); // 记录bootLib List<File> libs = IOUtil.listFileOnly(new File(tmpDir, "BOOT-INF/lib"), JAR_SUFFIX); bootLibSet.addAll(libs); } // 加载BOOT-INF/lib/下的.jar classInstanceSet.addAll(loadNormalJar(new ArrayList<>(bootLibSet), false, includePrefixSet, excludePrefixSet)); // 加载BOOT-INF/classes/下的.class bootClassRootDirSet.forEach(bootClassRootDir -> { Set<File> tmpSet = new HashSet<>(); tmpSet.add(bootClassRootDir); classInstanceSet.addAll(loadClass(tmpSet, includePrefixSet, excludePrefixSet)); // 删除BOOT-INF目录 IOUtil.delete(bootClassRootDir.getParentFile()); }); // 加载jar中与BOOT-INF平级的其他类 bootClassRootDirSet.forEach(bootClassRootDir -> { Set<File> tmpSet = new HashSet<>(); tmpSet.add(bootClassRootDir.getParentFile().getParentFile()); classInstanceSet.addAll( loadClass(tmpSet, includePrefixSet, excludePrefixSet) ); } ); // 删除临时目录 tmpDirSet.forEach(IOUtil::delete); return classInstanceSet; } /** * 加载(普通)jar文件(中的所有class) * <p> * 注: jar文件中若包含其他的的jar文件,其他的jar文件里面的class是不会被加载的。 * * @param jarFileList 要加载的jar文件集合 * @param instanceClass 是否实例化Class对象 * @param includePrefixSet 当instanceClass为true时, 通过前缀控制是否实例化Class对象 * <br /> * 注: 若includePrefixSet为null或者为空集合,那么默认实例化所有的class * @param excludePrefixSet 当instanceClass为true时, 通过前缀控制是否排除实例化Class对象 * <br /> * 注: excludePrefixSet优先级高于includePrefixSet。 * @return 已加载了的class集合 */ private static Set<Class<?>> loadNormalJar(List<File> jarFileList, boolean instanceClass, Set<String> includePrefixSet, Set<String> excludePrefixSet) { Set<Class<?>> classInstanceSet = new HashSet<>(); if (jarFileList == null || jarFileList.size() == 0) { return classInstanceSet; } verifyJarFile(jarFileList); try { for (File jar : jarFileList) { URL url = jar.toURI().toURL(); ADD_URL_METHOD.invoke(CLASS_LOADER, url); if (!instanceClass) { continue; } ZipFile zipFile = null; try { zipFile = new ZipFile(jar); Enumeration<? extends ZipEntry> entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry zipEntry = entries.nextElement(); String zipEntryName = zipEntry.getName(); if (!zipEntryName.endsWith(CLASS_SUFFIX)) { continue; } String classLongName = zipEntryName .substring(0, zipEntryName.length() - CLASS_SUFFIX_LENGTH) .replace("/", "."); if (excludePrefixSet != null && excludePrefixSet.size() > 0) { if (excludePrefixSet.stream().anyMatch(classLongName::startsWith)) { continue; } } if (includePrefixSet != null && includePrefixSet.size() > 0) { if (includePrefixSet.stream().noneMatch(classLongName::startsWith)) { continue; } } Class<?> instance = createClassInstance(classLongName); if (instance != null) { classInstanceSet.add(instance); } } } finally { IOUtil.close(zipFile); } } } catch (Exception e) { throw new RuntimeException(e); } return classInstanceSet; } /** * 校验jar文件合法性(存在 && 是.jar后缀的文件) * * @param jarFileList 要校验的jar文件 */ private static void verifyJarFile(List<File> jarFileList) { Objects.requireNonNull(jarFileList, "jarFileList cannot be empty."); jarFileList.forEach(file -> { if (!file.exists()) { throw new IllegalArgumentException("file [" + file.getAbsolutePath() + "] non-exist."); } if (!file.getName().endsWith(JAR_SUFFIX)) { throw new IllegalArgumentException("file [" + file.getAbsolutePath() + "] is not a jar file."); } }); } /** * 根据全类名创建class实例 * * @param classLongName 全类名 * @return class实例(注 : 当创建异常时 , 返回null) */ private static Class<?> createClassInstance(String classLongName) { Class<?> instance = null; try { instance = Class.forName(classLongName); } catch (Throwable e) { log.warn("create instance [{}] fail. throwable -> {}, message -> {}", new Object[]{classLongName, e.getClass().getSimpleName(), e.getMessage()}); } return instance; } /** * 判断jar文件是否是boot-jar文件 * * @param jar 带判断的jar文件 * @return true-是boot-jar, false-普通jar */ private static boolean isBootJar(File jar) { ZipFile zipFile = null; try { zipFile = new ZipFile(jar); Enumeration<? extends ZipEntry> entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry zipEntry = entries.nextElement(); String zipEntryName = zipEntry.getName(); if (zipEntryName.startsWith("BOOT-INF/classes")) { return true; } } return false; } catch (IOException e) { throw new RuntimeException(e); } finally { IOUtil.close(zipFile); } } /** * IO工具类 * * @author JustryDeng * @since 2021/4/23 20:40:47 */ @SuppressWarnings("AlibabaClassNamingShouldBeCamel") private static final class IOUtil { /** * 只罗列文件(即:只返回文件) * <p> * 注:dirOrFile对象本身也会被作为罗列对象。 * </p> * * @param dirOrFile 要罗列的文件夹(或者文件) * @param suffix 要筛选的文件的后缀(若suffix为null, 则不作筛选) * @return 罗列结果 */ public static List<File> listFileOnly(File dirOrFile, String... suffix) { if (!dirOrFile.exists()) { throw new IllegalArgumentException("listFileOnly [" + dirOrFile.getAbsolutePath() + "] non exist."); } return listFile(dirOrFile, 1).stream() .filter(file -> { if (suffix == null) { return true; } String fileName = file.getName(); return Arrays.stream(suffix).anyMatch(fileName::endsWith); }).collect(Collectors.toList()); } /** * 罗列所有文件文件夹 * <p> * 注:dirOrFile对象本身也会被作为罗列对象。 * </p> * * @param dirOrFile 要罗列的文件夹(或者文件) * @param mode 罗列模式(0-罗列文件和文件夹; 1-只罗列文件; 2-只罗列文件夹) * @return 罗列结果 */ public static List<File> listFile(File dirOrFile, int mode) { List<File> fileContainer = new ArrayList<>(16); listFile(dirOrFile, fileContainer, mode); return fileContainer; } /** * 罗列所有文件文件夹 * <p> * 注:dirOrFile对象本身也会被作为罗列对象。 * </p> * * @param dirOrFile 要罗列的文件夹(或者文件) * @param fileContainer 罗列结果 * @param mode 罗列模式(0-罗列文件和文件夹; 1-只罗列文件; 2-只罗列文件夹) */ public static void listFile(File dirOrFile, List<File> fileContainer, int mode) { if (!dirOrFile.exists()) { return; } int onlyDirMode = 2; if (mode != 0 && mode != 1 && mode != onlyDirMode) { throw new IllegalArgumentException("mode [" + mode + "] is non-supported. 0,1,2is only support."); } if (dirOrFile.isDirectory()) { File[] files = dirOrFile.listFiles(); if (files != null) { for (File f : files) { listFile(f, fileContainer, mode); } } if (mode == 0 || mode == onlyDirMode) { fileContainer.add(dirOrFile); } } else { if (mode == 0 || mode == 1) { fileContainer.add(dirOrFile); } } } /** * 将srcFileBytes写出为destFile文件 * <p> * 注: 若源文件存在,则会覆盖原有的内容。 * </p> * * @param srcFileBytes 字节 * @param destFile 文件 * @param createIfNecessary 如果需要的话,创建文件 */ public static void toFile(byte[] srcFileBytes, File destFile, boolean createIfNecessary) { OutputStream os = null; try { if (destFile.isDirectory()) { throw new RuntimeException("destFile [" + destFile.getAbsolutePath() + "] must be file rather than dir."); } if (createIfNecessary && !destFile.exists()) { File parentFile = destFile.getParentFile(); if (!parentFile.exists() || !parentFile.isDirectory()) { /* * 进入此if,即代表parentFile存在,且为file, 而我们又需要创建一个同名的文件夹。 * 如果系统不支持创建与文件同名(大小写不敏感)的文件夹的话,那么创建结果为false */ boolean mkdirs = parentFile.mkdirs(); if (!mkdirs) { // step0. 将与与文件夹名冲突的文件重命名为:原文件名_时间戳 Arrays.stream(Objects.requireNonNull(parentFile.getParentFile().listFiles())) .filter(file -> file.getName().equalsIgnoreCase(parentFile.getName())).findFirst() .ifPresent(conflictFile -> { String renameFilePath = conflictFile.getAbsolutePath() + "_" + System.currentTimeMillis(); boolean renameResult = conflictFile.renameTo(new File(renameFilePath)); log.warn("rename file [" + conflictFile.getAbsolutePath() + "] to [" + renameFilePath + "] " + (renameResult ? "success" : "fail") + "."); }); // step1. 再次创建文件夹 mkdirs = parentFile.mkdirs(); if (!mkdirs) { log.warn("create dir [" + parentFile.getAbsolutePath() + "] fail."); } } } //noinspection ResultOfMethodCallIgnored destFile.createNewFile(); } else if (!destFile.exists()) { throw new IllegalArgumentException("destFile [" + destFile.getAbsolutePath() + "] non exist."); } os = new FileOutputStream(destFile); os.write(srcFileBytes, 0, srcFileBytes.length); os.flush(); } catch (IOException e) { throw new RuntimeException(" toFile [" + destFile.getAbsolutePath() + "] occur exception.", e); } finally { close(os); } } /** * 将inputStream转换为byte[] * <p> * 注:此方法会释放inputStream * </p> * * @param inputStream 输入流 * @return 字节 */ public static byte[] toBytes(InputStream inputStream) throws IOException { ByteArrayOutputStream output = new ByteArrayOutputStream(); try { byte[] buffer = new byte[4096]; int n; while (-1 != (n = inputStream.read(buffer))) { output.write(buffer, 0, n); } return output.toByteArray(); } finally { close(output, inputStream); } } /** * 删除文件/文件夹 * * @param dirOrFile 要删的除文件/文件夹 */ public static void delete(File dirOrFile) { if (!dirOrFile.exists()) { return; } if (dirOrFile.isFile()) { boolean success = dirOrFile.delete(); if (!success) { log.debug("delete file [" + dirOrFile.getAbsolutePath() + "] fail."); } } else { File[] files = dirOrFile.listFiles(); if (files != null) { for (File f : files) { delete(f); } } } //noinspection ResultOfMethodCallIgnored dirOrFile.delete(); } /** * 关闭流 * * @param ioArr 待关闭的io */ public static void close(Closeable... ioArr) { if (ioArr == null) { return; } for (Closeable io : ioArr) { if (io == null) { continue; } try { io.close(); } catch (IOException e) { // ignore } } } } /** * jar/war操作工具类 * * @author JustryDeng * @since 2021/4/25 21:58:52 */ private static final class JarUtil { /** * 解压jar(or war)至指定的目录 * * @see JarUtil#unJarWar(String, String, boolean, Collection) */ public static <T extends Collection<String>> List<String> unJarWar(String jarWarPath, String targetDir) { return unJarWar(jarWarPath, targetDir, true, null); } /** * 解压jar(or war)至指定的目录 * * @param jarWarPath 待解压的jar(or war)文件 * @param targetDir 解压后文件放置的文件夹 * @param delOldTargetDirIfAlreadyExist 若targetDir已存在,是否先将原来的targetDir进行删除 * @param entryNamePrefixes 只有当entryName为指定的前缀时,才对该entry进行解压(若为null或者长度为0, 则解压所有文件) 如: ["BOOT-INF/classes/", "BOOT-INF/classes/com/example/ssm/author/JustryDeng.class"] * <br/> * 注:当entry对应jar或者war中的目录时,那么其值形如 BOOT-INF/classes/ * <br/> * 注:当entry对应jar或者war中的文件时,那么其值形如 BOOT-INF/classes/com/example/ssm/author/JustryDeng.class * @return 解压出来的文件(包含目录)的完整路径 */ public static <T extends Collection<String>> List<String> unJarWar(String jarWarPath, String targetDir, boolean delOldTargetDirIfAlreadyExist, T entryNamePrefixes) { List<String> list = new ArrayList<>(); File target = new File(targetDir); if (delOldTargetDirIfAlreadyExist) { LoadJarClassUtil1.IOUtil.delete(target); } guarantyDirExist(target); ZipFile zipFile = null; try { zipFile = new ZipFile(new File(jarWarPath)); ZipEntry entry; File targetFile; Enumeration<? extends ZipEntry> entries = zipFile.entries(); while (entries.hasMoreElements()) { entry = entries.nextElement(); String entryName = entry.getName(); // 若entryNamePrefixes不为空,则不解压前缀不匹配的文件或文件夹 if (entryNamePrefixes != null && entryNamePrefixes.size() > 0 && entryNamePrefixes.stream().noneMatch(entryName::startsWith)) { continue; } if (entry.isDirectory()) { targetFile = new File(target, entryName); guarantyDirExist(targetFile); } else { // 有时遍历时,文件先于文件夹出来,所以也得保证目录存在 int lastSeparatorIndex = entryName.lastIndexOf("/"); if (lastSeparatorIndex > 0) { guarantyDirExist(new File(target, entryName.substring(0, lastSeparatorIndex))); } // 解压文件 targetFile = new File(target, entryName); byte[] bytes = LoadJarClassUtil1.IOUtil.toBytes(zipFile.getInputStream(entry)); LoadJarClassUtil1.IOUtil.toFile(bytes, targetFile, true); list.add(targetFile.getAbsolutePath()); } } } catch (IOException e) { throw new RuntimeException(e); } finally { LoadJarClassUtil1.IOUtil.close(zipFile); } return list; } /** * 保证目录存在 * * @param dir 目录 */ public static void guarantyDirExist(File dir) { if (!dir.exists()) { //noinspection ResultOfMethodCallIgnored dir.mkdirs(); } } } /** * 测试 */ public static void main(String[] args) { Set<String> includePrefixSet = new HashSet<>(); Set<String> excludePrefixSet = new HashSet<>(); excludePrefixSet.add("BOOT-INF.classes"); String jarName = "my-jar"; // 测试加载jar loadJar(new File("C:\\Users\\JustryDeng\\Desktop\\TMP\\" + jarName + ".jar"), includePrefixSet, excludePrefixSet ).stream().sorted(Comparator.comparing(Class::getName)). forEach(x -> System.err.println(x.getName())); System.out.println("\n ================== 分割线 ================== \n"); // 测试加载class loadClass(new HashSet<>( Arrays.asList( new File("C:\\Users\\JustryDeng\\Desktop\\TMP\\" + jarName), new File("C:\\Users\\JustryDeng\\Desktop\\TMP\\" + jarName + "\\BOOT-INF\\classes") ) ), includePrefixSet, excludePrefixSet ).stream().sorted(Comparator.comparing(Class::getName)). forEach(x -> System.err.println(x.getName())); } public static byte[] toByteArray(InputStream input) throws IOException { ByteArrayOutputStream output = new ByteArrayOutputStream(); byte[] buffer = new byte[4096]; int n = 0; while (-1 != (n = input.read(buffer))) { output.write(buffer, 0, n); } return output.toByteArray(); } }