All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ideaaedi.commonds.clazz.LoadJarClassHelper Maven / Gradle / Ivy

The newest version!
package com.ideaaedi.commonds.clazz;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

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.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * 动态加载jar或者class
 *
 * @author JustryDeng 
 * @since 1.0.0
 */
@Slf4j
public final class LoadJarClassHelper {
    
    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 final Method ADD_URL_METHOD;
    
    /** 类加载器 */
    @Getter
    private final URLClassLoader classLoader;
    
    {
        try {
            ADD_URL_METHOD = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
            ADD_URL_METHOD.setAccessible(true);
            URLClassLoader currClassLoader = (URLClassLoader)LoadJarClassHelper.class.getClassLoader();
            classLoader = new URLClassLoader(currClassLoader.getURLs(), currClassLoader.getParent());
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * @see LoadJarClassHelper#loadJar(File, boolean, Set, Set)
     */
    public Set> loadJar(File jarOrDirFile, Set includePrefixSet, Set excludePrefixSet) {
        return this.loadJar(jarOrDirFile, false, includePrefixSet, excludePrefixSet);
    }
    
    /**
     * 加载指定的jar文件中的所有class(或: 加载指定目录(含其子孙目录)下的所有jar文件中的所有class)
     * 

* 注:普通的jar包与spring-boot jar包都支持。 * * @param jarOrDirFile * 要加载的jar文件(或jar文件所在的目录) *
* 注:如果jarOrDir是目录,那么该目录包括其子孙目录下的所有jar都会被加载。 * @param instanceLibClass * 是否实例化lib中的Class对象 * @param includePrefixSet * 当通过前缀控制是否实例化Class对象 *
* 注: 若includePrefixSet为null或者为空集合,那么默认实例化所有的class * @param excludePrefixSet * 通过前缀控制是否排除实例化Class对象 *
* 注: excludePrefixSet优先级高于includePrefixSet。 * * @return 已加载了的class实例集合 */ public Set> loadJar(File jarOrDirFile, boolean instanceLibClass, Set includePrefixSet, Set excludePrefixSet) { Set> classInstanceSet = new HashSet<>(); if (jarOrDirFile == null || !jarOrDirFile.exists()) { log.warn("jarOrDirFile is null Or jarOrDirFile is non-exist."); return classInstanceSet; } List jarFileList = IOUtil.listFileOnly(jarOrDirFile, JAR_SUFFIX); List bootJarFileList = new ArrayList<>(16); List normalJarFileList = new ArrayList<>(16); jarFileList.forEach(jar -> { if (isBootJar(jar)) { bootJarFileList.add(jar); } else { normalJarFileList.add(jar); } }); classInstanceSet.addAll(loadBootJar(bootJarFileList, instanceLibClass, includePrefixSet, excludePrefixSet)); classInstanceSet.addAll(loadNormalJar(normalJarFileList, true, includePrefixSet, excludePrefixSet)); return classInstanceSet; } /** * 加载指定路径下所有class文件 * * @param classLongNameRootDirSet * classLongNameRootDir集合, * 其中classLongNameRootDir为顶级包的父目录
* 举例说明: * 假设,现有结构/dir1/dir2/com/aaa/bbb/ccc/Qwer.class, 其中Qwer的全类名为 com.aaa.bbb.ccc.Qwer * 那么,在这里面,顶级包就是com, classLongNameRootDir就应该是/dir1/dir2/ * @param includePrefixSet * 通过前缀控制是否实例化Class对象 *
* 注: 若includePrefixSet为null或者为空集合,那么默认实例化所有的class * @param excludePrefixSet * 通过前缀控制是否排除实例化Class对象 *
* 注: excludePrefixSet优先级高于includePrefixSet。 * * @return 已加载了的class实例集合 */ public Set> loadClass(Set classLongNameRootDirSet, Set includePrefixSet, Set 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(classLoader, classLongNameRootDir.toURI().toURL()); } catch (IllegalAccessException | InvocationTargetException | MalformedURLException e) { throw new RuntimeException(e); } }); // (去重)采集所有类全类名 Set classLongNameSet = new HashSet<>(); classLongNameRootDirSet.forEach(classLongNameRootDir -> { int classLongNameStartIndex = classLongNameRootDir.getAbsolutePath().length() + 1; List 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(this::createClassInstance) .filter(Objects::nonNull) .collect(Collectors.toSet()); } /** * 加载(spring-boot打包出来的)jar文件(中的所有class) *

* 注: jar文件中,BOOT-INF/lib目录(含其子孙目录)下的所有jar文件,会被当做normal-jar,也一并进行加载。 * 注: jar文件中其余位置的jar文件(如果有的话)不会被加载. * * @param jarFileList * 要加载的jar文件集合 * @param instanceLibClass * 是否实例化lib中的Class对象 * @param includePrefixSet * 通过前缀控制是否实例化Class对象 *
* 注: 若includePrefixSet为null或者为空集合,那么默认实例化所有的class * @param excludePrefixSet * 通过前缀控制是否排除实例化Class对象 *
* 注: excludePrefixSet优先级高于includePrefixSet。 * * @return 已加载了的class文件全类名集合 */ public Set> loadBootJar(List jarFileList, boolean instanceLibClass, Set includePrefixSet, Set excludePrefixSet) { Set> classInstanceSet = new HashSet<>(); if (jarFileList == null || jarFileList.size() == 0) { return classInstanceSet; } verifyJarFile(jarFileList); Set bootClassRootDirSet = new HashSet<>(); Set bootLibSet = new HashSet<>(); Set tmpDirSet = new HashSet<>(); for (File file : jarFileList) { String absolutePath = file.getAbsolutePath(); String tmpDir = absolutePath.substring(0, absolutePath.length() - JAR_SUFFIX_LENGTH) + TMP_DIR_SUFFIX + System.currentTimeMillis() + "__"; // 记录临时目录 tmpDirSet.add(new File(tmpDir)); JarUtil.unJarWar(absolutePath, tmpDir); // 记录bootClassRootDir bootClassRootDirSet.add(new File(tmpDir, "BOOT-INF/classes")); // 记录bootLib List libs = IOUtil.listFileOnly(new File(tmpDir, "BOOT-INF/lib"), JAR_SUFFIX); bootLibSet.addAll(libs); } // 加载BOOT-INF/lib/下的.jar classInstanceSet.addAll(loadNormalJar(new ArrayList<>(bootLibSet), instanceLibClass, includePrefixSet, excludePrefixSet)); // 加载BOOT-INF/classes/下的.class bootClassRootDirSet.forEach(bootClassRootDir -> { Set 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 tmpSet = new HashSet<>(); tmpSet.add(bootClassRootDir.getParentFile().getParentFile()); classInstanceSet.addAll( loadClass(tmpSet, includePrefixSet, excludePrefixSet) ); } ); // 删除临时目录 tmpDirSet.forEach(IOUtil::delete); return classInstanceSet; } /** * 加载(普通)jar文件(中的所有class) *

* 注: jar文件中若包含其他的的jar文件,其他的jar文件里面的class是不会被加载的。 * * @param jarFileList * 要加载的jar文件集合 * @param instanceClass * 是否实例化Class对象 * @param includePrefixSet * 当instanceClass为true时, 通过前缀控制是否实例化Class对象 *
* 注: 若includePrefixSet为null或者为空集合,那么默认实例化所有的class * @param excludePrefixSet * 当instanceClass为true时, 通过前缀控制是否排除实例化Class对象 *
* 注: excludePrefixSet优先级高于includePrefixSet。 * * @return 已加载了的class集合 */ public Set> loadNormalJar(List jarFileList, boolean instanceClass, Set includePrefixSet, Set excludePrefixSet) { Set> 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(classLoader, url); if (!instanceClass) { continue; } ZipFile zipFile = null; try { zipFile = new ZipFile(jar); Enumeration 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; } /** * 根据全类名创建class实例 * * @param classLongName * 全类名 * @return class实例(注: 当创建异常时,返回null) */ public Class createClassInstance(String classLongName) { Class instance = null; try { instance = classLoader.loadClass(classLongName); } catch (Throwable e) { log.warn("create instance [{}] fail. throwable -> {}, message -> {}", classLongName, e.getClass().getSimpleName(), e.getMessage()); } return instance; } /** * 校验jar文件合法性(存在 && 是.jar后缀的文件) * * @param jarFileList * 要校验的jar文件 */ private void verifyJarFile(List 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."); } }); } /** * 判断jar文件是否是boot-jar文件 * * @param jar * 带判断的jar文件 * @return true-是boot-jar, false-普通jar */ private boolean isBootJar(File jar) { ZipFile zipFile = null; try { zipFile = new ZipFile(jar); Enumeration 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 1.0.0 */ @SuppressWarnings("AlibabaClassNamingShouldBeCamel") private static final class IOUtil { /** * 只罗列文件(即:只返回文件) *

* 注:dirOrFile对象本身也会被作为罗列对象。 *

* * @param dirOrFile * 要罗列的文件夹(或者文件) * @param suffix * 要筛选的文件的后缀(若suffix为null, 则不作筛选) * * @return 罗列结果 */ public static List 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 || suffix.length == 0) { return true; } String fileName = file.getName(); return Arrays.stream(suffix).anyMatch(fileName::endsWith); }).collect(Collectors.toList()); } /** * 罗列所有文件文件夹 *

* 注:dirOrFile对象本身也会被作为罗列对象。 *

* * @param dirOrFile * 要罗列的文件夹(或者文件) * @param mode * 罗列模式(0-罗列文件和文件夹; 1-只罗列文件; 2-只罗列文件夹) * * @return 罗列结果 */ public static List listFile(File dirOrFile, int mode) { List fileContainer = new ArrayList<>(16); listFile(dirOrFile, fileContainer, mode); return fileContainer; } /** * 罗列所有文件文件夹 *

* 注:dirOrFile对象本身也会被作为罗列对象。 *

* * @param dirOrFile * 要罗列的文件夹(或者文件) * @param fileContainer * 罗列结果 * @param mode * 罗列模式(0-罗列文件和文件夹; 1-只罗列文件; 2-只罗列文件夹) */ public static void listFile(File dirOrFile, List 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文件 *

* 注: 若源文件存在,则会覆盖原有的内容。 *

* * @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[] *

* 注:此方法会释放inputStream *

* * @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 1.0.0 */ private static final class JarUtil { /** * 解压jar(or war)至指定的目录 * * @see JarUtil#unJarWar(String, String, boolean, Collection) */ public static > List 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"] *
* 注:当entry对应jar或者war中的目录时,那么其值形如 BOOT-INF/classes/ *
* 注:当entry对应jar或者war中的文件时,那么其值形如 BOOT-INF/classes/com/example/ssm/author/JustryDeng.class * @return 解压出来的文件(包含目录)的完整路径 */ public static > List unJarWar(String jarWarPath, String targetDir, boolean delOldTargetDirIfAlreadyExist, T entryNamePrefixes) { List list = new ArrayList<>(); File target = new File(targetDir); if (delOldTargetDirIfAlreadyExist) { IOUtil.delete(target); } guarantyDirExist(target); ZipFile zipFile = null; try { zipFile = new ZipFile(new File(jarWarPath)); ZipEntry entry; File targetFile; Enumeration 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 = IOUtil.toBytes(zipFile.getInputStream(entry)); IOUtil.toFile(bytes, targetFile, true); list.add(targetFile.getAbsolutePath()); } } } catch (IOException e) { throw new RuntimeException(e); } finally { IOUtil.close(zipFile); } return list; } /** * 保证目录存在 * * @param dir * 目录 */ public static void guarantyDirExist(File dir) { if (!dir.exists()) { //noinspection ResultOfMethodCallIgnored dir.mkdirs(); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy