浏览代码

add compiler

Looly 5 年之前
父节点
当前提交
b2ee5fbfec
共有 26 个文件被更改,包括 741 次插入374 次删除
  1. 2 0
      CHANGELOG.md
  2. 34 0
      hutool-core/src/main/java/cn/hutool/core/compiler/CompilerException.java
  3. 56 0
      hutool-core/src/main/java/cn/hutool/core/compiler/CompilerUtil.java
  4. 26 0
      hutool-core/src/main/java/cn/hutool/core/compiler/DiagnosticUtil.java
  5. 11 32
      hutool-core/src/main/java/cn/hutool/core/compiler/JavaClassFileManager.java
  6. 75 0
      hutool-core/src/main/java/cn/hutool/core/compiler/JavaFileObjectUtil.java
  7. 16 90
      hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceCompiler.java
  8. 14 11
      hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceFileObject.java
  9. 24 12
      hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java
  10. 17 1
      hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java
  11. 25 0
      hutool-core/src/main/java/cn/hutool/core/io/file/FileNameUtil.java
  12. 20 3
      hutool-core/src/main/java/cn/hutool/core/io/file/FileReader.java
  13. 27 11
      hutool-core/src/main/java/cn/hutool/core/io/file/FileWriter.java
  14. 12 0
      hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java
  15. 0 7
      hutool-core/src/main/java/cn/hutool/core/io/resource/BytesResource.java
  16. 90 0
      hutool-core/src/main/java/cn/hutool/core/io/resource/CharSequenceResource.java
  17. 73 0
      hutool-core/src/main/java/cn/hutool/core/io/resource/FileObjectResource.java
  18. 0 33
      hutool-core/src/main/java/cn/hutool/core/io/resource/InputStreamResource.java
  19. 9 3
      hutool-core/src/main/java/cn/hutool/core/io/resource/Resource.java
  20. 5 48
      hutool-core/src/main/java/cn/hutool/core/io/resource/StringResource.java
  21. 0 38
      hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java
  22. 1 1
      hutool-core/src/main/java/cn/hutool/core/lang/JarClassLoader.java
  23. 52 0
      hutool-core/src/main/java/cn/hutool/core/lang/ResourceClassLoader.java
  24. 142 84
      hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java
  25. 1 0
      hutool-core/src/test/java/cn/hutool/core/compiler/JavaSourceCompilerTest.java
  26. 9 0
      hutool-core/src/test/java/cn/hutool/core/io/resource/ResourceUtilTest.java

+ 2 - 0
CHANGELOG.md

@@ -24,6 +24,8 @@
 * 【cache  】     增加CacheListener(issue#1257@Github)
 * 【core   】     TimeInterval支持分组(issue#1238@Github)
 * 【core   】     增加compile包(pr#1243@Github)
+* 【core   】     增加ResourceClassLoader、CharSequenceResource、FileObjectResource
+* 【core   】     修改IoUtil.read(Reader)逻辑默认关闭Reader
 
 ### Bug修复
 * 【cron   】     修复CronTimer可能死循环的问题(issue#1224@Github)

+ 34 - 0
hutool-core/src/main/java/cn/hutool/core/compiler/CompilerException.java

@@ -0,0 +1,34 @@
+package cn.hutool.core.compiler;
+
+import cn.hutool.core.exceptions.ExceptionUtil;
+import cn.hutool.core.util.StrUtil;
+
+/**
+ * 编译异常
+ *
+ * @author looly
+ * @since 5.5.2
+ */
+public class CompilerException extends RuntimeException {
+	private static final long serialVersionUID = 1L;
+
+	public CompilerException(Throwable e) {
+		super(ExceptionUtil.getMessage(e), e);
+	}
+
+	public CompilerException(String message) {
+		super(message);
+	}
+
+	public CompilerException(String messageTemplate, Object... params) {
+		super(StrUtil.format(messageTemplate, params));
+	}
+
+	public CompilerException(String message, Throwable throwable) {
+		super(message, throwable);
+	}
+
+	public CompilerException(Throwable throwable, String messageTemplate, Object... params) {
+		super(StrUtil.format(messageTemplate, params), throwable);
+	}
+}

+ 56 - 0
hutool-core/src/main/java/cn/hutool/core/compiler/CompilerUtil.java

@@ -0,0 +1,56 @@
+package cn.hutool.core.compiler;
+
+import javax.tools.DiagnosticListener;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+import javax.tools.ToolProvider;
+
+/**
+ * 源码编译工具类,主要封装{@link JavaCompiler} 相关功能
+ *
+ * @author looly
+ * @since 5.5.2
+ */
+public class CompilerUtil {
+	/**
+	 * java 编译器
+	 */
+	public static final JavaCompiler SYSTEM_COMPILER = ToolProvider.getSystemJavaCompiler();
+
+	/**
+	 * 编译指定的源码文件
+	 *
+	 * @param sourceFiles 源码文件路径
+	 * @return 0表示成功,否则其他
+	 */
+	public static boolean compile(String... sourceFiles) {
+		return 0 == SYSTEM_COMPILER.run(null, null, null, sourceFiles);
+	}
+
+	/**
+	 * 获取{@link JavaFileManager}
+	 *
+	 * @return {@link JavaFileManager}
+	 */
+	public static JavaFileManager getFileManager() {
+		return SYSTEM_COMPILER.getStandardFileManager(null, null, null);
+	}
+
+	/**
+	 * 新建编译任务
+	 *
+	 * @param fileManager {@link JavaFileManager},用于管理已经编译好的文件
+	 * @param diagnosticListener 诊断监听
+	 * @param options 选项,例如 -cpXXX等
+	 * @param compilationUnits 编译单元,即需要编译的对象
+	 * @return {@link JavaCompiler.CompilationTask}
+	 */
+	public static JavaCompiler.CompilationTask getTask(
+			JavaFileManager fileManager,
+			DiagnosticListener<? super JavaFileObject> diagnosticListener,
+			Iterable<String> options,
+			Iterable<? extends JavaFileObject> compilationUnits) {
+		return SYSTEM_COMPILER.getTask(null, fileManager, diagnosticListener, options, null, compilationUnits);
+	}
+}

+ 26 - 0
hutool-core/src/main/java/cn/hutool/core/compiler/DiagnosticUtil.java

@@ -0,0 +1,26 @@
+package cn.hutool.core.compiler;
+
+import javax.tools.DiagnosticCollector;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 诊断工具类
+ *
+ * @author looly
+ * @since 5.5.2
+ */
+public class DiagnosticUtil {
+
+	/**
+	 * 获取{@link DiagnosticCollector}收集到的诊断信息,以文本返回
+	 *
+	 * @param collector {@link DiagnosticCollector}
+	 * @return 诊断消息
+	 */
+	public static String getMessages(DiagnosticCollector<?> collector) {
+		final List<?> diagnostics = collector.getDiagnostics();
+		return diagnostics.stream().map(String::valueOf)
+				.collect(Collectors.joining(System.lineSeparator()));
+	}
+}

+ 11 - 32
hutool-core/src/main/java/cn/hutool/core/compiler/JavaClassFileManager.java

@@ -1,7 +1,7 @@
 package cn.hutool.core.compiler;
 
-import cn.hutool.core.io.IORuntimeException;
-import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.io.resource.FileObjectResource;
+import cn.hutool.core.lang.ResourceClassLoader;
 import cn.hutool.core.util.ClassLoaderUtil;
 import cn.hutool.core.util.ObjectUtil;
 
@@ -10,16 +10,16 @@ import javax.tools.ForwardingJavaFileManager;
 import javax.tools.JavaFileManager;
 import javax.tools.JavaFileObject;
 import javax.tools.JavaFileObject.Kind;
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.SecureClassLoader;
 import java.util.HashMap;
 import java.util.Map;
 
 /**
- * Java 字节码文件对象
+ * Java 字节码文件对象管理器
+ *
+ * <p>
  * 正常我们使用javac命令编译源码时会将class文件写入到磁盘中,但在运行时动态编译类不适合保存在磁盘中
- * 我们采取此对象来管理运行时动态编译类生成的字节码
+ * 我们采取此对象来管理运行时动态编译类生成的字节码。
+ * </p>
  *
  * @author lzpeng
  * @since 5.5.2
@@ -29,7 +29,7 @@ class JavaClassFileManager extends ForwardingJavaFileManager<JavaFileManager> {
 	/**
 	 * 存储java字节码文件对象映射
 	 */
-	private final Map<String, JavaFileObject> javaFileObjectMap = new HashMap<>();
+	private final Map<String, FileObjectResource> classFileObjectMap = new HashMap<>();
 
 	/**
 	 * 加载动态编译生成类的父类加载器
@@ -55,28 +55,7 @@ class JavaClassFileManager extends ForwardingJavaFileManager<JavaFileManager> {
 	 */
 	@Override
 	public ClassLoader getClassLoader(final Location location) {
-		return new SecureClassLoader(parent) {
-
-			/**
-			 * 查找类
-			 * @param name 类名
-			 * @return 类的class对象
-			 * @throws ClassNotFoundException 未找到类异常
-			 */
-			@Override
-			protected Class<?> findClass(final String name) throws ClassNotFoundException {
-				final JavaFileObject javaFileObject = javaFileObjectMap.get(name);
-				if (null != javaFileObject) {
-					try(final InputStream inputStream = javaFileObject.openInputStream()){
-						final byte[] bytes = IoUtil.readBytes(inputStream);
-						return defineClass(name, bytes, 0, bytes.length);
-					} catch (IOException e) {
-						throw new IORuntimeException(e);
-					}
-				}
-				throw new ClassNotFoundException(name);
-			}
-		};
+		return new ResourceClassLoader<>(this.parent, this.classFileObjectMap);
 	}
 
 	/**
@@ -86,13 +65,13 @@ class JavaClassFileManager extends ForwardingJavaFileManager<JavaFileManager> {
 	 * @param location  源码位置
 	 * @param className 类名
 	 * @param kind      文件类型
-	 * @param sibling   Java源码对象
+	 * @param sibling   Java源码对象
 	 * @return Java字节码文件对象
 	 */
 	@Override
 	public JavaFileObject getJavaFileForOutput(final Location location, final String className, final Kind kind, final FileObject sibling) {
 		final JavaFileObject javaFileObject = new JavaClassFileObject(className, kind);
-		javaFileObjectMap.put(className, javaFileObject);
+		this.classFileObjectMap.put(className, new FileObjectResource(javaFileObject));
 		return javaFileObject;
 	}
 

+ 75 - 0
hutool-core/src/main/java/cn/hutool/core/compiler/JavaFileObjectUtil.java

@@ -0,0 +1,75 @@
+package cn.hutool.core.compiler;
+
+import cn.hutool.core.io.file.FileNameUtil;
+import cn.hutool.core.util.ZipUtil;
+
+import javax.tools.JavaFileObject;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.ZipFile;
+
+/**
+ * {@link JavaFileObject} 相关工具类封装
+ *
+ * @author lzpeng, looly
+ * @since 5.5.2
+ */
+public class JavaFileObjectUtil {
+
+	/**
+	 * 获取指定文件下的所有待编译的java文件,并以{@link JavaFileObject}形式返回
+	 *
+	 * @param file 文件或目录,文件支持.java、.jar和.zip文件
+	 * @return 所有待编译的 {@link JavaFileObject}
+	 */
+	public static List<JavaFileObject> getJavaFileObjects(File file) {
+		final List<JavaFileObject> result = new ArrayList<>();
+		final String fileName = file.getName();
+
+		if (isJavaFile(fileName)) {
+			result.add(new JavaSourceFileObject(file.toURI()));
+		} else if (isJarOrZipFile(fileName)) {
+			result.addAll(getJavaFileObjectByZipOrJarFile(file));
+		}
+		return result;
+	}
+
+	/**
+	 * 是否是jar 或 zip 文件
+	 *
+	 * @param fileName 文件名
+	 * @return 是否是jar 或 zip 文件
+	 */
+	public static boolean isJarOrZipFile(String fileName) {
+		return FileNameUtil.isType(fileName, "jar", "zip");
+	}
+
+	/**
+	 * 是否是java文件
+	 *
+	 * @param fileName 文件名
+	 * @return 是否是.java文件
+	 */
+	public static boolean isJavaFile(String fileName) {
+		return FileNameUtil.isType(fileName, "java");
+	}
+
+	/**
+	 * 通过zip包或jar包创建Java文件对象
+	 *
+	 * @param file 压缩文件
+	 * @return Java文件对象
+	 */
+	private static List<JavaFileObject> getJavaFileObjectByZipOrJarFile(File file) {
+		final List<JavaFileObject> collection = new ArrayList<>();
+		final ZipFile zipFile = ZipUtil.toZipFile(file, null);
+		ZipUtil.read(zipFile, (zipEntry) -> {
+			final String name = zipEntry.getName();
+			if (isJavaFile(name)) {
+				collection.add(new JavaSourceFileObject(name, ZipUtil.getStream(zipFile, zipEntry)));
+			}
+		});
+		return collection;
+	}
+}

+ 16 - 90
hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceCompiler.java

@@ -9,28 +9,21 @@ import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.URLUtil;
 
 import javax.tools.DiagnosticCollector;
-import javax.tools.JavaCompiler;
 import javax.tools.JavaCompiler.CompilationTask;
 import javax.tools.JavaFileManager;
 import javax.tools.JavaFileObject;
 import javax.tools.StandardLocation;
-import javax.tools.ToolProvider;
 import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Enumeration;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
 
 /**
  * Java 源码编译器
@@ -40,11 +33,6 @@ import java.util.zip.ZipFile;
 public class JavaSourceCompiler {
 
 	/**
-	 * java 编译器
-	 */
-	private static final JavaCompiler JAVA_COMPILER = ToolProvider.getSystemJavaCompiler();
-
-	/**
 	 * 待编译的文件 可以是 .java文件 压缩文件 文件夹 递归搜索文件夹内的zip包和jar包
 	 */
 	private final List<File> sourceFileList = new ArrayList<>();
@@ -68,10 +56,10 @@ public class JavaSourceCompiler {
 	/**
 	 * 构造
 	 *
-	 * @param parent 父类加载器
+	 * @param parent 父类加载器,null则使用默认类加载器
 	 */
 	private JavaSourceCompiler(ClassLoader parent) {
-		this.parentClassLoader = parent;
+		this.parentClassLoader = ObjectUtil.defaultIfNull(parent, ClassLoaderUtil.getClassLoader());
 	}
 
 
@@ -145,22 +133,19 @@ public class JavaSourceCompiler {
 	 * @return 类加载器
 	 */
 	public ClassLoader compile() {
-		final ClassLoader parent = ObjectUtil.defaultIfNull(this.parentClassLoader, ClassLoaderUtil.getClassLoader());
-
 		// 获得classPath
 		final List<File> classPath = getClassPath();
 		final URL[] urLs = URLUtil.getURLs(classPath.toArray(new File[0]));
-		final URLClassLoader ucl = URLClassLoader.newInstance(urLs, parent);
+		final URLClassLoader ucl = URLClassLoader.newInstance(urLs, this.parentClassLoader);
 		if (sourceCodeMap.isEmpty() && sourceFileList.isEmpty()) {
 			// 没有需要编译的源码
 			return ucl;
 		}
+
 		// 没有需要编译的源码文件返回加载zip或jar包的类加载器
-		final Iterable<JavaFileObject> javaFileObjectList = getJavaFileObject();
 
 		// 创建编译器
-		final JavaFileManager standardJavaFileManager = JAVA_COMPILER.getStandardFileManager(null, null, null);
-		final JavaFileManager javaFileManager = new JavaClassFileManager(ucl, standardJavaFileManager);
+		final JavaFileManager javaFileManager = new JavaClassFileManager(ucl, CompilerUtil.getFileManager());
 
 		// classpath
 		final List<String> options = new ArrayList<>();
@@ -172,17 +157,14 @@ public class JavaSourceCompiler {
 
 		// 编译文件
 		final DiagnosticCollector<? super JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
-		final CompilationTask task = JAVA_COMPILER.getTask(null, javaFileManager, diagnosticCollector,
-				options, null, javaFileObjectList);
+		final List<JavaFileObject> javaFileObjectList = getJavaFileObject();
+		final CompilationTask task = CompilerUtil.getTask(javaFileManager, diagnosticCollector, options, javaFileObjectList);
 		if (task.call()) {
+			// 加载编译后的类
 			return javaFileManager.getClassLoader(StandardLocation.CLASS_OUTPUT);
 		} else {
 			// 编译失败,收集错误信息
-			final List<?> diagnostics = diagnosticCollector.getDiagnostics();
-			final String errorMsg = diagnostics.stream().map(String::valueOf)
-					.collect(Collectors.joining(System.lineSeparator()));
-			// CompileException
-			throw new RuntimeException(errorMsg);
+			throw new CompilerException(DiagnosticUtil.getMessages(diagnosticCollector));
 		}
 	}
 
@@ -194,7 +176,7 @@ public class JavaSourceCompiler {
 	private List<File> getClassPath() {
 		List<File> classPathFileList = new ArrayList<>();
 		for (File file : libraryFileList) {
-			List<File> jarOrZipFile = FileUtil.loopFiles(file, this::isJarOrZipFile);
+			List<File> jarOrZipFile = FileUtil.loopFiles(file, (subFile)-> JavaFileObjectUtil.isJarOrZipFile(subFile.getName()));
 			classPathFileList.addAll(jarOrZipFile);
 			if (file.isDirectory()) {
 				classPathFileList.add(file);
@@ -208,20 +190,14 @@ public class JavaSourceCompiler {
 	 *
 	 * @return 待编译的Java文件对象
 	 */
-	private Iterable<JavaFileObject> getJavaFileObject() {
-		final Collection<JavaFileObject> collection = new ArrayList<>();
+	private List<JavaFileObject> getJavaFileObject() {
+		final List<JavaFileObject> collection = new ArrayList<>();
+
+		// 源码文件
 		for (File file : sourceFileList) {
-			// .java 文件
-			final List<File> javaFileList = FileUtil.loopFiles(file, this::isJavaFile);
-			for (File javaFile : javaFileList) {
-				collection.add(getJavaFileObjectByJavaFile(javaFile));
-			}
-			// 压缩包
-			final List<File> jarOrZipFileList = FileUtil.loopFiles(file, this::isJarOrZipFile);
-			for (File jarOrZipFile : jarOrZipFileList) {
-				collection.addAll(getJavaFileObjectByZipOrJarFile(jarOrZipFile));
-			}
+			FileUtil.walkFiles(file, (subFile)-> collection.addAll(JavaFileObjectUtil.getJavaFileObjects(file)));
 		}
+
 		// 源码Map
 		collection.addAll(getJavaFileObjectByMap(this.sourceCodeMap));
 		return collection;
@@ -252,54 +228,4 @@ public class JavaSourceCompiler {
 		return new JavaSourceFileObject(file.toURI());
 	}
 
-	/**
-	 * 通过zip包或jar包创建Java文件对象
-	 *
-	 * @param file 压缩文件
-	 * @return Java文件对象
-	 */
-	private Collection<JavaFileObject> getJavaFileObjectByZipOrJarFile(final File file) {
-		final Collection<JavaFileObject> collection = new ArrayList<>();
-		try {
-			final ZipFile zipFile = new ZipFile(file);
-			final Enumeration<? extends ZipEntry> entries = zipFile.entries();
-			while (entries.hasMoreElements()) {
-				final ZipEntry zipEntry = entries.nextElement();
-				final String name = zipEntry.getName();
-				if (name.endsWith(".java")) {
-					final InputStream inputStream = zipFile.getInputStream(zipEntry);
-					final JavaSourceFileObject fileObject = new JavaSourceFileObject(name, inputStream);
-					collection.add(fileObject);
-				}
-			}
-			return collection;
-		} catch (IOException e) {
-			e.printStackTrace();
-		}
-		return Collections.emptyList();
-	}
-
-
-	/**
-	 * 是否是jar 或 zip 文件
-	 *
-	 * @param file 文件
-	 * @return 是否是jar 或 zip 文件
-	 */
-	private boolean isJarOrZipFile(final File file) {
-		final String fileName = file.getName();
-		return fileName.endsWith(".jar") || fileName.endsWith(".zip");
-	}
-
-	/**
-	 * 是否是.java文件
-	 *
-	 * @param file 文件
-	 * @return 是否是.java文件
-	 */
-	private boolean isJavaFile(final File file) {
-		final String fileName = file.getName();
-		return fileName.endsWith(".java");
-	}
-
 }

+ 14 - 11
hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceFileObject.java

@@ -10,7 +10,11 @@ import java.net.URI;
 import java.nio.charset.Charset;
 
 /**
- * Java 源码文件对象
+ * Java 源码文件对象,支持:<br>
+ * <ol>
+ *     <li>源文件</li>
+ *     <li>代码内容</li>
+ * </ol>
  *
  * @author lzpeng
  * @since 5.5.2
@@ -34,23 +38,22 @@ class JavaSourceFileObject extends SimpleJavaFileObject {
 	/**
 	 * 构造
 	 *
-	 * @param name        需要编译的文件
-	 * @param inputStream 输入流
+	 * @param className 需要编译的类
+	 * @param code      需要编译的类源码
 	 */
-	protected JavaSourceFileObject(String name, InputStream inputStream) {
-		this(URI.create("string:///" + name));
-		this.inputStream = inputStream;
+	protected JavaSourceFileObject(String className, String code, Charset charset) {
+		this(className, IoUtil.toStream(code, charset));
 	}
 
 	/**
 	 * 构造
 	 *
-	 * @param className 需要编译的类
-	 * @param code      需要编译的类源码
+	 * @param name        需要编译的文件
+	 * @param inputStream 输入流
 	 */
-	protected JavaSourceFileObject(String className, String code, Charset charset) {
-		this(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension));
-		this.inputStream = IoUtil.toStream(code, charset);
+	protected JavaSourceFileObject(String name, InputStream inputStream) {
+		this(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension));
+		this.inputStream = inputStream;
 	}
 
 	/**

+ 24 - 12
hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java

@@ -1,6 +1,7 @@
 package cn.hutool.core.io;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.ListUtil;
 import cn.hutool.core.io.file.FileCopier;
 import cn.hutool.core.io.file.FileMode;
 import cn.hutool.core.io.file.FileNameUtil;
@@ -50,6 +51,7 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.function.Consumer;
 import java.util.jar.JarFile;
 import java.util.zip.CRC32;
 import java.util.zip.Checksum;
@@ -64,11 +66,11 @@ public class FileUtil extends PathUtil {
 	/**
 	 * Class文件扩展名
 	 */
-	public static final String CLASS_EXT = ".class";
+	public static final String CLASS_EXT = FileNameUtil.EXT_CLASS;
 	/**
 	 * Jar文件扩展名
 	 */
-	public static final String JAR_FILE_EXT = ".jar";
+	public static final String JAR_FILE_EXT = FileNameUtil.EXT_JAR;
 	/**
 	 * 在Jar中的路径jar的扩展名形式
 	 */
@@ -171,25 +173,33 @@ public class FileUtil extends PathUtil {
 	 * @return 文件列表
 	 */
 	public static List<File> loopFiles(File file, FileFilter fileFilter) {
-		final List<File> fileList = new ArrayList<>();
 		if (null == file || false == file.exists()) {
-			return fileList;
+			return ListUtil.empty();
 		}
 
+		final List<File> fileList = new ArrayList<>();
+		walkFiles(file, fileList::add);
+		return fileList;
+	}
+
+	/**
+	 * 递归遍历目录并处理目录下的文件
+	 *
+	 * @param file     文件或目录,文件直接处理
+	 * @param consumer 文件处理器,只会处理文件
+	 * @since 5.5.2
+	 */
+	public static void walkFiles(File file, Consumer<File> consumer) {
 		if (file.isDirectory()) {
 			final File[] subFiles = file.listFiles();
 			if (ArrayUtil.isNotEmpty(subFiles)) {
 				for (File tmp : subFiles) {
-					fileList.addAll(loopFiles(tmp, fileFilter));
+					walkFiles(tmp, consumer);
 				}
 			}
 		} else {
-			if (null == fileFilter || fileFilter.accept(file)) {
-				fileList.add(file);
-			}
+			consumer.accept(file);
 		}
-
-		return fileList;
 	}
 
 	/**
@@ -968,7 +978,7 @@ public class FileUtil extends PathUtil {
 	 * 移动文件或者目录
 	 *
 	 * @param src        源文件或者目录
-	 * @param target       目标文件或者目录
+	 * @param target     目标文件或者目录
 	 * @param isOverride 是否覆盖目标,只有目标为文件才覆盖
 	 * @throws IORuntimeException IO异常
 	 * @see PathUtil#move(Path, Path, boolean)
@@ -2900,6 +2910,7 @@ public class FileUtil extends PathUtil {
 
 	/**
 	 * 将流的内容写入文件<br>
+	 * 此方法会自动关闭输入流
 	 *
 	 * @param dest 目标文件
 	 * @param in   输入流
@@ -2912,6 +2923,7 @@ public class FileUtil extends PathUtil {
 
 	/**
 	 * 将流的内容写入文件<br>
+	 * 此方法会自动关闭输入流
 	 *
 	 * @param in           输入流
 	 * @param fullFilePath 文件绝对路径
@@ -2923,7 +2935,7 @@ public class FileUtil extends PathUtil {
 	}
 
 	/**
-	 * 将文件写入流中
+	 * 将文件写入流中,此方法不会概念比输出流
 	 *
 	 * @param file 文件
 	 * @param out  流

+ 17 - 1
hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java

@@ -500,13 +500,25 @@ public class IoUtil {
 	}
 
 	/**
-	 * 从Reader中读取String,读取完毕后并不关闭Reader
+	 * 从Reader中读取String,读取完毕后关闭Reader
 	 *
 	 * @param reader Reader
 	 * @return String
 	 * @throws IORuntimeException IO异常
 	 */
 	public static String read(Reader reader) throws IORuntimeException {
+		return read(reader, true);
+	}
+
+	/**
+	 * 从{@link Reader}中读取String
+	 *
+	 * @param reader {@link Reader}
+	 * @param isClose 是否关闭{@link Reader}
+	 * @return String
+	 * @throws IORuntimeException IO异常
+	 */
+	public static String read(Reader reader, boolean isClose) throws IORuntimeException {
 		final StringBuilder builder = StrUtil.builder();
 		final CharBuffer buffer = CharBuffer.allocate(DEFAULT_BUFFER_SIZE);
 		try {
@@ -515,6 +527,10 @@ public class IoUtil {
 			}
 		} catch (IOException e) {
 			throw new IORuntimeException(e);
+		} finally{
+			if(isClose){
+				IoUtil.close(reader);
+			}
 		}
 		return builder.toString();
 	}

+ 25 - 0
hutool-core/src/main/java/cn/hutool/core/io/file/FileNameUtil.java

@@ -16,6 +16,19 @@ import java.util.regex.Pattern;
 public class FileNameUtil {
 
 	/**
+	 * .java文件扩展名
+	 */
+	public static final String EXT_JAVA = ".java";
+	/**
+	 * .class文件扩展名
+	 */
+	public static final String EXT_CLASS = ".class";
+	/**
+	 * .jar文件扩展名
+	 */
+	public static final String EXT_JAR = ".jar";
+
+	/**
 	 * 类Unix路径分隔符
 	 */
 	public static final char UNIX_SEPARATOR = CharUtil.SLASH;
@@ -232,5 +245,17 @@ public class FileNameUtil {
 	public static boolean containsInvalid(String fileName) {
 		return (false == StrUtil.isBlank(fileName)) && ReUtil.contains(FILE_NAME_INVALID_PATTERN_WIN, fileName);
 	}
+
+	/**
+	 * 根据文件名检查文件类型,忽略大小写
+	 *
+	 * @param fileName 文件名,例如hutool.png
+	 * @param extNames 被检查的扩展名数组,同一文件类型可能有多种扩展名,扩展名不带“.”
+	 * @return 是否是指定扩展名的类型
+	 * @since 5.5.2
+	 */
+	public static boolean isType(String fileName, String... extNames) {
+		return StrUtil.equalsAnyIgnoreCase(extName(fileName), extNames);
+	}
 	// -------------------------------------------------------------------------------------------- name end
 }

+ 20 - 3
hutool-core/src/main/java/cn/hutool/core/io/file/FileReader.java

@@ -31,7 +31,7 @@ public class FileReader extends FileWrapper {
 	 * 创建 FileReader
 	 * @param file 文件
 	 * @param charset 编码,使用 {@link CharsetUtil}
-	 * @return {@link FileReader}
+	 * @return FileReader
 	 */
 	public static FileReader create(File file, Charset charset){
 		return new FileReader(file, charset);
@@ -40,7 +40,7 @@ public class FileReader extends FileWrapper {
 	/**
 	 * 创建 FileReader, 编码:{@link FileWrapper#DEFAULT_CHARSET}
 	 * @param file 文件
-	 * @return {@link FileReader}
+	 * @return FileReader
 	 */
 	public static FileReader create(File file){
 		return new FileReader(file);
@@ -244,19 +244,36 @@ public class FileReader extends FileWrapper {
 			throw new IORuntimeException(e);
 		}
 	}
+
+	/**
+	 * 将文件写入流中,此方法不会关闭比输出流
+	 *
+	 * @param out 流
+	 * @return 写出的流byte数
+	 * @throws IORuntimeException IO异常
+	 */
+	public long writeToStream(OutputStream out) throws IORuntimeException {
+		return writeToStream(out, false);
+	}
 	
 	/**
 	 * 将文件写入流中
 	 * 
 	 * @param out 流
+	 * @param isCloseOut 是否关闭输出流
 	 * @return 写出的流byte数
 	 * @throws IORuntimeException IO异常
+	 * @since 5.5.2
 	 */
-	public long writeToStream(OutputStream out) throws IORuntimeException {
+	public long writeToStream(OutputStream out, boolean isCloseOut) throws IORuntimeException {
 		try (FileInputStream in = new FileInputStream(this.file)){
 			return IoUtil.copy(in, out);
 		}catch (IOException e) {
 			throw new IORuntimeException(e);
+		} finally{
+			if(isCloseOut){
+				IoUtil.close(out);
+			}
 		}
 	}
 

+ 27 - 11
hutool-core/src/main/java/cn/hutool/core/io/file/FileWriter.java

@@ -1,5 +1,12 @@
 package cn.hutool.core.io.file;
 
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.IORuntimeException;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.CharsetUtil;
+import cn.hutool.core.util.StrUtil;
+
 import java.io.BufferedOutputStream;
 import java.io.BufferedWriter;
 import java.io.File;
@@ -13,13 +20,6 @@ import java.util.Collection;
 import java.util.Map;
 import java.util.Map.Entry;
 
-import cn.hutool.core.io.FileUtil;
-import cn.hutool.core.io.IORuntimeException;
-import cn.hutool.core.io.IoUtil;
-import cn.hutool.core.lang.Assert;
-import cn.hutool.core.util.CharsetUtil;
-import cn.hutool.core.util.StrUtil;
-
 /**
  * 文件写入器
  * @author Looly
@@ -32,7 +32,7 @@ public class FileWriter extends FileWrapper{
 	 * 创建 FileWriter
 	 * @param file 文件
 	 * @param charset 编码,使用 {@link CharsetUtil}
-	 * @return {@link FileWriter}
+	 * @return FileWriter
 	 */
 	public static FileWriter create(File file, Charset charset){
 		return new FileWriter(file, charset);
@@ -41,7 +41,7 @@ public class FileWriter extends FileWrapper{
 	/**
 	 * 创建 FileWriter, 编码:{@link FileWrapper#DEFAULT_CHARSET}
 	 * @param file 文件
-	 * @return {@link FileWriter}
+	 * @return FileWriter
 	 */
 	public static FileWriter create(File file){
 		return new FileWriter(file);
@@ -302,13 +302,26 @@ public class FileWriter extends FileWrapper{
 
 	/**
 	 * 将流的内容写入文件<br>
-	 * 此方法会关闭输入流
-	 * 
+	 * 此方法会自动关闭输入流
+	 *
 	 * @param in 输入流,不关闭
 	 * @return dest
 	 * @throws IORuntimeException IO异常
 	 */
 	public File writeFromStream(InputStream in) throws IORuntimeException {
+		return writeFromStream(in, true);
+	}
+
+	/**
+	 * 将流的内容写入文件
+	 * 
+	 * @param in 输入流,不关闭
+	 * @param isCloseIn 是否关闭输入流
+	 * @return dest
+	 * @throws IORuntimeException IO异常
+	 * @since 5.5.2
+	 */
+	public File writeFromStream(InputStream in, boolean isCloseIn) throws IORuntimeException {
 		FileOutputStream out = null;
 		try {
 			out = new FileOutputStream(FileUtil.touch(file));
@@ -317,6 +330,9 @@ public class FileWriter extends FileWrapper{
 			throw new IORuntimeException(e);
 		} finally {
 			IoUtil.close(out);
+			if(isCloseIn){
+				IoUtil.close(in);
+			}
 		}
 		return file;
 	}

+ 12 - 0
hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java

@@ -95,6 +95,18 @@ public class PathUtil {
 	 * 遍历指定path下的文件并做处理
 	 *
 	 * @param start    起始路径,必须为目录
+	 * @param visitor  {@link FileVisitor} 接口,用于自定义在访问文件时,访问目录前后等节点做的操作
+	 * @see Files#walkFileTree(Path, java.util.Set, int, FileVisitor)
+	 * @since 5.5.2
+	 */
+	public static void walkFiles(Path start, FileVisitor<? super Path> visitor) {
+		walkFiles(start, -1, visitor);
+	}
+
+	/**
+	 * 遍历指定path下的文件并做处理
+	 *
+	 * @param start    起始路径,必须为目录
 	 * @param maxDepth 最大遍历深度,-1表示不限制深度
 	 * @param visitor  {@link FileVisitor} 接口,用于自定义在访问文件时,访问目录前后等节点做的操作
 	 * @see Files#walkFileTree(Path, java.util.Set, int, FileVisitor)

+ 0 - 7
hutool-core/src/main/java/cn/hutool/core/io/resource/BytesResource.java

@@ -3,11 +3,9 @@ package cn.hutool.core.io.resource;
 import cn.hutool.core.io.IORuntimeException;
 import cn.hutool.core.util.StrUtil;
 
-import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 import java.io.Serializable;
-import java.io.StringReader;
 import java.net.URL;
 import java.nio.charset.Charset;
 
@@ -60,11 +58,6 @@ public class BytesResource implements Resource, Serializable {
 	}
 
 	@Override
-	public BufferedReader getReader(Charset charset) {
-		return new BufferedReader(new StringReader(readStr(charset)));
-	}
-
-	@Override
 	public String readStr(Charset charset) throws IORuntimeException {
 		return StrUtil.str(this.bytes, charset);
 	}

+ 90 - 0
hutool-core/src/main/java/cn/hutool/core/io/resource/CharSequenceResource.java

@@ -0,0 +1,90 @@
+package cn.hutool.core.io.resource;
+
+import cn.hutool.core.io.IORuntimeException;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.CharsetUtil;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.Serializable;
+import java.io.StringReader;
+import java.net.URL;
+import java.nio.charset.Charset;
+
+/**
+ * {@link CharSequence}资源,字符串做为资源
+ * 
+ * @author looly
+ * @since 5.5.2
+ */
+public class CharSequenceResource implements Resource, Serializable {
+	private static final long serialVersionUID = 1L;
+
+	private final CharSequence data;
+	private final CharSequence name;
+	private final Charset charset;
+
+	/**
+	 * 构造,使用UTF8编码
+	 *
+	 * @param data 资源数据
+	 */
+	public CharSequenceResource(CharSequence data) {
+		this(data, null);
+	}
+
+	/**
+	 * 构造,使用UTF8编码
+	 *
+	 * @param data 资源数据
+	 * @param name 资源名称
+	 */
+	public CharSequenceResource(CharSequence data, String name) {
+		this(data, name, CharsetUtil.CHARSET_UTF_8);
+	}
+
+	/**
+	 * 构造
+	 *
+	 * @param data 资源数据
+	 * @param name 资源名称
+	 * @param charset 编码
+	 */
+	public CharSequenceResource(CharSequence data, CharSequence name, Charset charset) {
+		this.data = data;
+		this.name = name;
+		this.charset = charset;
+	}
+
+	@Override
+	public String getName() {
+		return this.name.toString();
+	}
+
+	@Override
+	public URL getUrl() {
+		return null;
+	}
+
+	@Override
+	public InputStream getStream() {
+		return new ByteArrayInputStream(readBytes());
+	}
+
+	@Override
+	public BufferedReader getReader(Charset charset) {
+		return IoUtil.getReader(new StringReader(this.data.toString()));
+	}
+
+	@Override
+	public String readStr(Charset charset) throws IORuntimeException {
+		return this.data.toString();
+	}
+
+	@Override
+	public byte[] readBytes() throws IORuntimeException {
+		return this.data.toString().getBytes(this.charset);
+	}
+
+}

+ 73 - 0
hutool-core/src/main/java/cn/hutool/core/io/resource/FileObjectResource.java

@@ -0,0 +1,73 @@
+package cn.hutool.core.io.resource;
+
+import cn.hutool.core.io.IORuntimeException;
+import cn.hutool.core.io.IoUtil;
+
+import javax.tools.FileObject;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.charset.Charset;
+
+/**
+ * {@link FileObject} 资源包装
+ *
+ * @author looly
+ * @since 5.5.2
+ */
+public class FileObjectResource implements Resource {
+
+	private final FileObject fileObject;
+
+	/**
+	 * 构造
+	 *
+	 * @param fileObject {@link FileObject}
+	 */
+	public FileObjectResource(FileObject fileObject) {
+		this.fileObject = fileObject;
+	}
+
+	/**
+	 * 获取原始的{@link FileObject}
+	 *
+	 * @return {@link FileObject}
+	 */
+	public FileObject getFileObject() {
+		return this.fileObject;
+	}
+
+	@Override
+	public String getName() {
+		return this.fileObject.getName();
+	}
+
+	@Override
+	public URL getUrl() {
+		try {
+			return this.fileObject.toUri().toURL();
+		} catch (MalformedURLException e) {
+			return null;
+		}
+	}
+
+	@Override
+	public InputStream getStream() {
+		try {
+			return this.fileObject.openInputStream();
+		} catch (IOException e) {
+			throw new IORuntimeException(e);
+		}
+	}
+
+	@Override
+	public BufferedReader getReader(Charset charset) {
+		try {
+			return IoUtil.getReader(this.fileObject.openReader(false));
+		} catch (IOException e) {
+			throw new IORuntimeException(e);
+		}
+	}
+}

+ 0 - 33
hutool-core/src/main/java/cn/hutool/core/io/resource/InputStreamResource.java

@@ -1,13 +1,8 @@
 package cn.hutool.core.io.resource;
 
-import cn.hutool.core.io.IORuntimeException;
-import cn.hutool.core.io.IoUtil;
-
-import java.io.BufferedReader;
 import java.io.InputStream;
 import java.io.Serializable;
 import java.net.URL;
-import java.nio.charset.Charset;
 
 /**
  * 基于{@link InputStream}的资源获取器<br>
@@ -56,32 +51,4 @@ public class InputStreamResource implements Resource, Serializable {
 	public InputStream getStream() {
 		return this.in;
 	}
-
-	@Override
-	public BufferedReader getReader(Charset charset) {
-		return IoUtil.getReader(this.in, charset);
-	}
-
-	@Override
-	public String readStr(Charset charset) throws IORuntimeException {
-		BufferedReader reader = null;
-		try {
-			reader = getReader(charset);
-			return IoUtil.read(reader);
-		} finally {
-			IoUtil.close(reader);
-		}
-	}
-
-	@Override
-	public byte[] readBytes() throws IORuntimeException {
-		InputStream in = null;
-		try {
-			in = getStream();
-			return IoUtil.readBytes(in);
-		} finally {
-			IoUtil.close(in);
-		}
-	}
-
 }

+ 9 - 3
hutool-core/src/main/java/cn/hutool/core/io/resource/Resource.java

@@ -58,7 +58,9 @@ public interface Resource {
 	 * @param charset 编码
 	 * @return {@link BufferedReader}
 	 */
-	BufferedReader getReader(Charset charset);
+	default BufferedReader getReader(Charset charset){
+		return IoUtil.getReader(getStream(), charset);
+	}
 	
 	/**
 	 * 读取资源内容,读取完毕后会关闭流<br>
@@ -68,7 +70,9 @@ public interface Resource {
 	 * @return 读取资源内容
 	 * @throws IORuntimeException 包装{@link IOException}
 	 */
-	String readStr(Charset charset) throws IORuntimeException;
+	default String readStr(Charset charset) throws IORuntimeException{
+		return IoUtil.read(getReader(charset));
+	}
 	
 	/**
 	 * 读取资源内容,读取完毕后会关闭流<br>
@@ -88,5 +92,7 @@ public interface Resource {
 	 * @return 读取资源内容
 	 * @throws IORuntimeException 包装IOException
 	 */
-	byte[] readBytes() throws IORuntimeException;
+	default byte[] readBytes() throws IORuntimeException{
+		return IoUtil.readBytes(getStream());
+	}
 }

+ 5 - 48
hutool-core/src/main/java/cn/hutool/core/io/resource/StringResource.java

@@ -1,15 +1,7 @@
 package cn.hutool.core.io.resource;
 
-import cn.hutool.core.io.IORuntimeException;
-import cn.hutool.core.io.IoUtil;
 import cn.hutool.core.util.CharsetUtil;
 
-import java.io.BufferedReader;
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-import java.io.Serializable;
-import java.io.StringReader;
-import java.net.URL;
 import java.nio.charset.Charset;
 
 /**
@@ -17,13 +9,11 @@ import java.nio.charset.Charset;
  * 
  * @author looly
  * @since 4.1.0
+ * @see CharSequenceResource
  */
-public class StringResource implements Resource, Serializable {
+public class StringResource extends CharSequenceResource {
 	private static final long serialVersionUID = 1L;
 
-	private final String data;
-	private final String name;
-	private final Charset charset;
 
 	/**
 	 * 构造,使用UTF8编码
@@ -31,7 +21,7 @@ public class StringResource implements Resource, Serializable {
 	 * @param data 资源数据
 	 */
 	public StringResource(String data) {
-		this(data, null);
+		super(data, null);
 	}
 
 	/**
@@ -41,7 +31,7 @@ public class StringResource implements Resource, Serializable {
 	 * @param name 资源名称
 	 */
 	public StringResource(String data, String name) {
-		this(data, name, CharsetUtil.CHARSET_UTF_8);
+		super(data, name, CharsetUtil.CHARSET_UTF_8);
 	}
 
 	/**
@@ -52,39 +42,6 @@ public class StringResource implements Resource, Serializable {
 	 * @param charset 编码
 	 */
 	public StringResource(String data, String name, Charset charset) {
-		this.data = data;
-		this.name = name;
-		this.charset = charset;
+		super(data, name, charset);
 	}
-
-	@Override
-	public String getName() {
-		return this.name;
-	}
-
-	@Override
-	public URL getUrl() {
-		return null;
-	}
-
-	@Override
-	public InputStream getStream() {
-		return new ByteArrayInputStream(readBytes());
-	}
-
-	@Override
-	public BufferedReader getReader(Charset charset) {
-		return IoUtil.getReader(new StringReader(this.data));
-	}
-
-	@Override
-	public String readStr(Charset charset) throws IORuntimeException {
-		return this.data;
-	}
-
-	@Override
-	public byte[] readBytes() throws IORuntimeException {
-		return this.data.getBytes(this.charset);
-	}
-
 }

+ 0 - 38
hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java

@@ -1,17 +1,13 @@
 package cn.hutool.core.io.resource;
 
 import cn.hutool.core.io.FileUtil;
-import cn.hutool.core.io.IORuntimeException;
-import cn.hutool.core.io.IoUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.URLUtil;
 
-import java.io.BufferedReader;
 import java.io.File;
 import java.io.InputStream;
 import java.io.Serializable;
 import java.net.URL;
-import java.nio.charset.Charset;
 
 /**
  * URL资源访问类
@@ -73,40 +69,6 @@ public class UrlResource implements Resource, Serializable{
 	}
 	
 	/**
-	 * 获得Reader
-	 * @param charset 编码
-	 * @return {@link BufferedReader}
-	 * @since 3.0.1
-	 */
-	@Override
-	public BufferedReader getReader(Charset charset){
-		return URLUtil.getReader(this.url, charset);
-	}
-	
-	//------------------------------------------------------------------------------- read
-	@Override
-	public String readStr(Charset charset) throws IORuntimeException{
-		BufferedReader reader = null;
-		try {
-			reader = getReader(charset);
-			return IoUtil.read(reader);
-		} finally {
-			IoUtil.close(reader);
-		}
-	}
-	
-	@Override
-	public byte[] readBytes() throws IORuntimeException{
-		InputStream in = null;
-		try {
-			in = getStream();
-			return IoUtil.readBytes(in);
-		} finally {
-			IoUtil.close(in);
-		}
-	}
-	
-	/**
 	 * 获得File
 	 * @return {@link File}
 	 */

+ 1 - 1
hutool-core/src/main/java/cn/hutool/core/lang/JarClassLoader.java

@@ -59,7 +59,7 @@ public class JarClassLoader extends URLClassLoader {
 				method.setAccessible(true);
 				final List<File> jars = loopJar(jarFile);
 				for (File jar : jars) {
-					ReflectUtil.invoke(loader, method, new Object[]{jar.toURI().toURL()});
+					ReflectUtil.invoke(loader, method, jar.toURI().toURL());
 				}
 			}
 		} catch (IOException e) {

+ 52 - 0
hutool-core/src/main/java/cn/hutool/core/lang/ResourceClassLoader.java

@@ -0,0 +1,52 @@
+package cn.hutool.core.lang;
+
+import cn.hutool.core.io.resource.Resource;
+import cn.hutool.core.util.ClassLoaderUtil;
+import cn.hutool.core.util.ObjectUtil;
+
+import java.security.SecureClassLoader;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 资源类加载器,可以加载任意类型的资源类
+ *
+ * @param <T> {@link Resource}接口实现类
+ * @author looly
+ * @since 5.5.2
+ */
+public class ResourceClassLoader<T extends Resource> extends SecureClassLoader {
+
+	private final Map<String, T> resourceMap;
+
+	/**
+	 * 构造
+	 *
+	 * @param parentClassLoader 父类加载器,null表示默认当前上下文加载器
+	 * @param resourceMap       资源map
+	 */
+	public ResourceClassLoader(ClassLoader parentClassLoader, Map<String, T> resourceMap) {
+		super(ObjectUtil.defaultIfNull(parentClassLoader, ClassLoaderUtil.getClassLoader()));
+		this.resourceMap = ObjectUtil.defaultIfNull(resourceMap, new HashMap<>());
+	}
+
+	/**
+	 * 增加需要加载的类资源
+	 * @param resource 资源,可以是文件、流或者字符串
+	 * @return this
+	 */
+	public ResourceClassLoader<T> addResource(T resource){
+		this.resourceMap.put(resource.getName(), resource);
+		return this;
+	}
+
+	@Override
+	protected Class<?> findClass(String name) throws ClassNotFoundException {
+		final Resource resource = resourceMap.get(name);
+		if (null != resource) {
+			final byte[] bytes = resource.readBytes();
+			return defineClass(name, bytes, 0, bytes.length);
+		}
+		return super.findClass(name);
+	}
+}

+ 142 - 84
hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java

@@ -5,6 +5,7 @@ import cn.hutool.core.io.FastByteArrayOutputStream;
 import cn.hutool.core.io.FileUtil;
 import cn.hutool.core.io.IORuntimeException;
 import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.io.resource.Resource;
 
 import java.io.BufferedInputStream;
 import java.io.ByteArrayInputStream;
@@ -19,6 +20,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.List;
+import java.util.function.Consumer;
 import java.util.zip.Deflater;
 import java.util.zip.DeflaterOutputStream;
 import java.util.zip.GZIPInputStream;
@@ -45,6 +47,37 @@ public class ZipUtil {
 	private static final Charset DEFAULT_CHARSET = CharsetUtil.defaultCharset();
 
 	/**
+	 * 将Zip文件转换为{@link ZipFile}
+	 *
+	 * @param file    zip文件
+	 * @param charset 解析zip文件的编码,null表示{@link CharsetUtil#CHARSET_UTF_8}
+	 * @return {@link ZipFile}
+	 */
+	public static ZipFile toZipFile(File file, Charset charset) {
+		try {
+			return new ZipFile(file, ObjectUtil.defaultIfNull(charset, CharsetUtil.CHARSET_UTF_8));
+		} catch (IOException e) {
+			throw new IORuntimeException(e);
+		}
+	}
+
+	/**
+	 * 获取指定{@link ZipEntry}的流,用于读取这个entry的内容
+	 *
+	 * @param zipFile  {@link ZipFile}
+	 * @param zipEntry {@link ZipEntry}
+	 * @return 流
+	 * @since 5.5.2
+	 */
+	public static InputStream getStream(ZipFile zipFile, ZipEntry zipEntry) {
+		try {
+			return zipFile.getInputStream(zipEntry);
+		} catch (IOException e) {
+			throw new IORuntimeException(e);
+		}
+	}
+
+	/**
 	 * 打包到当前目录,使用默认编码UTF-8
 	 *
 	 * @param srcPath 源文件路径
@@ -190,7 +223,7 @@ public class ZipUtil {
 	/**
 	 * 对文件或文件目录进行压缩
 	 *
-	 * @param out    生成的Zip到的目标流,包括文件名。注意:zipPath不能是srcPath路径下的子文件夹
+	 * @param out        生成的Zip到的目标流,包括文件名。注意:zipPath不能是srcPath路径下的子文件夹
 	 * @param charset    编码
 	 * @param withSrcDir 是否包含被打包目录,只针对压缩目录有效。若为false,则只压缩目录下的文件或目录,为true则将本目录也压缩
 	 * @param filter     文件过滤器,通过实现此接口,自定义要过滤的文件(过滤掉哪些文件或文件夹不加入压缩)
@@ -205,16 +238,16 @@ public class ZipUtil {
 	/**
 	 * 对文件或文件目录进行压缩
 	 *
-	 * @param zipOutputStream    生成的Zip到的目标流,不关闭此流
-	 * @param withSrcDir 是否包含被打包目录,只针对压缩目录有效。若为false,则只压缩目录下的文件或目录,为true则将本目录也压缩
-	 * @param filter     文件过滤器,通过实现此接口,自定义要过滤的文件(过滤掉哪些文件或文件夹不加入压缩)
-	 * @param srcFiles   要压缩的源文件或目录。如果压缩一个文件,则为该文件的全路径;如果压缩一个目录,则为该目录的顶层目录路径
+	 * @param zipOutputStream 生成的Zip到的目标流,不关闭此流
+	 * @param withSrcDir      是否包含被打包目录,只针对压缩目录有效。若为false,则只压缩目录下的文件或目录,为true则将本目录也压缩
+	 * @param filter          文件过滤器,通过实现此接口,自定义要过滤的文件(过滤掉哪些文件或文件夹不加入压缩)
+	 * @param srcFiles        要压缩的源文件或目录。如果压缩一个文件,则为该文件的全路径;如果压缩一个目录,则为该目录的顶层目录路径
 	 * @throws IORuntimeException IO异常
 	 * @since 5.1.1
 	 */
 	public static void zip(ZipOutputStream zipOutputStream, boolean withSrcDir, FileFilter filter, File... srcFiles) throws IORuntimeException {
 		String srcRootDir;
-		try{
+		try {
 			for (File srcFile : srcFiles) {
 				if (null == srcFile) {
 					continue;
@@ -300,7 +333,7 @@ public class ZipUtil {
 	 *
 	 * @param zipFile 生成的Zip文件,包括文件名。注意:zipPath不能是srcPath路径下的子文件夹
 	 * @param paths   流数据在压缩文件中的路径或文件名
-	 * @param ins     要压缩的源
+	 * @param ins     要压缩的源,添加完成后自动关闭流
 	 * @return 压缩文件
 	 * @throws UtilException IO异常
 	 * @since 3.0.9
@@ -333,7 +366,31 @@ public class ZipUtil {
 		try {
 			out = getZipOutputStream(zipFile, charset);
 			for (int i = 0; i < paths.length; i++) {
-				addFile(ins[i], paths[i], out);
+				add(ins[i], paths[i], out);
+			}
+		} finally {
+			IoUtil.close(out);
+		}
+		return zipFile;
+	}
+
+	/**
+	 * 对流中的数据加入到压缩文件<br>
+	 * 路径列表和流列表长度必须一致
+	 *
+	 * @param zipFile   生成的Zip文件,包括文件名。注意:zipPath不能是srcPath路径下的子文件夹
+	 * @param charset   编码
+	 * @param resources 需要压缩的资源,资源的路径为{@link Resource#getName()}
+	 * @return 压缩文件
+	 * @throws UtilException IO异常
+	 * @since 5.5.2
+	 */
+	public static File zip(File zipFile, Charset charset, Resource... resources) throws UtilException {
+		ZipOutputStream out = null;
+		try {
+			out = getZipOutputStream(zipFile, charset);
+			for (Resource resource : resources) {
+				add(resource.getStream(), resource.getName(), out);
 			}
 		} finally {
 			IoUtil.close(out);
@@ -437,17 +494,10 @@ public class ZipUtil {
 	 * @param outFile 解压到的目录
 	 * @param charset 编码
 	 * @return 解压的目录
-	 * @throws UtilException IO异常
 	 * @since 3.2.2
 	 */
-	public static File unzip(File zipFile, File outFile, Charset charset) throws UtilException {
-		ZipFile zip;
-		try {
-			zip = new ZipFile(zipFile, charset);
-		} catch (IOException e) {
-			throw new IORuntimeException(e);
-		}
-		return unzip(zip, outFile);
+	public static File unzip(File zipFile, File outFile, Charset charset) {
+		return unzip(toZipFile(zipFile, charset), outFile);
 	}
 
 	/**
@@ -460,67 +510,72 @@ public class ZipUtil {
 	 * @since 4.5.8
 	 */
 	public static File unzip(ZipFile zipFile, File outFile) throws IORuntimeException {
-		if(outFile.exists() && outFile.isFile()){
+		if (outFile.exists() && outFile.isFile()) {
 			throw new UtilException("Target path [{}] exist!", outFile.getAbsolutePath());
 		}
-		try {
-			final Enumeration<? extends ZipEntry> em = zipFile.entries();
-			ZipEntry zipEntry;
-			File outItemFile;
-			while (em.hasMoreElements()) {
-				zipEntry = em.nextElement();
-				// FileUtil.file会检查slip漏洞,漏洞说明见http://blog.nsfocus.net/zip-slip-2/
-				outItemFile = FileUtil.file(outFile, zipEntry.getName());
-				if (zipEntry.isDirectory()) {
-					// 创建对应目录
-					//noinspection ResultOfMethodCallIgnored
-					outItemFile.mkdirs();
-				} else {
-					// 写出文件
-					write(zipFile, zipEntry, outItemFile);
-				}
+		read(zipFile, (zipEntry) -> {
+			// FileUtil.file会检查slip漏洞,漏洞说明见http://blog.nsfocus.net/zip-slip-2/
+			File outItemFile = FileUtil.file(outFile, zipEntry.getName());
+			if (zipEntry.isDirectory()) {
+				// 创建对应目录
+				//noinspection ResultOfMethodCallIgnored
+				outItemFile.mkdirs();
+			} else {
+				// 写出文件
+				write(zipFile, zipEntry, outItemFile);
 			}
-		} finally {
-			IoUtil.close(zipFile);
-		}
+		});
+
 		return outFile;
 	}
 
 	/**
 	 * 获取压缩包中的指定文件流
+	 *
 	 * @param zipFile 压缩文件
-	 * @param path 需要提取文件的文件名或路径
+	 * @param path    需要提取文件的文件名或路径
 	 * @return 压缩文件流,如果未找到返回{@code null}
 	 * @since 5.5.2
 	 */
-	public static InputStream get(File zipFile, Charset charset, String path){
-		try {
-			return get(new ZipFile(zipFile, charset), path);
-		} catch (IOException e) {
-			throw new IORuntimeException(e);
-		}
+	public static InputStream get(File zipFile, Charset charset, String path) {
+		return get(toZipFile(zipFile, charset), path);
 	}
 
 	/**
 	 * 获取压缩包中的指定文件流
+	 *
 	 * @param zipFile 压缩文件
-	 * @param path 需要提取文件的文件名或路径
+	 * @param path    需要提取文件的文件名或路径
 	 * @return 压缩文件流,如果未找到返回{@code null}
 	 * @since 5.5.2
 	 */
-	public static InputStream get(ZipFile zipFile, String path){
+	public static InputStream get(ZipFile zipFile, String path) {
 		final ZipEntry entry = zipFile.getEntry(path);
-		if(null != entry){
-			try {
-				return zipFile.getInputStream(entry);
-			} catch (IOException e) {
-				throw new IORuntimeException(e);
-			}
+		if (null != entry) {
+			return getStream(zipFile, entry);
 		}
 		return null;
 	}
 
 	/**
+	 * 读取并处理Zip文件中的每一个{@link ZipEntry}
+	 *
+	 * @param zipFile  Zip文件
+	 * @param consumer {@link ZipEntry}处理器
+	 * @since 5.5.2
+	 */
+	public static void read(ZipFile zipFile, Consumer<ZipEntry> consumer) {
+		try {
+			final Enumeration<? extends ZipEntry> em = zipFile.entries();
+			while (em.hasMoreElements()) {
+				consumer.accept(em.nextElement());
+			}
+		} finally {
+			IoUtil.close(zipFile);
+		}
+	}
+
+	/**
 	 * 解压<br>
 	 * ZIP条目不使用高速缓冲。
 	 *
@@ -549,27 +604,40 @@ public class ZipUtil {
 	 * @since 4.5.8
 	 */
 	public static File unzip(ZipInputStream zipStream, File outFile) throws UtilException {
+		read(zipStream, (zipEntry) -> {
+			// FileUtil.file会检查slip漏洞,漏洞说明见http://blog.nsfocus.net/zip-slip-2/
+			File outItemFile = FileUtil.file(outFile, zipEntry.getName());
+			if (zipEntry.isDirectory()) {
+				// 目录
+				//noinspection ResultOfMethodCallIgnored
+				outItemFile.mkdirs();
+			} else {
+				// 文件
+				FileUtil.writeFromStream(zipStream, outItemFile);
+			}
+		});
+		return outFile;
+	}
+
+	/**
+	 * 读取并处理Zip流中的每一个{@link ZipEntry}
+	 *
+	 * @param zipStream zip文件流,包含编码信息
+	 * @param consumer  {@link ZipEntry}处理器
+	 * @since 5.5.2
+	 */
+	public static void read(ZipInputStream zipStream, Consumer<ZipEntry> consumer) {
 		try {
 			ZipEntry zipEntry;
 			File outItemFile;
 			while (null != (zipEntry = zipStream.getNextEntry())) {
-				// FileUtil.file会检查slip漏洞,漏洞说明见http://blog.nsfocus.net/zip-slip-2/
-				outItemFile = FileUtil.file(outFile, zipEntry.getName());
-				if (zipEntry.isDirectory()) {
-					// 目录
-					//noinspection ResultOfMethodCallIgnored
-					outItemFile.mkdirs();
-				} else {
-					// 文件
-					FileUtil.writeFromStream(zipStream, outItemFile);
-				}
+				consumer.accept(zipEntry);
 			}
 		} catch (IOException e) {
 			throw new UtilException(e);
 		} finally {
 			IoUtil.close(zipStream);
 		}
-		return outFile;
 	}
 
 	/**
@@ -622,17 +690,15 @@ public class ZipUtil {
 	public static byte[] unzipFileBytes(File zipFile, Charset charset, String name) {
 		ZipFile zipFileObj = null;
 		try {
-			zipFileObj = new ZipFile(zipFile, charset);
+			zipFileObj = toZipFile(zipFile, charset);
 			final Enumeration<ZipEntry> em = (Enumeration<ZipEntry>) zipFileObj.entries();
 			ZipEntry zipEntry;
 			while (em.hasMoreElements()) {
 				zipEntry = em.nextElement();
 				if ((false == zipEntry.isDirectory()) && name.equals(zipEntry.getName())) {
-					return IoUtil.readBytes(zipFileObj.getInputStream(zipEntry));
+					return IoUtil.readBytes(getStream(zipFileObj, zipEntry));
 				}
 			}
-		} catch (IOException e) {
-			throw new UtilException(e);
 		} finally {
 			IoUtil.close(zipFileObj);
 		}
@@ -946,8 +1012,8 @@ public class ZipUtil {
 	 * @return {@link ZipOutputStream}
 	 */
 	private static ZipOutputStream getZipOutputStream(OutputStream out, Charset charset) {
-		if(out instanceof ZipOutputStream) {
-			return (ZipOutputStream)out;
+		if (out instanceof ZipOutputStream) {
+			return (ZipOutputStream) out;
 		}
 		return new ZipOutputStream(out, ObjectUtil.defaultIfNull(charset, DEFAULT_CHARSET));
 	}
@@ -980,7 +1046,7 @@ public class ZipUtil {
 				zip(childFile, srcRootDir, out, filter);
 			}
 		} else {// 如果是文件或其它符号,则直接压缩该文件
-			addFile(file, subPath, out);
+			add(file, subPath, out);
 		}
 	}
 
@@ -993,19 +1059,19 @@ public class ZipUtil {
 	 * @throws UtilException IO异常
 	 * @since 4.0.5
 	 */
-	private static void addFile(File file, String path, ZipOutputStream out) throws UtilException {
-		addFile(FileUtil.getInputStream(file), path, out);
+	private static void add(File file, String path, ZipOutputStream out) throws UtilException {
+		add(FileUtil.getInputStream(file), path, out);
 	}
 
 	/**
 	 * 添加文件流到压缩包,添加后关闭流
 	 *
-	 * @param in   需要压缩的输入流
+	 * @param in   需要压缩的输入流,使用完后自动关闭
 	 * @param path 压缩的路径
 	 * @param out  压缩文件存储对象
 	 * @throws UtilException IO异常
 	 */
-	private static void addFile(InputStream in, String path, ZipOutputStream out) throws UtilException {
+	private static void add(InputStream in, String path, ZipOutputStream out) throws UtilException {
 		if (null == in) {
 			return;
 		}
@@ -1058,7 +1124,7 @@ public class ZipUtil {
 			}
 
 			// 压缩文件不能位于被压缩的目录内
-			if(srcFile.isDirectory() && FileUtil.isSub(srcFile, zipFile.getParentFile())){
+			if (srcFile.isDirectory() && FileUtil.isSub(srcFile, zipFile.getParentFile())) {
 				throw new UtilException("Zip file path [{}] must not be the child directory of [{}] !", zipFile.getPath(), srcFile.getPath());
 			}
 		}
@@ -1086,15 +1152,7 @@ public class ZipUtil {
 	 * @throws IORuntimeException IO异常
 	 */
 	private static void write(ZipFile zipFile, ZipEntry zipEntry, File outItemFile) throws IORuntimeException {
-		InputStream in = null;
-		try {
-			in = zipFile.getInputStream(zipEntry);
-			FileUtil.writeFromStream(in, outItemFile);
-		} catch (IOException e) {
-			throw new IORuntimeException(e);
-		} finally {
-			IoUtil.close(in);
-		}
+		FileUtil.writeFromStream(getStream(zipFile, zipEntry), outItemFile);
 	}
 
 	/**

+ 1 - 0
hutool-core/src/test/java/cn/hutool/core/compiler/JavaSourceCompilerTest.java

@@ -21,6 +21,7 @@ public class JavaSourceCompilerTest {
 	 */
 	@Test
 	public void testCompile() throws ClassNotFoundException {
+		// 依赖A,编译B和C
 		final File libFile = ZipUtil.zip(FileUtil.file("lib.jar"),
 				new String[]{"a/A.class", "a/A$1.class", "a/A$InnerClass.class"},
 				new InputStream[]{

+ 9 - 0
hutool-core/src/test/java/cn/hutool/core/io/resource/ResourceUtilTest.java

@@ -1,5 +1,6 @@
 package cn.hutool.core.io.resource;
 
+import cn.hutool.core.io.IoUtil;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -10,4 +11,12 @@ public class ResourceUtilTest {
 		final String str = ResourceUtil.readUtf8Str("test.xml");
 		Assert.assertNotNull(str);
 	}
+
+	@Test
+	public void stringResourceTest(){
+		final StringResource stringResource = new StringResource("testData", "test");
+		Assert.assertEquals("test", stringResource.getName());
+		Assert.assertArrayEquals("testData".getBytes(), stringResource.readBytes());
+		Assert.assertArrayEquals("testData".getBytes(), IoUtil.readBytes(stringResource.getStream()));
+	}
 }